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

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

#include <vtkSphereSource.h>
#include <vtkArrowSource.h>
#include <vtkMatrix4x4.h>
#include <vtkSmartPointer.h>

#if defined(_WIN32) && !defined(__MINGW32__) // MSVC only
#ifndef M_PI
#define M_PI 3.14159265358979
#endif
#ifndef M_PI_2
#define M_PI_2 M_PI*M_PI
#endif
#endif


namespace camitk {
//----------------------- constructors ------------------------
GeometricObject::GeometricObject(Geometry g) : myType(g) {
    defaultValues();
    init();
}

GeometricObject::GeometricObject(Geometry g, const double x, const double y, const double z) : myType(g) {
    defaultValues();
    init();
    setPosition(x, y, z);
}

GeometricObject::GeometricObject(Geometry g, Direction dir) : myType(g) {
    defaultValues();
    myDirection = dir;
    init();
}

GeometricObject::GeometricObject(Geometry g, float boundingBox[6]) : myType(g) {
    defaultValues();
    for (int i = 0; i < 6; i++) {
        bounds[i] = boundingBox[i];
    }
    init();
}


//---------------------- destructor ---------------------
GeometricObject::~GeometricObject() {
//TODO
}

//---------------------- defaultValues ---------------------
void GeometricObject::defaultValues() {
    for (int i = 0; i < 6; i += 2) {
        bounds[i] = 0.0;
        bounds[i + 1] = 1.0;
    }
    myDirection = USER_DEFINED;
}

//---------------------- init ---------------------
void GeometricObject::init() {
    // create the source
    mySource = nullptr;
    switch (myType) {
        case ARROW:
            mySource = vtkSmartPointer<vtkArrowSource>::New();
            break;
        case SPHERE:
            mySource = vtkSmartPointer<vtkSphereSource>::New();
            break;
    }

    // mapper
    myMapper = nullptr;
    myMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    myMapper->SetInputConnection(mySource->GetOutputPort());

    // actor
    myActor = nullptr;
    myActor = vtkSmartPointer<vtkActor>::New();
    myActor->SetMapper(myMapper);
    // this musn't be pickable
    myActor->PickableOff();

    // specific direction
    switch (myDirection) {
        case X :
            bounds[0] = 0;
            bounds[1] = 1.0;
            bounds[2] = bounds[3] = bounds[4] = bounds[5] = 0.0;
            myActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
            break;
        case Y :
            bounds[2] = 0;
            bounds[3] = 1.0;
            bounds[0] = bounds[1] = bounds[4] = bounds[5] = 0.0;
            myActor->GetProperty()->SetColor(0.0, 1.0, 0.0);
            myActor->RotateZ(90.0);
            break;
        case Z:
            bounds[4] = 0;
            bounds[5] = 1.0;
            bounds[0] = bounds[1] = bounds[3] = bounds[4] = 0.0;
            myActor->GetProperty()->SetColor(0.0, 0.0, 1.0);
            myActor->RotateY(-90.0);
            break;
        case USER_DEFINED:
            // default color : reddish
            myActor->GetProperty()->SetColor(1.0, 0.2, 0.2);
            break;
    }

    // size
    setSize(0.1);

    // position: (xmin, ymin, zmin)
    setPosition(bounds[0], bounds[2], bounds[4]);
}

// ------------- getActor --------------
vtkSmartPointer<vtkActor> GeometricObject::getActor() {
    return myActor;
}

//---------------------- setPosition --------------------
void GeometricObject::setPosition(const double x, const double y, const double z) {
    myActor->SetPosition(x, y, z);
}

//----------------------- setColor ---------------------
void GeometricObject::setColor(const double r, const double g, const double b) {
    myActor->GetProperty()->SetColor(r, g, b);
}

//----------------------- setSize ---------------------
void GeometricObject::setSize(const double s) {
    myActor->SetScale(s);
}

//--------------------------- setDirection --------------------
void GeometricObject::setDirection(const double x, const double y, const double z) {
    //-- get original position
    double pos[3];
    myActor->GetPosition(pos);

    //-- cancel current orientation
    // dearest solution, but works:
    vtkSmartPointer<vtkMatrix4x4> mat = vtkSmartPointer<vtkMatrix4x4>::New();
    mat->Identity();
    myActor->PokeMatrix(mat);

    //-- compute the new direction
    float xx = sqrt(x * x + z * z);
    float aroundZ;
    if (xx < 1e-10) {
        if (y > 0) {
            aroundZ = M_PI / 2;
        }
        else {
            aroundZ = -M_PI / 2;
        }
    }
    else {
        aroundZ = atan2(y, (double)xx);
    }
    float aroundY;
    aroundY = atan2(-z, x);

    aroundZ *= 180.0 / M_PI;
    aroundY *= 180.0 / M_PI;

    //-- rotate around world's axis, and not vtkProp3D axis (do not use RotateZ(..) and RotateY(..)
    if (fabs(aroundZ) > 1e-10) {
        myActor->RotateWXYZ(aroundZ, 0.0, 0.0, 1.0);
    }
    if (fabs(aroundY) > 1e-10) {
        myActor->RotateWXYZ(aroundY, 0.0, 1.0, 0.0);
    }

    //-- set back original position
    myActor->SetPosition(pos[0], pos[1], pos[2]);

}


}
