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

#ifndef CAMITK_TRANSFORMATION_H
#define CAMITK_TRANSFORMATION_H

// -- Core stuff
#include "CamiTKAPI.h"
#include "InterfacePersistence.h"

// -- Qt stuff
#include <QString>
#include <QUuid>

// -- VTK stuff
#include <vtkMatrix4x4.h>
#include <vtkAbstractTransform.h>
#include <vtkTransform.h>
#include <vtkSmartPointer.h>

#include <memory>

namespace camitk {

class TransformationManager;
class FrameOfReference;

/**
 * Transformation represents a geometrical transformation between two FrameOfReferences
 *
 * It supports linear and non-linear transforms stored in a vtkTransform (linear) or any
 * vtkAbstractTransform (non-linear)
 *
 * It has a direction (from a FrameOfReference to another FrameOfReference)
 *
 * Its constructor is private as Transformation objects must only be created through
 * TransformationManager::getTransformationManager() (although it is possible to instantiate your own
 * TransformationManager if you know what you're doing!)
 *
 * @warning
 * Transformation are instantiated/stored/managed/destroyed by TransformationManager::getTransformationManager(), therefore
 * you should not keep a pointer to any Transformation, just call TransformationManager::getTransformationOwnership(..)
 * when you need to access it. This guarantees the coherence of the complete reference system and avoid dangling pointers and
 * memory leaks.
 *
 * \code {.cpp}
...
    FrameOfReference* from = TransformationManager::addFrameOfReference("Source Frame");
    FrameOfReference* to = TransformationManager::addFrameOfReference("Destination Frame");
    Transformation* t = TransformationManager::addTransformation(from, to);
    ...
    t->setMatrix(...);
   // note: t MUST not be a member
   // call TransformationManager::getTransformationOwnership(from, to) to access it later on
 * \endcode
 *
 * @see TransformationManager
 */
class CAMITK_API Transformation: public InterfacePersistence {

private:
    /// Users can only create transformations through a TransformationManager
    friend TransformationManager;

    /**
     * Create an identity transformation between two instances of FrameOfReference
     */
    Transformation(const std::shared_ptr<FrameOfReference>& from, const std::shared_ptr<FrameOfReference>& to, QUuid id = QUuid::createUuid());

    /**
     * Create a Transformation between two instances of FrameOfReference with the provided linear vtkTransform
     */
    Transformation(const std::shared_ptr<FrameOfReference>& from, const std::shared_ptr<FrameOfReference>& to, vtkSmartPointer<vtkTransform> tr, QUuid id = QUuid::createUuid());

    /**
     * Sets the Transformation to a linear transformation using the matrix m
     * @warning: this will deep copy the matrix (so Transformation will not be affected
     * if the provided matrix is changed after the function is called)
     */
    void setMatrix(vtkSmartPointer<vtkMatrix4x4> m);

public:

    /**
     * Get the internal vtkTransform (linear transformation) or a nullptr
     *
     * Note: this method should return a vtkSmartPointer to a const vtkTransform
     * Unfortunately, at some stage in some part of your VTK pipeline, you might
     * need a non-const vtkTransform.
     *
     * \warning You should only use this method to send the vtkTransform to a vtk method.
     * \warning **NEVER** use this method to modify the content of the matrix directly, as
     * it might generate inconsistencies in the transformation management.
     * If you need to change the values you **MUST** use TransformationManager::updateTransformation(..)
     */
    vtkSmartPointer<vtkTransform> getTransform() const;

    /**
     * Get the internal 4x4 matrix if the Transformation is linear, otherwise nullptr
     *
     * Note: this method should return a pointer to a const vtkMatrix4x4
     * Unfortunately, at some stage in some part of your VTK pipeline, you might
     * need a non-const vtkMatrix4x4 (e.g. to setup a vtkActor).
     *
     * \warning You should only use this method to send the vtkMatrix4x4* to a vtk method.
     * \warning **NEVER** use this method to modify the content of the matrix directly, as
     * it might generate inconsistencies in the transformation management.
     * If you need to change the values you **MUST** use TransformationManager::updateTransformation(..)
     */
    vtkMatrix4x4* getMatrix() const;

    /**
     * Get the name of the Transformation
     */
    QString getName() const;

    /**
     * Set the name of the Transformation
     */
    void setName(QString n);

    /**
     * @brief Get the description of the Transformation
     * Description is used to provide more information than the name (e.g. method used to compute it)
     */
    QString getDescription() const;

    /**
     * @brief Set the description of the Transformation
     * Description is used to provide more information than the name (e.g. method used to compute it)
     */
    void setDescription(QString desc);

    /**
     * Get the FrameOfReference the Transformation starts from (origin)
     */
    const FrameOfReference* getFrom() const;

    /**
     * Get the FrameOfReference that the Transformation goes to (destination)
     */
    const FrameOfReference* getTo() const;

    /// @name Implementation of InterfacePersistence
    /// @{
    /**
     * Convert the Transformation to a QVariant (for serializing)
     */
    QVariant toVariant() const override;

    /**
     * Fill the Transformation from a QVariant
     */
    void fromVariant(const QVariant& v) override;

    /**
     * Get the unique identifier of this Transformation
     */
    QUuid getUuid() const override;

    /**
     * Set the unique identifier of this transformation if the current one is Null
     * @warning This should almost never be used except when loading a Transformation from a camitk file
     * @return true if the UUID was changed, false if it was not (because it already had a non-null value)
     */
    bool setUuid(QUuid id) override;

    /// @}

private:

    /**
     * Create and return the inverse transformation (if this Transformation is inversible)
     *
     * This function creates a raw Transformation* and the caller is the owner of the Transformation object
     * (e.g. responsible for deleting it)
     */
    Transformation* getInverse();

    /**
     * Set a vtkTransform.
     *
     * This will use the vtkMatrix from the vtkTransform, not the vtkTransform itself
     * So if the vtkTransform is updated later, the Transformation will not update
     */
    void setTransform(vtkSmartPointer<vtkTransform> tr);

    /**
     * Set the destination FrameOfReference
     */
    void setTo(const std::shared_ptr<FrameOfReference>& f);

    /**
     * Set the origin FrameOfReference
     */
    void setFrom(const std::shared_ptr<FrameOfReference>& f);

private:

    /// @brief  Unique identifier, used for serialization with InterfacePersistence
    QUuid uuid;

    /// @brief Name, a short description
    QString name;

    /// @brief A more detailed description than provided by the name
    QString description;

    /// Whether this Transformation's inverse can be computed
    bool inversible;

    /// @brief Internal linear transformation
    vtkSmartPointer<vtkTransform> transform;

    /// @brief Origin FrameOfReference, where the Transformation starts from
    std::shared_ptr<FrameOfReference> from;

    /// @brief Destination FrameOfReference, where the Transformation goes to
    std::shared_ptr<FrameOfReference> to;
};

// -------------------- getTransform --------------------
inline vtkSmartPointer<vtkTransform> Transformation::getTransform() const {
    return transform;
}

// -------------------- setName --------------------
inline void Transformation::setName(QString n) {
    name = n;
}

// -------------------- getDescription --------------------
inline QString Transformation::getDescription() const {
    return description;
}

// -------------------- setDescription --------------------
inline void Transformation::setDescription(QString desc) {
    description = desc;
}

// -------------------- getUuid --------------------
inline QUuid Transformation::getUuid() const {
    return uuid;
};

// -------------------- getFrom --------------------
inline const FrameOfReference* Transformation::getFrom() const {
    return from.get();
}

// -------------------- setFrom --------------------
inline void Transformation::setFrom(const std::shared_ptr<FrameOfReference>& f) {
    from = f;
}

// -------------------- getTo --------------------
inline const FrameOfReference* Transformation::getTo() const {
    return to.get();
}

// -------------------- setTo --------------------
inline void Transformation::setTo(const std::shared_ptr<FrameOfReference>& f) {
    to = f;
}


} // namespace camitk

#endif // CAMITK_TRANSFORMATION_H