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

#include "Component.h"
#include "MeshSelectionModel.h"

#include <QTableView>

class vtkPointSet;
class vtkSelection;
class vtkSelectionNode;

class QTableView;
class QComboBox;

namespace camitk {

class MeshDataModel;
class MeshDataView;
class MeshSelectionView;

/**
 * @ingroup group_sdk_libraries_core_component_mesh
 *
 * @brief
 * Basic component to manage any kind of mesh.
 */
class CAMITK_API MeshComponent : public Component {
    Q_OBJECT
public:

    /// @enum DataType Data fields can have different dimensions
    enum DataType {
        SCALARS = 1,  ///< 1D (scalar value)
        VECTORS = 2,  ///< 3D (3D vector)
        TENSORS = 4,  ///< 9D (3x3 matrix)
        OTHERS = 8    ///< other dimensions (warning: nothing special are managed by this class, no specific interaction)
    };

    /// @enum FieldType Data fields can be applied to one of this
    enum FieldType {
        POINTS = 1,  ///< data are attached to point
        CELLS = 2,   ///< data are attached to cells
        MESH = 4     ///< data are attached to the whole mesh (generic field data of Vtk)
    };

    /// @enum SpecificRepresentation 3D data can be represented by 1 value in different ways
    enum SpecificRepresentation {
        VECTOR_3D,          ///< 3D data are represented with 3D arrows or hedge hog (simple lines starting from the point or center of the cell) depending on current 3D Vector representation)
        NORM,               ///< 3D data are represented in 1D using the norm of the three components
        FIRST_COMPONENT,    ///< Use only the value of the first component
        SECOND_COMPONENT,   ///< Use only the value of the second component
        THIRD_COMPONENT,    ///< Use only the value of the third component
        COLOR               ///< 3D data are represented as a specific color - TODO need to build a specific color map LUT containing as many colors as there is point/cell
    };

    ///@enum VectorRepresentation how are vector data represented in 3D
    enum VectorRepresentation {
        HEDGE_HOG,         ///< 3D data are represented with hedge hog (simple lines starting from the point or center of the cell)
        ARROW,             ///< 3D data are represented with 3D arrows (nicer hedge hog)
        UNSCALED_ARROW     ///< same as arrow, but with a scale factor equals to 10% of the bounding radius
    };

    /** Creates a top-level MeshComponent from a file.
     *  \note this is only to be used from a Component Extension open(...) or from an Action that creates data from a filter or transformation of a vtkPointSet.
     *  This method may throw an AbortException if a problem occurs.
     *
     *  Please consider using MeshComponent(vtkSmartPointer<vtkPointSet> , const QString &)
     */
    MeshComponent(const QString& file, const QString& name = "Mesh");

    /** Creates a top-level MeshComponent from a vtkPointSet (and instantiate its 3D representation).
     *
     * \note the filename is set to the empty string "".
     *
     * \note if aPointSet is nullptr, the representation is not initialized, it is the responsibility of the subclass to initialize it later
     *
     * @param aPointSet point set of the new MeshComponent
     * @param name name to be given to the Component
     */
    MeshComponent(vtkSmartPointer<vtkPointSet> aPointSet, const QString& name);

    /** Creates a MeshComponent as a sub component of another Component using a vtkPointSet (and instantiate its 3D representation).
     *
     * \note if aPointSet is nullptr, the representation is not initialized, it is the responsibility of the subclass to initialize it later
     *
     * @param parentComponent the parent component of the new MeshComponent
     * @param aPointSet point set of the new MeshComponent
     * @param name name to be given to the new MeshComponent
     */
    MeshComponent(Component* parentComponent, vtkSmartPointer<vtkPointSet> aPointSet, const QString& name);

    /// Destructor
    ~MeshComponent() override;

    /// set the component name and update frame name
    void setName(const QString&) override;

    /// reimplemented to save the last pick point id
    void pointPicked(vtkIdType pointId, bool) override;

    /// reimplemented to save the last pick point id
    void cellPicked(vtkIdType cellId, bool) override;

    /// get the last pick point id, @return -1 if no point where picked
    vtkIdType getPickedPointId();

    /// get the last pick point id, @return -1 if no point where picked
    vtkIdType getPickedCellId();

    /// there is more than one property widgets (to view as tabs in the property explorer)
    unsigned int getNumberOfPropertyWidget() override;

    /// the proprety widgets are: default property widget, selection view and data view
    QWidget* getPropertyWidgetAt(unsigned int i = 0) override;

    /// Get the pixmap that will be displayed for this node
    QPixmap getIcon() override;

    /**
     * @name Selection management
     * @see http://www.kitware.com/media/html/SelectionsInVTK.html
     *
     * The selection are stored using a list of vtkSelectionNode.
     * Since this list is managed through the a Qt model/view,
     * please do not modify directly the vtkSelectionNode and use
     * the methods dedicated to selections.
     *
     * @{
     */

    /**
     * @brief Get the selection list.
     *
     * The selection list contains vtkSelectionNode.
     *
     * \note TODO This method should be const, in case a subcomponent needs to add extra/specific behavior.
     * And then:
     * - To loop over selections, use getNumberOfSelections() and getSelectionAt().
     * - To remove selection, use removeSelection().
     * - To add selections, use addSelection() or addToSelectedSelection().
     *
     * @return the selection list
     */
    QList< vtkSmartPointer<vtkSelectionNode> >& getSelections();

    /**
     * @brief Get the number of selections.
     *
     * @return the number of selections.
     */
    unsigned int getNumberOfSelections() const;

    /**
     * @brief Get active selections
     *
     * @return active selections
     */
    vtkSmartPointer<vtkSelection> getActiveSelection() const;

    /**
     * @brief Get a selection from its name.
     *
     * TODO This method should return a const ref, so that external code cannot modify it
     *
     * @param name name of the selection
     * @return the selection node
     */
    vtkSmartPointer<vtkSelectionNode> getSelection(const QString& name) const;

    /**
     * @brief Get a selection from its index in the list.
     *
     * TODO This method should return a const ref, so that external code cannot modify it
     *
     * @param index index of the selection
     * @return the selection node
     */
    vtkSmartPointer<vtkSelectionNode> getSelectionAt(unsigned int index) const;

    /**
     * @brief Get the selection index in the list from its name.
     *
     * @param name name of the selection
     * @return the selection index or -1 if there is no selection of that name
     */
    int getSelectionIndex(const QString& name) const;

    /**
     * @brief Add a selection.
     *
     * If the name of the selection already exists, the data of the existing
     * selection are updated according to the SelectionPolicy flag.
     *
     * @param name name of the selection
     * @param fieldType field type of the selection (one of vtkSelectionNode::SelectionField)
     * @param contentType content type (one of vtkSelectionNode::SelectionContent)
     * @param array array of the selection
     * @param policy policy to update the existing selection
     * @return the index of the added selection in the selection list
     */
    virtual int addSelection(const QString& name, int fieldType, int contentType,  vtkSmartPointer< vtkAbstractArray > array, MeshSelectionModel::InsertionPolicy policy = MeshSelectionModel::REPLACE);

    /**
     * @brief Add a selection to the currently selected selection.
     *
     * If there is no selected selection, one is created with the name "Picked Selection".
     *
     * @param fieldType field type of the selection (one of vtkSelectionNode::SelectionField)
     * @param contentType content type (one of vtkSelectionNode::SelectionContent)
     * @param array array of the selection
     * @param policy policy to updated the selection selection
     * @return the index of the selected selection in the selection list
     */
    virtual int addToSelectedSelection(int fieldType, int contentType,  vtkSmartPointer< vtkAbstractArray > array, MeshSelectionModel::InsertionPolicy policy = MeshSelectionModel::REPLACE);

    /**
     * @}
     */

    /**
     * @name Data management
     * @{
     */

    /** @brief Get the number of data arrays of a given type without taking the specific representation into account.
     *
     *  This method does not take into account:
     *  - the field arrays
     *  - the specific representation of 3D data (i.e., representation of 3D data as norm or component#i values)
     *
     *  @param fieldFlag is a FieldType or a combinaison of field types.
     *  @returns the number of arrays corresponding to the field flag
     */
    int getNumberOfDataArray(int fieldFlag = POINTS | CELLS);

    /**
     * @brief Get the data array of specified field type and name.
     *
     * @param fieldType field type
     * @param arrayName array name
     *
     * @return data array
     */
    vtkSmartPointer<vtkDataArray> getDataArray(FieldType fieldType, const QString& arrayName);

    /**
     * @brief Get the data array of specified field type and index.
     *
     * @param fieldType field type
     * @param index index
     *
     * @return data array
     */
    vtkSmartPointer<vtkDataArray> getDataArray(FieldType fieldType, int index);

    /**
     * @brief Add a data array.
     *
     * @param fieldType field type
     * @param name name given to the array to add
     * @param data data array
     */
    void addDataArray(FieldType fieldType, const QString& name, vtkSmartPointer<vtkDataArray> data);

    /**
     * @brief Remove a data array.
     *
     * @param fieldType field type
     * @param name name of the array to remove
     */
    void removeDataArray(FieldType fieldType, const QString& name);

    /**
     * @brief Add a data array linked to the points.
     *
     * @param name name
     * @param data data array
     */
    void addPointData(const QString& name, vtkSmartPointer<vtkDataArray> data);

    /**
     * @brief Add a data array linked to the cells.
     *
     * @param name name
     * @param data data array
     */
    void addCellData(const QString& name, vtkSmartPointer<vtkDataArray> data);

    /// Returns the current data view model (model as the M in Qt MVC design pattern)
    MeshDataModel* getDataModel();

    /// Returns the corresponding vtkFieldData (point data, cell data or field data)
    vtkSmartPointer<vtkFieldData> getFieldData(FieldType);

    /// get the current visibility status of a given data (identified with its name) of a given field type
    bool getDataRepresentationVisibility(FieldType, const QString&) ;

    /// set the visibility of a given representation for a given data (identified by its name) of a given field type (create it if needed)
    /// By default this->refresh() is called unless blockRefresh is set to true
    void setDataRepresentationVisibility(FieldType fieldType, const QString&  name, bool visibility, bool blockRefresh = false);

    /// hide all the data representation of a given data type (hide all by default)
    /// By default this->refresh() is called unless blockRefresh is set to true
    void setDataRepresentationOff(int dataType = SCALARS | VECTORS | TENSORS, bool blockRefresh = false);

    /**
     * @name Enum management and helper methods
     * @{
     */
    /// static method that returns the FieldType enum as a QString
    static const QMap< int, QString >& getFieldNames();

    /// helper method that returns the field type as a string
    static const QString getFieldName(const FieldType);

    /// static method that returns the DataType enum as a QString
    static const QMap< int, QString >& getDataTypeNames();

    /// Helper method that returns the datatype as a string
    static const QString getDataTypeName(const DataType);

    /// Returns the data type of a data array depending on the number of components of the given data array:
    static const DataType getDataType(vtkSmartPointer<vtkDataArray>);

    /** Returns the data type string of an data array depending on the number of components of the given data array:
     *  If the number of components is different than 1, 3 and 9 (the managed type SCALARS, VECTORS, TENSORS)
     *  then the method returns a string equals to the number of components.
     */
    static const QString getDataTypeName(vtkSmartPointer<vtkDataArray>);

    /// Helper method that returns the SpecificRepresentation as a QString
    static const QString getSpecificRepresentationName(const SpecificRepresentation);

    /**
     * @}
     */


    /**
     * * @name InterfacePersistence
     * Customized InterfacePersistence methods to support geometry data (e.g. color)
     */
    ///@{
    /// Convert all data from the object to a QVariant (usually a QVariantMap)
    virtual QVariant toVariant() const override;

    /// Load data from a QVariant to initialize the current object
    virtual void fromVariant(const QVariant&) override;
    ///@}


protected:

    /// build the instance of Geometry from the given vtkPointSet
    void initRepresentation(vtkSmartPointer<vtkPointSet>);

    /// initialize selections
    void initSelection();

    /// initialize data
    void initData();

    /// create and initialize dynamic properties
    void initDynamicProperties();

protected slots:

    /// called when the selection is modified
    void changeSelectedSelection(const QItemSelection& selected, const QItemSelection& deselected);

    /// remove the selected selection
    void removeSelectedSelections();

    /// remove the selected selection
    void removeSelectedData();

    /// called when the datatype for vector data is modified (refresh the data model)
    void displayTypePolicyChanged(int);

    /// called when the representation for vector data is modified (remove all actors and refresh the data model)
    void vectorRepresentationPolicyChanged(int);

private:

    /// the concrete building of the 3D objects (Slice/Geometry): none in this case, everything is done by initRepresentation(vtkPointSet)
    void initRepresentation() override final {}

    /// this bool is set to true during initRepresentation(vtkPointSet) and is used to avoid unnecessary calls to refresh()
    bool isInInitRepresentation;

    /// initialisation of the mesh component members
    void init();

    /// the initial frame created by this mesh (useful in case renaming may also rename the frame)
    FrameOfReference* initialFrameOfReference;

    /// the last picked point
    vtkIdType pickedPointId;

    /// the last picked cell
    vtkIdType pickedCellId;

    /// manages current selection using vtk
    vtkSmartPointer<vtkSelection> currentSelection;

    /// list of selections
    QList< vtkSmartPointer<vtkSelectionNode> > selectionList;

    /// selection model (model as the M in Qt MVC design pattern)
    MeshSelectionModel* selectionModel;

    /// selection GUI View (view as the V in Qt MVC design pattern)
    MeshSelectionView* selectionView;

    /// selection widget
    QWidget* selectionWidget;

    /// action to remove selections
    QAction* removeSelections;

    /// action to merge selections
    QAction* mergeSelection;

    /// action to inspect selection
    QAction* inspectSelection;

    /// combo box to select the selection insertion policy
    QComboBox* insertionPolicyBox;

    /// data model (model as the M in Qt MVC design pattern)
    MeshDataModel* dataModel;

    /// data GUI View (view as the V in Qt MVC design pattern)
    MeshDataView* dataView;

    /// selection widget
    QWidget* dataWidget;

    /// combo box to select how to display vector data (data with 3 components)
    QComboBox* displayTypePolicyBox;

    /// combo box to select how the vector are represented in 3D
    QComboBox* vectorRepresentationPolicyBox;

    /** map of specific 3D Data representations.
     *  The value of key is the specific array name that concatenate the 3D data name and the specific representation name.
     */
    QMap<QString, vtkSmartPointer<vtkDataArray> > specific3DDataRepresentation;

    /// number of specific 3D representation for cell data
    unsigned int numberOfCellDataSpecificRepresentation;

    /// map of visibility status of data
    QMap<vtkSmartPointer<vtkDataArray>, bool> dataRepresentationVisibility;

    /// list of all vector data 3D actors' names (needed for cleaning up)
    QStringList vectorActors;

    /// create the data representation of a given data (identified with its name) of a given field type, default visibility is off
    void createDataRepresentation(FieldType, const QString&, SpecificRepresentation representation = VECTOR_3D) ;

    // Returns the very specific name used for the additional prop that represent this data
    const QString getDataPropName(FieldType, const QString&);

    /// show the specific scalar array
    void setScalarDataRepresentationOn(vtkSmartPointer<vtkDataArray>);

    /// initialize FieldType QString map
    static QMap< int, QString > initFieldNames();

    /// initialize DataType QString map
    static QMap< int, QString > initDataNames();

    /// action to remove data
    QAction* removeData;

    /// action to inspect data
    QAction* inspectData;

};

}

#endif // MESHCOMPONENT_H
