/*****************************************************************************
 * $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 "TransformationEditor.h"

// CamiTK includes
#include <Property.h>
#include <Application.h>
#include <TransformationExplorer.h>
#include <TransformationManager.h>
#include <ImageComponent.h>
#include <Log.h>

// This action process Component, include is required
#include <Component.h>

// Double values range
#include <limits>
#include <QDoubleValidator>
#include <QLineEdit>
#include <QLocale>


using namespace camitk;

// -------------------- init --------------------
void TransformationEditor::init() {
    // Add initialization if required
    myWidget = nullptr;
    // Number of decimals
    myPrecision = 4;
    currentTransformation = nullptr;

    TransformationExplorer* transfoExplorer = dynamic_cast<TransformationExplorer*>(Application::getViewer("Transformation Explorer"));
    if (transfoExplorer != nullptr) {
        connect(transfoExplorer, &TransformationExplorer::currentTransformationChanged, this, &TransformationEditor::setCurrentTransformation);
    }
}

// -------------------- process --------------------
Action::ApplyStatus TransformationEditor::process() {
    // This action has custom buttons, and no "Apply"
    return SUCCESS;
}

// -------------------- targetDefined --------------------
void TransformationEditor::targetDefined() {
    // Don't do anything, the Transformation should be selected from TransformationExplorer, not ComponentExplorer
}

// -------------------- parameterChanged --------------------
void TransformationEditor::parameterChanged(QString parameterName) {
    // No parameter
}

// -------------------- getUI --------------------
QWidget* TransformationEditor::getUI() {
    // Instantiate and manage the custom GUI from file TransformationEditor.ui
    if (myWidget == nullptr) {
        myWidget = new QWidget();
        ui.setupUi(myWidget);

        // Insert the matrix widgets
        transformMatrixElements.resize(4);
        for (int j = 0; j < 4; j++) {
            transformMatrixElements[j].resize(4);
            for (int i = 0; i < 4; i++) {
                auto* tmpLineEdit = new QLineEdit();
                tmpLineEdit->setText("line: " + QString::number(i) + ", column: " + QString::number(j));
                tmpLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
                transformMatrixElements[j][i] = tmpLineEdit;
                ui.transformationGridLayout->addWidget(tmpLineEdit, i + 1, j + 1);
            }
        }

        // Init the translation widgets
        ui.xTranslationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.yTranslationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.zTranslationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.xTranslationLineEdit->setText("0");
        ui.yTranslationLineEdit->setText("0");
        ui.zTranslationLineEdit->setText("0");

        // Init the rotation widgets
        ui.xRotationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.yRotationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.zRotationLineEdit->setValidator(new QDoubleValidator(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), myPrecision));
        ui.xRotationLineEdit->setText("0");
        ui.yRotationLineEdit->setText("0");
        ui.zRotationLineEdit->setText("0");

        // Reset tab order
        // First Line
        myWidget->setTabOrder(transformMatrixElements[0][0], transformMatrixElements[1][0]);
        myWidget->setTabOrder(transformMatrixElements[1][0], transformMatrixElements[2][0]);
        myWidget->setTabOrder(transformMatrixElements[2][0], transformMatrixElements[3][0]);
        myWidget->setTabOrder(transformMatrixElements[3][0], transformMatrixElements[0][1]);
        // Second Line
        myWidget->setTabOrder(transformMatrixElements[0][1], transformMatrixElements[1][1]);
        myWidget->setTabOrder(transformMatrixElements[1][1], transformMatrixElements[2][1]);
        myWidget->setTabOrder(transformMatrixElements[2][1], transformMatrixElements[3][1]);
        myWidget->setTabOrder(transformMatrixElements[3][1], transformMatrixElements[0][2]);
        // Third Line
        myWidget->setTabOrder(transformMatrixElements[0][2], transformMatrixElements[1][2]);
        myWidget->setTabOrder(transformMatrixElements[1][2], transformMatrixElements[2][2]);
        myWidget->setTabOrder(transformMatrixElements[2][2], transformMatrixElements[3][2]);
        myWidget->setTabOrder(transformMatrixElements[3][2], transformMatrixElements[0][3]);
        // Fourth Line
        myWidget->setTabOrder(transformMatrixElements[0][3], transformMatrixElements[1][3]);
        myWidget->setTabOrder(transformMatrixElements[1][3], transformMatrixElements[2][3]);
        myWidget->setTabOrder(transformMatrixElements[2][3], transformMatrixElements[3][3]);
        myWidget->setTabOrder(transformMatrixElements[3][3], ui.setTransformationPushButton);

        myWidget->setTabOrder(ui.setTransformationPushButton, ui.xRotationLineEdit);
        myWidget->setTabOrder(ui.xRotationLineEdit, ui.yRotationLineEdit);
        myWidget->setTabOrder(ui.yRotationLineEdit, ui.zRotationLineEdit);
        myWidget->setTabOrder(ui.zRotationLineEdit, ui.rotatePushButton);
        myWidget->setTabOrder(ui.rotatePushButton, ui.setRotationPushButton);

        myWidget->setTabOrder(ui.setRotationPushButton, ui.xTranslationLineEdit);
        myWidget->setTabOrder(ui.xTranslationLineEdit, ui.yTranslationLineEdit);
        myWidget->setTabOrder(ui.yTranslationLineEdit, ui.zTranslationLineEdit);
        myWidget->setTabOrder(ui.zTranslationLineEdit, ui.translatePushButton);
        myWidget->setTabOrder(ui.translatePushButton, ui.setTranslationPushButton);

        // Connect buttons to our methods...
        connect(ui.setTransformationPushButton, &QPushButton::clicked, this, &TransformationEditor::setTransformationFromUI);
        connect(ui.rotatePushButton, &QPushButton::clicked, this, [this]() {
            this->rotate(true);
        });
        connect(ui.setRotationPushButton, &QPushButton::clicked, this, [this]() {
            this->rotate(false);
        });
        connect(ui.translatePushButton, &QPushButton::clicked, this, &TransformationEditor::translate);
        connect(ui.setTranslationPushButton, &QPushButton::clicked, this, &TransformationEditor::setTranslation);
        connect(ui.removeTransformationButton, &QPushButton::clicked, this, &TransformationEditor::removeTransformation);

        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                connect(transformMatrixElements[j][i], SIGNAL(returnPressed()), this, SLOT(transformationChanged()));
            }
        }

        connect(ui.xTranslationLineEdit, SIGNAL(returnPressed()), this, SLOT(translationChanged()));
        connect(ui.yTranslationLineEdit, SIGNAL(returnPressed()), this, SLOT(translationChanged()));
        connect(ui.zTranslationLineEdit, SIGNAL(returnPressed()), this, SLOT(translationChanged()));

        connect(ui.xRotationLineEdit, SIGNAL(returnPressed()), this, SLOT(rotationChanged()));
        connect(ui.yRotationLineEdit, SIGNAL(returnPressed()), this, SLOT(rotationChanged()));
        connect(ui.zRotationLineEdit, SIGNAL(returnPressed()), this, SLOT(rotationChanged()));

        // Disable edits
        setCurrentTransformation(nullptr);
    }

    return myWidget;
}
void TransformationEditor::transformationChanged() {

}

void TransformationEditor::setCurrentTransformation(const Transformation* tr) {
    if (myWidget == nullptr) {
        return;
    }
    std::shared_ptr<Transformation> sharedPtrTransfo = TransformationManager::getTransformationOwnership(tr);

    if (sharedPtrTransfo == nullptr) {
        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                transformMatrixElements[j][i]->setText(0);
                transformMatrixElements[j][i]->setDisabled(true);
            }
        }
        ui.transformationGroupBox->setTitle("Transformation Editor");
    }
    else {
        // Fill the UI
        vtkMatrix4x4* matrix = tr->getMatrix();
        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                transformMatrixElements[j][i]->setEnabled(true);
                transformMatrixElements[j][i]->setText(myLocale.toString(matrix->GetElement(i, j), 'g', myPrecision));
            }
        }
        ui.transformationGroupBox->setTitle(tr->getName());
    }
    ui.setTransformationPushButton->setEnabled(sharedPtrTransfo != nullptr);
    ui.setRotationPushButton->setEnabled(sharedPtrTransfo != nullptr);
    ui.setTranslationPushButton->setEnabled(sharedPtrTransfo != nullptr);
    ui.translatePushButton->setEnabled(sharedPtrTransfo != nullptr);
    ui.rotatePushButton->setEnabled(sharedPtrTransfo != nullptr);
    ui.removeTransformationButton->setEnabled(sharedPtrTransfo != nullptr);

    ui.xRotationLineEdit->setEnabled(sharedPtrTransfo != nullptr);
    ui.yRotationLineEdit->setEnabled(sharedPtrTransfo != nullptr);
    ui.zRotationLineEdit->setEnabled(sharedPtrTransfo != nullptr);
    ui.vtkRotationModeCheckBox->setEnabled(sharedPtrTransfo != nullptr);
    ui.xTranslationLineEdit->setEnabled(sharedPtrTransfo != nullptr);
    ui.yTranslationLineEdit->setEnabled(sharedPtrTransfo != nullptr);
    ui.zTranslationLineEdit->setEnabled(sharedPtrTransfo != nullptr);

    // update the current transformation
    currentTransformation = sharedPtrTransfo.get();
}

void TransformationEditor::setTransformationFromUI() {
    // Create a new matrix and fill it from the UI
    vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New();
    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            matrix->SetElement(i, j, myLocale.toDouble(transformMatrixElements[j][i]->text()));
        }
    }
    // Apply the new matrix
    TransformationManager::updateTransformation(currentTransformation, matrix.GetPointer());
    refreshApplication();
}

void TransformationEditor::translationChanged() {}

void TransformationEditor::translate() {
    vtkSmartPointer<vtkMatrix4x4> beforeTranslationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    beforeTranslationMatrix->DeepCopy(currentTransformation->getMatrix());
    vtkSmartPointer<vtkTransform> beforeTranslation = vtkSmartPointer<vtkTransform>::New();
    beforeTranslation->SetMatrix(beforeTranslationMatrix);
    beforeTranslation->Translate(
        myLocale.toDouble(ui.xTranslationLineEdit->text()),
        myLocale.toDouble(ui.yTranslationLineEdit->text()),
        myLocale.toDouble(ui.zTranslationLineEdit->text()));
    TransformationManager::updateTransformation(currentTransformation, beforeTranslation);
    setCurrentTransformation(currentTransformation); // Update UI
    refreshApplication();
}

void TransformationEditor::setTranslation() {
    vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New();
    matrix->DeepCopy(currentTransformation->getMatrix());
    matrix->SetElement(0, 3, myLocale.toDouble(ui.xTranslationLineEdit->text()));
    matrix->SetElement(1, 3, myLocale.toDouble(ui.yTranslationLineEdit->text()));
    matrix->SetElement(2, 3, myLocale.toDouble(ui.zTranslationLineEdit->text()));
    TransformationManager::updateTransformation(currentTransformation, matrix.GetPointer());
    setCurrentTransformation(currentTransformation); // Update UI
    refreshApplication();
}

void TransformationEditor::rotationChanged() {}

void TransformationEditor::removeTransformation() {
    std::shared_ptr<Transformation> transfoToDelete = TransformationManager::getTransformationOwnership(currentTransformation);
    if (transfoToDelete != nullptr) {
        bool result = TransformationManager::removeTransformation(transfoToDelete);
        if (result) {
            currentTransformation = nullptr;
            refreshApplication(); // We need to refresh all viewers that may be affected
        }
    }
    setCurrentTransformation(currentTransformation); // Update UI
}

void TransformationEditor::rotate(bool relative) {
    vtkSmartPointer<vtkMatrix4x4> beforeRotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    beforeRotationMatrix->DeepCopy(currentTransformation->getMatrix());
    vtkSmartPointer<vtkTransform> beforeRotation = vtkSmartPointer<vtkTransform>::New();
    beforeRotation->SetMatrix(beforeRotationMatrix);

    beforeRotation->PostMultiply();
    double initialPos[3];
    beforeRotation->GetPosition(initialPos);
    // First translate back to origin
    beforeRotation->Translate(-initialPos[0], -initialPos[1], -initialPos[2]);

    if (!relative) {
        beforeRotation->Identity();
    }

    if (ui.vtkRotationModeCheckBox->isChecked()) {
        beforeRotation->RotateX(myLocale.toDouble(ui.xRotationLineEdit->text()));
        beforeRotation->RotateY(myLocale.toDouble(ui.yRotationLineEdit->text()));
        beforeRotation->RotateZ(myLocale.toDouble(ui.zRotationLineEdit->text()));
    }
    else {
        beforeRotation->RotateZ(myLocale.toDouble(ui.zRotationLineEdit->text()));
        beforeRotation->RotateX(myLocale.toDouble(ui.xRotationLineEdit->text()));
        beforeRotation->RotateY(myLocale.toDouble(ui.yRotationLineEdit->text()));
    }


    beforeRotation->Translate(initialPos[0], initialPos[1], initialPos[2]);
    beforeRotation->Modified();

    TransformationManager::updateTransformation(currentTransformation, beforeRotation);
    setCurrentTransformation(currentTransformation); // Update UI
    refreshApplication();
}