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

#include <sstream>
#include <Log.h>

#include <QFileInfo>
#include <QVector3D>

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

#include <vtkUnstructuredGrid.h>
#include <vtkTetra.h>
#include <vtkHexahedron.h>
#include <vtkWedge.h>
#include <vtkPyramid.h>
#include <vtkCellArray.h>
#include <vtkSelection.h>
#include <vtkSelectionNode.h>

using namespace camitk;

// -------------------- default constructor  --------------------
MshComponent::MshComponent(const QString& file) : MeshComponent(file, QFileInfo(file).baseName()) {

    std::ifstream inputFile(file.toStdString().c_str(), std::ios::in);
    std::string line;

    // choose the right format
    while (std::getline(inputFile, line)) {
        if (!line.compare("$Nodes")) {
            readV2Format(inputFile);
            return;
        }
        else if (!line.compare("$NOD")) {
            readLegacyFormat(inputFile);
            return;
        }
    }
}

void MshComponent::readLegacyFormat(std::ifstream& fileStream) {
    std::string line;

    int n, eltType, nbNodes;
    vtkIdType vtkEltTypeId;
    double pt[3];
    vtkSmartPointer<vtkUnstructuredGrid> mesh = vtkSmartPointer<vtkUnstructuredGrid>::New();
    mesh->Allocate();

    long nbCells = 0;

    //build the vtkPoinSet from the file
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();

    std::getline(fileStream, line);    // nb vertices
    while (std::getline(fileStream, line)) {
        std::istringstream stream(line, std::istringstream::in);
        if ((!line.compare("$ENDNOD")) || (!line.compare("$ENDNOD\r"))) {
            break;
        }
        stream >> n;
        for (int i = 0; i < 3; i++) {
            stream >> pt[i];
        }
        points->InsertNextPoint(pt);
    }
    mesh->SetPoints(points);

    // get elements
    while (std::getline(fileStream, line)) {
        if ((!line.compare("$ELM")) || (!line.compare("$ELM\r"))) {
            break;
        }
    }

    std::getline(fileStream, line);    // nb elements
    std::istringstream stream(line, std::istringstream::in);
    stream >> n;

    while (std::getline(fileStream, line)) {
        std::istringstream stream(line, std::istringstream::in);
        if ((!line.compare("$ENDELM")) || (!line.compare("$ENDELM\r"))) {
            break;
        }
        // element number
        stream >> n;
        // element type
        stream >> eltType;

        for (int i = 0; i < 3; i++) {
            stream >> n;
        }

        switch (eltType) {
            case 1 :
                vtkEltTypeId = VTK_LINE;
                nbNodes = 2;
                break;
            case 2 :
                vtkEltTypeId = VTK_TRIANGLE;
                nbNodes = 3;
                break;
            case 4 :
                vtkEltTypeId = VTK_TETRA;
                nbNodes = 4;
                break;
            case 5 :
                vtkEltTypeId = VTK_HEXAHEDRON;
                nbNodes = 8;
                break;
            case 6 :
                vtkEltTypeId = VTK_WEDGE;
                nbNodes = 6;
                break;
            case 7 :
                vtkEltTypeId = VTK_PYRAMID;
                nbNodes = 5;
                break;
            default :
                CAMITK_INFO(tr("Unknown element type: %1. Skipping.").arg(QString::number(eltType)))
                continue;
        }

        vtkSmartPointer<vtkIdList> idList = vtkSmartPointer<vtkIdList>::New();
        for (int j = 0; j < nbNodes; j++) {
            stream >> n;
            idList->InsertNextId(n - 1);
        }

        mesh->InsertNextCell(vtkEltTypeId, idList);
        nbCells++;
    }

    // instantiate the Geometry
    initRepresentation(mesh);

    // add the represention in the 3D viewer
    setVisibility("3D Viewer", true);
}

void MshComponent::readV2Format(std::ifstream& fileStream) {
    std::string line;

    int n, eltType, nbNodes, nbTags, tag;
    std::string phyName;
    vtkIdType vtkEltTypeId;
    double pt[3];
    vtkSmartPointer<vtkUnstructuredGrid> mesh = vtkSmartPointer<vtkUnstructuredGrid>::New();
    mesh->Allocate();
    std::map< int, std::pair< std::string, std::vector< int > > > physicalRegions;

    long nbCells = 0;

    //build the vtkPoinSet from the file
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();

    std::getline(fileStream, line);    // nb vertices
    while (std::getline(fileStream, line)) {
        std::istringstream stream(line, std::istringstream::in);
        if ((!line.compare("$EndNodes")) || (!line.compare("$EndNodes\r"))) {
            break;
        }
        stream >> n;
        for (int i = 0; i < 3; i++) {
            stream >> pt[i];
        }
        points->InsertNextPoint(pt);
    }
    mesh->SetPoints(points);

    // get elements
    while (std::getline(fileStream, line)) {
        if ((!line.compare("$Elements")) || (!line.compare("$Elements\r"))) {
            break;
        }
    }

    std::getline(fileStream, line);    // nb elements
    std::istringstream stream(line, std::istringstream::in);
    stream >> n;

    while (std::getline(fileStream, line)) {
        std::istringstream stream(line, std::istringstream::in);
        if ((!line.compare("$EndElements")) || (!line.compare("$EndElements\r"))) {
            break;
        }
        // element number
        stream >> n;
        // element type
        stream >> eltType;
        // number of tags
        stream >> nbTags;

        // We interpret tags differently than the original gmsh file format.
        // The tags here represents ids of region which elements belongs to.
        for (int i = 0; i < nbTags; i++) {
            stream >> tag;
            // pass the first that correspond to gmsh physical entity
            if (i > 0) {
                physicalRegions[tag].second.push_back(n - 1);
            }
        }

        switch (eltType) {
            case 1 :
                vtkEltTypeId = VTK_LINE;
                nbNodes = 2;
                break;
            case 2 :
                vtkEltTypeId = VTK_TRIANGLE;
                nbNodes = 3;
                break;
            case 4 :
                vtkEltTypeId = VTK_TETRA;
                nbNodes = 4;
                break;
            case 5 :
                vtkEltTypeId = VTK_HEXAHEDRON;
                nbNodes = 8;
                break;
            case 6 :
                vtkEltTypeId = VTK_WEDGE;
                nbNodes = 6;
                break;
            case 7 :
                vtkEltTypeId = VTK_PYRAMID;
                nbNodes = 5;
                break;
            default :
                CAMITK_INFO(tr("Unknown element type: %1. Skipping.").arg(QString::number(eltType)))
                continue;
        }

        vtkSmartPointer<vtkIdList> idList = vtkSmartPointer<vtkIdList>::New();
        for (int j = 0; j < nbNodes; j++) {
            stream >> n;
            idList->InsertNextId(n - 1);
        }

        mesh->InsertNextCell(vtkEltTypeId, idList);
        nbCells++;
    }

    // get physical names
    while (std::getline(fileStream, line)) {
        if ((!line.compare("$PhysicalNames")) || (!line.compare("$PhysicalNames\r"))) {
            break;
        }
    }

    std::getline(fileStream, line);    // nb physical names
    /*    std::istringstream stream ( line, std::istringstream::in );
        stream >> n*/;

    while (std::getline(fileStream, line)) {
        std::istringstream stream(line, std::istringstream::in);
        if ((!line.compare("$EndPhysicalNames")) || (!line.compare("$EndPhysicalNames\r"))) {
            break;
        }

        // physical dimension
        stream >> n;
        // physical number
        stream >> tag;
        // physical name
        stream >> phyName;

        physicalRegions[tag].first = phyName;
    }

    // add the physical regions

    std::map< int, std::pair< std::string, std::vector< int > > >::iterator it;

    for (it = physicalRegions.begin(); it != physicalRegions.end(); ++it) {
        QString phyName = it->second.first.c_str();
        phyName.replace(QString("\""), QString(""));
        //addSelection (phyName , vtkSelectionNode::CELL,
        //vtkSelectionNode::INDICES );

        vtkSmartPointer<vtkIdTypeArray> ids = vtkSmartPointer<vtkIdTypeArray>::New();
        for (unsigned int i = 0; i < it->second.second.size(); i++) {
            ids->InsertNextValue(it->second.second[i]);
        }

        addSelection(phyName, vtkSelectionNode::CELL, vtkSelectionNode::INDICES,
                     ids, MeshSelectionModel::MERGE);

        //addToSelection(phyName, ids);
    }

    // instantiate the Geometry
    initRepresentation(mesh);

    // add the represention in the 3D viewer
    setVisibility("3D Viewer", true);
}
