/*****************************************************************************
 * $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$
 ****************************************************************************/

// Description: very simple vtk unstructured grid export to biomecanichal pml
//              this is equivalent to use vtk2pml -> optimizeMesh -> elementsToNeighborhood
//
// Note: this transformation is done manually, without the help of the VTK API
// Pros: no dependencies
// Cons: lots of things/case/vtk types not take into account, very simple transformation
//
// the first argument is the name of a vtk file

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
#include <sstream>

using namespace std;

// the input .obj file
std::ifstream vtkFile;
unsigned lineNr;
bool eof;

typedef struct {
    string directive;
    string data;
    bool ok;
} ObjLine;

ObjLine currentLine;

// -------------------- getNext ------------------------
// get the next current line
void getNext() {
    char l[200];
    string line;
    currentLine.ok = false;

    while (!eof && !currentLine.ok) {
        eof = !(vtkFile.getline(l, 199));
        lineNr++;

        if (l[0] != '#') {
            line = l;

            unsigned int space = line.find(' ');

            if (space != string::npos && space != 0) {
                currentLine.directive = line.substr(0, space);
                currentLine.data = line.substr(space + 1);
                currentLine.ok = true;
            }
        }
    }

}


// -------------------- getCountFromDirective ------------------------
int getCountFromDirective(string directive) {
    // go to the first directive
    while (!eof && currentLine.directive != directive) {
//  cout << "l=" << currentLine.directive << endl;
        getNext();
    }

    if (!eof) {
        return atoi(currentLine.data.c_str());
    }
    else {
        return 0;
    }
}

// -----------------------------------------------------------------------------------

#include <math.h>
#include <iostream>
#include <string>
using namespace std;

#include <pml/PhysicalModel.h>
#include <pml/MultiComponent.h>
#include <pml/StructuralComponent.h>
#include <pml/Atom.h>

/// global variables
// -------------------- the physical model being processed ------------------------
PhysicalModel* pm;

// -------------------- all cluster ------------------------
class Cluster;
vector <Cluster*> allClusters;

// first atom is now replaced by 2nd atom
map<Atom*, Atom*> replacement;

double precision;
unsigned int nrOfNewAtoms;

/** class Cluster
 *
 *  a cluster is a set of point that are nearly at the same place (at a given precision)
 */
class Cluster {
public:
    /// create a cluster of this atom, using the given precision
    Cluster(double distance);

    ~Cluster();

    /// get composing atom by index
    Atom* getAtom(unsigned int id);

    /// add an atom to the cluster if in the given precision (true if added) (update center)
    bool addAtom(Atom*);

    /// get the center (create if necessary)
    Atom* getCenter();

    /// get nr of atoms in the cluster
    unsigned int nrOfAtoms();

    /// current atom index for new atoms
    static unsigned int atomIndex;

private:
    /// cluster center
    Atom* center;

    /// the cluster cell
    Cell* myCell;

    /// distance precision
    double distance;

    /// update center position
    void updateCenter();
};

unsigned int Cluster::atomIndex = 10000;


// -------------------- constructor/destructor ------------------------
Cluster::Cluster(double distance) {
    this->distance = distance;
    myCell = new Cell(pm, StructureProperties::POLY_VERTEX);
    center = new Atom(pm, Cluster::atomIndex++);
    Cluster::atomIndex++;
    nrOfNewAtoms++;
    //pm->addAtom(center);
}

Cluster::~Cluster() {
    delete myCell;
    delete center;
}

// -------------------- updateCenter ------------------------
void Cluster::updateCenter() {
    double pos[3];
    double posC[3];
    unsigned int size = myCell->getNumberOfStructures();

    for (unsigned int j = 0; j < 3; j++) {
        posC[j] = 0.0;
    }

    for (unsigned int i = 0; i < size; i++) {
        ((Atom*)myCell->getStructure(i))->getPosition(pos);
        for (unsigned int j = 0; j < 3; j++) {
            posC[j] += pos[j];
        }
    }

    for (unsigned int j = 0; j < 3; j++) {
        posC[j] /= ((double)size);
    }

    center->setPosition(posC);
}

// -------------------- getCenter ------------------------
Atom* Cluster::getCenter() {
    return center;
}

// -------------------- getAtom ------------------------
Atom* Cluster::getAtom(unsigned int id) {
    return ((Atom*)myCell->getStructure(id));
}

// -------------------- nrOfAtoms ------------------------
unsigned int Cluster::nrOfAtoms() {
    return myCell->getNumberOfStructures();
}

// -------------------- addAtom ------------------------
bool Cluster::addAtom(Atom* a) {
    double posC[3];
    center->getPosition(posC);
    double pos[3];
    a->getPosition(pos);
    double dist = 0.0;
    for (unsigned int i = 0; i < 3; i++) {
        dist += (posC[i] - pos[i]) * (posC[i] - pos[i]);
    }
    dist = sqrt(dist);

    if (nrOfAtoms() == 0 || dist < distance) {
        myCell->addStructure(a);
        updateCenter();
        return true;
    }
    else {
        return false;
    }
}


// -------------------- insert ------------------------
Cluster* insert(Atom* a) {
    vector <Cluster*>::iterator it;

    // look into allClusters for equivalence
    it = allClusters.begin();
    while (it != allClusters.end() && !(*it)->addAtom(a)) {
        ++it;
    }

    // not found => insert
    if (it == allClusters.end()) {
        // no cluster found: create a new one
        Cluster* c = new Cluster(precision);
        c->addAtom(a);
        allClusters.push_back(c);
        return c;
    }
    else  {
        return (*it);
    }

}

// -------------------- update ------------------------
void update(MultiComponent* mc) {
    if (mc == nullptr) {
        cout << "0 cells)" << endl;
        return;
    }

    cout << mc->getNumberOfCells() << " cells)" << endl;
    unsigned int degeneratedCount = 0;

    // WARNING non-rentrant loop
    for (unsigned int i = 0; i < mc->getNumberOfCells(); i++) {
        Cell* c = mc->getCell(i);
        StructuralComponent* sc = c->getAtoms();
        c->deleteAllStructures();
        cout << '\r' << i << flush;
        // remove previous list
        for (unsigned int j = 0; j < sc->getNumberOfStructures(); j++) {
            c->removeStructure(sc->getStructure(j));
        }
        // check it was done properly
        if (c->getNumberOfStructures() > 0)  {
            cout << "Cell #" << c->getIndex() << " cannot delete all atoms (still has " << c->getNumberOfStructures() << ")" << endl;
        }
        // add new atoms
        map<Atom*, Atom*>::iterator proxy;
        for (unsigned int j = 0; j < sc->getNumberOfStructures(); j++) {
            proxy = replacement.find((Atom*)sc->getStructure(j));
            if (proxy == replacement.end()) {
                // not found?!
                cout << "Cell #" << c->getIndex() << " cannot find replacement for Atom#" << sc->getStructure(i)->getIndex() << endl;
            }
            else {
                c->addStructureIfNotIn(proxy->second);
            }
        }
        // check that the cell did not degenerate
        StructureProperties::GeometricType t = c->getType();
        int normalSize = ((t == StructureProperties::LINE) ? 2 :
                          ((t == StructureProperties::TRIANGLE) ? 3 :
                           ((t == StructureProperties::QUAD) ? 4 :
                            ((t == StructureProperties::TETRAHEDRON) ? 4 :
                             ((t == StructureProperties::WEDGE) ? 6 :
                              ((t == StructureProperties::HEXAHEDRON) ? 8 : -1))))));
        if (normalSize != -1 && normalSize != (int)c->getNumberOfStructures()) {
            cout << "deleting degenerated Cell #" << c->getIndex() << ": it is a " <<
                 StructureProperties::toString(t) << " and should have " << normalSize <<
                 " atoms, but has now " << c->getNumberOfStructures() << flush;
            degeneratedCount++;
            c->getStructuralComponent(0)->removeStructure(c);
            // a cell was deleted, as it is a non re-entrant loop, get back one index
            i--;
        }
    }
    cout << flush;
    cout << degeneratedCount << " degenerated cells removed                                                        " << endl;
}

// -------------------- saveClusters ------------------------
void saveClusters(string clusterFile) {
    stringstream clusterOSS;
    clusterOSS << precision;


    string nonClusterFile = clusterFile;
    unsigned int pLast = clusterFile.rfind(".");

    if (pLast != string::npos) {
        clusterFile.erase(pLast);
        clusterFile += "-clusters-" + clusterOSS.str() + ".txt";
    }
    else {
        clusterFile = "clusters-" + clusterOSS.str() + ".txt";
    }

    pLast = nonClusterFile.rfind(".");
    if (pLast != string::npos) {
        nonClusterFile.erase(pLast);
        nonClusterFile += "-non-clusters-" + clusterOSS.str() + ".txt";
    }
    else {
        nonClusterFile = "non-clusters-" + clusterOSS.str() + ".txt";
    }

    cout << "-> please wait while saving cluster informations in " << clusterFile << " (" << pm->getNumberOfAtoms() - allClusters.size() << " clusters) and non-clusters in " << nonClusterFile << endl;
    ofstream clusterOutputFile(clusterFile.c_str());
    ofstream nonClusterOutputFile(nonClusterFile.c_str());
    for (vector <Cluster*>::iterator it = allClusters.begin(); it != allClusters.end(); ++it) {
        if ((*it)->nrOfAtoms() > 1) {
            clusterOutputFile << (*it)->getCenter()->getIndex() << " : ";
            for (unsigned int i = 0; i < (*it)->nrOfAtoms(); i++) {
                clusterOutputFile << (*it)->getAtom(i)->getIndex() << " ";
            }
            clusterOutputFile << endl;
        }
        else {
            nonClusterOutputFile << (*it)->getAtom(0)->getIndex() << endl;
        }
    }
    clusterOutputFile.close();
    nonClusterOutputFile.close();
}

// -------------------- optimize ------------------------
void optimize(string clusterFile) {
    StructuralComponent* atoms = pm->getAtoms();

    // create clusters
    for (unsigned int i = 0; i < atoms->getNumberOfStructures(); i++) {
        insert((Atom*) atoms->getStructure(i));
        cout << '\r' <<  i << flush;
    }
    cout << endl;

    cout << "found " << allClusters.size() << " clusters, for " <<  atoms->getNumberOfStructures() << " atoms" << endl;
    cout << "-> please wait while removing " << atoms->getNumberOfStructures() - allClusters.size() << " atoms" << endl;

    // create newAtoms with all atoms that are alone in their clusters and
    // all cluster centers
    StructuralComponent* newAtoms = new StructuralComponent(pm);
    for (vector <Cluster*>::iterator it = allClusters.begin(); it != allClusters.end(); ++it) {
        if ((*it)->nrOfAtoms() == 1) {
            newAtoms->addStructure((*it)->getAtom(0));
            replacement.insert(pair<Atom*, Atom*>((*it)->getAtom(0), (*it)->getAtom(0)));
        }
        else {
            newAtoms->addStructure((*it)->getCenter());
            // memorize replacements
            for (unsigned int i = 0; i < (*it)->nrOfAtoms(); i++) {
                replacement.insert(pair<Atom*, Atom*>((*it)->getAtom(i), (*it)->getCenter()));
            }
        }
    }

    // save the clusters and non cluster
    saveClusters(clusterFile);

    // update all atoms using replacements
    cout << "-> please wait while replacing atoms in exclusive components (";
    update(pm->getExclusiveComponents());
    cout << "-> please wait while replacing atoms in informative components (";
    update(pm->getInformativeComponents());

    // Use new atoms instead of atoms
    cout << "-> please wait while changing atoms" << endl;
    pm->setAtoms(newAtoms, false); // do not delete old atom list!
}


/// class facet to old and compare facet
class Facet {
public:
    /// create a facet using size nodes and their indexes
    Facet(unsigned int size, unsigned int id[]);

    /// destructor
    virtual ~Facet();

    /// if it is the same (equivalent) facet, increment used (return true if equivalence)
    bool testEquivalence(unsigned int size, unsigned int id[]);

    /// return the corresponding PML cell
    Cell* getCell(PhysicalModel*) const;

    /// print on stdout
    void debug();

    /// get the number of time it is being used
    unsigned int getUsed() const;

private:
    /// is this atom index present in this facet (no check on the order)
    bool isIn(unsigned int) const;

    /// the facet atom indexes
    unsigned int* id;

    /// nr of atoms composing the facet (3 = triangle, 4 = quad)
    unsigned int size;

    /// nr of times the facet is used
    unsigned int used;
};


// -------------------- constructor/destructor ------------------------
Facet::Facet(unsigned int size, unsigned int id[]) {
    this->size = size;
    this->id = new unsigned int[size];
    for (unsigned int i = 0; i < size; i++) {
        this->id[i] = id[i];
    }
    used = 1;
}

Facet::~Facet() {
    delete [] id;
}

// -------------------- debug ------------------------
void Facet::debug() {
    switch (size) {
        case 3:
            cout << "triangle <";
            break;
        case 4:
            cout << "quad <";
            break;
        default:
            cout << "unknown facet <";
            break;
    }
    unsigned int i;
    for (i = 0; i < size - 1; i++) {
        cout << id[i] << ",";
    }
    cout << id[i] << "> used " << used << " times" << endl;
}


// -------------------- testEquivalence ------------------------
bool Facet::testEquivalence(unsigned int size, unsigned int id[]) {
    if (this->size != size) {
        return false;
    }
    else {
        unsigned int i = 0;
        while (i < size && isIn(id[i])) {
            i++;
        }

        if (i == size) {
            used++;
        }

        return (i == size);
    }
}

// -------------------- isIn ------------------------
bool Facet::isIn(unsigned int index) const {
    unsigned int i = 0;
    while (i < size && id[i] != index) {
        i++;
    }
    return (i != size);
}

// -------------------- getCell ------------------------
Cell* Facet::getCell(PhysicalModel* pm) const {
    Cell* c;
    // create the correct geometric type cell
    switch (size) {
        case 3:
            c = new Cell(nullptr, StructureProperties::TRIANGLE);
            break;
        case 4:
            c = new Cell(nullptr, StructureProperties::QUAD);
            break;
        default:
            c = nullptr;
    }
    // get the atom corresponding to the index stored in id
    // and insert them in the cell
    for (unsigned int i = 0; i < size; i++) {
        Atom* a = pm->getAtom(id[i]);
        if (a == nullptr) {
            cout << "Argh! Cannot find atom #" << id[i] << endl;
        }
        else {
            c->addStructureIfNotIn(a);
        }
    }
    return c;
}

// -------------------- getUsed ------------------------
unsigned int Facet::getUsed() const {
    return used;
}

// -------------------- Neigborhood Map ------------------------
// associative map of all the neighboors for a given index of an atom
map<unsigned int, Cell*> neighMap;

// -------------------- getIterator ------------------------
// get the iterator on the correct atom index in the neighMap
// if non existant create it
map<unsigned int, Cell*>::iterator getIterator(unsigned int index) {
    map<unsigned int, Cell*>::iterator it;

    // find atom index in the map
    it = neighMap.find(index);

    // not present, insert a new cell, associated with index
    if (it == neighMap.end()) {
        // instantiate
        Cell* nc = new Cell(nullptr, StructureProperties::POLY_VERTEX);
        // name
        stringstream n(std::stringstream::out);
        n << "Atom #" << index << '\0';
        ((Structure*)nc)->setName(n.str());
        // insert the new cell in the map
        pair<map<unsigned int, Cell*>::iterator, bool> ins = neighMap.insert(map<unsigned int, Cell*>::value_type(index, nc));
        // it show where it has been inserted
        it = ins.first;
    }

    return it;
}

// -------------------- generateNeighborhood ------------------------
/// generate the neighborhoods
StructuralComponent* generateNeighborhood(StructuralComponent* sc) {
    // an iterator
    map<unsigned int, Cell*>::iterator it;

    // for each cells recreate neighborhoods
    for (unsigned int i = 0; i < sc->getNumberOfCells(); i++) {
        // c is an hexahedron
        Cell* c = sc->getCell(i);

        switch (c->getType()) {
            case StructureProperties::TRIANGLE:
                if (c->getNumberOfStructures() != 3) {
                    cout << "cell #" << c->getIndex() << " of type TRIANGLE does not contains 3 atoms (found only " <<  c->getNumberOfStructures() << "). Cell skipped." << endl;
                }
                else {
                    // TRIANGLE
                    //       1      facet:   lines:
                    //      / \     0,1,2    0,1
                    //     /   \             1,2
                    //    2-----0            2,0

                    // -- add atom 0 neighbors
                    // get neighbors list for atom 0
                    it = getIterator(c->getStructure(0)->getIndex());
                    // it.second is the cell containing all the atoms that are neighbors of atom 0
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));

                    // -- add atom 1 neighbors
                    it = getIterator(c->getStructure(1)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(0));

                    // -- add atom 2 neighbors
                    it = getIterator(c->getStructure(2)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                }
                break;
            case StructureProperties::QUAD:
                if (c->getNumberOfStructures() != 4) {
                    cout << "cell #" << c->getIndex() << " of type QUAD does not contains 4 atoms (found only " <<  c->getNumberOfStructures() << "). Cell skipped." << endl;
                }
                else {
                    // QUAD
                    //    3--------2      lines:
                    //    |        |      0,1
                    //    |        |      1,2
                    //    |        |      2,3
                    //    0--------1      3,0

                    // -- add atom 0 neighbors
                    // get neighbors list for atom 0
                    it = getIterator(c->getStructure(0)->getIndex());
                    // it.second is the cell containing all the atoms that are neighbors of atom 0
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    // complete neighborhood
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(3));

                    // -- add atom 1 neighbors
                    it = getIterator(c->getStructure(1)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    // complete neighborhood
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(0));

                    // -- add atom 2 neighbors
                    it = getIterator(c->getStructure(2)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    // complete neighborhood
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(1));

                    // -- add atom 3 neighbors
                    it = getIterator(c->getStructure(3)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    // complete neighborhood
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                }
                break;
            case StructureProperties::WEDGE:
                if (c->getNumberOfStructures() != 6) {
                    cout << "cell #" << c->getIndex() << " of type WEDGE does not contains 6 atoms (found only " <<  c->getNumberOfStructures() << "). Cell skipped." << endl;
                }
                else {
                    // WEDGE
                    //     1-------------4       facets (quad):   facets (triangles):     lines:
                    //     /\           . \       2,5,4,1          0,2,1                   0,1      2,5
                    //    /  \         /   \      0,1,4,3          3,4,5                   0,2      3,4
                    //   0- - \ - - - 3     \     2,0,3,5                                  1,2      4,5
                    //     \   \         \   \                                             0,3      5,3
                    //       \ 2-----------\--5                                            1,4
                    it = getIterator(c->getStructure(0)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(3));

                    it = getIterator(c->getStructure(1)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(4));

                    it = getIterator(c->getStructure(2)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));

                    it = getIterator(c->getStructure(3)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(4));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));
                    (*it).second->addStructureIfNotIn(c->getStructure(0));

                    it = getIterator(c->getStructure(4)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));
                    (*it).second->addStructureIfNotIn(c->getStructure(1));

                    it = getIterator(c->getStructure(5)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(4));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                }
                break;

            case StructureProperties::TETRAHEDRON:
                if (c->getNumberOfStructures() != 4) {
                    cout << "cell #" << c->getIndex() << " of type TETRAHEDRON does not contains 4 atoms (found only " <<  c->getNumberOfStructures() << "). Cell skipped." << endl;
                }
                else {
                    // tetrahedron are defined as follow:
                    //                    3                   triangular base: 0,1,2
                    //                  /| \                          -
                    //                 / |  \                 So to generate the neighborhodd,
                    //                2__|___\1               we just have to loop on all the
                    //                \  |   /                atoms and add them their corresponding neigh
                    //                 \ |  /                 This is easy as in a tetrahedron all atoms
                    //                  \|/                   are neighbors to all atoms...
                    //                  0
                    for (unsigned int j = 0; j < 4; j++) {
                        // get current atom
                        it = getIterator(c->getStructure(j)->getIndex());
                        // add all others to its neighborhood
                        (*it).second->addStructureIfNotIn(c->getStructure((j + 1) % 4));
                        (*it).second->addStructureIfNotIn(c->getStructure((j + 2) % 4));
                        (*it).second->addStructureIfNotIn(c->getStructure((j + 3) % 4));
                    }
                }
                break;

            case StructureProperties::HEXAHEDRON:
                if (c->getNumberOfStructures() != 8) {
                    cout << "cell #" << c->getIndex() << " of type HEXAHEDRON does not contains 8 atoms (found only " <<  c->getNumberOfStructures() << "). Cell skiped." << endl;
                }
                else {
                    // hexahedron are defined as follow:
                    //              2-------------6
                    //             / \           / \      So to generate the neighborhood,
                    //            /   \         /   \     we just have to loop on all the
                    //           1-----\-------5     \    atoms and add them their corresponding neigh
                    //           \     3-------------7
                    //            \   /         \   /
                    //             \ /           \ /
                    //              0-------------4

                    // atom 0 is neigbor of atoms : 1, 3, 4
                    it = getIterator(c->getStructure(0)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(4));

                    // atom 1 is neigbor of atoms : 0, 2, 5
                    it = getIterator(c->getStructure(1)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));

                    // atom 2 is neigbor of atoms : 1, 3, 6
                    it = getIterator(c->getStructure(2)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(6));

                    // atom 3 is neigbor of atoms : 0, 2, 7
                    it = getIterator(c->getStructure(3)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(7));

                    // atom 4 is neigbor of atoms : 0, 5, 7
                    it = getIterator(c->getStructure(4)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(0));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));
                    (*it).second->addStructureIfNotIn(c->getStructure(7));

                    // atom 5 is neigbor of atoms : 1, 4, 6
                    it = getIterator(c->getStructure(5)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(1));
                    (*it).second->addStructureIfNotIn(c->getStructure(4));
                    (*it).second->addStructureIfNotIn(c->getStructure(6));

                    // atom 6 is neigbor of atoms : 2, 5, 7
                    it = getIterator(c->getStructure(6)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(2));
                    (*it).second->addStructureIfNotIn(c->getStructure(5));
                    (*it).second->addStructureIfNotIn(c->getStructure(7));

                    // atom 7 is neigbor of atoms : 3, 4, 6
                    it = getIterator(c->getStructure(7)->getIndex());
                    (*it).second->addStructureIfNotIn(c->getStructure(3));
                    (*it).second->addStructureIfNotIn(c->getStructure(4));
                    (*it).second->addStructureIfNotIn(c->getStructure(6));
                }
                break;
            default:
                cout << "Cannot generate neighborhood for cell #" << c->getIndex() << ": it is not of known type (TRIANGLE, QUAD, WEDGE, HEXAHEDRON or TETRAHEDRON)." << endl;
                break;
        }
    }

    // now that we have in neighMap all the neighborhoods, just add them in a new SC
    StructuralComponent* neigh = new StructuralComponent(nullptr, "Neighborhoods");

    for (it = neighMap.begin(); it != neighMap.end(); ++it) {
        neigh->addStructure((*it).second);
    }

    return neigh;
}

// -------------------- All border facets ------------------------
/// storing all the facets
vector <Facet*> allFacets;

// -------------------- equivalent ------------------------
// check if equivalent of already existing facet
void equivalent(int size, unsigned int id[]) {
    vector <Facet*>::iterator it;

    // look into allFacets for equivalence
    it = allFacets.begin();
    while (it != allFacets.end() && !(*it)->testEquivalence(size, id)) {
        ++it;
    }

    // not found => insert
    if (it == allFacets.end()) {
        allFacets.push_back(new Facet(size, id));
    }
}


// -------------------- generateExternalSurface ------------------------
/// generate the outside surface
MultiComponent* generateExternalSurface(StructuralComponent* sc) {
    // outside/external facets are facets that are used in only one element

    // for each cells update the counter
    for (unsigned int i = 0; i < sc->getNumberOfCells(); i++) {
        // c is an hexahedron
        Cell* c = sc->getCell(i);

        // Facets have to be described in anticlockwise (trigonometrywise) when
        // looking at them from outside
        switch (c->getType()) {
            case StructureProperties::TRIANGLE: {
                // TRIANGLE (as viewed from outside)
                //       1      facet:   lines:
                //      / \     0,1,2    0,1
                //     /   \             1,2
                //    2-----0            2,0
                unsigned idT[3];
                idT[0] = c->getStructure(0)->getIndex();
                idT[1] = c->getStructure(1)->getIndex();
                idT[2] = c->getStructure(2)->getIndex();
                equivalent(3, idT);
                break;
            }
            case StructureProperties::QUAD: {
                // QUAD
                //    3--------2      lines:
                //    |        |      0,1
                //    |        |      1,2
                //    |        |      2,3
                //    0--------1      3,0

                unsigned idT[4];
                idT[0] = c->getStructure(0)->getIndex();
                idT[1] = c->getStructure(1)->getIndex();
                idT[2] = c->getStructure(2)->getIndex();
                idT[3] = c->getStructure(3)->getIndex();
                equivalent(4, idT);
                break;
            }
            case StructureProperties::WEDGE: {
                // WEDGE
                //     1-------------4       facets (quad):   facets (triangles):     lines:
                //     /\           . \       2,5,4,1          0,2,1                   0,1      2,5
                //    /  \         /   \      0,1,4,3          3,4,5                   0,2      3,4
                //   0- - \ - - - 3     \     2,0,3,5                                  1,2      4,5
                //     \   \         \   \                                             0,3      5,3
                //       \ 2-----------\--5                                            1,4
                unsigned int idQ[4];
                idQ[0] = c->getStructure(2)->getIndex();
                idQ[1] = c->getStructure(5)->getIndex();
                idQ[2] = c->getStructure(4)->getIndex();
                idQ[3] = c->getStructure(1)->getIndex();
                equivalent(4, idQ);

                idQ[0] = c->getStructure(0)->getIndex();
                idQ[1] = c->getStructure(1)->getIndex();
                idQ[2] = c->getStructure(4)->getIndex();
                idQ[3] = c->getStructure(3)->getIndex();
                equivalent(4, idQ);

                idQ[0] = c->getStructure(2)->getIndex();
                idQ[1] = c->getStructure(0)->getIndex();
                idQ[2] = c->getStructure(3)->getIndex();
                idQ[3] = c->getStructure(5)->getIndex();
                equivalent(4, idQ);

                unsigned int idT[3];
                idT[0] = c->getStructure(0)->getIndex();
                idT[1] = c->getStructure(2)->getIndex();
                idT[2] = c->getStructure(1)->getIndex();
                equivalent(3, idT);

                idT[0] = c->getStructure(3)->getIndex();
                idT[1] = c->getStructure(4)->getIndex();
                idT[2] = c->getStructure(5)->getIndex();
                equivalent(3, idT);
                break;
            }
            case StructureProperties::TETRAHEDRON: {
                // tetrahedron are defined as follow:
                //                    3
                //                  /| \                  So to generate the facet list,
                //                 / |  \                 we just have to loop on all the
                //                1__|___\ 2              tetrahedron and add the corresponding 4 facets :
                //                \  |   /                f0=0,1,2      f2=0,3,1
                //                 \ |  /                 f1=0,2,3      f3=2,1,3
                //                  \|/
                //                  0
                unsigned int id[3];
                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(1)->getIndex();
                id[2] = c->getStructure(2)->getIndex();
                equivalent(3, id);

                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(2)->getIndex();
                id[2] = c->getStructure(3)->getIndex();
                equivalent(3, id);

                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(3)->getIndex();
                id[2] = c->getStructure(1)->getIndex();
                equivalent(3, id);

                id[0] = c->getStructure(2)->getIndex();
                id[1] = c->getStructure(1)->getIndex();
                id[2] = c->getStructure(3)->getIndex();
                equivalent(3, id);
                break;
            }

            case StructureProperties::HEXAHEDRON: {
                // hexahedron are defined as follow:
                //              2-------------6
                //             / \           . \      So to generate the facet list,
                //            /   \         /   \     we just have to loop on all the
                //           1- - -\ - - - 5     \    hexahedron and add the corresponding 6 facets :
                //           \     3-------------7    f0=0,3,2,1     f3=3,7,6,2
                //            \   /         \   /     f1=0,4,7,3     f4=1,2,6,5
                //             \ /           . /      f2=0,1,5,4     f5=4,5,6,7
                //              0-------------4

                unsigned int id[4];
                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(3)->getIndex();
                id[2] = c->getStructure(2)->getIndex();
                id[3] = c->getStructure(1)->getIndex();
                equivalent(4, id);

                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(4)->getIndex();
                id[2] = c->getStructure(7)->getIndex();
                id[3] = c->getStructure(3)->getIndex();
                equivalent(4, id);

                id[0] = c->getStructure(0)->getIndex();
                id[1] = c->getStructure(1)->getIndex();
                id[2] = c->getStructure(5)->getIndex();
                id[3] = c->getStructure(4)->getIndex();
                equivalent(4, id);

                id[0] = c->getStructure(3)->getIndex();
                id[1] = c->getStructure(7)->getIndex();
                id[2] = c->getStructure(6)->getIndex();
                id[3] = c->getStructure(2)->getIndex();
                equivalent(4, id);

                id[0] = c->getStructure(1)->getIndex();
                id[1] = c->getStructure(2)->getIndex();
                id[2] = c->getStructure(6)->getIndex();
                id[3] = c->getStructure(5)->getIndex();
                equivalent(4, id);

                id[0] = c->getStructure(4)->getIndex();
                id[1] = c->getStructure(5)->getIndex();
                id[2] = c->getStructure(6)->getIndex();
                id[3] = c->getStructure(7)->getIndex();
                equivalent(4, id);
                break;
            }
            default:
                cout << "Cannot generate sub facets for cell #" << c->getIndex() << ": it is not of known type (TRIANGLE, QUAD, WEDGE, HEXAHEDRON or TETRAHEDRON)." << endl;
                break;
        }
    }

    // now that we have in facetMap all the facet and the number of times they have been used
    // just add in a new SC all the one that are used only once
    StructuralComponent* facet = new StructuralComponent(nullptr, "extern");

    for (vector <Facet*>::iterator it = allFacets.begin(); it != allFacets.end(); ++it) {
        //(*it)->debug();
        if ((*it)->getUsed() == 1) { // used only once
            facet->addStructure((*it)->getCell(sc->getPhysicalModel()));
        }
    }

    // delete all facets
    for (vector <Facet*>::iterator it = allFacets.begin(); it != allFacets.end(); ++it) {
        delete (*it);
    }

    // add incompressibility constraint parameter
    facet->getProperties()->set("incompressibility", true);

    // create the enclosed volume
    MultiComponent* mc = new MultiComponent(nullptr, "Enclosed Volumes");
    mc->addSubComponent(facet);

    // return the SC
    return mc;
}



// -------------------- vtk2pml ------------------------
string vtk2pml(string filename) {
    // create output file name
    string outFilename = filename;

    unsigned int pLast = outFilename.rfind(".");
    if (pLast != string::npos) {
        outFilename.erase(pLast);
    }
    outFilename += "0.pml";

    // read the vtk file
    vtkFile.open(filename.c_str());

    if (!vtkFile) {
        cerr << "Error: Can't open the file named " << filename << endl;
        exit(1);
    }

    ofstream outputFile(outFilename.c_str());

    cout << "Transforming vtk to pml..." << endl;
    outputFile << "<!-- Created by vtk2pml (c) E Promayon, TIMC (c) 2009 -->" << endl;

    outputFile << "<physicalModel name=\"from vtk\" >" << endl;

    // atoms
    // looking for "POINTS n float"
    unsigned int nAtoms = getCountFromDirective("POINTS");
    //cout << "Found " << nAtoms << " atoms." << endl;

    outputFile << "<atoms>" << endl;

    outputFile << "<structuralComponent name=\"vertices\">" << endl;

    double x, y, z;

    for (unsigned int i = 1; i <= nAtoms; i++) {
        // read position
        vtkFile >> x;
        vtkFile >> y;
        vtkFile >> z;
        // write atom
        outputFile << "<atom>" << endl;
        // indexes start at 0
        outputFile << "<atomProperties index=\"" << i - 1 << "\" x=\"" << x << "\"  y=\"" << y << "\" z=\"" << z << "\"  />" << endl;
        outputFile << "</atom>" << endl;
    }

    outputFile << "</structuralComponent>" << endl;

    outputFile << "</atoms>" << endl;

    cout <<  "-> created " << nAtoms << " atoms" << endl;

    // regions
    outputFile << "<exclusiveComponents>" << endl;
    outputFile << "<multiComponent name=\"Exclusive Components\">" << endl;
    outputFile << "<structuralComponent name=\"Regions\" mass=\"1\" viscosityW=\"1\" >" << endl;
    outputFile << "<cell>" << endl;
    outputFile << "<cellProperties name=\"cell\" mode=\"NONE\" type=\"POLY_VERTEX\" materialType=\"elastic\" shapeW=\"300\" masterRegion=\"1\" incompressibility=\"1\" />" << endl;
    outputFile << "<color r=\"0.8\" g=\"0.8\" b=\"0.2\" a=\"1.0\" />" << endl;

    for (unsigned int i = 1; i <= nAtoms; i++) {
        outputFile << "<atomRef index=\"" << i - 1 <<  "\"/>" << endl;
    }

    outputFile << "</cell>" << endl;

    outputFile << "</structuralComponent>" << endl;

    cout <<  "-> created region" << endl;

// tetrahedra => elements (we can then convert the elements to neighborhood using the corresponding physicalmodel lib tool)
    unsigned int nPolygons = getCountFromDirective("POLYGONS");

    outputFile << "<structuralComponent name=\"Elements\"  mode=\"WIREFRAME_AND_SURFACE\" >" << endl;

    outputFile << "<color r=\".95\" g=\"0.89\" b=\"0.63\" a=\"1.0\"/>" << endl;

    cout <<  "-> creating polygons (nb of polygons to read=" << nPolygons << ")" << endl;

    int id;
    int nPoints;

    for (unsigned int i = 0; i < nPolygons; i++) {
        outputFile << "<cell>" << endl;
        outputFile << "<cellProperties type=\"";
        vtkFile >> nPoints;

        switch (nPoints) {
            case 3:
            default:
                outputFile << "TRIANGLE\" />" << endl;
                break;
        }

        for (unsigned int j = 0; j < nPoints; j++) {
            vtkFile >> id;
            outputFile << "<atomRef index=\"" << id << "\" />" << endl;
        }

        /*    // discard the next "offset" number of integer
            for ( unsigned int j=0; j<offset; j++ )
              vtkFile >> id;
        */
        outputFile << "</cell>" << endl;
    }

    cout <<  "...finished (" << nPolygons << " polygons created)" << endl;

    cout << "Results in " << outFilename << endl;

    outputFile << "</structuralComponent>" << endl;
    outputFile << "</multiComponent>" << endl;
    outputFile << "</exclusiveComponents>" << endl;
    outputFile << "</physicalModel>" << endl;

    outputFile.close();

    return outFilename;
}


// -------------------- optimizeMesh ------------------------
string optimizeMesh(string filename, double precision) {
    try {
        // read the pml
        cout << "-> please wait while opening " << filename << " for optimizing precision " << precision << endl;
        pm = new PhysicalModel(filename.c_str());

        cout << "-> please wait while optimizing " << pm->getNumberOfAtoms() << " atoms..." << endl;

        optimize(filename);

        // save the result
        stringstream clusterOSS;
        clusterOSS << precision;
        unsigned int pLast = filename.rfind(".") - 1;
        if (pLast != string::npos) {
            filename.erase(pLast);
        }
        filename += "1.pml";

        cout << "-> please wait while saving " << filename << " (" << nrOfNewAtoms << " new atoms)" << endl;

        ofstream outputFile(filename.c_str());
        pm->setName(pm->getName() + " optimized for " + clusterOSS.str());
        // do not optimize output (do not touch cell and atom id)
        pm->xmlPrint(outputFile, false);
        outputFile.close();

        delete pm;

        return filename;

    }
    catch (const PMLAbortException& ae) {
        cout << "AbortException: Physical model aborted:" << endl ;
        cout << ae.what() << endl;
    }

    return "";

}

// -------------------- elementsToNeighborhood ------------------------
string elementsToNeighborhood(string filename) {

    try {
        // read the pml
        cout << "-> please wait while reading " << filename << " for adding neighborhood/enclosed volumes" << endl;
        PhysicalModel pm(filename.c_str());

        // find the structural component "Elements" in the exclusive components
        Component* c = pm.getComponentByName("Elements");
        if (!c) {
            throw PMLAbortException("Cannot find any component named \"Elements\"");
        }

        // check if it is a correct one
        if (!c->isExclusive() || !c->isInstanceOf("StructuralComponent")) {
            throw PMLAbortException("Cannot translate the \"Elements\" component: not exclusive or not a structural component.");
        }

        // check if c is made of hexahedron
        StructuralComponent* sc = (StructuralComponent*) c;
        if (sc->composedBy() != StructuralComponent::CELLS || sc->getNumberOfStructures() == 0 || (sc->getCell(0)->getType() != StructureProperties::HEXAHEDRON &&
                sc->getCell(0)->getType() != StructureProperties::TETRAHEDRON &&
                sc->getCell(0)->getType() != StructureProperties::WEDGE &&
                sc->getCell(0)->getType() != StructureProperties::TRIANGLE &&
                sc->getCell(0)->getType() != StructureProperties::QUAD)) {
            throw PMLAbortException("Cannot translate the \"Elements\" component: does not contains any cell geometric type HEXAHEDRON, WEDGE, TRIANGLE, QUAD or TETRAHEDRON. Exiting");
        }

        // now, we have the right component, let's do some work...
        cout << "-> please wait while generating neighborhood" << endl;
        StructuralComponent* neighborhoods = generateNeighborhood(sc);
        cout << "-> please wait while generating external surface" << endl;
        MultiComponent* polyhedrons = generateExternalSurface(sc);

        // add the new SC to the informative component
        if (neighborhoods) {
            pm.getExclusiveComponents()->addSubComponent(neighborhoods);
        }
        if (polyhedrons) {
            pm.getExclusiveComponents()->addSubComponent(polyhedrons);
        }

        // save the result
        unsigned int pLast = filename.rfind(".") - 1;
        if (pLast != string::npos) {
            filename.erase(pLast);
        }
        filename += ".pml";

        cout << "-> please wait while saving result in " << filename << endl;

        ofstream outputFile(filename.c_str());
        pm.setName(pm.getName() + " neighborhoods");
        // do not change cell and atom id
        pm.xmlPrint(outputFile, false);

        return filename;

    }
    catch (const PMLAbortException& ae) {
        cout << "AbortException: Physical model aborted:" << endl ;
        cout << ae.what() << endl;
    }

    return "";
}

int main(int argc, char** argv) {

    if (argc != 3) {
        cout << "Usage:" << endl;
        cout << "\t" << argv[0] << " file.vtk precision" << endl;
        cout << "output to: file.pml" << endl;
        cout << "precision is a float (each cluster of points included in a sphere of radius precisious will be merged)" << endl;
        cout << "PML " << PhysicalModel::VERSION << endl;
        exit(-1);
    }

    // get the vtk filename
    string filename(argv[1]);
    precision = atof(argv[2]);

    string pml = vtk2pml(filename);

    string optimizedPml = optimizeMesh(pml, precision);

    string modelPml = elementsToNeighborhood(optimizedPml);

    cout << endl;
    cout << "Finished. Results is in " << modelPml << endl;
}
