/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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$
 ****************************************************************************/

// Local
#include "FrameEditor.h"

// CamiTK
#include <InteractiveViewer.h>
#include <Log.h>

using namespace camitk;

// --------------- constructor -------------------
FrameEditor::FrameEditor(ActionExtension* extension) : Action(extension) {
    setName("Frame Editor");
    setDescription(tr("This action helps to modify a component's frame from its parent frame (or the world frame if it has no parent) by setting translations and rotation parameters."));
    setComponent("Component");

    // Setting classification family and tags
    setFamily("Frame");
    addTag(tr("Edit"));
    addTag(tr("Move"));
    addTag(tr("Visualization"));

    myWidget = nullptr;
}


// --------------- init -------------------
void FrameEditor::init() {
    myWidget = new QWidget();
    ui.setupUi(myWidget);

    transfromMatrixElements.resize(4);
    for (int j = 0; j < 4; j++) {
        transfromMatrixElements[j].resize(4);
        for (int i = 0; i < 4; i++) {
            auto* tmpLineEdit = new QLineEdit();
            tmpLineEdit->setText("line: " + QString::number(i) + ", column: " + QString::number(j));
            transfromMatrixElements[j][i] = tmpLineEdit;
            ui.transformationGridLayout->addWidget(tmpLineEdit, i + 1, j + 1);
        }
    }

    ui.keepWorldTransformCheckBox->setText("keep local transf.");
    ui.keepWorldTransformCheckBox->setToolTip("if true keeps the local transform from parent. The current interface frame may move from world coordinates as its local transform is now expressed relatively to the new parent. If false, change the transform from parent to keep the global (world) transform the same as the previous one. ");


    // Reset tab order
    myWidget->setTabOrder(ui.displayFrameCheckBox, ui.frameSizeSpinBox);
    myWidget->setTabOrder(ui.frameSizeSpinBox, ui.parentFrameComboBox);
    myWidget->setTabOrder(ui.parentFrameComboBox, ui.keepWorldTransformCheckBox);
    myWidget->setTabOrder(ui.keepWorldTransformCheckBox, ui.setParentFramePushButton);
    myWidget->setTabOrder(ui.setParentFramePushButton, transfromMatrixElements[0][0]);
    // First Line
    myWidget->setTabOrder(transfromMatrixElements[0][0], transfromMatrixElements[1][0]);
    myWidget->setTabOrder(transfromMatrixElements[1][0], transfromMatrixElements[2][0]);
    myWidget->setTabOrder(transfromMatrixElements[2][0], transfromMatrixElements[3][0]);
    myWidget->setTabOrder(transfromMatrixElements[3][0], transfromMatrixElements[0][1]);
    // Second Line
    myWidget->setTabOrder(transfromMatrixElements[0][1], transfromMatrixElements[1][1]);
    myWidget->setTabOrder(transfromMatrixElements[1][1], transfromMatrixElements[2][1]);
    myWidget->setTabOrder(transfromMatrixElements[2][1], transfromMatrixElements[3][1]);
    myWidget->setTabOrder(transfromMatrixElements[3][1], transfromMatrixElements[0][2]);
    // Third Line
    myWidget->setTabOrder(transfromMatrixElements[0][2], transfromMatrixElements[1][2]);
    myWidget->setTabOrder(transfromMatrixElements[1][2], transfromMatrixElements[2][2]);
    myWidget->setTabOrder(transfromMatrixElements[2][2], transfromMatrixElements[3][2]);
    myWidget->setTabOrder(transfromMatrixElements[3][2], transfromMatrixElements[0][3]);
    // Forth Line
    myWidget->setTabOrder(transfromMatrixElements[0][3], transfromMatrixElements[1][3]);
    myWidget->setTabOrder(transfromMatrixElements[1][3], transfromMatrixElements[2][3]);
    myWidget->setTabOrder(transfromMatrixElements[2][3], transfromMatrixElements[3][3]);
    myWidget->setTabOrder(transfromMatrixElements[3][3], ui.setTrasformationPushButton);

    myWidget->setTabOrder(ui.setTrasformationPushButton, 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 to this...
    connect(ui.displayFrameCheckBox, SIGNAL(toggled(bool)), this, SLOT(displayFrameToggled(bool)));
    connect(ui.frameSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(frameSizeChanged(int)));
    connect(ui.setParentFramePushButton, SIGNAL(clicked()), this, SLOT(setParentFrame()));
    connect(ui.setTrasformationPushButton, SIGNAL(clicked()), this, SLOT(setTransformation()));
    connect(ui.rotatePushButton, SIGNAL(clicked()), this, SLOT(rotate()));
    connect(ui.setRotationPushButton, SIGNAL(clicked()), this, SLOT(setRotation()));
    connect(ui.translatePushButton, SIGNAL(clicked()), this, SLOT(translate()));
    connect(ui.setTranslationPushButton, SIGNAL(clicked()), this, SLOT(setTranslation()));

    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            connect(transfromMatrixElements[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()));

}


// --------------- destructor -------------------
FrameEditor::~FrameEditor() {
    if (myWidget) {
        delete myWidget;
    }
}

// --------------- getWidget -------------------
QWidget* FrameEditor::getWidget() {
    if (!myWidget) {
        init();
    }

    currentComponent = getTargets().last();
    initializeDialogWithCurrentComponent();

    return myWidget;
}


// --------------- initializeDialogWithCurrentComponent -------------------
void FrameEditor::initializeDialogWithCurrentComponent() {
    // update checkbox
    ui.displayFrameCheckBox->blockSignals(true);
    bool isFrameDisplayed = currentComponent->getFrameVisibility(InteractiveViewer::get3DViewer());
    ui.displayFrameCheckBox->setChecked(isFrameDisplayed);
    ui.displayFrameCheckBox->blockSignals(false);

    // Update the Parent Frame
    bool parentFrameSignalState = ui.parentFrameComboBox->blockSignals(true);
    ui.parentFrameComboBox->clear();
    ui.parentFrameComboBox->addItem("World");
    ComponentList allComps = Application::getAllComponents();
    foreach (Component* comp, allComps) {
        if (comp != currentComponent) {
            ui.parentFrameComboBox->addItem(comp->getName());
        }
    }
    InterfaceFrame* parentFrame = currentComponent->getParentFrame();
    Component* parentFrameComponent = dynamic_cast<Component*>(parentFrame);
    if (parentFrameComponent == NULL) {
        ui.parentFrameComboBox->setCurrentText("World");
    }
    else {
        ui.parentFrameComboBox->setCurrentText(parentFrameComponent->getName());
    }
    ui.parentFrameComboBox->blockSignals(true);

    // Update the transformation Matrix
    vtkSmartPointer<vtkMatrix4x4> currentMatrix = currentComponent->getTransform()->GetMatrix();
    bool transformSignalState = ui.transformationGroupBox->blockSignals(true);
    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            transfromMatrixElements[j][i]->setText(QString::number(currentMatrix->GetElement(i, j)));
        }
    }

    ui.transformationGroupBox->blockSignals(transformSignalState);

    auto* pos = new double[3];
    currentComponent->getTransform()->GetPosition(pos);

    bool translationSignalState = ui.translationGroupBox->blockSignals(true);
    ui.xTranslationLineEdit->setText(QString::number(pos[0]));
    ui.yTranslationLineEdit->setText(QString::number(pos[1]));
    ui.zTranslationLineEdit->setText(QString::number(pos[2]));
    ui.translationGroupBox->blockSignals(translationSignalState);

    auto* rotation = new double[3];
    currentComponent->getTransform()->GetOrientation(rotation);

    bool rotationSignalState = ui.rotationGroupBox->blockSignals(true);
    ui.xRotationLineEdit->setText(QString::number(rotation[0]));
    ui.yRotationLineEdit->setText(QString::number(rotation[1]));
    ui.zRotationLineEdit->setText(QString::number(rotation[2]));
    ui.rotationGroupBox->blockSignals(rotationSignalState);
}

// --------------- displayFrameToggled -------------------
void FrameEditor::displayFrameToggled(bool display) {
    int frameSize = ui.frameSizeSpinBox->value();
    currentComponent->getFrameAxisActor()->SetTotalLength(frameSize, frameSize, frameSize);
    currentComponent->setFrameVisibility(InteractiveViewer::get3DViewer(), display);

    InteractiveViewer::get3DViewer()->refresh();

}

// --------------- frameSizeChanged -------------------
void FrameEditor::frameSizeChanged(int val) {
    bool display = ui.displayFrameCheckBox->isChecked();
    currentComponent->getFrameAxisActor()->SetTotalLength(val, val, val);
    currentComponent->setFrameVisibility(InteractiveViewer::get3DViewer(), display);

    InteractiveViewer::get3DViewer()->refresh();
}

// --------------- setParentFrame -------------------
void FrameEditor::setParentFrame() {
    bool keepTransform = ui.keepWorldTransformCheckBox->isChecked();

    // Look for the parentFrame in the combobox
    Component* parentFrame = NULL;
    QString parentFrameName = ui.parentFrameComboBox->currentText();

    ComponentList allComponents = Application::getAllComponents();
    int k = 0;
    while ((k < allComponents.size()) && (parentFrame == NULL)) {
        if (parentFrameName == allComponents.at(k)->getName()) {
            parentFrame = allComponents.at(k);
        }
        k++;
    }
    currentComponent->setParentFrame(parentFrame, keepTransform);

    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}

// --------------- transformationChanged -------------------
void FrameEditor::transformationChanged() {
    // Update corresponding translation and rotation
    vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New();
    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            matrix->SetElement(i, j, transfromMatrixElements[j][i]->text().toDouble());
        }
    }
    vtkSmartPointer<vtkTransform> matrixTransform = vtkSmartPointer<vtkTransform>::New();
    matrixTransform->SetMatrix(matrix);


    auto* pos = new double[3];
    matrixTransform->GetPosition(pos);

    bool translationSignalState = ui.translationGroupBox->blockSignals(true);
    ui.xTranslationLineEdit->setText(QString::number(pos[0]));
    ui.yTranslationLineEdit->setText(QString::number(pos[1]));
    ui.zTranslationLineEdit->setText(QString::number(pos[2]));
    ui.translationGroupBox->blockSignals(translationSignalState);

    auto* rotation = new double[3];
    matrixTransform->GetOrientation(rotation);

    bool rotationSignalState = ui.rotationGroupBox->blockSignals(true);
    ui.xRotationLineEdit->setText(QString::number(rotation[0]));
    ui.yRotationLineEdit->setText(QString::number(rotation[1]));
    ui.zRotationLineEdit->setText(QString::number(rotation[2]));
    ui.rotationGroupBox->blockSignals(rotationSignalState);

}

// --------------- setTransformation -------------------
void FrameEditor::setTransformation() {
    vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New();
    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            matrix->SetElement(i, j, transfromMatrixElements[j][i]->text().toDouble());
        }
    }
    vtkSmartPointer<vtkTransform> matrixTransform = vtkSmartPointer<vtkTransform>::New();
    matrixTransform->SetMatrix(matrix);
    currentComponent->setTransform(matrixTransform);

    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}

// --------------- translationChanged -------------------
void FrameEditor::translationChanged() {
    CAMITK_TRACE(tr("Frame translation changed"))
}

// --------------- translate -------------------
void FrameEditor::translate() {
    currentComponent->translate(ui.xTranslationLineEdit->text().toDouble(),
                                ui.yTranslationLineEdit->text().toDouble(),
                                ui.zTranslationLineEdit->text().toDouble());

    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}

// --------------- setTranslation -------------------
void FrameEditor::setTranslation() {
    currentComponent->setTransformTranslationVTK(ui.xTranslationLineEdit->text().toDouble(),
            ui.yTranslationLineEdit->text().toDouble(),
            ui.zTranslationLineEdit->text().toDouble());

    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}

// --------------- rotationChanged -------------------
void FrameEditor::rotationChanged() {
    CAMITK_TRACE(tr("Frame rotation changed"))
}

// --------------- rotate -------------------
void FrameEditor::rotate() {
    if (ui.setToOriginFirstCheckBox->isChecked()) {
        currentComponent->rotate(ui.xRotationLineEdit->text().toDouble(),
                                 ui.yRotationLineEdit->text().toDouble(),
                                 ui.zRotationLineEdit->text().toDouble());

    }
    else {
        currentComponent->rotateVTK(ui.xRotationLineEdit->text().toDouble(),
                                    ui.yRotationLineEdit->text().toDouble(),
                                    ui.zRotationLineEdit->text().toDouble());
    }

    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}

// --------------- setRotation -------------------
void FrameEditor::setRotation() {
    if (ui.setToOriginFirstCheckBox->isChecked()) {
        currentComponent->setTransformRotation(ui.xRotationLineEdit->text().toDouble(),
                                               ui.yRotationLineEdit->text().toDouble(),
                                               ui.zRotationLineEdit->text().toDouble());
    }
    else {
        currentComponent->setTransformRotationVTK(ui.xRotationLineEdit->text().toDouble(),
                ui.yRotationLineEdit->text().toDouble(),
                ui.zRotationLineEdit->text().toDouble());

    }
    InteractiveViewer::get3DViewer()->refresh();
    initializeDialogWithCurrentComponent();
}


// --------------- apply -------------------
Action::ApplyStatus FrameEditor::apply() {
    return SUCCESS;
}

