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

// CEP stuff
#include <PMLComponent.h>

//-- PML
// need to be before "using namespace camitk"
#include <pml/PhysicalModel.h>
#include <pml/MultiComponent.h>

// CamiTK stuff
#include <Application.h>

using namespace camitk;

//-- vtk
#include <vtkCell.h>
#include <vtkAbstractArray.h>

// --------------- getName -------------------
QString PMLComponentExtension::getName() const {
    return "PML Component";
}

// --------------- getDescription -------------------
QString PMLComponentExtension::getDescription() const {
    return "<b>New PML COMPONENT!</b>";// Manage Physical Model <em>.pml</em> files in <b>CamiTK</b>.<br/>CamiTK was initially mainly developed to support this format. Lots of things are possible with a physical model!";
}

// --------------- getFileExtensions -------------------
QStringList PMLComponentExtension::getFileExtensions() const {
    QStringList ext;
    ext << "pml";
    return ext;
}

// --------------- open -------------------
camitk::Component* PMLComponentExtension::open(const QString& fileName) {

    Application::showStatusBarMessage("Loading " + fileName + "...");

    // instantiate the component
    auto* pmlComponent = new PMLComponent(fileName);

    // reset the progress bar
    Application::resetProgressBar();

    return pmlComponent;
}

// --------------- save -------------------
bool PMLComponentExtension::save(camitk::Component* component) const {
    PMLComponent* comp = dynamic_cast<PMLComponent*>(component);

    if (comp) {
        // easy!
        // Just generate an ostream from the filename and then xmlPrint pop
        std::ofstream outputFile(comp->getFileName().toStdString().c_str());
        comp->getPhysicalModel()->xmlPrint(outputFile);
        outputFile.close();
        return true;
    }
    else {
        // save from generic MeshComponent
        MeshComponent* meshComp = dynamic_cast<MeshComponent*>(component);

        if (meshComp && meshComp->getPointSet()->GetNumberOfPoints() > 0) {
            //-- create a new physical model
            PhysicalModel* newPM = new PhysicalModel();

            // extract the atoms from the Geometry
            vtkSmartPointer<vtkPoints> thePoints = vtkSmartPointer<vtkPoints>::New();
            thePoints->DeepCopy(meshComp->getPointSet()->GetPoints());

            //-- create the structural components for the atoms
            StructuralComponent* theAtoms = new StructuralComponent(newPM, "All Atoms");
            double pos[3];

            // create the atom structures
            for (int i = 0; i < thePoints->GetNumberOfPoints(); i++) {
                thePoints->GetPoint(i, pos);
                theAtoms->addStructure(new Atom(newPM, pos), false);
            }

            // set the atom sc
            newPM->setAtoms(theAtoms);

            //-- create the unique structural components containing all the vtkCell, this is an exclusive component
            unsigned int nrOfCells = meshComp->getPointSet()->GetNumberOfCells();
            StructuralComponent* sc = new StructuralComponent(newPM, "All Cells");

            // fill in this new structural component with all vtk cells
            for (vtkIdType i = 0; i < (vtkIdType) nrOfCells; i++) {
                // create a cell for each cell
                vtkCell* theCell = meshComp->getPointSet()->GetCell(i);

                // translate cell type
                StructureProperties::GeometricType cellType;

                switch (theCell->GetCellType()) {
                    case VTK_TETRA:
                        cellType = StructureProperties::TETRAHEDRON;
                        break;

                    case VTK_HEXAHEDRON:
                        cellType = StructureProperties::HEXAHEDRON;
                        break;

                    case VTK_WEDGE:
                        cellType = StructureProperties::WEDGE;
                        break;

                    case VTK_PYRAMID:
                        cellType = StructureProperties::PYRAMID;
                        break;

                    case VTK_LINE:
                        cellType = StructureProperties::LINE;
                        break;

                    case VTK_POLY_LINE:
                        cellType = StructureProperties::POLY_LINE;
                        break;

                    case VTK_POLY_VERTEX:
                        cellType = StructureProperties::POLY_VERTEX;
                        break;

                    case VTK_TRIANGLE:
                        cellType = StructureProperties::TRIANGLE;
                        break;

                    case VTK_QUAD:
                        cellType = StructureProperties::QUAD;
                        break;

                    default:
                        cellType = StructureProperties::INVALID;
                        break;
                }

                // create the corresponding PML cell
                Cell* c = new Cell(newPM, cellType);

                // fill-in the cell structures (atoms)
                for (int i = 0; i < theCell->GetNumberOfPoints(); i++) {
                    // get the corresponding atom
                    Atom* a = newPM->getAtom(theCell->GetPointId(i));  // dynamic_cast<Atom *>(theAtoms->getStructure(theCell->GetPointId(i)));
                    // set the corresponding atom as a structure composing the cell
                    c->addStructure(a, false);
                }

                // insert the cell in the structure
                sc->addStructure(c, false);
            }

            // create the exclusive multi-component
            MultiComponent* exclusiveComponents = new MultiComponent(newPM);

            // insert all the cells (i.e. the sc component)
            exclusiveComponents->addSubComponent(sc);
            exclusiveComponents->setName("Exclusive Components");

            // insert this exclusive component into the pm
            newPM->setExclusiveComponents(exclusiveComponents);

            // create the informative components from the selection
            MultiComponent* informativeComponents =
                new MultiComponent(newPM, "Informative Components");
            newPM->setInformativeComponents(informativeComponents);

            StructuralComponent* scinf;
            vtkSmartPointer<vtkAbstractArray> idArray;
            std::string selName;

            for (unsigned int i = 0; i < meshComp->getNumberOfSelections(); i++) {
                // TODO : test if it is a cell selection
                selName = meshComp->getSelectionAt(i)->GetSelectionList()->GetName();
                scinf = new StructuralComponent(newPM, selName);
                idArray = meshComp->getSelectionAt(i)->GetSelectionList();

                for (int j = 0; j < idArray->GetNumberOfTuples(); j++) {
                    Cell* c = sc->getCell(idArray->GetVariantValue(j).ToInt());
                    // TODO : this don't work with the Explore PML Component
                    // since it use cellRef that are not managed
                    scinf->addStructure(c, false);
                }

                // add the selection component to the informative components.
                newPM->getInformativeComponents()->addSubComponent(scinf);
            }

            // save it!
            std::ofstream outputFile(meshComp->getFileName().toStdString().c_str());
            newPM->xmlPrint(outputFile);
            outputFile.close();
            return true;
        }
    }

    return false;

}
