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

// -- Core stuff
#include "SingleImageComponent.h"
#include "ImageComponent.h" // for friend fromVariant method

// -- QT stuff classes
#include <QVector3D>

namespace camitk {

/**
 * @ingroup group_sdk_libraries_core_component_image
 *
 * @brief
 * This Component manages the specific case of arbitrary orientation of a sub-component of the image component.
 *
 * Arbitrary slice frame is used to position and orientate the slice inside the parent image volume.
 *
 * It does have a Slice representation (InterfaceBitMap), not a Geometry.
 *
 * To change the orientation and translation of the arbitrary slice, modify the corresponding properties:
 * - "Translation" should be a double number between 0.0 and 100.0 (see note below)
 * - "Rotation" is a 3D vector (QVector3D, i.e. 3 float values) representing the current rotation in the inner
 * frame (note: the origin of the inner frame is always set at the arbitrary/resliced image center)
 *
 * These properties allow the displacement and change of orientation but constrained it
 * so that the center of the arbitrary slice is always inside the parent ImageComponent
 * volume.
 *
 * For arbitrary slice, the default transform is set to be on the z plane
 * but in the middle of the volume.
 *
 * \note translation, slice index and number of slices
 * - For the arbitrary slice, the slice index is constrained to [0..100] and corresponds to the
 * percentage of translation inside the current orientation image bounds (transaltion along the vector that is
 * perpendicular to the current orientation of the arbitrary slice and bounded by the 3D image boundary).
 * - The "Translation" property is therefore expressed as a percentage value
 * - The initial position of the frame, while having no rotation, has therefore a translation equals to (50%, 50%, 50%)
 * - The "Translation" property is therefore initialized at z = 0.5 = 50%
 * - setSlice(int) on arbitrary slice considers the slice as percentage
 * - setSlice(int) updates the current translation value (slice index is converted to percent in volume translation)
 * - getSlice() returns the current value of the Translation property converted to a value in [0..100]
 * - getNumberOfSlices always returns 100
 *
 * The main frame of this component is set to the image component (parent component) data frame.
 * The arbitrary frame manages the transformation from the current arbitrary orientation cutting plane
 * to the main frame (i.e., the image data frame).
 *
 * Note that when a rotation is applied, the translation property might be updated with respect to the
 * new orientation (sometimes the translation value will flicker between two position when the Z axis of
 * the plane intersect two different external "face" of the image volume cube).
 *
 * In the source code, the following naming convention is used for the maths:
 * - transformation matrices are noted T
 * - T_a2m is the transformation matrix from the arbitrary frame to the main frame coordinate system (i.e., to the image component data frame)
 * - A_m is the coordinate of point A in the main frame coordinate system (i.e., in the image component data frame)
 * - A_a is the coordinate of point A in the arbitrary frame coordinate system
 * - AB_m is the vector AB described in the main frame coordinate system (i.e., in the image component data frame)
 * - AB_a is the vector AB described in the arbitrary frame coordinate system
 *
 */
class CAMITK_API ArbitrarySingleImageComponent : public camitk::SingleImageComponent {
    Q_OBJECT

public:
    /// Constructor
    ArbitrarySingleImageComponent(Component* parentComponent, const QString& name, vtkSmartPointer<vtkWindowLevelLookupTable> lut);

    /// Destructor
    virtual ~ArbitrarySingleImageComponent() override;

    /// @name overriden from Component to manage arbitrary orientation
    ///
    /// @{

    /// propertyValueChanged: when "Translation" and "Rotation" properties are modify, this will update the slice orientation and position
    virtual void propertyValueChanged(QString name) override;

    /// update the property values from the current arbitrary transformation
    /// (this can be called for instance when when a workspace is loaded)
    /// Note: the signals are blocked during this update
    virtual void updatePropertyFromTransformation();

    /// reset the transformation to the image component parent so that the frame
    /// of the arbitrary slice is in the center of the volume along the z axis by default
    virtual void resetTransform();

    /// set slice as a percentage on the z axis translation
    /// \note To set the slice using absolute value, please use updateTranslation and getTranslationInVolume instead
    virtual void setSlice(int) override;

    /// rewritten because the setSlice(int) method is overriden (compiler needs this)
    virtual void setSlice(double, double, double) override;

    /// return the slice as the percentage of translation (between 0 and 100)
    /// \note To set the slice using absolute value, please use updateTranslation and getTranslationInVolume instead
    virtual int getSlice() const override;

    /// always return 100 (as getSlice() gives a percentage value, the max number of slices is 100.
    /// \note To set the slice using absolute value, please use updateTranslation and getTranslationInVolume instead
    virtual int getNumberOfSlices() const override;
    ///@}

    /// @name Specific helper methods to manage arbitrary orientation and its representation
    ///
    /// @{

    /// Compute the current position of the cutting plane center in the image coordinate system
    /// (i.e., the data frame of the image component)
    /// \note center is filled as an homogeneous 3D position (last component is equals to 1.0)
    void getArbitraryCenter(double center[4]) const;

    /// Compute the current z-direction (normal vector) in the image coordinate system
    /// (i.e., the data frame of the image component)
    /// \note normalVector is filled as an homogeneous 3D vector (last component is equals to 0.0)
    void getArbitraryPlaneNormal(double normalVector[4]) const;
    ///@}

    /// Returns a 3D cross cursor vtkActor to show the picked voxel (override to give it in the right frame)
    vtkSmartPointer<vtkActor> get3DCursor() override;

    /// Returns the arbitrary transformation (from arbitraryFrame to main frame of this component, i.e.n the image data Frame)
    const Transformation* getArbitraryTransformation() const {
        return arbitraryTransformation.get();
    }

    /// Get the arbitrary frame that is located in the chosen arbitrary position
    const FrameOfReference* getArbitraryFrame() const {
        return arbitraryFrame.get();
    }

    /// @brief Set the ArbitraryFrame
    /// @warning you must update arbitraryTransform after changing the arbitrary frame so that the transform links the new arbitraryFrame to the image data frame
    void setArbitraryFrame(const std::shared_ptr<FrameOfReference>& fr) {
        arbitraryFrame = fr;
    }

    /// @brief Set the Frame
    /// @warning you must update arbitraryTransform after changing the arbitrary frame so that the transform links the new arbitraryFrame to the image data frame
    void setFrame(const std::shared_ptr<FrameOfReference>& fr) override;

    /// Get all FrameOfReference owned by this image
    /// @arg includeChildrenFrames Include the frames of this image's children along with its own
    ////@return A multimap that associates each FrameOfReference to the Components that own it
    virtual QMultiMap<const FrameOfReference*, Component*> getAllFrames(bool includeChildrenFrames) override;

    /// Get all Transformation owned by this image
    /// @arg includeChildrenTransformations Include the Transformation of this image's children along with its own
    ////@return A multimap that associates each Transformation to the Components that own it
    virtual QMultiMap<const Transformation*, Component*> getAllTransformations(bool includeChildrenTransformations) override;


private:
    /// the fromVariant method need to access initArbitraryTransformation(Transformation*) in order to load the exact
    /// same transformation from .camitk file
    friend void ImageComponent::fromVariant(const QVariant&);

    /// Initialize the arbitrary slice frame and the component's frame (the data image frame) and the transformation from the arbitrary frame to the image data frame.
    /// If no transformation is provided, create an identity transformation from arbitraryFrame to the image data frame.
    /// This function will also set the Slice reslicer transform to the same transform
    /// @return the transformation from the arbitrary frame to the image data frame (either the new one or tr).
    void initArbitraryTransformation(const std::shared_ptr<FrameOfReference>& arbitraryFrame, const std::shared_ptr<FrameOfReference>& dataFrame, const std::shared_ptr<Transformation>& tr = nullptr);

    /// reset the arbitrary transformation matrix as (center on x, center on y, 0), with no rotation
    void resetArbitraryTransformationMatrix();

    /// update the arbitrary slice translation inside the volume using the current "Translation"
    /// value (z translation in percent).
    /// This method is private and does modify the "Translation"/"Rotation" property values.
    /// Please use setPropertyValue("Translation", ...) if you want to translate the arbitrary slice.
    virtual void updateTranslation();

    /// update the arbitrary slice rotation
    /// This method is private and does modify the "Translation"/"Rotation" property values.
    /// Please use setPropertyValue("Rotation", ...) if you want to change the arbitrary slice orientation
    virtual void updateRotation();

    /// Get the current translation relatively to the volume and intersection extremity
    /// along the z axis of the arbitrary frame.
    ///
    /// @return 0.0 means that the center of the slice is at the first/min border of the image volume,
    /// 1.0 means that the center of the slice is at the last/max border of the image volume.
    double computeTranslationRatio() const;

    /// compute the intersections between the line that starts at the center of the arbitrary frame
    /// and is normal to the cutting plane (z axis of the arbitrary frame) with the image border.
    ///
    /// The intersections can be either in the front/back faces, left/right faces, or top/bottom faces
    /// of the image.
    ///
    /// \note
    /// - all coordinates must be expressed in the image data frame
    /// @param Cz_min is the intersection of the line of vector -z
    /// @param Cz_max is the intersection of the line of vector +z
    void computeIntersectionsWithImageBorders(QVector3D& Cz_min, QVector3D& Cz_max) const;

    /// compute the intersections of the line that starts at the given origin with the image borders.
    /// This compute the two intersections of origin ± upVector with the image borders
    /// The intersections can be either in the front/back faces, left/right faces, or top/bottom faces
    /// of the image
    ///
    /// \note
    /// - all coordinates must be expressed in the image data frame
    /// - upVector should be normalized
    /// - vector from intersectionMin to intersectionMax is the same direction as upVector
    ///
    /// @param origin is the point of origin of the intersecting line
    /// @param upVector is the vector that points upward
    /// @param intersectionMin is the intersection of the vector -upVector starting at point origin with the image border
    /// @param intersectionMax is the intersection of the vector +upVector starting at point origin with the image borders
    void computeIntersectionsWithImageBorders(const QVector3D& origin, const QVector3D& upVector, QVector3D& intersectionMin, QVector3D& intersectionMax) const;

    /// check if the center of the frame transformed usin the given matrix stays inside the initial image volume
    bool checkCenter(vtkSmartPointer<vtkMatrix4x4>) const;

    /// return true only in point is inside the image volume
    bool pointInsideVolume(QVector3D) const;

    /// dimension of the whole image (kept here for simplifying code)
    int* dimensions;

    /// spacing of the image (kept here for simplifying code)
    double* spacing;

    /// Custom FrameOfReference
    std::shared_ptr<FrameOfReference> arbitraryFrame;

    /// Custom Transformation that stores the transformation from arbitraryFrame -> frame (which is the data frame of the parent ImageComponent)
    std::shared_ptr<Transformation> arbitraryTransformation;

    /// @name Math utility methods
    /// TODO have this in a specific class?
    ///
    /// @{
    /// variadic method that enables clear expression of the transformation composition chain
    /// Instead of using vtkMatrix4x4::Multiply4x4 multiple times, these methods allow
    /// for expressing all transformation in one line.
    /// For instance:
    /// \code
    /// vtkSmartPointer<vtkMatrix4x4> intermediate1 = vtkSmartPointer<vtkMatrix4x4>::New();
    /// vtkMatrix4x4::Multiply4x4(a, b, intermediate1);
    /// vtkSmartPointer<vtkMatrix4x4> intermediate2 = vtkSmartPointer<vtkMatrix4x4>::New();
    /// vtkMatrix4x4::Multiply4x4(intermediate1, c, intermediate2);
    /// vtkSmartPointer<vtkMatrix4x4> resultingTransformation = vtkSmartPointer<vtkMatrix4x4>::New();
    /// vtkMatrix4x4::Multiply4x4(intermediate2, d, resultingTransformation);
    /// \endcode
    /// You can just write:
    /// \code
    /// vtkSmartPointer<vtkMatrix4x4> resultingTransformation = Multiply4x4(a, b, c, d);
    /// \endcode
    /// which result in a much clearer/cleaner way of expressing the transformation composition
    template<typename T> static vtkSmartPointer<vtkMatrix4x4> Multiply4x4(T, T);
    template<typename T, typename... Args> static vtkSmartPointer<vtkMatrix4x4> Multiply4x4(T a, T b, Args... args);

    /// compute the intersection between a line and a plane
    /// @see http://www.realtimerendering.com/intersections.html
    /// @param lineVector a vector in the direction of the line
    /// @param linePoint a point on the line
    /// @param planeNormal a normal vector to the plane
    /// @param planePoint a point on the plane
    /// @param intersection (output) the point of intersection between the line and the plane
    /// @return false if there is no intersection, true otherwise
    static bool linePlaneIntersectionPoint(QVector3D, QVector3D, QVector3D, QVector3D, QVector3D&);

    /// Round a float to 4 digits
    /// This is a great tool to avoid precision errors (when rotation/cos/sin are around, precision is difficult to maintain)
    static float roundTo4Decimals(float);

    /// Round a QVector3D to 4 digits
    /// This is a great tool to avoid precision errors (when rotation/cos/sin are around, precision is difficult to maintain)
    static QVector3D roundTo4Decimals(QVector3D);

    /// Clean matrix reset too small value to 0.0 and reset last line to 0 0 0 1 (homogeneous coord)
    /// It might be useful to use just before inverting as dividing by small values might
    /// creates artificially high floating values
    static void cleanMatrix(vtkSmartPointer<vtkMatrix4x4> matrixToClean, double epsilon = 1e-5);
    /// @}
};

}

#endif // ARBITRARYSINGLEIMAGEVOLUMECOMPONENT_H
