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

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

#include <QString>
#include <QUuid>
#include <QColor>
#include <QPair>

namespace camitk {

class TransformationManager;

/**
 * Unit is a type that defines the unit used by data (e.g. voxel size)
 *
 * It is a string following the Unified Codes for Units of Measurement
 * standard (https://ucum.org) also used by DICOM.
 */
using Unit = QString;

/**
 * FrameOfReference is only a label for an abstract coordinate system.
 * It is used as origins and destinations of transformations.
 *
 * There is no information stored in a FrameOfReference except
 * - an ID,
 * - a name and description
 * - a number of dimensions (between 1 and 5, default is 3) and units ("mm" for the first 3 dimensions)
 * - a AnatomicalOrientation that can describe whether there is a known anatomical
 * orientation associated to each axis (@see AnatomicalOrientation)
 * - a color (used in the transformation graph visualisation)
 *
 * All constructors are protected, use TransformationManager to create a
 * new instance.
 *
 * @see Transformation, TransformationManager, AnatomicalOrientation
 *
 */
class CAMITK_API FrameOfReference: public InterfacePersistence {

private:

    /// TransformationManager is the only class allowed to instantiate FrameOfReference
    friend TransformationManager;

    /**
     * Empty FrameOfReference Constructor.
     * This constructor should only be used to create a temporary FrameOfReference which UUid should
     * be later set to a valid value.
     *
     * @warning The UUID of this frame is Null, it must be set to a valid value before use!
     */
    FrameOfReference();

    /**
     * Basic Frame Constructor
     * @warning The UUID of this frame is Null, it must be set to a valid value before use!
     */
    explicit FrameOfReference(QString name, QString description = "");

    /**
     * Standard Frame Constructor
     * This constructor needs explicit values for all members. For the uuid, you can generate a new uuid by calling QUuid::create()
     */
    FrameOfReference(QUuid uuid, QString name, QString description, int numberOfDimensions, const AnatomicalOrientation& ao, std::vector<Unit>& units);

    /// copy constructor should copy all the members but reset Uuid, index and color
    explicit FrameOfReference(const FrameOfReference&);

    /**
     * Reset the unique identifier even if it is already set.
     * This is private because this is necessary only for TransformationManager creating a copy of a FrameOfReference
     * @return True if the ID was changed, false if it was not (because it was already set)
     */
    bool resetUuid(QUuid newId);

public:
    /**
     * Get the anatomical information of the Frame
     */
    const AnatomicalOrientation& getAnatomicalOrientation() const;

    /**
     * Get the anatomical information of the Frame (non-const version)
     */
    AnatomicalOrientation& getAnatomicalOrientation();

    /**
     * Set anatomical orientation information
     */
    void setAnatomicalOrientation(const AnatomicalOrientation& anatomicalOrientation);

    /**
     * Set anatomical orientation information.
     *
     * @param threeLetterCode Set a standard 3-letter orientation code
     *  (e.g. "RAI" for X axis from Right to Left, Y axis from Anterior to Posterior,
     *  Z axis from Inferior to Superior)
     */
    void setAnatomicalOrientation(QString threeLetterCode);

    /**
     * Get the number of dimensions of this FrameOfReference
     */
    int getNumberOfDimensions() const;

    /**
     * Set the number of dimensions of this FrameOfReference
     *
     * Usually, the first three dimensions are spatial coordinates,
     * the 4th one is time, and other dimensions may be used to represent
     * other data type within the same 5D volume
     */
    void setNumberOfDimensions(int numberOfDimensions);

    /// get the unit of the given dimension if dimension is valid otherwise returns an invalid Unit
    Unit getUnit(int dimension);

    /**
     * Set the unit of one dimension
     * @param u should be a string following the Unified Codes for Units of Measurement standard (https://ucum.org) also used by DICOM. Common values are "mm" for space, "s" for time
     */
    void setUnit(int dimension, Unit u);

    /**
     * Get the FrameOfReference name
     */
    QString getName() const;

    /**
     * Set the name of the FrameOfReference
     * Should be a short string, use setDescription if you want to store a more
     * detailed description of the FrameOfReference
     */
    void setName(QString name);

    /**
     * Get the description of the FrameOfReference.
     * Optional string, more detailed than the name
    */
    QString getDescription() const;

    /**
     * Set the description of the FrameOfReference.
     * Optional string, more detailed than the name
    */
    void setDescription(QString desc);

    /**
     * Get the color for graphical representation of the FrameOfReference
     * Set the color the first time this is called to avoid "loosing" colors in the palette.
     */
    const QColor& getColor();

    /**
     * Set a color for graphical representation of the FrameOfReference
     */
    void setColor(const QColor& color);

    /**
     * Get the Anatomical orientation label of the corresponding axis/direction (or empty string if there is no label).
     *
     * @param axis The axis index (0,1,2)
     * @param minDirection if true, return the label of the minimum/negative direction, otherwise return the label of the maximum/position direction for the given axis
     */
    QString getAnatomicalOrientationLabel(int axis, bool minDirection) const;

    /**
     * Equality operator
     *  @return If the Uiids are the same, return true, even if the anatomical orientation or description do not match, otherwise false
     */
    bool operator==(const FrameOfReference& b) const;

    /**
     * Difference operator
     *  @return True if the Uiids are different, otherwise false
     */
    bool operator!=(const FrameOfReference& b) const;

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

    /**
     * Load the Frame content from a QVariant
     */
    void fromVariant(const QVariant& variant) override;

    /**
     * Set the unique identifier only if it is not already set
     * @return True if the ID was changed, false if it was not (because it was already set)
     */
    bool setUuid(QUuid newId) override;

    /**
     * Get the unique identifier of the Frame
     */
    QUuid getUuid() const override;
    /// @}

    /**
     * Get the index (a non-unique ID useful for user interaction)
     * indexes start at 1
     */
    int getIndex();


private:
    /// Anatomical information for the frame axes
    AnatomicalOrientation anatomicalOrientation;

    /// units, usually {"mm", "mm", "mm", "s"}, its size must be equal to numberOfDimensions
    std::vector<Unit> units = {"mm", "mm", "mm", "s", ""};

    /// Number of dimensions, often 3, sometimes 4 for time
    int numberOfDimensions = 3;

    /// Unique identifier, essential for Persistence
    QUuid uuid;

    /// Index number intended for user interaction (simpler than UUID)
    /// index value of 0 means the color and index is not initialized yet
    int index;

    /// name of the FrameOfReference
    QString name;

    /// description of the FrameOfReference
    QString description;

    /// Color for a graphical representation of the Frame
    /// The initial color is invalid, and will be attributed only when getColor() is called the
    /// first time, this will avoid to jump over colors in the palette for unused frame (e.g.,
    /// when a workspace file is read)
    QColor color;

    // init color and index (only when required, i.e. in getIndex()/getColor() and not during initialization)
    void init();

    /// get the next available color (used for frame colors) and the associated index
    static QPair<QColor, int> getNextColorAndIndex();
};

// -------------------- getAnatomicalOrientation --------------------
inline const AnatomicalOrientation& FrameOfReference::getAnatomicalOrientation() const {
    return anatomicalOrientation;
};

inline AnatomicalOrientation& FrameOfReference::getAnatomicalOrientation() {
    return anatomicalOrientation;
};

// -------------------- setAnatomicalOrientation --------------------
inline void FrameOfReference::setAnatomicalOrientation(const AnatomicalOrientation& anatomicalOrientation) {
    this->anatomicalOrientation = anatomicalOrientation;
}

// -------------------- setAnatomicalOrientation --------------------
inline void FrameOfReference::setAnatomicalOrientation(QString threeLetterCode) {
    anatomicalOrientation.setOrientation(threeLetterCode);
}

// -------------------- getNumberOfDimensions --------------------
inline int FrameOfReference::getNumberOfDimensions() const {
    return numberOfDimensions;
}

// -------------------- getName --------------------
inline QString FrameOfReference::getName() const {
    return name;
}

// -------------------- setName --------------------
inline void FrameOfReference::setName(QString name) {
    this->name = name;
}

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

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

// -------------------- getAnatomicalOrientationLabel --------------------
inline QString FrameOfReference::getAnatomicalOrientationLabel(int axis, bool minDirection) const {
    return anatomicalOrientation.getLabel(axis, minDirection);
}

// -------------------- operator== --------------------
inline bool FrameOfReference::operator==(const FrameOfReference& b) const {
    return this->getUuid() == b.getUuid();
}

// -------------------- operator!= --------------------
inline bool FrameOfReference::operator!=(const FrameOfReference& b) const {
    return this->getUuid() != b.getUuid();
}

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

}

#endif // FRAMEOFREFERENCE_H
