/*****************************************************************************
 * $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 stuff
#include <Action.h>
#include <Log.h>
#include <Geometry.h>
#include <Property.h>
#include <Application.h>
#include <AbortException.h>
#include "PMLComponent.h"

// Qt stuff
#include <QFileInfo>
#include <QPixmap>

// PML library includes
#include <PhysicalModel.h>
#include <MultiComponent.h>
#include <Atom.h>
#include <StructuralComponent.h>
#include <CellProperties.h>

// VTK stuff
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkDataSetMapper.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkViewport.h>
#include <CamiTKReEnableWarnings>

#include<vtkUnstructuredGrid.h>
#include <vtkCellArray.h>
#include <vtkCell.h>
#include <vtkHexahedron.h>
#include <vtkTetra.h>
#include <vtkWedge.h>
#include <vtkPyramid.h>
#include <vtkPolyLine.h>
#include <vtkLine.h>
#include <vtkTriangle.h>
#include <vtkQuad.h>
#include <vtkVertex.h>
#include <vtkPolyVertex.h>
#include <vtkProp.h>
#include <vtkSphereSource.h>
#include <vtkGlyph3D.h>
#include <vtkSelection.h>
#include <vtkDoubleArray.h>
#include <vtkDataSetAttributes.h>

using namespace camitk;

//-------------------- Constructor -------------------
PMLComponent::PMLComponent(const QString& file) : MeshComponent(file) {
    // Open the .pml file using library-pml
    try {
        Application::showStatusBarMessage("Loading " + QFileInfo(file).fileName() + "...");
        physicalModel = new PhysicalModel(file.toStdString().c_str());
    }
    catch (PMLAbortException& e) {
        throw AbortException("The PML XML document cannot be opened: " + std::string(e.what()));
    }

    init();
}

PMLComponent::PMLComponent(PhysicalModel* p, const QString& originalFile) : MeshComponent(originalFile) {
    physicalModel = p;
    init();
}

//-------------------- Destructor -------------------
PMLComponent::~PMLComponent() {
    atomPointIdMap.clear();

    delete physicalModel;
    physicalModel = nullptr;
}

//-------------------- getPhysicalModel -------------------
PhysicalModel* PMLComponent::getPhysicalModel() {
    return this->physicalModel;
}

//-------------------- init -------------------
void PMLComponent::init() {
    // Create the mesh corresponding to the model
    Application::showStatusBarMessage("Creating PML 3D representation...");
    create3DStructure();
    Application::showStatusBarMessage("Creating Atom VTK pipeline...");
    createAtomSelectionVTKPipeline();
    Application::showStatusBarMessage("PML Initialization done...", 1000);

    neverSelected = true;

    Application::resetProgressBar();
}

//-------------------- setSelected -------------------
void PMLComponent::setSelected(const bool selected, const bool recursive) {
    camitk::Component::setSelected(selected, false);

    // set the default action to be the PML explorer
    if (neverSelected && selected) {
        Action* defaultAction = Application::getAction("Explore PML Content");

        if (defaultAction) {
            defaultAction->getQAction()->trigger();
        }

        neverSelected = false;
    }
}

//-------------------- initDynamicProperties -------------------
void PMLComponent::initDynamicProperties() {
    setName(QString(physicalModel->getName().c_str()));

    // First add all generic mesh component properties
    MeshComponent::initDynamicProperties();

    // Then add all R/O specific properties
    Property* nbOfAtomProp = new Property("Number of Atoms", QVariant((int) physicalModel->getNumberOfAtoms()), "The number of atoms in the PML file", "");
    nbOfAtomProp->setReadOnly(true);
    addProperty(nbOfAtomProp);

    Property* nbOfCellProp = new Property("Number of Cells", QVariant((int) physicalModel->getNumberOfCells()), "The number of cells in the PML file", "");
    nbOfCellProp->setReadOnly(true);
    addProperty(nbOfCellProp);

    Property* nbOfECProp = new Property("Number of Exclusive Components", QVariant((int) physicalModel->getNumberOfExclusiveComponents()), "The number of exclusive components in the PML file", "");
    nbOfECProp->setReadOnly(true);
    addProperty(nbOfECProp);

    Property* nbOfICProp = new Property("Number of Informative Components", QVariant((int) physicalModel->getNumberOfInformativeComponents()), "The number of exclusive components in the PML file", "");
    nbOfICProp->setReadOnly(true);
    addProperty(nbOfICProp);

    // finally add all the specific editable properties
    for (unsigned int i = 0; i < physicalModel->getProperties()->numberOfFields(); i++) {
        addProperty(new Property(physicalModel->getProperties()->getField(i).c_str(), QVariant(physicalModel->getProperties()->getString(physicalModel->getProperties()->getField(i)).c_str()), "Custom property read from XML", ""));
    }
}

//-------------------- propertyValueChanged -------------------
void PMLComponent::propertyValueChanged(QString name) {
    // modify existing properties
    Properties* pmProp = physicalModel->getProperties();

    if (pmProp->isAField(name.toStdString())) {
        // set the corresponding property (field)
        pmProp->set(name.toStdString(), getPropertyValue(name).toString().toStdString());
    }
    else {
        // this was not a property, check inherited class
        MeshComponent::propertyValueChanged(name);
    }
}

//-------------------- create3DStructure -------------------
void PMLComponent::create3DStructure() {
    // The unstructured grid that will contain our 3D structure
    if (mainGrid) {
        mainGrid->Delete();    // free memory
        atomPointIdMap.clear();
        scCellIdArrayMap.clear();
        scCellSelectionMap.clear();
    }

    mainGrid = vtkUnstructuredGrid::New();
    mainGrid->Allocate(physicalModel->getNumberOfCells(), physicalModel->getNumberOfCells());

    // Direct memory sharing of the position array (FAST!!!)
    // see discussion http://public.kitware.com/pipermail/vtkusers/2010-January/056521.html
    vtkSmartPointer<vtkDoubleArray> underlyingArray = vtkSmartPointer<vtkDoubleArray>::New();
    underlyingArray->SetNumberOfComponents(3);    // 3 dimensions
    underlyingArray->SetArray(physicalModel->getPositionPointer(), 3 * physicalModel->getNumberOfAtoms(), 1);

    // The points corresponding to the atoms
    thePoints = vtkSmartPointer<vtkPoints>::New();
    // we need double precision to share memory pointer
    thePoints->SetData(underlyingArray);

    // to get the pointer from the vtkPoints, use:
    // double * pts = (double *)thePoints->GetVoidPointer(0)

    // build the correspondance map
    for (unsigned int atomOrderId = 0; atomOrderId < physicalModel->getAtoms()->getNumberOfStructures(); atomOrderId++) {
        Atom* a = dynamic_cast<Atom*>(physicalModel->getAtoms()->getStructure(atomOrderId));
        // register the atom, vtkPointId pair
        atomPointIdMap.insert(std::AtomPointIdPair(a, atomOrderId));
    }

    mainGrid->SetPoints(thePoints);
    Application::setProgressBarValue((int)((1.0 / (float)(physicalModel->getNumberOfAtoms() + physicalModel->getNumberOfCells())) * 100.0));

    // Parse the exclusive components
    MultiComponent* ecs = physicalModel->getExclusiveComponents();
    parseMultiComponent(ecs);

    // Parse the informative components
    MultiComponent* ics = physicalModel->getInformativeComponents();

    if (ics) {
        parseMultiComponent(ics);
    }

    // set the component geometry to this one (initRepresentation also calls initDynamicProperties()...)
    initRepresentation(mainGrid);

    // Tell this component is visible in the 3D viewer, otherwise we won't see it!
    setVisibility("3D Viewer", true);
}

//-------------------- parseMultiComponent -------------------
void PMLComponent::parseMultiComponent(MultiComponent* parsedMC) {
    for (unsigned int nbComp = 0; nbComp < parsedMC->getNumberOfSubComponents(); nbComp++) {
        if (StructuralComponent* sc = dynamic_cast<StructuralComponent*>(parsedMC->getSubComponent(nbComp))) {
            // check if this structural component is made of cells or simply a list of atoms references.
            if (sc->getNumberOfCells() > 0) {
                // create the 3D VTK structure for each cell and store it in the map
                for (unsigned int indexCell = 0; indexCell < sc->getNumberOfCells(); indexCell++) {
                    Cell* cellPML = sc->getCell(indexCell);
                    vtkSmartPointer<vtkCell> cellVTK = cellToVTK(cellPML);
                    int cellIndex = mainGrid->InsertNextCell(cellVTK->GetCellType(), cellVTK->GetPointIds());
                    cellPML->setIndex(cellIndex);
                }
            }
            else {
                vtkSmartPointer<vtkPolyVertex> scVTK = atomSCToVTK(sc);
                mainGrid->InsertNextCell(scVTK->GetCellType(), scVTK->GetPointIds());
            }
        }
        else if (MultiComponent* mc = dynamic_cast<MultiComponent*>(parsedMC->getSubComponent(nbComp))) {
            parseMultiComponent(mc);    // recursive call to parse multicomponent children
        }
        else {
            CAMITK_ERROR(tr("Component is neither a SC nor a MC: %1").arg(QString::number(nbComp)))
        }
    }
}


//-------------------- cellToVTK -------------------
vtkSmartPointer<vtkCell> PMLComponent::cellToVTK(Cell* cell) {
    // The vtk cell we will construct, note that vtkCell is an abstract type (interface).
    vtkSmartPointer<vtkCell> cell3D;

    // Get the cell type
    switch (cell->getType()) {
        case StructureProperties::TETRAHEDRON:
            cell3D = vtkTetra::New();
            break;

        case StructureProperties::HEXAHEDRON:
            cell3D = vtkHexahedron::New();
            break;

        case StructureProperties::WEDGE:
            cell3D = vtkWedge::New();
            break;

        case StructureProperties::PYRAMID:
            cell3D = vtkPyramid::New();
            break;

        case StructureProperties::POLY_LINE:
            cell3D = vtkPolyLine::New();
            break;

        case StructureProperties::LINE:
            cell3D = vtkLine::New();
            break;

        case StructureProperties::POLY_VERTEX:
            cell3D = vtkPolyVertex::New();
            break;

        case StructureProperties::TRIANGLE:
            cell3D = vtkTriangle::New();
            break;

        case StructureProperties::QUAD:
            cell3D = vtkQuad::New();
            break;

        default:
            cell3D = vtkVertex::New();
            break;
    }

    // insert the atoms of the cell by references
    cell3D->GetPointIds()->SetNumberOfIds(cell->getNumberOfStructures());

    for (unsigned int i = 0; i < cell->getNumberOfStructures(); i++) {
        Atom* a = (Atom*) cell->getStructure(i);
        cell3D->GetPointIds()->SetId(i, getPointId(a));
    }

    // Update progressbar status
    static unsigned int cellCreated = 0;
    cellCreated++;
    Application::setProgressBarValue((int)(((float)(physicalModel->getNumberOfAtoms() + cellCreated) / (float)(physicalModel->getNumberOfAtoms() + physicalModel->getNumberOfCells())) * 100.0));

    return cell3D;
}

//-------------------- atomSCToVTK -------------------
vtkSmartPointer<vtkPolyVertex> PMLComponent::atomSCToVTK(StructuralComponent* sc) {
    // A structural component with no Cell information only contains a list of atoms references
    // => It is a polygon
    vtkSmartPointer<vtkPolyVertex> cellPolygon = vtkPolyVertex::New();

    // insert the atoms of the cell by references
    cellPolygon->GetPointIds()->SetNumberOfIds(sc->getNumberOfStructures());

    for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
        Atom* a = (Atom*) sc->getStructure(i);
        cellPolygon->GetPointIds()->SetId(i, getPointId(a));
    }

    return cellPolygon;
}

//-------------------- refreshDisplay -------------------
void PMLComponent::refreshDisplay() {
    // Force the modification flag of the point set (just in case position were modified somewhere else,
    // for instance in a MML monitor) in order to obtain a real refresh
    getPointSet()->Modified();
    // This is a forced/active refresh (breaking CamiTK programming guidelines)
    refresh();
    // shade the current selected component in order to see the selected item (SC, MC, Atoms or Cells)
    myGeometry->setEnhancedModes(InterfaceGeometry::Shaded);
}

//-------------------- createAtomSelectionVTKPipeline -------------------
void PMLComponent::createAtomSelectionVTKPipeline() {

    // Create everything to manage atom selection
    selectedAtomSelection = vtkSmartPointer<vtkSelection>::New();
    vtkSmartPointer<vtkSelectionNode> selectedAtomSelectionNode = vtkSmartPointer<vtkSelectionNode>::New();
    selectedAtomSelectionNode->SetFieldType(vtkSelectionNode::POINT);
    selectedAtomSelectionNode->SetContentType(vtkSelectionNode::INDICES);
    selectedAtomIdArray = vtkIdTypeArray::New();
    // The selection extractor, attached to the selection and the 3D structure (unstructured grid)
    vtkSmartPointer<vtkExtractSelection> selectedAtomExtractor = vtkSmartPointer<vtkExtractSelection>::New();
    selectedAtomExtractor->SetInputConnection(0, getDataPort());
    selectedAtomExtractor->SetInputData(1, selectedAtomSelection);

    // create the atom Glyph Actor
    vtkSmartPointer<vtkSphereSource> glyphSphere = vtkSmartPointer<vtkSphereSource>::New();
    glyphSphere->SetRadius(getBoundingRadius() / 50.0);

    vtkSmartPointer<vtkGlyph3D> atomSphere = vtkSmartPointer<vtkGlyph3D>::New();
    atomSphere->SetInputConnection(selectedAtomExtractor->GetOutputPort());
    atomSphere->SetSourceConnection(glyphSphere->GetOutputPort());

    vtkSmartPointer<vtkDataSetMapper> selectionMapper = vtkSmartPointer<vtkDataSetMapper>::New();
    selectionMapper->SetInputConnection(atomSphere->GetOutputPort());

    vtkSmartPointer<vtkActor> atomGlyphActor = vtkSmartPointer<vtkActor>::New();
    atomGlyphActor->SetPickable(false);
    atomGlyphActor->GetProperty()->SetRepresentationToSurface();
    atomGlyphActor->GetProperty()->SetColor(1, 0, 0);
    atomGlyphActor->GetProperty()->SetOpacity(0.8);
    atomGlyphActor->SetMapper(selectionMapper);
    atomGlyphActor->SetVisibility(true);

    // insert the glyph in the visualization pipeline, as an extraprop
    removeProp("Selected Atoms");
    addProp("Selected Atoms", atomGlyphActor);
}

//-------------------- createCellSelectionVTKPipeline -------------------
void PMLComponent::createCellSelectionVTKPipeline(const StructuralComponent* sc) {
    // create the vtk pipeline if needed
    if (!scCellSelectionMap.contains(sc->getName().c_str())) {
        // Create the vtkSelection
        vtkSmartPointer<vtkSelection> selection = vtkSelection::New();

        // The selection extractor, bound to the selection and the 3D structure (unstructured grid)
        vtkSmartPointer<vtkExtractSelection> selectionExtractor = vtkSmartPointer<vtkExtractSelection>::New();
        selectionExtractor->SetInputConnection(0, getDataPort());
        selectionExtractor->SetInputData(1, selection);

        // The selection actor, with its color properties adapted to the input structural component
        vtkSmartPointer<vtkActor> selectionActor = vtkSmartPointer<vtkActor>::New();
        selectionActor->SetPickable(false);

        double color_R, color_G, color_B, color_A = 0.5;
        sc->getColor(&color_R, &color_G, &color_B, &color_A);
        selectionActor->GetProperty()->SetColor(color_R, color_G, color_B);
        selectionActor->GetProperty()->SetPointSize(5);
        selectionActor->GetProperty()->SetOpacity(0.5);

        // The selection mapper to be used
        vtkSmartPointer<vtkDataSetMapper> selectionMapper = vtkSmartPointer<vtkDataSetMapper>::New();
        selectionActor->SetMapper(selectionMapper);
        selectionMapper->SetInputConnection(selectionExtractor->GetOutputPort());

        // Register the actor for this structural component selection within the component
        removeProp("Selected cells for " + QString(sc->getName().c_str()));
        addProp("Selected cells for " + QString(sc->getName().c_str()), selectionActor);

        // Store the references for selection in the corresponding QMap
        vtkSmartPointer<vtkSelectionNode> selectionNodes = vtkSelectionNode::New();
        selectionNodes->SetFieldType(vtkSelectionNode::CELL);
        selectionNodes->SetContentType(vtkSelectionNode::INDICES);
        vtkSmartPointer<vtkIdTypeArray> selectionIdTypesArray = vtkIdTypeArray::New();

        scCellSelectionMap.insert(QString(sc->getName().c_str()), selection);
        scCellIdArrayMap.insert(QString(sc->getName().c_str()), selectionIdTypesArray);
    }

}

//-------------------- selectAtom -------------------
void PMLComponent::selectAtom(const Atom* atom) {
    // insert the id for the selection.
    selectedAtomIdArray->InsertNextValue(getPointId(atom));
}

//-------------------- selectCell -------------------
void PMLComponent::selectCell(Cell* cell, bool showAtomGlyph) {
    const StructuralComponent* parent = cell->getParentSC();

    // create the pipeline if needed
    createCellSelectionVTKPipeline(parent);

    // Find the corresponding vtk selection node according to the parent
    vtkSmartPointer<vtkIdTypeArray> array = scCellIdArrayMap.value(parent->getName().c_str());

    if (!array) {
        CAMITK_ERROR(tr("Array is null. Aborting."))
        return;
    }

    array->InsertNextValue(cell->getIndex());    // the Cell's index = vtkCell's id in the 3D structure.

    // select also the atom
    if (showAtomGlyph) {
        for (unsigned int i = 0; i < cell->getNumberOfStructures(); i++) {
            Atom* a = dynamic_cast<Atom*>(cell->getStructure(i));
            selectAtom(a);
        }
    }
}

//-------------------- selectSC -------------------
void PMLComponent::selectSC(StructuralComponent* sc, bool showAtomGlyph) {
    // Hightlight all the content of the given structural component
    switch (sc->composedBy()) {
        case StructuralComponent::ATOMS:
            for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
                if (Atom* a = dynamic_cast<Atom*>(sc->getStructure(i))) {
                    selectAtom(a);
                }
            }

            break;

        case StructuralComponent::CELLS:
            for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
                if (Cell* c = dynamic_cast<Cell*>(sc->getStructure(i))) {
                    selectCell(c, showAtomGlyph);
                }
            }

            break;

        default:
            break;
    }
}

//-------------------- selectMC -------------------
void PMLComponent::selectMC(MultiComponent* selectedMC, bool showAtomGlyph) {
    // Select all the multicomponent subcomponents in the 3D structure
    for (unsigned int subCompIndex = 0; subCompIndex < selectedMC->getNumberOfSubComponents(); subCompIndex++) {
        if (MultiComponent* mc = dynamic_cast<MultiComponent*>(selectedMC->getSubComponent(subCompIndex))) {
            selectMC(mc, showAtomGlyph);
        }
        else if (StructuralComponent* sc = dynamic_cast<StructuralComponent*>(selectedMC->getSubComponent(subCompIndex))) {
            selectSC(sc, showAtomGlyph);
        }
    }
}

//-------------------- updateSelection -------------------
void PMLComponent::updateSelection() {

    // refresh atom selection actors
    selectedAtomSelection->RemoveAllNodes();
    vtkSmartPointer<vtkSelectionNode> selectedAtomSelectionNode = vtkSmartPointer<vtkSelectionNode>::New();
    selectedAtomSelectionNode->SetFieldType(vtkSelectionNode::POINT);
    selectedAtomSelectionNode->SetContentType(vtkSelectionNode::INDICES);
    selectedAtomSelectionNode->SetSelectionList(selectedAtomIdArray);
    selectedAtomSelection->AddNode(selectedAtomSelectionNode);
    MeshComponent::addSelection("Selected Atoms", vtkSelectionNode::POINT, vtkSelectionNode::INDICES, selectedAtomIdArray, MeshSelectionModel::REPLACE);

    // refresh all cells selection actors + merge all in one big selection
    vtkSmartPointer<vtkIdTypeArray> allSelectedCells = vtkSmartPointer<vtkIdTypeArray>::New();

    for (QMap<QString, vtkSmartPointer<vtkSelection> >::const_iterator it = scCellSelectionMap.constBegin();
            it != scCellSelectionMap.constEnd(); ++it) {
        QString scName = it.key();
        vtkSmartPointer<vtkSelection> cellsSelection = it.value();
        vtkSmartPointer<vtkIdTypeArray> array = scCellIdArrayMap.value(scName);
        cellsSelection->RemoveAllNodes();
        vtkSmartPointer<vtkSelectionNode> cellSelectionNode  = vtkSelectionNode::New();
        cellSelectionNode->SetFieldType(vtkSelectionNode::CELL);
        cellSelectionNode->SetContentType(vtkSelectionNode::INDICES);
        cellSelectionNode->SetSelectionList(array);
        cellsSelection->AddNode(cellSelectionNode);

        // merge the selection indices
        for (vtkIdType id = 0; id < array->GetNumberOfTuples(); id++) {
            allSelectedCells->InsertNextTuple(id, array);
        }

    }

    MeshComponent::addSelection("Selected Cells", vtkSelectionNode::CELL, vtkSelectionNode::INDICES, allSelectedCells, MeshSelectionModel::REPLACE);

}

//-------------------- unselectItem -------------------
void PMLComponent::unselectItems() {
    // reset selectedAtomIdArray
    selectedAtomIdArray = vtkIdTypeArray::New();

    // Reinsert a new vtkIdTypeArray for each SC in the Cells maps to clean selections
    for (QMap<QString, vtkSmartPointer<vtkIdTypeArray> >::const_iterator it = scCellIdArrayMap.constBegin();
            it != scCellIdArrayMap.constEnd(); ++it) {
        scCellIdArrayMap.insert(it.key(), vtkIdTypeArray::New());
    }
}


//------------------------ addSelection ---------------------
int PMLComponent::addSelection(const QString& name, int fieldType, int contentType, vtkSmartPointer< vtkAbstractArray > array, MeshSelectionModel::InsertionPolicy policy) {
    if (fieldType == vtkSelectionNode::POINT) {
        // point picked -> atom selection
        vtkSmartPointer<vtkIdTypeArray> idTypeArray = vtkIdTypeArray::SafeDownCast(array);
        if (idTypeArray) {
            selectedAtomIdArray = idTypeArray;
            updateSelection();
            return getSelectionIndex("Selected Atoms");
        }
        return -1;
    }
    else {
        // something else happened
        return MeshComponent::addSelection(name, fieldType, contentType, array, policy);
    }
}

//------------------------ getPixmap ---------------------
#include "physicalmodel_20x20.xpm"
QPixmap* PMLComponent::myPixmap = nullptr;
QPixmap PMLComponent::getIcon() {
    if (!myPixmap) {
        myPixmap = new QPixmap(physicalmodel_20x20);
    }

    return (*myPixmap);
}


