/*****************************************************************************
 * $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 "MeshQuality.h"

#include <MeshComponent.h>
#include <Application.h>
#include <InteractiveGeometryViewer.h>
#include <Log.h>

// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkMeshQuality.h>
#include <CamiTKReEnableWarnings>

#include <vtkCellData.h>
#include <vtkCellType.h>
#include <vtkCell.h>
#include <vtkFloatArray.h>

// Qt
#include <QPushButton>
#include <QVBoxLayout>
#include <QHeaderView>

using namespace camitk;

// WARNING If you change the order of one of QStringList below, change the switch case in corresponding "computeXXX" methods
QStringList MeshQuality::trisFuncList = QStringList() << "Area" << "Aspect Frobenius" << "Aspect Ratio" << "Condition" << "Distortion" << "Edge Ratio" << "Maximum Angle" << "Minimum Angle" << "Radius Ratio" << "Relative Size Squared" << "Scaled Jacobian" << "Shape" << "Shape and Size";
QStringList MeshQuality::quadsFuncList = QStringList() << "Area" << "Aspect Ratio" << "Condition" << "Distortion" << "Edge Ratio" << "Jacobian" << "Maximum Angle" << "Maximum Aspect Frobenius" << "Maximum Edge Ratio" << "Mean Aspect Frobenius" << "Minimum Angle" << "Oddy" << "Radius Ratio" << "Relative Size Squared" << "Scaled Jacobian" << "Shape" << "Shape and Size" << "Shear" << "Shear and Size" << "Skew" << "Stretch" << "Taper" << "Warpage";
QStringList MeshQuality::hexasFuncList = QStringList() << "Diagonal" << "Dimension" << "Distortion" << "Edge Ratio" << "Jacobian" << "Maximum Aspect Frobenius" << "Maximum Edge Ratio" << "Mean Aspect Frobenius" << "Oddy" << "Scaled Jacobian" << "Relative Size Squared" << "Shape" << "Shape and Size" << "Shear" << "Shear and Size" << "Skew" << "Stretch" << "Taper" << "Volume" ;
QStringList MeshQuality::tetrasFuncList = QStringList() << "Aspect Beta" << "Aspect Frobenius" << "Aspect Gamma" << "Aspect Ratio" << "Collapse Ratio" << "Condition" << "Distortion" << "Edge Ratio" << "Jacobian" << "Minimum Angle" << "Radius Ratio" << "Relative Size Squared" << "Scaled Jacobian" << "Shape" << "Shape and Size" << "Volume";

// -------------------- MeshQuality --------------------
MeshQuality::MeshQuality(ActionExtension* extension) : Action(extension) {
    setName("Mesh Quality");
    setDescription("Display basic mesh quality information (for more information: Knupp et al, The verdict geometric quality library).");
    setComponentClassName("MeshComponent");
    setFamily("Basic Mesh");
    addTag("Quality");
    addTag("Distort");
    addTag("Degenerated");

    //-- widget lazy instantiation
    informationFrame = nullptr;
    isConnected = false;
}

// --------------- getWidget -------------------
QWidget* MeshQuality::getWidget() {
    if (!informationFrame) {
        //-- the frame
        informationFrame = new QFrame();
        informationFrame->setFrameShape(QFrame::StyledPanel);
        informationFrame->setFrameShadow(QFrame::Sunken);
        informationFrame->setLineWidth(3);

        //-- Create the specific widgets
        informationLabel = new QLabel();
        informationLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
        elementsComboBox = new QComboBox();
        qualityMeasureComboBox = new QComboBox();
        elementInfoLabel  = new QLabel();
        elementInfoLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
        qualityInfo = new QTableWidget();

        //-- the grid layout, put every GUI elements in it
        auto* informationFrameLayout = new QGridLayout();

        //-- add the widgets
        informationFrameLayout->addWidget(informationLabel, 0, 0, 1, 2);
        informationFrameLayout->addWidget(elementsComboBox, 1, 0, 1, 1);
        informationFrameLayout->addWidget(qualityMeasureComboBox, 2, 0, 1, 1);
        informationFrameLayout->addWidget(elementInfoLabel, 3, 0, 1, 2);
        informationFrameLayout->addWidget(qualityInfo, 4, 0, 1, 2);

        //-- set the layout for the action widget
        informationFrame->setLayout(informationFrameLayout);
    }

    if (!isConnected) {
        //-- run the action every time a picking is done in the axial/sagittal or coronal planes
        QObject::connect(elementsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updateComboBox(int)));
        QObject::connect(qualityMeasureComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(computeMeasure(int)));
        isConnected = true;
    }

    apply();

    return informationFrame;
}

// --------------- apply -------------------
Action::ApplyStatus MeshQuality::apply() {

    // get the last selected image component
    MeshComponent* meshComponent = dynamic_cast<MeshComponent*>(getTargets().last());
    // avoid an unnecessary re-compute => avoid time consuming for big mesh
    if (currentMeshName != meshComponent->getName()) {
        currentMeshName = meshComponent->getName();

        if (informationFrame != nullptr) {
            informationLabel->setText(getInfos(meshComponent));
        }
    }
    return SUCCESS;
}

// ------------------getInfos-------------------------------
QString MeshQuality::getInfos(MeshComponent* meshComponent) {
    // the information message (in rich text, i.e., html)
    QString message = "<b>Selected Component:</b> " + currentMeshName + "<br/>";
    message += "Number of Cells: " + QString("%1").arg(meshComponent->getPointSet()->GetNumberOfCells(), 6) + "<br/>";

    // compute number of each element type and save indices => necessary to update tableWidget
    std::map<unsigned char, std::vector<vtkIdType> >::iterator elementsMapIt;
    elementsMap.clear();
    for (vtkIdType i = 0; i < meshComponent->getPointSet()->GetNumberOfCells(); i++) {
        elementsMap[meshComponent->getPointSet()->GetCellType(i)].push_back(i);
    }

    // update message and ComboBox by checking which elements are in the current mesh
    elementsComboBox->clear();
    for (elementsMapIt = elementsMap.begin(); elementsMapIt != elementsMap.end(); ++elementsMapIt) {
        switch (elementsMapIt->first) {
            case VTK_TRIANGLE :
                message += "&nbsp;&nbsp;> Triangles: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>";
                //-- update Element type ComboBox
                elementsComboBox->addItem(tr("Triangles"));
                break;
            case VTK_TETRA :
                message += "&nbsp;&nbsp;> Tetras: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>";
                //-- update Element type ComboBox
                elementsComboBox->addItem(tr("Tetrahedra"));
                break;
            case VTK_HEXAHEDRON :
                message += "&nbsp;&nbsp;> Hexas: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>";
                //-- update Element type ComboBox
                elementsComboBox->addItem(tr("Hexahedra"));
                break;
            case VTK_QUAD :
                message += "&nbsp;&nbsp;> Quads: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>";
                //-- update Element type ComboBox
                elementsComboBox->addItem(tr("Quads"));
                break;
            case VTK_WEDGE:
                message += "&nbsp;&nbsp;> Prisms: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>&nbsp;&nbsp;<i>No quality measure for this type.</i><br/>";
                break;
            case VTK_PYRAMID :
                message += "&nbsp;&nbsp;> Pyramids: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>&nbsp;&nbsp;<i>No quality measure for this type.</i><br/>";
                break;
            default :
                message += "&nbsp;&nbsp;> Errors: " + QString("%1").arg(elementsMapIt->second.size(), 6) + "<br/>";
                break;
        }
    }
    message += "<br/><b>Display quality measures:</b> ";
    return message;
}

// ---------------computeMeasure--------
void MeshQuality::computeMeasure(int i) {

    // the most time consuming part
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    vtkSmartPointer<vtkDoubleArray> qualityArray;
    if (elementsComboBox->currentText() == "Triangles") {
        qualityArray = computeTrisQuality(dynamic_cast<MeshComponent*>(getTargets().last()), i);
    }
    else if (elementsComboBox->currentText() == "Tetrahedra") {
        qualityArray = computeTetrasQuality(dynamic_cast<MeshComponent*>(getTargets().last()), i);
    }
    else if (elementsComboBox->currentText() == "Hexahedra") {
        qualityArray = computeHexasQuality(dynamic_cast<MeshComponent*>(getTargets().last()), i);
    }
    else if (elementsComboBox->currentText() == "Quads") {
        qualityArray = computeQuadsQuality(dynamic_cast<MeshComponent*>(getTargets().last()), i);
    }
    else {
        // should never happen
        CAMITK_ERROR(tr("Elements given in input have no computable quality methods."))
        return;
    }
    // update tableWidget
    updateTableWidget(qualityArray);
    // update mesh color min and max are already computed above => no need in method parameter
    updateMeshColor(qualityArray);

    // restore the normal cursor
    QApplication::restoreOverrideCursor();
}

void MeshQuality::updateMeshColor(vtkSmartPointer< vtkDoubleArray > qualityArray) {
    std::vector<vtkIdType> indices;
    MeshComponent* meshComponent = dynamic_cast<MeshComponent*>(getTargets().last());

    //WARNING : Only UNFIORM TRIANGLES or UNIFORM QUADS mesh are colored at this stage
    if (elementsComboBox->currentText() == "Triangles") {
        indices = elementsMap[VTK_TRIANGLE];
    }
    else if (elementsComboBox->currentText() == "Quads") {
        indices = elementsMap[VTK_QUAD];
    }
    else if (elementsComboBox->currentText() == "Hexahedra") {
        indices = elementsMap[VTK_HEXAHEDRON];
    }
    else if (elementsComboBox->currentText() == "Tetrahedra") {
        indices = elementsMap[VTK_TETRA];
    }

    if (indices.size() > 0 && elementsMap.size() == 1) {
        vtkSmartPointer<vtkFloatArray> cellData = vtkSmartPointer<vtkFloatArray>::New();
        for (unsigned int i = 0; i < indices.size(); i++) {
            vtkIdType id;
            id = i;
            double val = qualityArray->GetValue(indices[id]);
            double scalar = getQualityColor(val, minAR, maxAR, minNR, maxNR);
            cellData->InsertValue(indices[id], scalar);
        }
        meshComponent->getPointSet()->GetCellData()->SetScalars(cellData);
        refreshApplication();
    }
}

// ---------------updateComboBox---------
void MeshQuality::updateComboBox(int) {

    //-- avoid to call computeMeasure twice due to the signal on Combobox
    QObject::disconnect(qualityMeasureComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(computeMeasure(int)));
    isConnected = false;
    qualityMeasureComboBox->clear();
    QObject::connect(qualityMeasureComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(computeMeasure(int)));
    isConnected = true;

    // update quality methods according to choosen element type
    if (elementsComboBox->currentText() == "Triangles") {
        qualityMeasureComboBox->addItems(trisFuncList);

    }
    else if (elementsComboBox->currentText() == "Tetrahedra") {
        qualityMeasureComboBox->addItems(tetrasFuncList);

    }
    else if (elementsComboBox->currentText() == "Hexahedra") {
        qualityMeasureComboBox->addItems(hexasFuncList);

    }
    else if (elementsComboBox->currentText() == "Quads") {
        qualityMeasureComboBox->addItems(quadsFuncList);
    }
}

// ---------------updateTableWidget--------------------------------------------------
void MeshQuality::updateTableWidget(vtkSmartPointer< vtkDoubleArray > qualityArray) {

    qualityInfo->setSortingEnabled(false); // temporarily for insertion!
    qualityInfo->setColumnCount(2);
    //Select entirely row && items are non editable
    qualityInfo->setSelectionBehavior(QAbstractItemView::SelectRows);
    qualityInfo->setEditTriggers(QAbstractItemView::NoEditTriggers);

    std::vector<vtkIdType> indices;
    if (elementsComboBox->currentText() == "Triangles") {
        indices = elementsMap[VTK_TRIANGLE];
    }
    else if (elementsComboBox->currentText() == "Tetrahedra") {
        indices = elementsMap[VTK_TETRA];
    }
    else if (elementsComboBox->currentText() == "Hexahedra") {
        indices = elementsMap[VTK_HEXAHEDRON];
    }
    else if (elementsComboBox->currentText() == "Quads") {
        indices = elementsMap[VTK_QUAD];
    }
    // resize the table
    qualityInfo->setRowCount((int)indices.size()); // if there are more than MAX_INT cells, too bad (that's over 2,147,483,647 cells anyway...)

    minV = VTK_DOUBLE_MAX;
    maxV = VTK_DOUBLE_MIN;
    double avg = 0.0;
    for (unsigned int i = 0; i < indices.size(); i++) {
        vtkIdType id;
        id = i;
        double val = qualityArray->GetValue(indices[id]);
        // used to sorting numbers in table
        QTableWidgetItem* itemId = new QTableWidgetItem;
        QTableWidgetItem* itemQ = new QTableWidgetItem;
        itemId->setData(Qt::DisplayRole, indices[i]);
        qualityInfo->setItem(i, 0, itemId);
        itemQ->setData(Qt::DisplayRole, val);
        qualityInfo->setItem(i, 1, itemQ);
        avg += val;
        if (val < minV) {
            minV = val;
        }
        if (val > maxV) {
            maxV = val;
        }
    }

    qualityInfo->setSortingEnabled(true);
    qualityInfo->sortByColumn(1, Qt::AscendingOrder);

    avg /= indices.size();

    QString elementInfo =  "&nbsp;&nbsp;> Minimum: " + QString("%1").arg(minV, 6) + "<br/>";
    elementInfo += "&nbsp;&nbsp;> Maximum: " + QString("%1").arg(maxV, 6) + "<br/>";
    elementInfo += "&nbsp;&nbsp;> Average: " + QString("%1").arg(avg, 6) + "<br/>";
    //-- update the element info label
    elementInfoLabel->setText(elementInfo);

    //-- keep lines below at the end to have a correct final layout
    QHeaderView* horizontalHeader = qualityInfo->horizontalHeader();
    horizontalHeader->setSectionResizeMode(QHeaderView::Stretch);

    QStringList headerTitles;
    headerTitles << "Cell Id" << "Quality";
    qualityInfo->setHorizontalHeaderLabels(headerTitles);
    qualityInfo->horizontalHeader()->setVisible(true);
    qualityInfo->verticalHeader()->setVisible(false);

}

// -----------------getQualityColor---------------------------------------------------------------------
double MeshQuality::getQualityColor(double val, double minAR, double maxAR, double minNR, double maxNR) {
    double xmin = VTK_DOUBLE_MAX, xmax = VTK_DOUBLE_MIN;
    bool sideMin = true;

    if (val >= minNR && val <= maxNR) {
        // acceptable value
        if (val >= minAR && val <= maxAR) {
            // good quality value = blue
            return 0;
        }
        else if (val < minAR) {
            xmax = minAR;
            if (minNR == VTK_DOUBLE_MIN) {
                xmin = minV;
            }
            else {
                xmin = minNR;
            }
        }
        else if (val > maxAR) {
            sideMin = false;
            xmin = maxAR;
            if (maxNR == VTK_DOUBLE_MAX) {
                xmax = maxV;
            }
            else {
                xmax = maxNR;
            }
        }
        else {
            // should never happen
            CAMITK_ERROR(tr("Mesh Quality: problem in value interpretation: val=%1, minAR=%2, maxAR=%3, minNR=%4, maxNR=%5")
                         .arg(QString::number(val),
                              QString::number(minAR),
                              QString::number(maxAR),
                              QString::number(minNR),
                              QString::number(maxNR)))
            return 1;
        }
    }
    else {
        // bad value = red
        return 1;
    }
    double dist = xmax - xmin;
    double temp = xmax - val;
    double res = fabs(temp / dist);
    if (!sideMin) {
        res = 1 - res;
    }
    // result is ranged between [0;1] to interpolate a color between [blue;red]
    return res;
}

// ------------------------------------------computeTrisQuality-------------------------------------------------
vtkSmartPointer<vtkDoubleArray> MeshQuality::computeTrisQuality(MeshComponent* meshComponent, int qualityTest) {
    //-- compute quality
    vtkSmartPointer<vtkMeshQuality> qualityFilter = vtkSmartPointer<vtkMeshQuality>::New();
    qualityFilter->SetInputConnection(meshComponent->getDataPort());
    switch (qualityTest) {
        case 0:
            qualityFilter->SetTriangleQualityMeasureToArea();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 1:
            qualityFilter->SetTriangleQualityMeasureToAspectFrobenius();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 2:
            qualityFilter->SetTriangleQualityMeasureToAspectRatio();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 3:
            qualityFilter->SetTriangleQualityMeasureToCondition();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 4:
            qualityFilter->SetTriangleQualityMeasureToDistortion();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 5:
            qualityFilter->SetTriangleQualityMeasureToEdgeRatio();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 6:
            qualityFilter->SetTriangleQualityMeasureToMaxAngle();
            minAR = 60;
            maxAR = 90;
            minNR = 60;
            maxNR = 180;
            break;
        case 7:
            qualityFilter->SetTriangleQualityMeasureToMinAngle();
            minAR = 30;
            maxAR = 60;
            minNR = 0;
            maxNR = 60;
            break;
        case 8:
            qualityFilter->SetTriangleQualityMeasureToRadiusRatio();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 9:
            qualityFilter->SetTriangleQualityMeasureToRelativeSizeSquared();
            minAR = 0.25;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 10:
            qualityFilter->SetTriangleQualityMeasureToScaledJacobian();
            minAR = 0.5;
            maxAR = (2 * sqrt(double(3)) / 3);
            minNR = -(2 * sqrt(double(3)) / 3);
            maxNR = (2 * sqrt(double(3)) / 3);
            break;
        case 11:
            qualityFilter->SetTriangleQualityMeasureToShape();
            minAR = 0.25;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 12:
            qualityFilter->SetTriangleQualityMeasureToShapeAndSize();
            minAR = 0.25;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;

    }
    qualityFilter->Update();
    vtkDataSet* qualityMesh = qualityFilter->GetOutput();

    return vtkDoubleArray::SafeDownCast(qualityMesh->GetCellData()->GetArray("Quality"));
}

// --------------------------------------------computeHexasQuality-------------------------------------------------
vtkSmartPointer< vtkDoubleArray > MeshQuality::computeHexasQuality(MeshComponent* meshComponent, int qualityTest) {
    //-- compute quality
    vtkSmartPointer<vtkMeshQuality> qualityFilter = vtkSmartPointer<vtkMeshQuality>::New();
    qualityFilter->SetInputConnection(meshComponent->getDataPort());
    switch (qualityTest) {
        case 0:
            qualityFilter->SetHexQualityMeasureToDiagonal();
            minAR = 0.65;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 1:
            qualityFilter->SetHexQualityMeasureToDimension();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 2:
            qualityFilter->SetHexQualityMeasureToDistortion();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 3:
            qualityFilter->SetHexQualityMeasureToEdgeRatio();
            minAR = 1;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 4:
            qualityFilter->SetHexQualityMeasureToJacobian();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 5:
            qualityFilter->SetHexQualityMeasureToMaxAspectFrobenius();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 6:
#if (VTK_MAJOR_VERSION >= 9 && VTK_MINOR_VERSION >= 2) // VTK 9.2 has renamed SetHexQualityMeasureToMaxEdgeRatios to SetHexQualityMeasureToMaxEdgeRatio (removing the trailing 's')
            qualityFilter->SetHexQualityMeasureToMaxEdgeRatio();
#else
            qualityFilter->SetHexQualityMeasureToMaxEdgeRatios();
#endif
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 7:
            qualityFilter->SetHexQualityMeasureToMedAspectFrobenius();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 8:
            qualityFilter->SetHexQualityMeasureToOddy();
            minAR = 0;
            maxAR = 0.5;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 9:
            qualityFilter->SetHexQualityMeasureToScaledJacobian();
            minAR = 0.5;
            maxAR = 1;
            minNR = -1;
            maxNR = 1;
            break;
        case 10:
            qualityFilter->SetHexQualityMeasureToRelativeSizeSquared();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 11:
            qualityFilter->SetHexQualityMeasureToShape();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 12:
            qualityFilter->SetHexQualityMeasureToShapeAndSize();
            minAR = 0.2;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 13:
            qualityFilter->SetHexQualityMeasureToShear();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 14:
            qualityFilter->SetHexQualityMeasureToShearAndSize();
            minAR = 0.2;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 15:
            qualityFilter->SetHexQualityMeasureToSkew();
            minAR = 0;
            maxAR = 0.5;
            minNR = 0;
            maxNR = 1;
            break;
        case 16:
            qualityFilter->SetHexQualityMeasureToStretch();
            minAR = 0.25;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 17:
            qualityFilter->SetHexQualityMeasureToTaper();
            minAR = 0;
            maxAR = 0.5;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 18:
            qualityFilter->SetHexQualityMeasureToVolume();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
    }
    qualityFilter->Update();
    vtkDataSet* qualityMesh = qualityFilter->GetOutput();

    return vtkDoubleArray::SafeDownCast(qualityMesh->GetCellData()->GetArray("Quality"));
}

// --------------------------------------------copputeQuadsQuality-------------------------------------------------
vtkSmartPointer< vtkDoubleArray > MeshQuality::computeQuadsQuality(MeshComponent* meshComponent, int qualityTest) {
    //-- compute quality
    vtkSmartPointer<vtkMeshQuality> qualityFilter = vtkSmartPointer<vtkMeshQuality>::New();
    qualityFilter->SetInputConnection(meshComponent->getDataPort());
    switch (qualityTest) {
        case 0:
            qualityFilter->SetQuadQualityMeasureToArea();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 1:
            qualityFilter->SetQuadQualityMeasureToAspectRatio();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 2:
            qualityFilter->SetQuadQualityMeasureToCondition();
            minAR = 1;
            maxAR = 4;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 3:
            qualityFilter->SetQuadQualityMeasureToDistortion();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 4:
            qualityFilter->SetQuadQualityMeasureToEdgeRatio();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 5:
            qualityFilter->SetQuadQualityMeasureToJacobian();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 6:
            qualityFilter->SetQuadQualityMeasureToMaxAngle();
            minAR = 90;
            maxAR = 135;
            minNR = 90;
            maxNR = 360;
            break;
        case 7:
            qualityFilter->SetQuadQualityMeasureToMaxAspectFrobenius();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 8:
#if (VTK_MAJOR_VERSION >= 9 && VTK_MINOR_VERSION >= 2) // VTK 9.2 has renamed SetQuadQualityMeasureToMaxEdgeRatio to SetQuadQualityMeasureToMaxEdgeRatio (removing the trailing 's')
            qualityFilter->SetQuadQualityMeasureToMaxEdgeRatio();
#else
            qualityFilter->SetQuadQualityMeasureToMaxEdgeRatios();
#endif
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 9:
            qualityFilter->SetQuadQualityMeasureToMedAspectFrobenius();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 10:
            qualityFilter->SetQuadQualityMeasureToMinAngle();
            minAR = 45;
            maxAR = 90;
            minNR = 0;
            maxNR = 90;
            break;
        case 11:
            qualityFilter->SetQuadQualityMeasureToOddy();
            minAR = 0;
            maxAR = 0.5;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 12:
            qualityFilter->SetQuadQualityMeasureToRadiusRatio();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 13:
            qualityFilter->SetQuadQualityMeasureToRelativeSizeSquared();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 14:
            qualityFilter->SetQuadQualityMeasureToScaledJacobian();
            minAR = 0.3;
            maxAR = 1;
            minNR = -1;
            maxNR = 1;
            break;
        case 15:
            qualityFilter->SetQuadQualityMeasureToShape();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 16:
            qualityFilter->SetQuadQualityMeasureToShapeAndSize();
            minAR = 0.2;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 17:
            qualityFilter->SetQuadQualityMeasureToShear();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 18:
            qualityFilter->SetQuadQualityMeasureToShearAndSize();
            minAR = 0.2;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 19:
            qualityFilter->SetQuadQualityMeasureToSkew();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 20:
            qualityFilter->SetQuadQualityMeasureToStretch();
            minAR = 0.25;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 21:
            qualityFilter->SetQuadQualityMeasureToTaper();
            minAR = 0;
            maxAR = 0.7;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 22:
            qualityFilter->SetQuadQualityMeasureToWarpage();
            minAR = 0;
            maxAR = 0.7;
            minNR = 0;
            maxNR = 2;
            break;
    }
    qualityFilter->Update();
    vtkDataSet* qualityMesh = qualityFilter->GetOutput();

    return vtkDoubleArray::SafeDownCast(qualityMesh->GetCellData()->GetArray("Quality"));
}

// --------------------------------------------computeTetrasQuality-------------------------------------------------
vtkSmartPointer< vtkDoubleArray > MeshQuality::computeTetrasQuality(MeshComponent* meshComponent, int qualityTest) {
    //-- compute quality
    vtkSmartPointer<vtkMeshQuality> qualityFilter = vtkSmartPointer<vtkMeshQuality>::New();
    qualityFilter->SetInputConnection(meshComponent->getDataPort());
    qualityFilter->SetHexQualityMeasureToDistortion();
    switch (qualityTest) {
        case 0:
#if (VTK_MAJOR_VERSION >= 9 && VTK_MINOR_VERSION >= 2) // VTK 9.2 has removed SetTetQualityMeasureToAspectBeta (see vtk git commit 5d09b44405543981130ff7684568766a73bbe81c)
            // not replaced, use default quality measure
#else
            qualityFilter->SetTetQualityMeasureToAspectBeta();
#endif
            minAR = 0.1;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 1:
            qualityFilter->SetTetQualityMeasureToAspectFrobenius();
            minAR = 1;
            maxAR = 1.3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 2:
            qualityFilter->SetTetQualityMeasureToAspectGamma();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 3:
            qualityFilter->SetTetQualityMeasureToAspectRatio();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 4:
            qualityFilter->SetTetQualityMeasureToCollapseRatio();
            minAR = 0.1;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 5:
            qualityFilter->SetTetQualityMeasureToCondition();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 6:
            qualityFilter->SetTetQualityMeasureToDistortion();
            minAR = 0.5;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 7:
            qualityFilter->SetTetQualityMeasureToEdgeRatio();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 8:
            qualityFilter->SetTetQualityMeasureToJacobian();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = 0;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 9:
            qualityFilter->SetTetQualityMeasureToMinAngle();
            minAR = 40;
            maxAR = 70.528779; /*approximately 180/PI*acos(1/3)*/
            minNR = 0;
            maxNR = 70.528779;
            break;
        case 10:
            qualityFilter->SetTetQualityMeasureToRadiusRatio();
            minAR = 1;
            maxAR = 3;
            minNR = 1;
            maxNR = VTK_DOUBLE_MAX;
            break;
        case 11:
            qualityFilter->SetTetQualityMeasureToRelativeSizeSquared();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 12:
            qualityFilter->SetTetQualityMeasureToScaledJacobian();
            minAR = 0.5;
            maxAR = sqrt(double(2)) / 2;
            minNR = -(sqrt(double(2)) / 2);
            maxNR = sqrt(double(2)) / 2;
            break;
        case 13:
            qualityFilter->SetTetQualityMeasureToShape();
            minAR = 0.3;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 14:
            qualityFilter->SetTetQualityMeasureToShapeAndSize();
            minAR = 0.2;
            maxAR = 1;
            minNR = 0;
            maxNR = 1;
            break;
        case 15:
            qualityFilter->SetTetQualityMeasureToVolume();
            minAR = 0;
            maxAR = VTK_DOUBLE_MAX;
            minNR = -VTK_DOUBLE_MAX;
            maxNR = VTK_DOUBLE_MAX;
            break;
    }
    qualityFilter->Update();
    vtkDataSet* qualityMesh = qualityFilter->GetOutput();

    return vtkDoubleArray::SafeDownCast(qualityMesh->GetCellData()->GetArray("Quality"));
}
