/*****************************************************************************
 * $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$
 ****************************************************************************/
#ifndef FRAME_H
#define FRAME_H

#include "CamiTKAPI.h"

#include "InterfaceFrame.h"

class vtkTransformPolyDataFilter;
class vtkAxesActor;

namespace camitk {
/**
 * Represents a hierarchy of frames.
 * Each frame is represented by its parent frame (by default the World frame) and
 * a linear transformation from its parent frame ; i.e.
 * the transformation to represents my points (expressed in my frame) in my parent's frame.
 *
 * Frames are in a tree hierarchy, in a top down way.
 *
 * The frame hierarchy only changes the REPRESENTATION of Components, not Components themselves
 * To obtain the real point position of child components, one should apply getWorldTransformation to them.
 *
 * When a frame moves, it should move from its parent, not from the world frame
 * (except if its parent frame is the world frame).
 *
 */

class CAMITK_API Frame : public InterfaceFrame {
public:
    /** Default constructor with transformation
     * @param transform is the vtk transformation to express the current frame coordinates into its parent frame.
     *      If no transform is givent the default transform is set to the identity matrix.
     * @param parentFrame is the base frame in the begin of the hierarchical system.
     *      If no parent is given, the default parent is set to NULL and considered as the World frame.
     */
    Frame(vtkSmartPointer<vtkTransform> transform = nullptr, Frame* parentFrame = nullptr);

    /// Default Destructor
    ~Frame() override;

    /**
     * Hierarchy accessors / Modifiers
     * @{
     */
    /// Get the Frame Unique identifier (can be set by user)
    const QString& getFrameName() const override final;

    /// Set the Frame Unique identifier
    void setFrameName(QString name) override;

    /// Get the parent frame
    InterfaceFrame* getParentFrame() const override;

    /**
     *  Set the parent frame to the parameter frame.
     *  During the parent transition you have the choice to keep or not the frame transform.
     *  If you keep the frame's initial transform after the parent transition, the frame's position will change as
     *  its transform's target frame changes.
     *  If you decide not to keep the initial frame's transform, this one is updated during the parent transition in order
     *  to keep the original frame position (in the world frame).
     *
     * Caution: if the parent frame in parameter is either the current frame
     * or one of its descendant, the parent frame is set to the world frame.
     */
    void setParentFrame(InterfaceFrame* parent, bool keepTransform = true) override final;

    /**
     *   Get the Children Frames from the current Frame in the Frame Hierarchy
     *    The Frame hierarchy may not be the same as the Component Hierarchy.
     */
    const QVector<InterfaceFrame*>& getChildrenFrame() const override;
    /** @} */

    /**
     * Transforms accessors / Modifiers
     * @{
     */
    /// Get a the transformation with respect to the world frame
    const vtkSmartPointer<vtkTransform> getTransformFromWorld() const override;

    /// Get a the transformation with respect to the parent frame
    const vtkSmartPointer<vtkTransform> getTransform() const override;

    /// Get a COPY of the transformation with respect to another frame
    const vtkSmartPointer<vtkTransform> getTransformFromFrame(InterfaceFrame* frame) const override;

    /**
     * Set the current input frame position (according to its parent Frame)
     * @param transform The 3D transform of the current frame to its parent.
     */
    void setTransform(vtkSmartPointer<vtkTransform> transform) override final;

    /**
     * Set the current frame transform to identity.
     * In other words, the current frame and its parent share the same 3D location.
     */
    void resetTransform() override;

    /**
     * Apply a translation relative to the current position
     */
    void translate(double x, double y, double z) override;

    /**
     * Apply rotations relative to the current position in the alphabetical order (X, Y, Z).
     * @note Prefer using the rotateVTK method if possible, this one involves errors when retrieving rotation angles
     * from a rotation matrix, as we use a VTK method for this.
     */
    void rotate(double aroundX, double aroundY, double aroundZ) override;

    /**
     * Apply a rotation relative to the current position, using the VTK rotation order (Z, X, Y)
     * @note Prefer using this method if you can.
     */
    void rotateVTK(double aroundX, double aroundY, double aroundZ) override;

    /**
     * Set the translation part of the 3D space transformation of the current frame.
     * @note Reminder: A 3D space transform is a 4x4 matrix, composed of a rotation and a translation.
     *
     */
    void setTransformTranslation(double x, double y, double z) override;

    /**
     * Set the translation part of the 3D space transformation of the current frame.
     * This translation uses the VTK rotation order (Z, X, Y) system.
     * @note Reminder: A 3D space transform is a 4x4 matrix, composed of a rotation and a translation.
     *
     */
    void setTransformTranslationVTK(double x, double y, double z) override;

    /**
     * Set the rotation part of the 3D space transformation of the current frame.
     * @note Reminder: A 3D space transform is a 4x4 matrix, composed of a rotation and a translation.
     *
     */
    void setTransformRotation(double aroundX, double aroundY, double aroundZ) override;

    /**
     * Set the rotation part of the 3D space transformation of the current frame.
     * This translation uses the VTK rotation order (Z, X, Y) system.
     * @note Reminder: A 3D space transform is a 4x4 matrix, composed of a rotation and a translation.
     *
     */
    void setTransformRotationVTK(double aroundX, double aroundY, double aroundZ) override;

    /** @} */

    /**
     * Gives the 3D representation of the frame (based on xyd arrows)
     */
    vtkSmartPointer<vtkAxesActor> getFrameAxisActor() override;

    ///@cond
    /**
     * TODO CAMITK_DEPRECATED. This section list all the methods marked as deprecated. They are to be removed in CamiTK 6.0
     * @deprecated
     *
     * DEPRECATED (CamiTK 6.0) -> to be removed
     * Use the setFrameVisibility(QString, bool) / getFrameVisibility(QString, bool) methods
     */
    /**
     * Set the Component Frame visible for a given viewer
     */
    CAMITK_API_DEPRECATED("Please use Frame::setFrameVisibility(QString viewerName, bool) instead") void setFrameVisibility(Viewer*, bool) override;

    /**
     * Get the Component Frame visibility for a given viewer
     */
    CAMITK_API_DEPRECATED("Please use Frame::getFrameVisibility(QString viewerName, bool) instead") bool getFrameVisibility(Viewer*) const override;
    ///@endcond

    /**
     * Set the Component Frame visible for a given viewer
     */
    void setFrameVisibility(QString, bool) override;

    /**
     * Get the Component Frame visibility for a given viewer
     */
    bool getFrameVisibility(QString) const override;

    /**
    * If the parent frame keeps track of its children, when a child is deleted
    * it should warn its parent
    */
    void removeFrameChild(InterfaceFrame* frame) override final;

    ///@{ Constructors helpers
    /// Private method to initialize private attributes (called only in constrctors but in all constructors)
    void initAttributes();
    ///@}

protected:

    /**
     * Frame unique identifier
     */
    QString frameName;

    /**
     *  The Parent Frame.
     *  If it is NULL, then the parent is the World Frame.
     */
    InterfaceFrame* parentFrame;

    /**
     * Transformation to represent my points (expressed in my frame) in my parent's frame.
     * Transformation to apply to this (and children) data to place them in my parent's frame.
     */
    vtkSmartPointer<vtkTransform> transformParentToMe;

    /**
     * Transformation to represent my points (expressed in my frame) in the world's frame.
     * i.e. Transformation to apply to this (and children) data to place them in the world's frame.
     *
     * This transformation is always re-calculated and stored only for convenience purpose.
     * It can not be set by a user.
     */
    vtkSmartPointer<vtkTransform> transformWorldToMe;

    /**
     * Transform Filter to store the transformation for the Frame's 3D representation.
     */
    vtkSmartPointer<vtkTransformPolyDataFilter> representationTransformFilter;

    /**
     * List of children Frames to keep track of Frame hierarchy
     */
    QVector<InterfaceFrame*> childrenFrame;

    /**
     * @brief Compute all the descendants of the input frame.
     */
    QVector<InterfaceFrame*> computeDescendants(InterfaceFrame*);

private:

    /// When the current Frame is set to parent, it must update its children list
    void addFrameChild(InterfaceFrame* frame) override;

    /// To be able to give a default unique identifier to each created frame
    static int nbTotalFrames;

    /// Geometric representation of the frame (vtkAxes)
    vtkSmartPointer<vtkAxesActor> axes;

    /// List of Viewers in which the frame is visible
    QMap<Viewer*, bool> frameViewers;

};
}
#endif // FRAME_H
