/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2024 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$
 ****************************************************************************/
// Local Includes
#include "Frame.h"
#include "Application.h"
#include "Viewer.h"

#include "Log.h"

// Includes from Vtk
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkAxesActor.h>
#include <vtkProperty2D.h>
#include <vtkCaptionActor2D.h>
#include <CamiTKReEnableWarnings>

#include <vtkTransform.h>
#include <vtkTransformPolyDataFilter.h>
#include <vtkTextProperty.h>
#include <vtkTextActor.h>

namespace camitk {

int Frame::nbTotalFrames = 0;

// ------------------------- Constructors ------------------------
Frame::Frame(vtkSmartPointer<vtkTransform> transform, Frame* parentFrame) {
    initAttributes();

    if (transform != nullptr) {
        setTransform(transform);
    }

    setParentFrame(parentFrame);
    this->parentFrame = parentFrame;
    transformWorldToMe->Identity();
    transformWorldToMe->PostMultiply();
    transformWorldToMe->Concatenate(transformParentToMe);
    transformWorldToMe->Update();

    if (this->parentFrame != nullptr) {
        transformWorldToMe->Concatenate(this->parentFrame->getTransformFromWorld());
        transformWorldToMe->Update();
        parentFrame->addFrameChild(this);
    }
}


// ------------------------- initAttributes ------------------------
void Frame::initAttributes() {
    frameName = "Frame" + QString::number(nbTotalFrames);
    nbTotalFrames ++;

    axes = nullptr;
    parentFrame = nullptr;

    transformParentToMe =  vtkSmartPointer<vtkTransform>::New();
    transformParentToMe->Identity();

    transformWorldToMe = vtkSmartPointer<vtkTransform>::New();
    transformWorldToMe->Identity();
    transformWorldToMe->PostMultiply();

    representationTransformFilter = vtkSmartPointer<vtkTransformPolyDataFilter>::New();
    representationTransformFilter->SetTransform(transformWorldToMe);
    representationTransformFilter->GlobalWarningDisplayOff();

    childrenFrame.clear();
}


// ------------------- Default Destructor ------------------------
Frame::~Frame() {

    if (parentFrame != nullptr) {
        parentFrame->removeFrameChild(this);
    }

    //  Declare my children as orpheans
    foreach (InterfaceFrame* f, childrenFrame) {
        f->setParentFrame(nullptr);
    }

    childrenFrame.clear();
    frameViewers.clear();

}

// ------------------- getFrameName ----------------------------------
const QString& Frame::getFrameName() const {
    return frameName;
}

// ------------------- setFrameName ----------------------------------
void Frame::setFrameName(QString name) {
    frameName = name;
}

// ------------------- getParentFrame ----------------------------------
InterfaceFrame* Frame::getParentFrame() const {
    return parentFrame;
}

// ------------------- getChildrenFrame ----------------------------------
const QVector<InterfaceFrame*>& Frame::getChildrenFrame() const {
    return childrenFrame;
}

// ------------------- setParentFrame ----------------------------------
void Frame::setParentFrame(InterfaceFrame* parent, bool keepTransform) {
    InterfaceFrame* checkedParent = parent;

    if (checkedParent) {
        // setParentFrame(this) should not be possible...
        // Neither should be setParentFrame from one of my child
        if (checkedParent->getFrameName() == getFrameName()) {
            checkedParent = nullptr;
        }
        else {
            // compute all the descendants of the current frame
            QVector<InterfaceFrame*> descendants = computeDescendants(this);

            // check if one of the descendant if the checked parent
            foreach (InterfaceFrame* descendant, descendants) {
                if (descendant->getFrameName() == checkedParent->getFrameName()) {
                    //remove the new parent in the list of children
                    removeFrameChild(checkedParent);
                    //In this usecase, the former descendant is attached to the world frame
                    checkedParent->setParentFrame(nullptr);
                    break;
                }
            }
        }
    }

    // If we don't keep transform => update it in order for the frame not to move during parent transition
    if (!keepTransform) {
        vtkSmartPointer<vtkTransform> newParentTransform = nullptr;
        newParentTransform = getTransformFromFrame(checkedParent);
        setTransform(newParentTransform);
    }

    // tell my former parent that I am no more its child
    if (parentFrame != nullptr) {
        parentFrame->removeFrameChild(this);
    }

    parentFrame = checkedParent;
    transformWorldToMe->Identity();
    transformWorldToMe->PostMultiply();
    transformWorldToMe->Concatenate(transformParentToMe);
    transformWorldToMe->Update();

    if (parentFrame != nullptr) {
        transformWorldToMe->Concatenate(parentFrame->getTransformFromWorld());
        transformWorldToMe->Update();
        parentFrame->addFrameChild(this);
    }

}

// ------------------- addFrameChild ----------------------------------
void Frame::addFrameChild(InterfaceFrame* frame) {
    if (!childrenFrame.contains(frame)) {
        childrenFrame.append(frame);
    }
}

// ------------------- removeFrameChild ----------------------------------
void Frame::removeFrameChild(InterfaceFrame* frame) {
    if (childrenFrame.indexOf(frame) != -1) {
        childrenFrame.remove(childrenFrame.indexOf(frame));
    }
}

// ------------------- getTransformFromWorld ----------------------------------
const vtkSmartPointer<vtkTransform> Frame::getTransformFromWorld() const {
    return transformWorldToMe;
}

// ------------------- getTransform ----------------------------------
const vtkSmartPointer<vtkTransform> Frame::getTransform() const {
    return transformParentToMe;
}

// ------------------- getTransformFromFrame ----------------------------------
const vtkSmartPointer<vtkTransform> Frame::getTransformFromFrame(InterfaceFrame* frame) const {
    // World -> Me
    vtkSmartPointer<vtkMatrix4x4> worldToMeMatrix = transformWorldToMe->GetMatrix();

    // World -> Frame
    vtkSmartPointer<vtkMatrix4x4> worldToFrameMatrix;

    if (frame == nullptr) { // Frame IS the world Frame !
        worldToFrameMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
        worldToFrameMatrix->Identity();
    }
    else {
        worldToFrameMatrix = frame->getTransformFromWorld()->GetMatrix();
    }

    // Frame -> World
    vtkSmartPointer<vtkMatrix4x4> frameToWorldMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Invert(worldToFrameMatrix, frameToWorldMatrix);

    // Frame -> Me = Frame -> World -> Me
    vtkSmartPointer<vtkMatrix4x4> frameToMeMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
    vtkMatrix4x4::Multiply4x4(frameToWorldMatrix, worldToMeMatrix, frameToMeMatrix);

    vtkSmartPointer<vtkTransform> result = vtkSmartPointer<vtkTransform>::New();
    result->SetMatrix(frameToMeMatrix);

    return result;
}

// ------------------- setTransform ----------------------------------
void Frame::setTransform(vtkSmartPointer<vtkTransform> transform) {
    transformParentToMe->SetMatrix(transform->GetMatrix());
    transformParentToMe->Update();
    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- resetTransform ----------------------------------
void Frame::resetTransform() {
    transformParentToMe->Identity();
    transformParentToMe->Update();
    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- translate ----------------------------------
void Frame::translate(double x, double y, double z) {
    transformParentToMe->Translate(x, y, z);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}


// ------------------- rotate ----------------------------------
void Frame::rotate(double aroundX, double aroundY, double aroundZ) {
    double* initialPos = transformParentToMe->GetPosition();

    transformParentToMe->Translate(-initialPos[0], -initialPos[1], -initialPos[2]);
    transformParentToMe->Update();

    transformParentToMe->RotateX(aroundX);
    transformParentToMe->RotateY(aroundY);
    transformParentToMe->RotateZ(aroundZ);
    transformParentToMe->Update();

    transformParentToMe->Translate(initialPos[0], initialPos[1], initialPos[2]);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- rotateVTK ----------------------------------
void Frame::rotateVTK(double aroundX, double aroundY, double aroundZ) {
    double* initialPos = transformParentToMe->GetPosition();

    transformParentToMe->Translate(-initialPos[0], -initialPos[1], -initialPos[2]);
    transformParentToMe->Update();

    transformParentToMe->RotateZ(aroundZ);
    transformParentToMe->RotateX(aroundX);
    transformParentToMe->RotateY(aroundY);
    transformParentToMe->Update();

    transformParentToMe->Translate(initialPos[0], initialPos[1], initialPos[2]);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- setTransformTranslation --------------------------
void Frame::setTransformTranslation(double x, double y, double z) {
//     CAMITK_INFO_ALT(QString("before n=%1").arg(getTransform()->GetNumberOfConcatenatedTransforms()))
//     CAMITK_INFO_ALT(QString("m=[%1,%2,%3,%4] [%5,%6,%7,%8] [%9,%10,%11,%12] [%13,%14,%15,%16]")
//         .arg(getTransform()->GetMatrix()->GetElement(0,0))
//         .arg(getTransform()->GetMatrix()->GetElement(0,1))
//         .arg(getTransform()->GetMatrix()->GetElement(0,2))
//         .arg(getTransform()->GetMatrix()->GetElement(0,3))
//         .arg(getTransform()->GetMatrix()->GetElement(1,0))
//         .arg(getTransform()->GetMatrix()->GetElement(1,1))
//         .arg(getTransform()->GetMatrix()->GetElement(1,2))
//         .arg(getTransform()->GetMatrix()->GetElement(1,3))
//         .arg(getTransform()->GetMatrix()->GetElement(2,0))
//         .arg(getTransform()->GetMatrix()->GetElement(2,1))
//         .arg(getTransform()->GetMatrix()->GetElement(2,2))
//         .arg(getTransform()->GetMatrix()->GetElement(2,3))
//         .arg(getTransform()->GetMatrix()->GetElement(3,0))
//         .arg(getTransform()->GetMatrix()->GetElement(3,1))
//         .arg(getTransform()->GetMatrix()->GetElement(3,2))
//         .arg(getTransform()->GetMatrix()->GetElement(3,3))
//          )

    double* initialRotation = transformParentToMe->GetOrientation();

    transformParentToMe->Identity();

    transformParentToMe->RotateX(initialRotation[0]);
    transformParentToMe->RotateY(initialRotation[1]);
    transformParentToMe->RotateZ(initialRotation[2]);
    transformParentToMe->Update();

    transformParentToMe->Translate(x, y, z);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();

//     CAMITK_INFO_ALT(QString("after n=%1").arg(getTransform()->GetNumberOfConcatenatedTransforms()))
//     CAMITK_INFO_ALT(QString("m=[%1,%2,%3,%4] [%5,%6,%7,%8] [%9,%10,%11,%12] [%13,%14,%15,%16]")
//         .arg(getTransform()->GetMatrix()->GetElement(0,0))
//         .arg(getTransform()->GetMatrix()->GetElement(0,1))
//         .arg(getTransform()->GetMatrix()->GetElement(0,2))
//         .arg(getTransform()->GetMatrix()->GetElement(0,3))
//         .arg(getTransform()->GetMatrix()->GetElement(1,0))
//         .arg(getTransform()->GetMatrix()->GetElement(1,1))
//         .arg(getTransform()->GetMatrix()->GetElement(1,2))
//         .arg(getTransform()->GetMatrix()->GetElement(1,3))
//         .arg(getTransform()->GetMatrix()->GetElement(2,0))
//         .arg(getTransform()->GetMatrix()->GetElement(2,1))
//         .arg(getTransform()->GetMatrix()->GetElement(2,2))
//         .arg(getTransform()->GetMatrix()->GetElement(2,3))
//         .arg(getTransform()->GetMatrix()->GetElement(3,0))
//         .arg(getTransform()->GetMatrix()->GetElement(3,1))
//         .arg(getTransform()->GetMatrix()->GetElement(3,2))
//         .arg(getTransform()->GetMatrix()->GetElement(3,3))
//          )

}

// ------------------- setTransformTranslationVTK --------------------------
void Frame::setTransformTranslationVTK(double x, double y, double z) {
    double* initialRotation = transformParentToMe->GetOrientation();

    transformParentToMe->Identity();

    // VTK rotation order is Z, X, Y
    transformParentToMe->RotateZ(initialRotation[2]);
    transformParentToMe->RotateX(initialRotation[0]);
    transformParentToMe->RotateY(initialRotation[1]);
    transformParentToMe->Update();

    transformParentToMe->Translate(x, y, z);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- setTransformRotation ------------------------------
void Frame::setTransformRotation(double aroundX, double aroundY, double aroundZ) {
    double* initialTranslation = transformParentToMe->GetPosition();

    transformParentToMe->Identity();

    transformParentToMe->RotateX(aroundX);
    transformParentToMe->RotateY(aroundY);
    transformParentToMe->RotateZ(aroundZ);
    transformParentToMe->Update();

    transformParentToMe->Translate(initialTranslation[0], initialTranslation[1], initialTranslation[2]);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- setTransformRotationVTK ------------------------------
void Frame::setTransformRotationVTK(double aroundX, double aroundY, double aroundZ) {
    double* initialTranslation = transformParentToMe->GetPosition();

    transformParentToMe->Identity();

    // VTK rotation order is Z, X, Y
    transformParentToMe->RotateZ(aroundZ);
    transformParentToMe->RotateX(aroundX);
    transformParentToMe->RotateY(aroundY);
    transformParentToMe->Update();

    transformParentToMe->Translate(initialTranslation[0], initialTranslation[1], initialTranslation[2]);
    transformParentToMe->Update();

    transformWorldToMe->Update();
    representationTransformFilter->Update();
}

// ------------------- getFrameAxisActor ----------------------------------
vtkSmartPointer<vtkAxesActor> Frame::getFrameAxisActor() {

    if (axes == nullptr) {
        axes = vtkSmartPointer<vtkAxesActor>::New();
        axes->SetShaftTypeToCylinder();
        axes->SetXAxisLabelText("x");
        axes->SetYAxisLabelText("y");
        axes->SetZAxisLabelText("z");
        axes->SetTotalLength(1.0, 1.0, 1.0); // unit length
        vtkSmartPointer<vtkTextProperty> axeXTextProp = vtkSmartPointer<vtkTextProperty>::New();
        axeXTextProp->SetFontSize(10);
        axeXTextProp->BoldOn();
        axeXTextProp->ItalicOn();
        axeXTextProp->ShadowOff();
        axeXTextProp->SetFontFamilyToArial();
        axes->GetXAxisCaptionActor2D()->SetCaptionTextProperty(axeXTextProp);
        // remove the autoscaling so that the font size is smaller than default autoscale
        axes->GetXAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
        // set the color
        axes->GetXAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.9, 0.4, 0.4);
        // make sure the label can be hidden by any geometry, like the axes
        axes->GetXAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

        vtkSmartPointer<vtkTextProperty> axeYTextProp = vtkSmartPointer<vtkTextProperty>::New();
        axeYTextProp->ShallowCopy(axeXTextProp);
        axes->GetYAxisCaptionActor2D()->SetCaptionTextProperty(axeYTextProp);
        axes->GetYAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
        axes->GetYAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.4, 0.9, 0.4);
        axes->GetYAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

        vtkSmartPointer<vtkTextProperty> axeZTextProp = vtkSmartPointer<vtkTextProperty>::New();
        axeZTextProp->ShallowCopy(axeXTextProp);
        axes->GetZAxisCaptionActor2D()->SetCaptionTextProperty(axeZTextProp);
        axes->GetZAxisCaptionActor2D()->GetTextActor()->SetTextScaleModeToNone();
        axes->GetZAxisCaptionActor2D()->GetCaptionTextProperty()->SetColor(0.4, 0.4, 0.9);
        axes->GetZAxisCaptionActor2D()->GetProperty()->SetDisplayLocationToBackground();

        axes->SetUserTransform(transformWorldToMe);

    }

    return axes;
}

// ------------------- setFrameVisibility ----------------------------------
void Frame::setFrameVisibility(QString viewerName, bool visible) {
    // get the viewer pointer
    Viewer* viewer = Application::getViewer(viewerName);

    // only insert real non-null viewers
    if (viewer != nullptr) {
        QMap<Viewer*, bool>::iterator it = frameViewers.find(viewer);

        if (it == frameViewers.end()) {
            frameViewers.insert(viewer, visible);
        }
        else {
            it.value() = visible;
        }
    }
}

void Frame::setFrameVisibility(Viewer* viewer, bool visible) {
    CAMITK_WARNING_ALT(QObject::tr("setFrameVisibility(Viewer*,bool) method is now deprecated and will be removed from CamiTK API, please use setFrameVisibility(QString,bool) instead"))

    QMap<Viewer*, bool>::iterator it = frameViewers.find(viewer);

    if (it == frameViewers.end()) {
        frameViewers.insert(viewer, visible);
    }
    else {
        it.value() = visible;
    }

}

// ------------------- getFrameVisibility ----------------------------------
bool Frame::getFrameVisibility(QString viewerName) const {
    // get the viewer pointer
    Viewer* viewer = Application::getViewer(viewerName);

    // only insert real non-null viewers
    if (viewer != nullptr) {
        QMap<Viewer*, bool>::const_iterator it = frameViewers.find(viewer);

        if (it == frameViewers.end()) {
            return false;
        }
        else {
            return it.value();
        }
    }
    return false;
}

bool Frame::getFrameVisibility(Viewer* viewer) const {
    CAMITK_WARNING_ALT(QObject::tr("getFrameVisibility(Viewer*) method is now deprecated and will be removed from CamiTK API, please use getFrameVisibility(QString) instead"))

    QMap<Viewer*, bool>::const_iterator it = frameViewers.find(viewer);

    if (it == frameViewers.end()) {
        return false;
    }
    else {
        return it.value();
    }
}

// ------------------- computeDescendants -------------------------------
QVector<InterfaceFrame*> Frame::computeDescendants(InterfaceFrame* frame) {

    QVector<InterfaceFrame*> descendants;

    // recursively call to get all the descendants of 'frame'
    foreach (InterfaceFrame* child, frame->getChildrenFrame()) {
        descendants.append(child);
        descendants += computeDescendants(child);
    }

    return descendants;
}

} // namespace
