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

#include "MeshComponent.h"
#include "Geometry.h"
#include "Property.h"
#include "Application.h"
#include "InteractiveViewer.h"
#include "MeshDataModel.h"
#include "MeshDataView.h"
#include "MeshSelectionView.h"
#include "FrameOfReference.h"
#include "Log.h"

//-- Qt stuff
#include <QToolBar>
#include <QVBoxLayout>
#include <QComboBox>

//-- vtk stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkExtractSelection.h>
#include <vtkDataSetMapper.h>
#include <vtkProperty.h>
#include "CamiTKReEnableWarnings"

#include <vtkPointSet.h>
#include <vtkUnstructuredGrid.h>
#include <vtkTetra.h>
#include <vtkHexahedron.h>
#include <vtkWedge.h>
#include <vtkPyramid.h>
#include <vtkCellArray.h>
#include <vtkGenericCell.h>
#include <vtkSelection.h>
#include <vtkSelectionNode.h>
#include <vtkCellData.h>
#include <vtkPointData.h>
#include <vtkDataSetAttributes.h>
#include <vtkHedgeHog.h>
#include <vtkTensorGlyph.h>
#include <vtkActor.h>
#include <vtkSphereSource.h>
#include <vtkCellCenters.h>
#include <vtkDoubleArray.h>
#include <vtkArrowSource.h>
#include <vtkGlyph3D.h>

namespace camitk {

// -------------------- constructors --------------------
MeshComponent::MeshComponent(const QString& file, const QString& name) : Component(file, name, Component::GEOMETRY) {
    init();
}

MeshComponent::MeshComponent(vtkSmartPointer<vtkPointSet> aPointSet, const QString& name) : Component("", name, Component::GEOMETRY) {
    init();
    initRepresentation(aPointSet);
    setModified();
}

MeshComponent::MeshComponent(Component* parentComponent, vtkSmartPointer<vtkPointSet> aPointSet, const QString& name) : Component(parentComponent, name, Component::GEOMETRY) {
    init();
    initRepresentation(aPointSet);
}

// -------------------- destructor --------------------
MeshComponent::~MeshComponent() {
    if (getPointSet() != nullptr) {
        setDataRepresentationOff(SCALARS | VECTORS | TENSORS, true);
    }

    if (selectionWidget != nullptr) {
        delete selectionWidget;
    }

    if (dataWidget != nullptr) {
        delete dataWidget;
    }
}

// -------------------- setName --------------------
void MeshComponent::setName(const QString& name) {
    Component::setName(name);
    // update frame name if still the same as the one created during instantiation
    if (initialFrameOfReference == frameOfReference.get()) {
        frameOfReference->setName(getName());
        frameOfReference->setDescription("Frame of mesh '" + getName() + "'");
    }
}

// -------------------- init --------------------
void MeshComponent::init() {
    initialFrameOfReference = frameOfReference.get();
    
    isInInitRepresentation = false;
    // no last picked point or cell, no selection
    pickedCellId = -1;
    pickedPointId = -1;

    // In recent VTK version (at least >=9) an empty selection generates an error in extract selection
    // unless the content and field types of the nodes are defined
    currentSelection = vtkSmartPointer<vtkSelection>::New();
    vtkSmartPointer<vtkSelectionNode> emptyNode = vtkSmartPointer<vtkSelectionNode>::New();
    emptyNode->SetContentType(vtkSelectionNode::INDICES);
    emptyNode->SetFieldType(vtkSelectionNode::POINT);
    currentSelection->AddNode(emptyNode);

    numberOfCellDataSpecificRepresentation = 0;

    // selection widget
    selectionWidget = new QWidget();
    selectionWidget->setObjectName("Selection");

    // selection actions
    removeSelections = new QAction(QPixmap(":/delete"), tr("Remove selection(s)"), this);
    removeSelections->setStatusTip(tr("Remove the selected selections"));
    removeSelections->setWhatsThis(tr("Remove the selected selections"));
    connect(removeSelections, SIGNAL(triggered()), this, SLOT(removeSelectedSelections()));

    inspectSelection = new QAction(QPixmap(":/settings"), tr("Inspect the selection"), this);
    inspectSelection->setStatusTip(tr("Inspect the selection"));
    inspectSelection->setWhatsThis(tr("Inspect the selection"));

    mergeSelection = new QAction(QPixmap(":/refresh"), tr("Merge the selections"), this);
    mergeSelection->setStatusTip(tr("Merge the selected selection"));
    mergeSelection->setWhatsThis(tr("Merge the selected delections (selection types must be identical)"));

    // selection model
    selectionModel = new MeshSelectionModel(this);

    // selection view
    selectionView = new MeshSelectionView(selectionWidget);
    selectionView->setSelectionBehavior(QAbstractItemView::SelectRows);
    selectionView->setModel(selectionModel);
    selectionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    selectionView->setEditTriggers(QAbstractItemView::DoubleClicked);

    // if the selection changes, the meshComponent needs to be notified
    connect(selectionView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(changeSelectedSelection(QItemSelection, QItemSelection)));

    insertionPolicyBox = new QComboBox(selectionView);
    insertionPolicyBox->setStatusTip(tr("Insertion policy"));
    insertionPolicyBox->setWhatsThis(tr("Insertion policy"));
    insertionPolicyBox->addItem("Replace", MeshSelectionModel::REPLACE);
    insertionPolicyBox->addItem("Merge", MeshSelectionModel::MERGE);
    insertionPolicyBox->addItem("Subtract", MeshSelectionModel::SUBTRACT);
    insertionPolicyBox->addItem("Discard", MeshSelectionModel::DISCARD);

    // build the selection widget
    auto* hbox = new QVBoxLayout(selectionWidget);
    auto* selectionToolbar = new QToolBar(selectionWidget);
    selectionToolbar->addAction(removeSelections);
    selectionToolbar->addAction(mergeSelection);
    selectionToolbar->addAction(inspectSelection);
    selectionToolbar->addWidget(insertionPolicyBox);
    hbox->addWidget(selectionView);
    hbox->addWidget(selectionToolbar);
    selectionWidget->setLayout(hbox);

    // data widget
    dataWidget = new QWidget();
    dataWidget->setObjectName("Data");

    // selection actions
    removeData = new QAction(QPixmap(":/delete"), tr("Remove data"), this);
    removeData->setStatusTip(tr("Remove the selected data"));
    removeData->setWhatsThis(tr("Remove the selected data"));
    connect(removeData, SIGNAL(triggered()), this, SLOT(removeSelectedData()));

    inspectData = new QAction(QPixmap(":/settings"), tr("Inspect data"), this);
    inspectData->setStatusTip(tr("Inspect data"));
    inspectData->setWhatsThis(tr("Inspect data"));
    inspectData->setEnabled(false);    // TODO implement a data inspector widget

    // data model
    dataModel = new MeshDataModel(this);

    // data view
    dataView = new MeshDataView();
    dataView->setSelectionBehavior(QAbstractItemView::SelectRows);
    dataView->setModel(dataModel);
    dataView->setSelectionMode(QAbstractItemView::ExtendedSelection);

    displayTypePolicyBox = new QComboBox(dataView);
    displayTypePolicyBox->setStatusTip(tr("Representation of vector data"));
    displayTypePolicyBox->setWhatsThis(tr("Representation of vector data"));
    displayTypePolicyBox->addItem("3D Vector", VECTOR_3D);
    displayTypePolicyBox->addItem("Norm", NORM);
    displayTypePolicyBox->addItem("1st Component", FIRST_COMPONENT);
    displayTypePolicyBox->addItem("2nd Component", SECOND_COMPONENT);
    displayTypePolicyBox->addItem("3nd Component", THIRD_COMPONENT);
    displayTypePolicyBox->addItem("Color", COLOR);

    connect(displayTypePolicyBox, SIGNAL(currentIndexChanged(int)), this, SLOT(displayTypePolicyChanged(int)));

    vectorRepresentationPolicyBox = new QComboBox(dataView);
    vectorRepresentationPolicyBox->setStatusTip(tr("Vector representation for vector data"));
    vectorRepresentationPolicyBox->setWhatsThis(tr("Vector representation for vector data"));
    vectorRepresentationPolicyBox->addItem("Arrow", ARROW);
    vectorRepresentationPolicyBox->addItem("Unscaled", UNSCALED_ARROW);
    vectorRepresentationPolicyBox->addItem("Hedge Hog", HEDGE_HOG);

    connect(vectorRepresentationPolicyBox, SIGNAL(currentIndexChanged(int)), this, SLOT(vectorRepresentationPolicyChanged(int)));

    // build the data widget
    auto* dataBox = new QVBoxLayout(dataWidget);
    auto* dataToolbar = new QToolBar(dataWidget);
    dataToolbar->addAction(removeData);
    dataToolbar->addAction(inspectData);
    dataToolbar->addWidget(displayTypePolicyBox);
    dataToolbar->addWidget(vectorRepresentationPolicyBox);
    dataBox->addWidget(dataView);
    dataBox->addWidget(dataToolbar);
    dataWidget->setLayout(dataBox);

}

// -------------------- toVariant --------------------
QVariant MeshComponent::toVariant() const {
    // Call standard component method
    QVariant variant = Component::toVariant();
    QVariantMap variantMap = variant.toMap();
    // Add color data for the three RenderingModes
    QVariantMap colorVariant;
    double color[4];
    this->getActorColor(RenderingMode::Surface, color, true);
    colorVariant.insert("surface", QVariant(QList<QVariant>({color[0], color[1], color[2], color[3]})));
    this->getActorColor(RenderingMode::Wireframe, color, true);
    colorVariant.insert("wireframe", QVariant(QList<QVariant>({color[0], color[1], color[2], color[3]})));
    this->getActorColor(RenderingMode::Points, color, true);
    colorVariant.insert("points", QVariant(QList<QVariant>({color[0], color[1], color[2], color[3]})));

    variantMap.insert("color", colorVariant);

    // Save RenderingModes
    // Convert flags to Int, then to Variant
    variantMap.insert("renderingModes", QVariant(static_cast<RenderingModes::Int>(this->getRenderingModes())));

    // TODO Add other values from InterfaceGeometry, such as lines as tube parameters

    return variantMap;
}

// -------------------- fromVariant --------------------
void MeshComponent::fromVariant(const QVariant& variant) {
    Component::fromVariant(variant);
    QVariantMap newValuesMap = variant.toMap();

    if (newValuesMap.contains("color")) {
        QVariantMap colorVariant = newValuesMap.value("color").toMap();
        if (colorVariant.contains("surface")) {
            QList<QVariant> color = colorVariant.value("surface").toList();
            this->setActorColor(RenderingMode::Surface, color[0].toDouble(), color[1].toDouble(), color[2].toDouble());
            this->setActorOpacity(RenderingMode::Surface, color[3].toDouble());
        }
        if (colorVariant.contains("wireframe")) {
            QList<QVariant> color = colorVariant.value("wireframe").toList();
            this->setActorColor(RenderingMode::Wireframe, color[0].toDouble(), color[1].toDouble(), color[2].toDouble());
            this->setActorOpacity(RenderingMode::Wireframe, color[3].toDouble());
        }
        if (colorVariant.contains("points")) {
            QList<QVariant> color = colorVariant.value("points").toList();
            this->setActorColor(RenderingMode::Points, color[0].toDouble(), color[1].toDouble(), color[2].toDouble());
            this->setActorOpacity(RenderingMode::Points, color[3].toDouble());
        }
    }
    if (newValuesMap.contains("renderingModes")) {
        this->setRenderingModes(static_cast<RenderingModes>(newValuesMap.value("renderingModes").toInt()));
    }
}

// -------------------- initRepresentation --------------------
void MeshComponent::initRepresentation(vtkSmartPointer<vtkPointSet> originalPointSet) {
    isInInitRepresentation = true;

    // if there is no point set yet, just do nothing
    if (originalPointSet == nullptr) {
        return;
    }

    // else replace the point set
    if (myGeometry) {
        myGeometry->setPointSet(originalPointSet);
    }
    else {
        myGeometry = new Geometry(this->getName(), originalPointSet);
    }

    // add it in the InteractiveViewer (automatically)
    setVisibility("3D Viewer", true);

    // initialize selection
    initSelection();

    // initialize data
    initData();

    // initialize dynamic properties
    initDynamicProperties();

    isInInitRepresentation = false;

}

// -------------------- initSelection --------------------
void MeshComponent::initSelection() {
    vtkSmartPointer<vtkExtractSelection> selectionExtractor = vtkSmartPointer<vtkExtractSelection>::New();
    vtkSmartPointer<vtkActor> selectionActor = vtkSmartPointer<vtkActor>::New();
    vtkSmartPointer<vtkDataSetMapper> selectionMapper = vtkSmartPointer<vtkDataSetMapper>::New();

    selectionExtractor->SetInputConnection(0, this->getDataPort());
    selectionExtractor->SetInputData(1, currentSelection);

    selectionMapper->SetInputConnection(selectionExtractor->GetOutputPort());

    selectionActor->SetPickable(false);
    selectionActor->GetProperty()->SetRepresentationToSurface();
    selectionActor->GetProperty()->SetLineWidth(5);
    selectionActor->GetProperty()->SetColor(1, 0, 0);
    selectionActor->GetProperty()->SetPointSize(10);
    selectionActor->GetProperty()->SetOpacity(0.2);
    selectionActor->SetMapper(selectionMapper);

    addProp("Selection", selectionActor);
}

// -------------------- initData --------------------
void MeshComponent::initData() {
    //-- add loaded data arrays
    for (vtkIdType i = 0; i < myGeometry->getPointSet()->GetPointData()->GetNumberOfArrays(); i++) {
        addPointData(myGeometry->getPointSet()->GetPointData()->GetArrayName(i), myGeometry->getPointSet()->GetPointData()->GetArray(i));
    }

    for (vtkIdType i = 0; i < myGeometry->getPointSet()->GetCellData()->GetNumberOfArrays(); i++) {
        addCellData(myGeometry->getPointSet()->GetCellData()->GetArrayName(i), myGeometry->getPointSet()->GetCellData()->GetArray(i));
    }

    //-- no active data by default
    setDataRepresentationOff(SCALARS | VECTORS | TENSORS, true);
}

// -------------------- cellPicked --------------------
void MeshComponent::cellPicked(vtkIdType cellId, bool) {
    pickedCellId = cellId;
}

// -------------------- pointPicked --------------------
void MeshComponent::pointPicked(vtkIdType pointId, bool) {
    pickedPointId = pointId;
}

// -------------------- getPickedCellId --------------------
vtkIdType MeshComponent::getPickedCellId() {
    return pickedCellId;
}

// -------------------- getPickedPointId --------------------
vtkIdType MeshComponent::getPickedPointId() {
    return pickedPointId;
}

// -------------------- initDynamicProperties --------------------
void MeshComponent::initDynamicProperties() {
    vtkIdType count = 0;

    if (getPointSet() != nullptr) {
        count = getPointSet()->GetNumberOfPoints();
    }

    Property* nbPoints = new Property("Number Of Points", QVariant(QString("%1").arg(count)), "Number of 3D Points composing the geometry", "");
    nbPoints->setReadOnly(true);
    addProperty(nbPoints);

    if (getPointSet() != nullptr) {
        count = getPointSet()->GetNumberOfCells();
    }

    Property* nbCells = new Property("Number Of Cells", QVariant(QString("%1").arg(count)), "Number of Cells composing the geometry", "");
    nbCells->setReadOnly(true);
    addProperty(nbCells);

    if (getPointSet() != nullptr && getPointSet()->GetNumberOfPoints() > 0) {
        // add a dynamic property to manage the surface color
        // setProperty("position point #1", QVector3D(1.0,0.0,0.0));
        vtkSmartPointer<vtkGenericCell> cell = vtkGenericCell::New();
        std::map<unsigned char, int> elementsMap;
        std::map<unsigned char, int>::iterator elementsMapIt;

        for (vtkIdType i = 0; i < getPointSet()->GetNumberOfCells(); i++) {
            getPointSet()->GetCell(i, cell);

            if (!elementsMap.count(cell->GetCellType())) {
                elementsMap[ cell->GetCellType()] = 0;
            }

            elementsMap[ cell->GetCellType() ]++;

        }

        // the list of all possible cell types is defined in VTKCellType enum of the VTKCellType class
        for (elementsMapIt = elementsMap.begin(); elementsMapIt != elementsMap.end(); ++elementsMapIt) {
            Property* cellProp;

            switch (elementsMapIt->first) {
                case VTK_EMPTY_CELL:
                    cellProp = new Property("Empty Cells", elementsMapIt->second, tr("Number Of Empty Cells"), "");
                    break;

                case VTK_VERTEX:
                    cellProp = new Property("Vertex", elementsMapIt->second, tr("Number Of Vertex Cells"), "");
                    break;

                case VTK_POLY_VERTEX:
                    cellProp = new Property("Edges", elementsMapIt->second, tr("Number Of Edge Cells"), "");
                    break;

                case VTK_LINE :
                    cellProp = new Property("Lines", elementsMapIt->second, tr("Number Of Line Cells"), "");
                    break;

                case VTK_POLY_LINE:
                    cellProp = new Property("Polylines", elementsMapIt->second, tr("Number Of Polylines Cells"), "");
                    break;

                case VTK_TRIANGLE :
                    cellProp = new Property("Triangles", elementsMapIt->second, tr("Number Of Triangle Cells"), "");
                    break;

                case VTK_TRIANGLE_STRIP:
                    cellProp = new Property("Triangle Strips", elementsMapIt->second, tr("Number Of Triangle Strip Cells"), "");
                    break;

                case VTK_POLYGON:
                    cellProp = new Property("Polygons", elementsMapIt->second, tr("Number Of Polygon Cells"), "");
                    break;

                case VTK_PIXEL:
                    cellProp = new Property("Pixels", elementsMapIt->second, tr("Number Of Pixel Cells"), "");
                    break;

                case VTK_QUAD:
                    cellProp = new Property("Quads", elementsMapIt->second, tr("Number Of Quad Cells"), "");
                    break;

                case VTK_TETRA :
                    cellProp = new Property("Tetrahedra", elementsMapIt->second, tr("Number Of Tetrahedral Cells"), "");
                    break;

                case VTK_VOXEL:
                    cellProp = new Property("Voxels", elementsMapIt->second, tr("Number Of Voxel Cells"), "");
                    break;

                case VTK_HEXAHEDRON :
                    cellProp = new Property("Hexahedra", elementsMapIt->second, tr("Number Of Hexahedral Cells"), "");
                    break;

                case VTK_WEDGE :
                    cellProp = new Property("Wedges", elementsMapIt->second, tr("Number Of Wedge Cells"), "");
                    break;

                case VTK_PYRAMID :
                    cellProp = new Property("Pyramids", elementsMapIt->second, tr("Number Of Pyramid Cells"), "");
                    break;

                case VTK_PENTAGONAL_PRISM:
                    cellProp = new Property("Pentagonal Prisms", elementsMapIt->second, tr("Number Of Pentagonal Prism Cells"), "");
                    break;

                case VTK_HEXAGONAL_PRISM:
                    cellProp = new Property("Hexagonal Prisms", elementsMapIt->second, tr("Number Of Hexagonal Prism Cells"), "");
                    break;

                default:
                    cellProp = new Property("Others", elementsMapIt->second, tr("Number Of <i>Other Type Of Cells</i>. <br/>It can be quadratic isoparametric cells, Cubic isoparametric cells, <br/>convex group of points, higher order cells in parametric form, <br/>higher order cells (see VTKCellType enum for more information)"), "");
                    break;
            }

            cellProp->setReadOnly(true);
            addProperty(cellProp);
        }
    }

    unsigned long memUsage = 0;

    if (getPointSet() != nullptr) {
        memUsage = getPointSet()->GetActualMemorySize();
    }

    Property* memoryUsage = new Property("Size In Memory", QVariant(QString("%1").arg(memUsage)), tr("Actual size of the data in kilobytes. <br/>This number is valid only after the pipeline has updated. <br/>The memory size returned is guaranteed to be greater than or <br/>equal to the memory required to represent the data<br/> (e.g., extra space in arrays, etc. are not included in the return value)."), "Kb");
    memoryUsage->setReadOnly(true);
    addProperty(memoryUsage);
}

// -------------------- getSelections --------------------
QList< vtkSmartPointer< vtkSelectionNode > >& MeshComponent::getSelections() {
    return selectionList;
}

// -------------------- getNumberOfSelections --------------------
unsigned int MeshComponent::getNumberOfSelections() const {
    return selectionList.size();
}

// -------------------- getActiveSelection --------------------
vtkSmartPointer<vtkSelection> MeshComponent::getActiveSelection() const {
    return currentSelection;
}

// -------------------- getSelection --------------------
vtkSmartPointer< vtkSelectionNode > MeshComponent::getSelection(const QString& name) const {
    int indexOfSelection = getSelectionIndex(name);

    if (indexOfSelection >= 0 && indexOfSelection < selectionList.size()) {
        return getSelectionAt(indexOfSelection);
    }
    else {
        return nullptr;
    }
}

// -------------------- getSelectionAt --------------------
vtkSmartPointer< vtkSelectionNode > MeshComponent::getSelectionAt(unsigned int index) const {
    return selectionList.at(index);
}

// -------------------- getSelectionIndex --------------------
int MeshComponent::getSelectionIndex(const QString& name) const {
    QList< vtkSmartPointer< vtkSelectionNode > >::const_iterator it = selectionList.constBegin();
    int index = 0;
    bool found = false;

    while (it != selectionList.end() && !found) {
        if ((*it)->GetSelectionList() && !QString::compare(QString((*it)->GetSelectionList()->GetName()), name)) {
            found = true;
        }
        else {
            index++;
            ++it;
        }
    }

    if (found) {
        return index;
    }
    else {
        return -1;
    }
}

// -------------------- addSelection --------------------
int MeshComponent::addSelection(const QString& name, int fieldType, int contentType, vtkSmartPointer< vtkAbstractArray > array, MeshSelectionModel::InsertionPolicy policy) {
    int index = selectionModel->insertSelection(name, fieldType, contentType, array, policy);
    return index;
}

// -------------------- addToSelectedSelection --------------------
int MeshComponent::addToSelectedSelection(int fieldType, int contentType, vtkSmartPointer< vtkAbstractArray > array, MeshSelectionModel::InsertionPolicy policy) {
    // use the current index to handle the case of multiple selection
    int index = selectionView->selectionModel()->currentIndex().row();
    // TODO : for now, we don't care about the policy parameter and use the one of the combo box
    // but in the future, the policy should be selected by pressing modifiers keys
    policy = (MeshSelectionModel::InsertionPolicy) insertionPolicyBox->itemData(insertionPolicyBox->currentIndex()).toInt();

    // check if the current selection is selected (maybe it is not necessary ...)
    if (selectionView->selectionModel()->isRowSelected(index, QModelIndex())) {
        index = addSelection(selectionList.at(index)->GetSelectionList()->GetName(), fieldType, contentType, array, policy);
    }
    else {
        index = addSelection("Picked Selection", fieldType, contentType, array, policy);
    }
    // forced clearing even if the selected index stay the same
    // if not, display will not be updated
    selectionView->clearSelection();
    // select the added selection and set the current index
    selectionView->selectionModel()->setCurrentIndex(selectionModel->index(index, 0, QModelIndex()), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);

    return index;
}

// -------------------- changeSelectedSelection --------------------
// TODO : rename updateSelection
void MeshComponent::changeSelectedSelection(const QItemSelection& selected, const QItemSelection& deselected) {
    // do not use selected since it contains only the new selected selections
    // QModelIndexList items = selected.indexes();
    QModelIndexList items = selectionView->selectionModel()->selectedRows();

    // Do not modify the selection if there is no selected row
    if (items.size() == 0) {
        return;
    }

    // remove all the selection nodes from the selection
    // -- we will add others, vtkSelection must not have 0 nodes (at least an empty node is required)
    currentSelection->RemoveAllNodes();

    // Select the PropertyExplorer 'Selection' tab for the currently selected component
    if (!Application::getSelectedComponents().isEmpty()) {
        Component* currentComponent = Application::getSelectedComponents().last();
        currentComponent->setIndexOfPropertyExplorerTab(1);
    }

    // add each selected selection nodes to the current selection
    for (QModelIndex index : items) {
        currentSelection->Union(selectionList.at(index.row()));
    }

    this->refresh();

}

// -------------------- displayTypePolicyChanged --------------------
void MeshComponent::displayTypePolicyChanged(int) {
    dataModel->refresh();
}


// -------------------- vectorRepresentationPolicyChanged --------------------
void MeshComponent::vectorRepresentationPolicyChanged(int) {
    setDataRepresentationOff(VECTORS);

    // remove all vector actors
    while (!vectorActors.empty()) {
        removeProp(vectorActors.takeFirst());
    }

    dataModel->refresh();
}

// -------------------- removeSelectedSelection --------------------
void MeshComponent::removeSelectedSelections() {
    // TODO : handle multiple selection
    int index = selectionView->selectionModel()->currentIndex().row();

    if (selectionView->selectionModel()->isRowSelected(index, QModelIndex())) {
        selectionModel->removeSelection(selectionList.at(index)->GetSelectionList()->GetName());
    }
}

// -------------------- getNumberOfDataArray --------------------
int MeshComponent::getNumberOfDataArray(int fieldFlag) {
    if (getPointSet() == nullptr) {
        return 0;
    }
    else {
        int count = 0;

        if (fieldFlag & POINTS) {
            // remove the number of specific representation inserted in point data
            count += + getPointSet()->GetPointData()->GetNumberOfArrays() - specific3DDataRepresentation.size() + numberOfCellDataSpecificRepresentation;
        }

        if (fieldFlag & CELLS) {
            // remove the number of specific representation inserted in cell data
            count += getPointSet()->GetCellData()->GetNumberOfArrays() - numberOfCellDataSpecificRepresentation;
        }

        if (fieldFlag & MESH) {
            count += getPointSet()->GetFieldData()->GetNumberOfArrays();
        }

        return count;
    }
}

// -------------------- createDataRepresentation --------------------
void MeshComponent::createDataRepresentation(FieldType field, const QString& name, SpecificRepresentation representation) {
    vtkSmartPointer<vtkDataArray> dataArray = getDataArray(field, name);
    vtkSmartPointer<vtkActor> dataActor = vtkSmartPointer< vtkActor >::New();

    if (dataArray != nullptr) {
        // create the representation depending on the type
        DataType dataType = getDataType(dataArray);

        switch (dataType) {
            case SCALARS:
                // nothing to do, scalar data already preseng in the array and representation managed by the mapper
                break;

            case VECTORS:
                if (representation == VECTOR_3D) {
                    VectorRepresentation vectorPolicy = (VectorRepresentation) vectorRepresentationPolicyBox->currentData().toInt();

                    if (vectorPolicy == HEDGE_HOG) {
                        // add the corresponding prop, but not visible
                        vtkSmartPointer<vtkHedgeHog> hedgeHog = vtkSmartPointer<vtkHedgeHog>::New();
                        vtkSmartPointer<vtkDataSetMapper> dataMapper = vtkSmartPointer< vtkDataSetMapper >::New();

                        if (field == POINTS) {
                            hedgeHog->SetInputConnection(getDataPort());
                            hedgeHog->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, name.toUtf8().constData());
                        }
                        else if (field == CELLS) {
                            vtkSmartPointer<vtkCellCenters> cellCentersFilter = vtkSmartPointer<vtkCellCenters>::New();
                            cellCentersFilter->SetInputConnection(getDataPort());
                            hedgeHog->SetInputConnection(cellCentersFilter->GetOutputPort());
                            hedgeHog->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, name.toUtf8().constData());
                        }

                        hedgeHog->SetScaleFactor(getBoundingRadius() / 10.0);
                        dataMapper->SetInputConnection(hedgeHog->GetOutputPort());
                        dataActor->SetMapper(dataMapper);
                        dataActor->GetProperty()->SetColor(0.0, 0.7, 0.0);
                        dataActor->VisibilityOff();

                        // add the prop to the component and update the list
                        addProp(getDataPropName(field, name), dataActor);
                        vectorActors.append(getDataPropName(field, name));
                    }
                    else {
                        // representation as ARROW
                        // add the corresponding prop, but not visible
                        vtkSmartPointer<vtkDataSetMapper> dataMapper = vtkSmartPointer< vtkDataSetMapper >::New();
                        vtkSmartPointer<vtkActor> dataActor = vtkSmartPointer< vtkActor >::New();
                        vtkSmartPointer<vtkArrowSource> arrowSource = vtkSmartPointer<vtkArrowSource>::New();
                        vtkSmartPointer<vtkGlyph3D> arrowGlyph = vtkSmartPointer<vtkGlyph3D>::New();

                        if (vectorPolicy == UNSCALED_ARROW) {
                            // the best would be to scale to a minimal %age of the bounding radius up to a maximal %age of bounding radius
                            // unfortunately it not easy to understand the scaling process of vtkGlyph3D
                            arrowGlyph->SetScaleFactor(getBoundingRadius() / 2.0);
                            arrowGlyph->SetScaleModeToDataScalingOff();
                            arrowGlyph->ClampingOn();
                            //arrowGlyph->ScalingOff();
                            //arrowGlyph->SetRange(getBoundingRadius() / 50.0, getBoundingRadius() / 10.0);
                        }

                        arrowGlyph->SetScaleModeToScaleByVector();
                        arrowGlyph->SetVectorModeToUseVector();
                        arrowGlyph->SetSourceConnection(arrowSource->GetOutputPort());

                        // Next line should be able to color the arrow depending on the field,
                        // but the color LUT is inversed, so comment for now
                        // arrowGlyph->SetColorModeToColorByScalar();
                        if (field == POINTS) {
                            arrowGlyph->SetInputConnection(getDataPort());
                        }
                        else if (field == CELLS) {
                            vtkSmartPointer<vtkCellCenters> cellCentersFilter = vtkSmartPointer<vtkCellCenters>::New();
                            cellCentersFilter->SetInputConnection(getDataPort());
                            arrowGlyph->SetInputConnection(cellCentersFilter->GetOutputPort());
                        }

                        dataMapper->SetInputConnection(arrowGlyph->GetOutputPort());
                        dataActor->SetMapper(dataMapper);
                        dataActor->GetProperty()->SetColor(0, 1, 0);
                        dataActor->VisibilityOff();

                        addProp(getDataPropName(field, name), dataActor);
                        vectorActors.append(getDataPropName(field, name));
                    }
                }
                else {
                    // create 1D representation
                    QString specificName(name + getSpecificRepresentationName(representation));
                    vtkSmartPointer<vtkDoubleArray> dataArrayToDisplay;
                    vtkSmartPointer<vtkUnsignedCharArray> colorArrayToDisplay;
                    if (representation == COLOR) {
                        colorArrayToDisplay = vtkSmartPointer<vtkUnsignedCharArray>::New();
                        colorArrayToDisplay->SetNumberOfComponents(3);
                        colorArrayToDisplay->SetName(specificName.toUtf8().constData());
                    }
                    else {
                        dataArrayToDisplay = vtkSmartPointer<vtkDoubleArray>::New();
                        dataArrayToDisplay->SetName(specificName.toUtf8().constData());
                    }

                    int numberOfValues;

                    if (field == POINTS) {
                        numberOfValues = getPointSet()->GetNumberOfPoints();
                    }
                    else {
                        // this is cell field
                        numberOfValues = getPointSet()->GetNumberOfCells();
                    }

                    // fill the data array
                    if (representation == COLOR) {
                        colorArrayToDisplay->SetNumberOfTuples(numberOfValues);
                    }
                    else {
                        dataArrayToDisplay->SetNumberOfValues(numberOfValues);
                    }

                    // use the representation flag to tranform the point's 3D vector to a scalar
                    for (vtkIdType i = 0; i < numberOfValues; ++i) {
                        double val[3];
                        dataArray->GetTuple(i, val);

                        switch (representation) {
                            case FIRST_COMPONENT:
                                dataArrayToDisplay->SetValue(i, val[0]);
                                break;

                            case SECOND_COMPONENT:
                                dataArrayToDisplay->SetValue(i, val[1]);
                                break;

                            case THIRD_COMPONENT:
                                dataArrayToDisplay->SetValue(i, val[2]);
                                break;

                            case NORM:
                                // compute 3D norm
                                dataArrayToDisplay->SetValue(i, sqrt(val[0]*val[0] + val[1]*val[1] + val[2]*val[2]));
                                break;
                            case COLOR:
                            default:
                                colorArrayToDisplay->SetTuple3(i, val[0], val[1], val[2]);
                                break;
                        }
                    }

                    if (representation == COLOR) {
                        // add it to the pointset field data directly, but NOT to the mesh component (it will stay hidden)
                        getFieldData(field)->AddArray(colorArrayToDisplay);
                        // insert it in the specific map
                        specific3DDataRepresentation.insert(specificName, colorArrayToDisplay);
                    }
                    else {
                        // add it to the pointset field data directly, but NOT to the mesh component (it will stay hidden)
                        getFieldData(field)->AddArray(dataArrayToDisplay);
                        // insert it in the specific map
                        specific3DDataRepresentation.insert(specificName, dataArrayToDisplay);
                    }

                    // update the cell specific representation counter so that getNumberOfDataArray answer is up-to-date
                    if (field == CELLS) {
                        numberOfCellDataSpecificRepresentation++;
                    }
                }

                break;

            case TENSORS: {
                double boundingRadius = getBoundingRadius();
                vtkSmartPointer<vtkTensorGlyph> tensorGlyph = vtkSmartPointer<vtkTensorGlyph>::New();
                vtkSmartPointer<vtkDataSetMapper> dataMapper = vtkSmartPointer< vtkDataSetMapper >::New();
                vtkSmartPointer<vtkActor> dataActor = vtkSmartPointer< vtkActor >::New();
                vtkSmartPointer<vtkSphereSource> glyphSphere = vtkSmartPointer<vtkSphereSource>::New();
                glyphSphere->SetRadius(boundingRadius / 10.0);

                if (field == POINTS) {
                    tensorGlyph->SetInputConnection(getDataPort());
                    tensorGlyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, name.toUtf8().constData());
                }
                else if (field == CELLS) {
                    vtkSmartPointer<vtkCellCenters> cellCentersFilter = vtkSmartPointer<vtkCellCenters>::New();
                    cellCentersFilter->SetInputConnection(getDataPort());
                    tensorGlyph->SetInputConnection(cellCentersFilter->GetOutputPort());
                    tensorGlyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, name.toUtf8().constData());
                }

                tensorGlyph->SetSourceConnection(glyphSphere->GetOutputPort());
                tensorGlyph->SetColorModeToEigenvalues();
                tensorGlyph->SetExtractEigenvalues(false);
                tensorGlyph->SetScaling(1);
                tensorGlyph->SetMaxScaleFactor(2);
                tensorGlyph->SetClampScaling(1);
                tensorGlyph->SetScaleFactor(boundingRadius / 10.0);
                dataMapper->SetInputConnection(tensorGlyph->GetOutputPort());
                dataActor->SetMapper(dataMapper);
                dataActor->GetProperty()->SetColor(0, 1, 0);
                dataActor->VisibilityOff();

                addProp(getDataPropName(field, name), dataActor);
            }
            break;

            default:
                break;
        }
    }
}

// -------------------- getDataRepresentationVisibility --------------------
bool MeshComponent::getDataRepresentationVisibility(FieldType field, const QString&  name) {
    vtkSmartPointer<vtkDataArray> dataArray = getDataArray(field, name);
    DataType type = getDataType(dataArray);

    if (type == OTHERS) {
        return false;
    }

    SpecificRepresentation representation = (SpecificRepresentation) displayTypePolicyBox->currentData().toInt();

    // if type is 1D, 9D or 3D as VECTOR_3D, just check if the data are shown or not
    if ((type == SCALARS) || (type == VECTORS && representation == VECTOR_3D) || (type == TENSORS)) {
        return dataRepresentationVisibility.value(dataArray);
    }
    else {
        // if this is a 3D data with a specific data representation (i.e., different than VECTOR_3D)
        // Then check if the specific data array exists first
        if (specific3DDataRepresentation.contains(name + getSpecificRepresentationName(representation))) {
            // and if it exists, check if it is shown
            return dataRepresentationVisibility.value(specific3DDataRepresentation.value(name + getSpecificRepresentationName(representation)));
        }
        else {
            return false;
        }
    }
}


// -------------------- setDataRepresentationOff --------------------
void MeshComponent::setDataRepresentationOff(int dataType, bool blockRefresh) {

    // if nothing is displayed, nothing to do
    if (dataRepresentationVisibility.size() == 0) {
        return;
    }

    //-- update the visibility flags for the given dataType
    for (auto it = dataRepresentationVisibility.constBegin(); it != dataRepresentationVisibility.constEnd(); ++it) {
        if (getDataType(it.key()) & dataType) {
            dataRepresentationVisibility.insert(it.key(), false);
        }
    }

    //-- update all data sets
    vtkSmartPointer< vtkDataSetAttributes > pointDataSet = vtkDataSetAttributes::SafeDownCast(getFieldData(POINTS));
    vtkSmartPointer< vtkDataSetAttributes > cellDataSet = vtkDataSetAttributes::SafeDownCast(getFieldData(CELLS));

    if (dataType & SCALARS) {
        pointDataSet->SetActiveScalars(nullptr);
        cellDataSet->SetActiveScalars(nullptr);

        // remove all color bar
        // TODO redesign viewer so that it does a getColorScaleRequired() or getActorColorScale() or
        // doesPropNeedsColorScale() etc... something like this
        InteractiveViewer* default3DViewer = dynamic_cast<InteractiveViewer*>(Application::getViewer("3D Viewer"));

        if (default3DViewer != nullptr) {
            default3DViewer->setColorScaleTitle("");
            default3DViewer->setColorScale(false);
        }
    }

    if (dataType & VECTORS) {
        pointDataSet->SetActiveVectors(nullptr);
        cellDataSet->SetActiveVectors(nullptr);
    }

    if (dataType & TENSORS) {
        pointDataSet->SetActiveTensors(nullptr);
        cellDataSet->SetActiveTensors(nullptr);
    }

    // -- remove visibility of all props
    if (dataType & VECTORS || dataType & TENSORS) {
        // update visibility flag and prop visibility for all vectors
        for (auto it = dataRepresentationVisibility.constBegin(); it != dataRepresentationVisibility.constEnd(); ++it) {
            if (getDataType(it.key()) & dataType) {
                // get the corresponding props
                vtkSmartPointer<vtkProp> prop = getProp(getDataPropName(POINTS, it.key()->GetName()));

                if (prop != nullptr) {
                    prop->VisibilityOff();
                }

                prop = getProp(getDataPropName(CELLS, it.key()->GetName()));

                if (prop != nullptr) {
                    prop->VisibilityOff();
                }
            }
        }
    }

    // refresh the property tab and 3D
    dataModel->refresh();

    if (!blockRefresh && !isInInitRepresentation) {
        refresh();
    }

}

// -------------------- setScalarDataRepresentationOn --------------------
void MeshComponent::setScalarDataRepresentationOn(vtkSmartPointer<vtkDataArray> dataArray) {
    // update mapper range
    double range[2];

    dataArray->GetRange(range);    // range of the 1st component = unique component
    setMapperScalarRange(range[0], range[1]);

    //-- show the color scale in 3D
    InteractiveViewer* default3DViewer = dynamic_cast<InteractiveViewer*>(Application::getViewer("3D Viewer"));

    if (default3DViewer != nullptr) {
        default3DViewer->setColorScale(true);
        default3DViewer->setColorScaleTitle(dataArray->GetName());
        default3DViewer->setColorScaleMinMax(range[0], range[1]);
    }
}

// -------------------- setDataRepresentationVisibility --------------------
void MeshComponent::setDataRepresentationVisibility(FieldType fieldType, const QString&  name, bool visibility, bool blockRefresh) {
    // currently not managed
    if (fieldType == MESH) {
        return;
    }

    vtkSmartPointer<vtkDataArray> dataArray = getDataArray(fieldType, name);
    SpecificRepresentation representation = (SpecificRepresentation) displayTypePolicyBox->currentData().toInt();

    // Set appropriate color mode
    if (representation == COLOR && visibility) {
        this->setColorMode(VTK_COLOR_MODE_DIRECT_SCALARS); // VTK_COLOR_MODE_DEFAULT VTK_COLOR_MODE_MAP_SCALARS VTK_COLOR_MODE_DIRECT_SCALARS
    }
    else {
        this->setColorMode(VTK_COLOR_MODE_DEFAULT);
    }

    if (dataArray == nullptr) {
        setDataRepresentationOff(SCALARS | VECTORS | TENSORS, true); // all off, block refresh
        return;
    }

    DataType type = getDataType(dataArray);

    if (!visibility) {
        if ((type == SCALARS) || (type == VECTORS && representation == VECTOR_3D) || (type == TENSORS)) {

            setDataRepresentationOff(type, true);
        }
        else {
            // COLOR is a 3D scalar, so both SCALARS with VECTOR data, so switch off both
            if (representation == COLOR) {
                setDataRepresentationOff(VECTORS | SCALARS, true);
            }
            else {
                // this is a specific representation of a vector: switch off the scalar representation
                setDataRepresentationOff(SCALARS, true);
            }
        }
    }
    else {
        vtkSmartPointer< vtkDataSetAttributes > dataSet = vtkDataSetAttributes::SafeDownCast(getFieldData(fieldType));

        switch (type) {
            case SCALARS: {
                // first remove all other scalar color map (only one possible color at at time
                setDataRepresentationOff(SCALARS, true);

                // update visibility status
                dataRepresentationVisibility.insert(dataArray, true);

                // not need to create the representation as the mapper can already handle scalar data

                // update the data set state
                dataSet->SetActiveScalars(name.toStdString().c_str());

                // show the data in the 3D viewer with scale
                setScalarDataRepresentationOn(dataArray);
            }
            break;

            case VECTORS:

                if (representation != VECTOR_3D) {
                    // first remove all other scalar color map (only one possible color at at time)
                    setDataRepresentationOff(SCALARS, true);

                    // create 1D representation (color map)
                    QString specificName(name + getSpecificRepresentationName(representation));

                    if (!specific3DDataRepresentation.contains(specificName)) {
                        createDataRepresentation(fieldType, name, representation);
                    }

                    // update the data set state, in this case the point data active scalars
                    // as the specific data are stored in the point data
                    //getPointSet()->GetPointData()->SetActiveScalars(specificName.toStdString().c_str());
                    dataSet->SetActiveScalars(specificName.toStdString().c_str());

                    // update visibility status
                    vtkSmartPointer<vtkDataArray> specificArray = specific3DDataRepresentation.value(specificName);
                    dataRepresentationVisibility.insert(specificArray, true);

                    // show the data in the 3D viewer with color scale (except for raw color data)
                    if (representation != COLOR) {
                        setScalarDataRepresentationOn(specificArray);
                    }

                    break; // specific vector representation is finished here
                }
                else {
                    // first remove all other vector representation (only one possible color at at time)
                    setDataRepresentationOff(VECTORS, true);

                    // just update the data set state for now (see case TENSORS for the rest)
                    dataSet->SetActiveVectors(name.toStdString().c_str());
                }

            // no break here as the action for tensors and 3D vector representation is the same...

            case TENSORS: {
                // ... which means we still need this test here (not elegant but better than repeating the following lines twice)
                if (type == TENSORS) {
                    // first remove all other vector representation (only one possible color at at time)
                    setDataRepresentationOff(TENSORS, true);

                    // update the data set state
                    dataSet->SetActiveTensors(name.toStdString().c_str());
                }

                // create 3D vectors representation (hedge hog) or tensor representation (tensor glyphs)
                vtkSmartPointer<vtkProp> representationProp = getProp(getDataPropName(fieldType, name));

                if (representationProp == nullptr) {
                    createDataRepresentation(fieldType, name);
                }

                // update visibility status
                dataRepresentationVisibility.insert(dataArray, true);

                // get the prop and set prop->VisibilityOn();
                getProp(getDataPropName(fieldType, name))->VisibilityOn();
            }
            break;

            default:
                break;
        }

    }

    // refresh the table and 3D viewer
    dataModel->refresh();

    if (!blockRefresh && !isInInitRepresentation) {
        refresh();
    }

}

// -------------------- getDataArray --------------------
vtkSmartPointer<vtkDataArray> MeshComponent::getDataArray(FieldType fieldType, const QString& arrayName) {
    vtkSmartPointer< vtkFieldData > dataField = getFieldData(fieldType);

    return dataField->GetArray(arrayName.toStdString().c_str());
}

vtkSmartPointer<vtkDataArray> MeshComponent::getDataArray(FieldType fieldType, int index) {
    vtkSmartPointer< vtkFieldData > dataField = getFieldData(fieldType);

    return dataField->GetArray(index);
}

// -------------------- addDataArray --------------------
void MeshComponent::addDataArray(FieldType fieldType, const QString& name, vtkSmartPointer< vtkDataArray > data) {

    // get the field data set attributes
    vtkSmartPointer< vtkFieldData > dataField = getFieldData(fieldType);

    // attach the new data
    data->SetName(name.toStdString().c_str());
    dataField->AddArray(data);

    // set the data as active
    setDataRepresentationVisibility(fieldType, name, true);

}

// -------------------- removeDataArray --------------------
void MeshComponent::removeDataArray(FieldType fieldType, const QString& name) {
    vtkSmartPointer< vtkFieldData > dataField = getFieldData(fieldType);

    if (dataField->HasArray(name.toStdString().c_str())) {
        setDataRepresentationVisibility(fieldType, name, false);
        dataField->RemoveArray(name.toStdString().c_str());
        removeProp(getDataPropName(fieldType, name));
    }

}

// -------------------- addPointData --------------------
void MeshComponent::addPointData(const QString& name, vtkSmartPointer< vtkDataArray > data) {
    addDataArray(POINTS, name, data);
}

// -------------------- addCellData --------------------
void MeshComponent::addCellData(const QString& name, vtkSmartPointer< vtkDataArray > data) {
    addDataArray(CELLS, name, data);
}

// -------------------- getDataPropName --------------------
const QString MeshComponent::getDataPropName(FieldType fieldType, const QString& arrayName) {
    // very specific name to avoid accidental override of user prop
    return QString(getName() + " representation of data array " + arrayName + " in " + getFieldName(fieldType) + " data");
}

// -------------------- getDataModel --------------------
MeshDataModel* MeshComponent::getDataModel() {
    return dataModel;
}

// -------------------- getFieldData --------------------
vtkSmartPointer<vtkFieldData> MeshComponent::getFieldData(FieldType fieldType) {
    switch (fieldType) {
        case POINTS:
            return getPointSet()->GetPointData();
            break;

        case CELLS:
            return getPointSet()->GetCellData();
            break;

        case MESH:
        default:
            return getPointSet()->GetFieldData();
            break;
    }

}

// -------------------- removeSelectedData --------------------
void MeshComponent::removeSelectedData() {
    // TODO : handle multiple selection
    // TODO : handle cell/mesh data array deletion
    int index = dataView->selectionModel()->currentIndex().row();

    if (dataView->selectionModel()->isRowSelected(index, QModelIndex())) {
        vtkSmartPointer<vtkDataArray> arrayToDelete = getDataArray(POINTS, index);

        if (arrayToDelete == nullptr) {
            CAMITK_WARNING(tr("Removal of data #%1 not implemented yet").arg(QString::number(index)))
        }
        else {
            removeDataArray(POINTS, arrayToDelete->GetName());
        }
    }
}

// -------------------- getNumberOfPropertyWidget --------------------
unsigned int MeshComponent::getNumberOfPropertyWidget() {
    return 2;
}

// -------------------- getPropertyWidgetAt --------------------
QWidget* MeshComponent::getPropertyWidgetAt(unsigned int i) {
    switch (i) {
        case 0 :
            return selectionWidget;
            break;

        case 1 :
            return dataWidget;
            break;

        default:
            return nullptr;
    }
}

// -------------------- getIcon --------------------
QPixmap MeshComponent::getIcon() {
    return QPixmap(":/cell");
}


//
// -- static method to manage enums
//

// -------------------- initFieldNames --------------------
QMap< int, QString > MeshComponent::initFieldNames() {
    QMap< int, QString > fieldNames;
    fieldNames[MeshComponent::POINTS] = "points";
    fieldNames[MeshComponent::CELLS] = "cells";
    fieldNames[MeshComponent::MESH] = "mesh";

    return fieldNames;
}

// -------------------- getFieldNames --------------------
const QMap< int, QString >& MeshComponent::getFieldNames() {
    static QMap<int, QString> fieldNames = initFieldNames();

    return fieldNames;
}


// -------------------- getFieldName --------------------
const QString MeshComponent::getFieldName(const FieldType fieldType) {
    return getFieldNames().value(fieldType);
}

// -------------------- initDataNames --------------------
QMap< int, QString > MeshComponent::initDataNames() {
    QMap< int, QString > dataNames;
    dataNames[MeshComponent::SCALARS] = "scalars";
    dataNames[MeshComponent::VECTORS] = "vectors";
    dataNames[MeshComponent::TENSORS] = "tensors";
    dataNames[MeshComponent::OTHERS] = "";

    return dataNames;
}

// -------------------- getDataTypeNames --------------------
const QMap< int, QString >& MeshComponent::getDataTypeNames() {
    static QMap<int, QString> dataNames = initDataNames();

    return dataNames;
}

// -------------------- getDataTypeName --------------------
const QString MeshComponent::getDataTypeName(const DataType dataType) {
    return getDataTypeNames().value(dataType);
}


// -------------------- getDataType --------------------
const MeshComponent::DataType MeshComponent::getDataType(vtkSmartPointer<vtkDataArray> array) {
    int numberOfComponents = array->GetNumberOfComponents();

    switch (numberOfComponents) {
        case 1:
            return MeshComponent::SCALARS;
            break;

        case 3 :
            return MeshComponent::VECTORS;
            break;

        case 9 :
            return MeshComponent::TENSORS;
            break;

        default:
            return MeshComponent::OTHERS;
            break;
    }

}

// -------------------- getDataTypeName --------------------
const QString MeshComponent::getDataTypeName(vtkSmartPointer<vtkDataArray> array) {
    MeshComponent::DataType dataType = getDataType(array);

    if (dataType != MeshComponent::OTHERS) {
        return getDataTypeName(dataType);
    }
    else {
        return QString::number(array->GetNumberOfComponents());
    }
}

// -------------------- getSpecificRepresentationName --------------------
const QString MeshComponent::getSpecificRepresentationName(const SpecificRepresentation displayType) {
    switch (displayType) {
        case MeshComponent::NORM:
            return " norm";
            break;

        case MeshComponent::FIRST_COMPONENT:
            return "[0]";
            break;

        case MeshComponent::SECOND_COMPONENT:
            return "[1]";
            break;

        case MeshComponent::THIRD_COMPONENT:
            return "[2]";
            break;

        case MeshComponent::COLOR:
            return " color";
            break;

        case MeshComponent::VECTOR_3D:
        default:
            return "";
            break;
    }
}

}








