/*****************************************************************************
 * $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$
 ****************************************************************************/
// CamiTK includes
#include "MergeMeshes.h"

#include <Application.h>
#include <Property.h>
#include <TransformationManager.h>
#include <Log.h>

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

#include <vtkType.h>
#include <vtkAppendFilter.h>
#include <vtkCellType.h>
#include <vtkIdList.h>
#include <vtkCell.h>
#include <vtkCommand.h>
#include <vtkCallbackCommand.h>

using namespace camitk;


// --------------- constructor -------------------
MergeMeshes::MergeMeshes(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Merge Meshes");
    setDescription(tr("<br>Create a new mesh from two input meshes (merge). <ul> \
                   <li>If points are exactly at the same coordinates, they will be merged. </li> \
                   <li>If cells (tetras, triangles) are overlapped, only one is kept (Points must be exactly at the same coordinates).</li> \
                   <li> If a triangle is enclosed in a volume during the merging action, it will be removed.</li></ul> \
                   <br><br><b>WARNING:</b>This action works only with point set, triangular surface mesh or tetrahedral and/or triangular volume mesh. \
                   <p>The resulting mesh frame will be:</p> \
                   <ul> \
                   <li>the same as both input meshes if they both have the same frame,</li> \
                   <li>a new frame with a default identity transformation to world if both frames have an independent frame and a default identity transformation to world,</li> \
                   <li>the same as the first mesh otherwise.</li> \
                   </ul>"));

    setComponentClassName("MeshComponent");

    // Setting classification family and tags
    this->setFamily("Mesh Processing");
    this->addTag(tr("Merge Meshes"));
}

// --------------- destructor -------------------
MergeMeshes::~MergeMeshes() {
    // do not delete the widget has it might have been used in the ActionViewer (i.e. the ownership might have been taken by the stacked widget)
}

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

    // set waiting cursor and status bar
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    Application::resetProgressBar();

    //check cases
    if (getTargets().size() < 2) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        CAMITK_WARNING(tr("Number of targets is insufficient (at least 2 meshes required). Action aborted."))
        return ABORTED;

    }

    if (getTargets().size() > 2) {
        // restore the normal cursor
        QApplication::restoreOverrideCursor();
        CAMITK_WARNING(tr("Merge meshes only works with 2 meshes. Action aborted."))
        return ABORTED;
    }

    // use the two selected last targets
    MeshComponent* firstMesh = dynamic_cast<MeshComponent*>(getTargets().first());
    MeshComponent* secondMesh = dynamic_cast<MeshComponent*>(getTargets().last());

    // merge duplicate points
    vtkSmartPointer<vtkAppendFilter> filter = vtkSmartPointer<vtkAppendFilter>::New();
    filter->AddInputConnection(firstMesh->getDataPort());
    filter->AddInputConnection(secondMesh->getDataPort());
    filter->SetMergePoints(1);

    vtkSmartPointer<vtkCallbackCommand> progressCallback = vtkSmartPointer<vtkCallbackCommand>::New();
    progressCallback->SetCallback(&Application::vtkProgressFunction);
    filter->AddObserver(vtkCommand::ProgressEvent, progressCallback);
    filter->Update();

    //-- create a new object BUT without duplicating the cells
    int nbCellMesh1 = firstMesh->getPointSet()->GetNumberOfCells();
    vtkSmartPointer<vtkPointSet> resultPointSet = removeDuplicateCells(filter->GetOutput(), nbCellMesh1, false);

    // create a new mesh component
    MeshComponent* merged = new MeshComponent(resultPointSet, firstMesh->getName() + "_" + secondMesh->getName() + "_merged");

    //-- check the frame configuration
    // if the frame are the same or if they are not both default identity to world
    // then use the first mesh frame
    // otherwise leave it as it is, i.e., the merged mesh has its own frame with default identity to world
    if (firstMesh->getFrame() == secondMesh->getFrame() 
        || !TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(firstMesh->getFrame(), TransformationManager::getWorldFrame())) 
        || !TransformationManager::isDefaultIdentityToWorld(TransformationManager::getTransformation(secondMesh->getFrame(), TransformationManager::getWorldFrame()))) {
        merged->setFrame(TransformationManager::getFrameOfReferenceOwnership(firstMesh->getFrame()));
    }
    refreshApplication();

    // restore the normal cursor and progress bar
    Application::resetProgressBar();
    QApplication::restoreOverrideCursor();

    return SUCCESS;
}

//-----------------------------------------removeDuplicateCells---------------------------------------
vtkSmartPointer< vtkUnstructuredGrid > MergeMeshes::removeDuplicateCells(vtkSmartPointer< vtkPointSet > input, int nbCellMesh1, bool saveIndices) {
    //-- reconstruct mesh
    vtkSmartPointer<vtkUnstructuredGrid> mergeMesh = vtkSmartPointer<vtkUnstructuredGrid>::New();
    mergeMesh->Allocate();
    mergeMesh->SetPoints(input->GetPoints());

    vtkIdType eltType;
    std::vector<vtkIdType> duplicateTRI, tris, duplicateTET;

    //-- Duplicate cells detection
    //WARNING: vtkMergeCells exists but seems to be more complicated (final number of cells must be known before process ?!!?)
    for (vtkIdType i = 0; i < input->GetNumberOfCells(); i++) {
        eltType = input->GetCellType(i);
        vtkSmartPointer<vtkIdList> cellsId1 = vtkSmartPointer<vtkIdList>::New();
        //var used to detect duplicate tetra
        std::vector<vtkIdType> nbrTET;

        switch (eltType) {
            case VTK_TETRA: {
                //get cells surrounding each point of this tetrahedron
                input->GetPointCells(input->GetCell(i)->GetPointId(0), cellsId1);
                vtkSmartPointer<vtkIdList> cellsId2 = vtkSmartPointer<vtkIdList>::New();
                input->GetPointCells(input->GetCell(i)->GetPointId(1), cellsId2);
                vtkSmartPointer<vtkIdList> cellsId3 = vtkSmartPointer<vtkIdList>::New();
                input->GetPointCells(input->GetCell(i)->GetPointId(2), cellsId3);
                vtkSmartPointer<vtkIdList> cellsId4 = vtkSmartPointer<vtkIdList>::New();
                input->GetPointCells(input->GetCell(i)->GetPointId(3), cellsId4);
                //do intersection; Results are cells containing ALL points above.
                cellsId1->IntersectWith(cellsId2);
                cellsId1->IntersectWith(cellsId3);
                cellsId1->IntersectWith(cellsId4);
                //save duplicate cells in an array to reconstruct mesh after
                for (vtkIdType j = 1; j < cellsId1->GetNumberOfIds(); j++) {
                    if (cellsId1->GetId(j) != i) {
                        duplicateTET.push_back(cellsId1->GetId(j));
                    }
                }
                break;
            }

            case VTK_TRIANGLE: {
                input->GetPointCells(input->GetCell(i)->GetPointId(0), cellsId1);
                vtkSmartPointer<vtkIdList> cellsId2 = vtkSmartPointer<vtkIdList>::New();
                input->GetPointCells(input->GetCell(i)->GetPointId(1), cellsId2);
                vtkSmartPointer<vtkIdList> cellsId3 = vtkSmartPointer<vtkIdList>::New();
                input->GetPointCells(input->GetCell(i)->GetPointId(2), cellsId3);
                //do intersection; Results is cells containing ALL points above.
                cellsId1->IntersectWith(cellsId2);
                cellsId1->IntersectWith(cellsId3);

                //var used to keep only one copy of element
                bool oneShot = false;
                //var used to save kept triangle
                std::vector<vtkIdType> keptTRI;
                keptTRI.clear();
                //var used to save points of tetras (unexpected results with vtk::getPoints)
                std::vector<std::vector<double> > nbrTET_points;
                nbrTET.clear();

                for (vtkIdType j = 0; j < cellsId1->GetNumberOfIds(); j++) {
                    if (input->GetCellType(cellsId1->GetId(j)) == VTK_TETRA) {
                        nbrTET.push_back(cellsId1->GetId(j));

                        //Get points by hand because getPoints (10 lines below approx) gives some unexpected results ?!!?
                        std::vector<double> temp;
                        for (int h = 0; h < 4; h++) {
                            temp.clear();
                            temp.push_back(input->GetCell(cellsId1->GetId(j))->GetPoints()->GetPoint(h)[0]);
                            temp.push_back(input->GetCell(cellsId1->GetId(j))->GetPoints()->GetPoint(h)[1]);
                            temp.push_back(input->GetCell(cellsId1->GetId(j))->GetPoints()->GetPoint(h)[2]);
                            nbrTET_points.push_back(temp);
                        }

                    }
                    else if (input->GetCellType(cellsId1->GetId(j)) == VTK_TRIANGLE) {
                        std::vector<vtkIdType>::iterator itTRI = std::find(duplicateTRI.begin(), duplicateTRI.end(), cellsId1->GetId(j));
                        std::vector<vtkIdType>::iterator ittri = std::find(tris.begin(), tris.end(), cellsId1->GetId(j));

                        if (oneShot && ittri == tris.end()) {
                            duplicateTRI.push_back(cellsId1->GetId(j));
                        }
                        else if (itTRI == duplicateTRI.end() && ittri == tris.end()) {
                            oneShot = true;
                            tris.push_back(cellsId1->GetId(j));
                            keptTRI.push_back(cellsId1->GetId(j));
                        }
                    }
                }

                // case where triangle is a face shared by two tetrahedrons (no more necessary)
                //-- test to identify triangle are no longer necessary
                unsigned int nbreDupTet;
                if (nbrTET.size() > 1) {
                    nbreDupTet = 0;
                    for (vtkIdType j = 0; (unsigned)j < nbrTET.size(); j++) {
                        std::vector<std::vector<double> > t1(nbrTET_points.begin() + (4 * j), nbrTET_points.begin() + ((4 * j) + 4));
                        for (vtkIdType k = 0; (unsigned) k < nbrTET.size(); k++) {
                            std::vector<std::vector<double> > t2(nbrTET_points.begin() + (4 * k), nbrTET_points.begin() + ((4 * k) + 4));
                            if (duplicateTetra(t1, t2)) {
                                nbreDupTet++;
                            }
                        }
                    }

                    if (nbreDupTet != (nbrTET.size()*nbrTET.size())) {
                        //tetras are not the same. A triangle between both is no longer necessary
                        for (vtkIdType j = 0; (unsigned) j < keptTRI.size(); j++) {
                            duplicateTRI.push_back(keptTRI[j]);
                        }
                    }
                }
                break;
            }
        }
    }

    //Should be 90% after vtkCommand and now it's finished
    Application::setProgressBarValue(95);

    // reconstruct the mesh without duplicates
    kVec.clear();
    double k1, k2;
    if (saveIndices) {
        k1 = getParameterValue("Value for mesh 1").toDouble();
        k2 = getParameterValue("Value for mesh 2").toDouble();
    }
    for (vtkIdType i = 0; i < input->GetNumberOfCells() ; i++) {
        switch (input->GetCellType(i)) {
            case VTK_TRIANGLE:
                if (!(std::find(duplicateTRI.begin(), duplicateTRI.end(), i) != duplicateTRI.end())) {
                    //it's not a duplicate cells, add it !
                    mergeMesh->InsertNextCell(VTK_TRIANGLE, input->GetCell(i)->GetPointIds());
                    if (saveIndices) {
                        if (i <= nbCellMesh1) {
                            kVec.push_back(k1);
                        }
                        else {
                            kVec.push_back(k2);
                        }
                    }
                }
                break;
            case VTK_TETRA:
                if (!(std::find(duplicateTET.begin(), duplicateTET.end(), i) != duplicateTET.end())) {
                    //it's not a duplicate cells, add it !
                    mergeMesh->InsertNextCell(VTK_TETRA, input->GetCell(i)->GetPointIds());
                    if (saveIndices) {
                        if (i <= nbCellMesh1) {
                            kVec.push_back(k1);
                        }
                        else {
                            kVec.push_back(k2);
                        }
                    }
                }
                break;
        }
    }

    duplicateTET.clear();
    duplicateTRI.clear();

    Application::setProgressBarValue(99);
    return mergeMesh;
}

//---------------duplicateTetra----------------------------------------------------------------------------
bool MergeMeshes::duplicateTetra(std::vector< std::vector<double> > t1, std::vector< std::vector<double> > t2) {
    bool point = false;
    for (unsigned int i = 0; i < t1.size(); i++) {
        std::vector<double> p = t1[i];
        point = false;
        for (unsigned int j = 0; j < t2.size(); j++) {
            std::vector<double> p2 = t2[j];
            if (p[0] == p2[0] && p[1] == p2[1] && p[2] == p2[2]) {
                point = true;
            }
        }
        //one point of the first tetra is not a point of the second tetra ==> tetra are not equal.
        if (!point) {
            return false;
        }
    }
    if (!point) {
        return false;
    }
    return true;
}

//---------------printKToFile------
void MergeMeshes::printKToFile() {
    QString fileNeigh = QString(getParameterValue("File path").toString());
    fileNeigh += ".csv";

    std::ofstream mshFile(fileNeigh.toUtf8());
    char separator = ',';

    for (unsigned int i = 0; i < kVec.size(); i++) {
        mshFile << i << separator << kVec[i] << std::endl;
    }
    mshFile.close();
}

