/*****************************************************************************
 * $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$
 ****************************************************************************/


// -- Core stuff
#include "PlaneC.h"

// -- VTK stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkProperty.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include "CamiTKReEnableWarnings"

#include <vtkPlaneSource.h>
#include <vtkPolyData.h>
#include <vtkTransform.h>
#include <vtkAbstractTransform.h>
#include <vtkPlanes.h>
#include <vtkSmartPointer.h>


namespace camitk {
//--------------------Constructor---------------------
PlaneC::PlaneC() :
    plane {vtkSmartPointer<vtkPlaneSource>::New()},
    planeMapper{vtkSmartPointer<vtkPolyDataMapper>::New()} {
    // create the mapper;
    planeMapper->SetInputConnection(plane->GetOutputPort());

    // creates the actor in order to give to the InteractiveViewer
    planeActor = vtkSmartPointer<vtkActor>::New();
    planeActor->SetMapper(planeMapper);
    planeActor->PickableOff();

    // Properties of the actor
    aProp = vtkSmartPointer<vtkProperty>::New();

    // clear the transformation data
    clear();

    // set the default visualization mode
    setVisuPlane();
}

//--------------------Destructor---------------------
PlaneC::~PlaneC() {
//TODO
}


//--------------------Set Visualization of Plane---------------------
void PlaneC::setVisuPlane() {
    //Initialize properties of the actor associated to the vtkPlaneSource
    aProp->SetColor(0.0, 0.0, 1.0);
    aProp->SetOpacity(0.1);
    //Visualization in wireframe
    aProp->SetRepresentationToWireframe();
    planeActor->SetProperty(aProp);
    planeActor->ApplyProperties();


}

//--------------------Set Visualization Of Active Plane in red---------------------
void PlaneC::setVisuActivePlane() {
    //Change properties of the actor associated to the vtkPlaneSource
    aProp->SetColor(1.0, 0.0, 0.0);//color red
    aProp->SetOpacity(0.2);
    //Visualization in surface
    aProp->SetRepresentationToSurface();
    planeActor->SetProperty(aProp);
    planeActor->ApplyProperties();
}

//--------------------Set Visualization Of Active Plane in blue---------------------
void PlaneC::setVisuActivePlaneOff() {
    //Change properties of the actor associated to the vtkPlaneSource
    aProp->SetColor(0.0, 0.0, 1.0);//color blue
    aProp->SetOpacity(0.1);
    //Visualization in surface
    aProp->SetRepresentationToSurface();
    planeActor->SetProperty(aProp);
    planeActor->ApplyProperties();
}


//--------------------Reset the Plane of the View---------------------
void PlaneC::clear() {
    for (double& value : transformation) {
        value = 0.0;
    }
    translation = 0.0;
    angle1 = 0.0; // no angle when I start
    angle2 = 0.0;
    translation1 = 0.0;

    // Initialization by default
    plane->SetOrigin(0.0, 0.0, 0.0);
    plane->SetPoint1(0.0, 1.0, 0.0);
    plane->SetPoint2(0.0, 0.0, 1.0);
}

//--------------------getActor----------
vtkSmartPointer<vtkActor> PlaneC::getActor() {
    return planeActor;
}

//--------------------Initialize a plane with PlaneCtype axis as normal----------
void PlaneC::init(PlaneC::PlaneCType type, double* bounds) {

    setOrigin(bounds[0], bounds[2], bounds[4]);

    switch (type) {

        case X_MAX:
            setXAxisPoint(bounds[0], bounds[3], bounds[4]);
            setYAxisPoint(bounds[0], bounds[2], bounds[5]);
            setTranslationMaxInPercent(bounds[1] - bounds[0]);
            translatePlaneX();
            break;
        case Y_MIN:
            setXAxisPoint(bounds[1], bounds[2], bounds[4]);
            setYAxisPoint(bounds[0], bounds[2], bounds[5]);
            break;
        case Y_MAX:
            setXAxisPoint(bounds[0], bounds[2], bounds[5]);
            setYAxisPoint(bounds[1], bounds[2], bounds[4]);
            setTranslationMaxInPercent(bounds[3] - bounds[2]);
            translatePlaneY();
            break;
        case Z_MIN:
            setXAxisPoint(bounds[0], bounds[3], bounds[4]);
            setYAxisPoint(bounds[1], bounds[2], bounds[4]);
            break;
        case Z_MAX:
            setXAxisPoint(bounds[1], bounds[2], bounds[4]);
            setYAxisPoint(bounds[0], bounds[3], bounds[4]);
            setTranslationMaxInPercent(bounds[5] - bounds[4]);
            translatePlaneZ();
            break;
        case X_MIN:
        case UNDEFINED:
        default:
            setXAxisPoint(bounds[0], bounds[2], bounds[5]);
            setYAxisPoint(bounds[0], bounds[3], bounds[4]);
            break;
    }



}


//--------------------Translate the Plane on Xaxis---------------------
void PlaneC::translatePlaneX() {

    vtkSmartPointer<vtkTransform> t = vtkSmartPointer<vtkTransform>::New();
    double a = (translation - translation1);
    translation1 = translation;

    t->Translate(a, 0, 0);

    double tab[3];
    plane->GetCenter(tab);
    //change the coordinates of the Plane center and all the plane is translated
    t->TransformPoint(tab, tab);
    plane->SetCenter(tab);

    t->Update();
}

//--------------------Translate the Plane on Yaxis---------------------
void PlaneC::translatePlaneY() {

    vtkSmartPointer<vtkTransform> t = vtkSmartPointer<vtkTransform>::New();
    double a = translation - translation1;
    translation1 = translation;

    t->Translate(0, a, 0);

    double tab[3];
    plane->GetCenter(tab);
    //change the coordinates of the Plane center and all the plane is translated
    t->TransformPoint(tab, tab);
    plane->SetCenter(tab);

    t->Update();
}

//--------------------Translate the Plane on Zaxis---------------------
void PlaneC::translatePlaneZ() {

    vtkSmartPointer<vtkTransform> t = vtkSmartPointer<vtkTransform>::New();
    double a = translation - translation1;
    translation1 = translation;

    t->Translate(0, 0, a);

    double tab[3];
    plane->GetCenter(tab);
    //change the coordinates of the Plane center and all the plane is translated
    t->TransformPoint(tab, tab);
    plane->SetCenter(tab);

    t->Update();
}


//--------------------Rotation around Axe1---------------------
void PlaneC ::rotationAxe1() {
    //coordinates of the origin
    double tabO[3];
    //coordinates of the center
    double tabC[3];
    //coordinates of the Point1
    double tabP[3];
    //coordinates of the Normal
    double tabN[3];
    //coordinates of the vector for the rotation
    double tab[3];

    //Initialization of each point
    plane->GetCenter(tabC);
    plane->GetOrigin(tabO);
    plane->GetPoint1(tabP);
    plane->GetNormal(tabN);
    //Initialization of the vector with the Axis1
    for (unsigned int i = 0; i < 3; i++) {
        tab[i] = tabP[i] - tabO[i];
    }


    // rotate with the difference between what I want (ang) and the current angle (angle1)
    double differenceAngle = transformation[1] - angle1;

    // if this rotation is not too small
    if (fabs(differenceAngle) > 0.5) {

        vtkSmartPointer<vtkTransform> transfo = vtkSmartPointer<vtkTransform>::New();
        //Initialization of the transfo with an angle and the vector
        transfo->RotateWXYZ(double(differenceAngle), tab);

        // record the new angle
        angle1 = transformation[1];

        //Apply the rotation to the normal and all the plane will rotate
        transfo->TransformPoint(tabN, tabN);
        plane->SetNormal(tabN);
        plane->Update();
        transfo->Update();
    }

}


//--------------------Rotation around Axe2---------------------
void PlaneC ::rotationAxe2() {
    //coordinates of the origin
    double tabO[3];
    //coordinates of the center
    double tabC[3];
    //coordinates of the Point2
    double tabP[3];
    //coordinates of the Normal
    double tabN[3];
    //coordinates of the vector for the rotation
    double tab[3];

    //Initialization of each point
    plane->GetCenter(tabC);
    plane->GetOrigin(tabO);
    plane->GetPoint2(tabP);
    plane->GetNormal(tabN);

    //Initialization of the vector with the Axis2
    for (int i = 0; i < 3; i++) {

        tab[i] = tabP[i] - tabO[i];
    }

    // rotate with the difference between what I want (ang) and the current angle (angle2)
    double differenceAngle = (double)transformation[2] - (double)angle2;

    // if this rotation is not too small
    if (fabs(differenceAngle) > 0.5) {
        vtkSmartPointer<vtkTransform> transfo = vtkSmartPointer<vtkTransform>::New();
        //Initialization of the transfo with an angle and the vector
        transfo->RotateWXYZ(differenceAngle, tab);

        // record the new angle
        angle2 = transformation[2];

        //Apply the rotation to the normal and all the plane will rotate
        transfo->TransformPoint(tabN, tabN);
        plane->SetNormal(tabN);
        plane->Update();
        transfo->Update();
    }

}

//--------------------Set the transformation---------------------
void PlaneC ::setTransfoPercentToRealValue(double* tab, double min, double max) {
    for (int i = 0; i < 3; i++) {
        transformation[i] = tab[i];
    }
    translation = transformation[0] * (max - min) / 100;
}

//--------------------Get the transformation---------------------
void PlaneC::getTransformationInPercent(double* t, double* r1, double* r2) {
    *t = transformation[0];
    *r1 = transformation[1];
    *r2 = transformation[2];
}

//--------------------Set the translation---------------------
void  PlaneC::setTranslationMaxInPercent(double trans) {
    translation = trans;
    transformation[0] = 100;
}

//--------------------Get the origin---------------------
void PlaneC::getOrigin(double* tab) {
    plane->GetOrigin(tab);

}

//--------------------Get the normal---------------------
void PlaneC::getNormal(double* tab) {
    plane->GetNormal(tab);

}
//--------------------Set the origin---------------------
void PlaneC::setOrigin(double x, double y, double z) {

    plane->SetOrigin(x, y, z);

}
//--------------------setXAxisPoint---------------------
void PlaneC::setXAxisPoint(double x, double y, double z) {

    plane->SetPoint1(x, y, z);

}
//--------------------setYAxisPoint---------------------
void PlaneC::setYAxisPoint(double x, double y, double z) {

    plane->SetPoint2(x, y, z);

}

}
