/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "RigidTransform.h"
#include <Component.h>
#include <InteractiveGeometryViewer.h>
#include <Log.h>
#include <Application.h>
using namespace camitk;

#include <QFileDialog>

#include <vtkUnstructuredGrid.h>
#include <vtkPolyData.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkAlgorithmOutput.h>


// --------------- constructor -------------------
RigidTransform::RigidTransform(ActionExtension* extension) : Action(extension) {
    setName("Rigid Transform");
    setEmbedded(false);
    setDescription("Rigid transformation of Components");
    setComponentClassName("MeshComponent");
    setFamily("Basic Mesh");
    addTag("Transform");
    addTag("Translate");
    addTag("Rotate");
    addTag("Scale");

    dialog = nullptr;
}


// --------------- init -------------------
void RigidTransform::init() {
    dialog = new QDialog();

    transformation = vtkSmartPointer<vtkTransform>::New();

    //-- init user interface
    myUI.setupUi(dialog);

    // initialize slider names
    myUI.tX->setName("X");
    myUI.tY->setName("Y");
    myUI.tZ->setName("Z");
    myUI.rX->setName("Around X");
    myUI.rY->setName("Around Y");
    myUI.rZ->setName("Around Z");
    myUI.sX->setName("X");
    myUI.sY->setName("Y");
    myUI.sZ->setName("Z");

    // connect everything
    connect(myUI.tX, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.tY, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.tZ, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.rX, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.rY, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.rZ, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.sX, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.sY, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.sZ, SIGNAL(valueChanged()), SLOT(update()));
    connect(myUI.okButton, SIGNAL(clicked()), SLOT(apply()));
    connect(myUI.closeButton, SIGNAL(clicked()), SLOT(close()));
    connect(myUI.resetButton, SIGNAL(clicked()), SLOT(reset()));
    connect(myUI.previewButton, SIGNAL(clicked()), SLOT(preview()));
    connect(myUI.uniformScaling, SIGNAL(clicked()), SLOT(update()));
    connect(myUI.autoRefresh, SIGNAL(clicked()), SLOT(update()));
    connect(myUI.loadButton, SIGNAL(clicked()), SLOT(load()));
    connect(myUI.saveButton, SIGNAL(clicked()), SLOT(save()));
}

// --------------- destructor -------------------
RigidTransform::~RigidTransform() {
    if (dialog) {
        delete dialog;
    }
}

// --------------- getWidget -------------------
QWidget* RigidTransform::getWidget() {
    if (!dialog) {
        init();
    }

    // disconnect and reset previous state
    close();
    reset();

    //-- connect the custom pipeline to the selected Component
    filterList.clear();

    for (Component* comp : getTargets()) {
        filterList.append(vtkSmartPointerTransformFilter::New());
        filterList.last()->SetTransform(transformation);
        filterList.last()->SetInputConnection(comp->getDataPort());
        comp->setDataConnection(filterList.last()->GetOutputPort());
    }

    return dialog;
}

//--------------- reset -------------
void RigidTransform::reset() {
    //-- initialize the transformation to Identity
    transformation->Identity();

    //-- init values
    double bounds[6];
    InteractiveGeometryViewer* default3DViewer = dynamic_cast<InteractiveGeometryViewer*>(Application::getViewer("3D Viewer"));

    if (default3DViewer != nullptr) {
        default3DViewer->getBounds(bounds);
    }
    else {
        for (int i = 0; i < 6; i += 2) {
            bounds[i] = VTK_DOUBLE_MAX;
            bounds[i + 1] = -VTK_DOUBLE_MAX;
        }
    }

    double xLength = bounds[1] - bounds[0];
    double yLength = bounds[3] - bounds[2];
    double zLength = bounds[5] - bounds[4];
    myUI.tX->init(-xLength, + xLength, 0.0);
    myUI.tY->init(-yLength, + yLength, 0.0);
    myUI.tZ->init(-zLength, + zLength, 0.0);
    myUI.rX->init(-180.0, 180.0, 0.0);
    myUI.rY->init(-180.0, 180.0, 0.0);
    myUI.rZ->init(-180.0, 180.0, 0.0);
    myUI.sX->init(0.0, 5.0, 1.0);
    myUI.sY->init(0.0, 5.0, 1.0);
    myUI.sZ->init(0.0, 5.0, 1.0);
    update();
}

//--------------- update -------------
void RigidTransform::update(bool forceUpdate) {
    myUI.previewButton->setEnabled(!myUI.autoRefresh->isChecked());

    // if "update the view" is checked ,the transformation is visualized
    if (myUI.autoRefresh->isChecked() || forceUpdate) {
        //if we don't want a transformation concateneted we need to initialize
        //the transfo with Identity
        if (!(myUI.concatenate->isChecked())) {
            transformation->Identity();
        }

        //-- Set the Translation
        transformation->Translate(double(myUI.tX->getValue()), double(myUI.tY->getValue()), double(myUI.tZ->getValue()));

        //-- Set the Rotation
        transformation->RotateX(double(myUI.rX->getValue()));
        transformation->RotateY(double(myUI.rY->getValue()));
        transformation->RotateZ(double(myUI.rZ->getValue()));

        //-- Set the Scaling
        double sx, sy, sz;
        myUI.sY->setEnabled(!myUI.uniformScaling->isChecked());
        myUI.sZ->setEnabled(!myUI.uniformScaling->isChecked());

        if (myUI.uniformScaling->isChecked()) {
            sx = double(myUI.sX->getValue());
            sy = double(myUI.sX->getValue());
            sz = double(myUI.sX->getValue());
        }
        else {
            sx = double(myUI.sX->getValue());
            sy = double(myUI.sY->getValue());
            sz = double(myUI.sZ->getValue());
        }

        transformation->Scale(sx, sy, sz);

        // Refresh all the used viewers
        InteractiveGeometryViewer* default3DViewer = dynamic_cast<InteractiveGeometryViewer*>(Application::getViewer("3D Viewer"));

        if (default3DViewer != nullptr) {
            default3DViewer->refresh();
        }
    }
}

//--------------- apply ------------
Action::ApplyStatus RigidTransform::apply() {
    update(true); // force visualization

    // apply the transformation
    unsigned int i = 0;

    for (Component* comp : getTargets()) {
        // get the result from the corresponding filter
        vtkSmartPointer<vtkPointSet> result = vtkPointSet::SafeDownCast(filterList[i]->GetOutputDataObject(0));

        if (result) {
            comp->setPointSet(result);
            comp->setModified();
        }
        else {
            CAMITK_WARNING(tr("Filter output is of type: %1. Action aborted").arg(QString(filterList[i]->GetOutputDataObject(0)->GetClassName())))
            return ABORTED;
        }

        // next filter
        i++;
    }

    // disconnect everyone
    close();
    return SUCCESS;
}

//--------------- close ------------
void RigidTransform::close() {
    //--disconnect the selected Component
    for (Component* comp : getTargets()) {
        comp->setDataConnection(nullptr);
    }

    // hide the dialog
    dialog->hide();
    // Refresh all the used viewers
    refreshApplication();
}

//--------------- preview ------------
void RigidTransform::preview() {
    update(true); // force visualization
}

//--------------- load ------------
void RigidTransform::load() {
    // get the file name
    QString fn = QFileDialog::getOpenFileName(dialog, "Load Transformation");

    if (!fn.isNull()) {
        std::ifstream in(fn.toStdString().c_str());
        double x, y, z, t;

        for (unsigned int i = 0; i < 4; i++) {
            in >> x >> y >> z >> t;
            transformation->GetMatrix()->SetElement(i, 0, x);
            transformation->GetMatrix()->SetElement(i, 1, y);
            transformation->GetMatrix()->SetElement(i, 2, z);
            transformation->GetMatrix()->SetElement(i, 3, t);
        }
    }

    myUI.tX->setValue(transformation->GetPosition()[0]);
    myUI.tY->setValue(transformation->GetPosition()[1]);
    myUI.tZ->setValue(transformation->GetPosition()[2]);
    myUI.rX->setValue(transformation->GetOrientation()[0]);
    myUI.rY->setValue(transformation->GetOrientation()[1]);
    myUI.rZ->setValue(transformation->GetOrientation()[2]);
    myUI.sX->setValue(transformation->GetScale()[0]);
    myUI.sY->setValue(transformation->GetScale()[1]);
    myUI.sZ->setValue(transformation->GetScale()[2]);
    update();
}

//--------------- save ------------
void RigidTransform::save() {
    // get the file name
    QString fn = QFileDialog::getSaveFileName(dialog, "Save Current Transformation");

    if (!fn.isNull()) {
        std::ofstream out(fn.toStdString().c_str());

        for (unsigned int i = 0; i < 4; i++) {
            for (unsigned int j = 0; j < 4; j++) {
                out << transformation->GetMatrix()->GetElement(i, j) << " ";
            }

            out << endl;
        }
    }
}


