/*****************************************************************************
 * $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 <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 facets ------------------------
class Connection;
vector <Connection*> allConnections;
vector <Atom*> newAtoms;

/** class facet
 *
 *  a connection can either be a line, triangular or quadrangular
 *
 *  1) LINE (line between two atoms in an element)
 *
 *  0 ----+---- 1
 *       n0
 *
 *  1 new atom can be created
 *  - n0 (center)
 *
 *
 *
 *  2) TRIANGLE (viewed from outside, normal pointing toward user)
 *
 *           2
 *         /  \
 *        /    \
 *    n2 +  n0  + n3
 *      /   x   \
 *     /         \
 *    0-----+-----1
 *         n1
 *  4 new atoms can be created:
 *  - n0 (center),
 *  - n1 (between 0 and 1),
 *  - n2 (between 0 and 2) and
 *  - n3 (between 1 and 2)
 *
 *
 *
 *  QUAD (viewed from outside, normal pointing toward user)
 *               n4
 *         3-----+-----2
 *         |           |
 *         |           |
 *      n2 +     x     + n3
 *         |    n0     |
 *         |           |
 *         0-----+-----1
 *               n1
 *  5 new atoms can be created:
 *  - n0 (center),
 *  - n1 (between 0 and 1),
 *  - n2 (between 0 and 3),
 *  - n3 (between 1 and 2) and
 *  - n4 (between 2 and 3)
 */
class Connection {
public:
    /// create a line connection using the given ptrs
    Connection(Atom*, Atom*);

    /// create a triangular connection using the given ptrs
    Connection(Atom*, Atom*, Atom*);

    /// create a quad connection using the given ptrs
    Connection(Atom*, Atom*, Atom*, Atom*);

    /// destructor
    virtual ~Connection();

    /// if it is the same (equivalent) facet, increment used (return true if equivalence)
    friend bool equivalent(Connection*, Connection*);

    /// print on stdout
    void debug();

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

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

    /// get new atom in the center (create it if not already there
    Atom* getCenter();

    /// compute coordinates of the center of any kind of cell
    static void getCenter(Cell* c, double center[3]);


private:
    /// is this atom index present in this connection (no check on the order)
    bool isIn(Atom*) const;

    /// initialization
    void init();

    /// the connection equivalent cell
    Cell* myCell;

    /// nr of times the connection is used
    unsigned int used;

    /// connection center
    Atom* n0;

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

};

unsigned int Connection::atomIndex = 20000;

// -------------------- getCenter ------------------------
void Connection::getCenter(Cell* c, double center[3]) {
    double pos[3];
    unsigned int size = c->getNumberOfStructures();

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

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

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

// -------------------- constructor/destructor ------------------------
Connection::Connection(Atom* a, Atom* b) {
    myCell = new Cell(pm, StructureProperties::LINE);
    myCell->addStructureIfNotIn(a);
    myCell->addStructureIfNotIn(b);
    init();
}
Connection::Connection(Atom* a, Atom* b, Atom* c) {
    myCell = new Cell(pm, StructureProperties::TRIANGLE);
    myCell->addStructureIfNotIn(a);
    myCell->addStructureIfNotIn(b);
    myCell->addStructureIfNotIn(c);
    init();
}

Connection::Connection(Atom* a, Atom* b, Atom* c, Atom* d) {
    myCell = new Cell(pm, StructureProperties::QUAD);
    myCell->addStructureIfNotIn(a);
    myCell->addStructureIfNotIn(b);
    myCell->addStructureIfNotIn(c);
    myCell->addStructureIfNotIn(d);
    init();
}

Connection::~Connection() {
    delete myCell;
}

void Connection::init() {
    used = 1;
    // create center
    double pos[3];
    getCenter(myCell, pos);
    n0 = new Atom(pm, Connection::atomIndex, pos);
    Connection::atomIndex++;
}

// -------------------- debug ------------------------
void Connection::debug() {
    switch (myCell->getNumberOfStructures()) {
        case 2:
            cout << "line <";
            break;
        case 3:
            cout << "triangle <";
            break;
        case 4:
            cout << "quad <";
            break;
        default:
            cout << "unknown connection <";
            break;
    }
    unsigned int i;
    for (i = 0; i < myCell->getNumberOfStructures(); i++) {
        cout << myCell->getStructure(i)->getIndex() << ",";
    }
    cout << myCell->getStructure(i)->getIndex() << "> used " << used << " times" << endl;
}

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

// -------------------- equivalent ------------------------
bool equivalent(Connection* f1, Connection* f2) {
    if (f1->myCell->getNumberOfStructures() != f2->myCell->getNumberOfStructures()) {
        return false;
    }
    else {
        unsigned int i = 0;
        unsigned int equi = 0;
        while (i < f1->myCell->getNumberOfStructures() && f2->isIn(f1->getAtom(i))) {
            i++;
        }
        if (i == f1->myCell->getNumberOfStructures()) {
            f1->used++;
            equi++;
        }

        return (i == f1->myCell->getNumberOfStructures());
    }
}

// -------------------- isIn ------------------------
bool Connection::isIn(Atom* a) const {
    unsigned int i = 0;
    while (i < myCell->getNumberOfStructures() && a != myCell->getStructure(i)) {
        i++;
    }
    return (i != myCell->getNumberOfStructures());
}


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

// -------------------- getCenter ------------------------
Atom* Connection::getCenter() {
    return n0;
}

// -------------------- newTetra ------------------------
Cell* newTetra(Atom* a, Atom* b, Atom* c, Atom* d) {
    Cell* cell = new Cell(pm, StructureProperties::TETRAHEDRON);
    cell->addStructureIfNotIn(a);
    cell->addStructureIfNotIn(b);
    cell->addStructureIfNotIn(c);
    cell->addStructureIfNotIn(d);
    return cell;
}

// -------------------- newHexa ------------------------
Cell* newHexa(Atom* a, Atom* b, Atom* c, Atom* d, Atom* e, Atom* f, Atom* g, Atom* h) {
    Cell* cell = new Cell(pm, StructureProperties::HEXAHEDRON);
    cell->addStructureIfNotIn(a);
    cell->addStructureIfNotIn(b);
    cell->addStructureIfNotIn(c);
    cell->addStructureIfNotIn(d);
    cell->addStructureIfNotIn(e);
    cell->addStructureIfNotIn(f);
    cell->addStructureIfNotIn(g);
    cell->addStructureIfNotIn(h);
    return cell;
}

// -------------------- insert ------------------------
// check if there is already an equivalent (existing) facet
// if not insert it in the list
// return the connection (if not inserted, return the equivalent connection already in the list)
Connection* insert(Connection* f) {
    vector <Connection*>::iterator it;

    // look into allConnections for equivalence
    it = allConnections.begin();
    while (it != allConnections.end() && !equivalent((*it), f)) {
        ++it;
    }

    // not found => insert
    if (it == allConnections.end()) {
        allConnections.push_back(f);
        Atom* a = f->getCenter();
        pm->addAtom(a);
        newAtoms.push_back(a);
        return f;
    }
    else  {
        return (*it);

    }

}

// ------------- getNewCenteredAtom ------------------
Atom* getNewCenteredAtom(Cell* c, unsigned int atomIndex)  {
    double center[3];
    Atom* a;

    Connection::getCenter(c, center);
    a = new Atom(pm, atomIndex, center);
    pm->addAtom(a);
    newAtoms.push_back(a);

    return a;
}

// -------------------- refine ------------------------
StructuralComponent* refine(StructuralComponent* sc) {
    StructuralComponent* r = new StructuralComponent(nullptr, "Refined Elements");

    // create a new sc with refined elements:
    //    for each element
    //      create n sub-elements (n=8 for hexa, n=4 for tetra)
    //      insert his sub-elements in the new SC
    unsigned int atomIndex = 10000;
    Atom* a;
    Cell* c;
    for (unsigned int i = 0; i < sc->getNumberOfCells(); i++) {
        // c is an hexahedron or a tetrahedron
        c = sc->getCell(i);

        // Connections have to be described in anticlockwise (trigonometrywise) when
        // looking at them from outside the object
        switch (c->getType()) {
            case StructureProperties::TETRAHEDRON: {
                // tetrahedron are defined as follow:
                //                    3
                //                  /| \                  So to generate the connection 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
                //insert(new Connection(c->getStructure(0), c->getStructure(1), c->getStructure(2)));
                //insert(new Connection(c->getStructure(0), c->getStructure(2), c->getStructure(3)));
                //insert(new Connection(c->getStructure(0), c->getStructure(3), c->getStructure(1)));
                //insert(new Connection(c->getStructure(2), c->getStructure(1), c->getStructure(3)));
                // subdividing facets in case of tetrahedron is not needed... (see part2 of this algo)
                //
                // So to generate a refined mesh, we just have to create one new atom in
                // the center and 4 new tetrahedrons
                //
                // 1. get a new atom in the center of the tetrahedron
                a = getNewCenteredAtom(c, atomIndex++);
                // 2. get proxy for atoms
                Atom* a0 = (Atom*) c->getStructure(0);
                Atom* a1 = (Atom*) c->getStructure(1);
                Atom* a2 = (Atom*) c->getStructure(2);
                Atom* a3 = (Atom*) c->getStructure(3);
                // 3. add the 4 new tetrahedrons
                r->addStructure(newTetra(a, a1, a2, a3));
                r->addStructure(newTetra(a0,  a, a2, a3));
                r->addStructure(newTetra(a0, a1,  a, a3));
                r->addStructure(newTetra(a0, a1, a2,  a));
                break;
            }

            case StructureProperties::HEXAHEDRON: {
                // hexahedron are defined as follow:
                //              2-------------6
                //             / \           . \      So to generate the connection 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
                //
                //  So to generate a refined mesh, 19 new atoms have to be generated:
                //  6 center of each facet
                //  + center of hexahedron
                //  + 12 center of lines
                //
                // get the 8 atom proxys
                Atom* a0 = (Atom*) c->getStructure(0);
                Atom* a1 = (Atom*) c->getStructure(1);
                Atom* a2 = (Atom*) c->getStructure(2);
                Atom* a3 = (Atom*) c->getStructure(3);
                Atom* a4 = (Atom*) c->getStructure(4);
                Atom* a5 = (Atom*) c->getStructure(5);
                Atom* a6 = (Atom*) c->getStructure(6);
                Atom* a7 = (Atom*) c->getStructure(7);
                // 1. insert all 6 facets
                Connection* cf0 = insert(new Connection(a0, a3, a2, a1));
                Connection* cf1 = insert(new Connection(a0, a4, a7, a3));
                Connection* cf2 = insert(new Connection(a0, a1, a5, a4));
                Connection* cf3 = insert(new Connection(a3, a7, a6, a2));
                Connection* cf4 = insert(new Connection(a1, a2, a6, a5));
                Connection* cf5 = insert(new Connection(a4, a5, a6, a7));
                // 2. insert all 12 lines
                Connection* cl0 = insert(new Connection(a0, a1));
                Connection* cl1 = insert(new Connection(a1, a2));
                Connection* cl2 = insert(new Connection(a2, a3));
                Connection* cl3 = insert(new Connection(a3, a0));
                Connection* cl4 = insert(new Connection(a4, a5));
                Connection* cl5 = insert(new Connection(a5, a6));
                Connection* cl6 = insert(new Connection(a6, a7));
                Connection* cl7 = insert(new Connection(a7, a4));
                Connection* cl8 = insert(new Connection(a0, a4));
                Connection* cl9 = insert(new Connection(a1, a5));
                Connection* cl10 = insert(new Connection(a2, a6));
                Connection* cl11 = insert(new Connection(a3, a7));
                // get a new atom in the center of the tetrahedron
                a = getNewCenteredAtom(c, atomIndex++);
                // get all the centers
                Atom* f0 = cf0->getCenter();
                Atom* f1 = cf1->getCenter();
                Atom* f2 = cf2->getCenter();
                Atom* f3 = cf3->getCenter();
                Atom* f4 = cf4->getCenter();
                Atom* f5 = cf5->getCenter();
                Atom* l0 = cl0->getCenter();
                Atom* l1 = cl1->getCenter();
                Atom* l2 = cl2->getCenter();
                Atom* l3 = cl3->getCenter();
                Atom* l4 = cl4->getCenter();
                Atom* l5 = cl5->getCenter();
                Atom* l6 = cl6->getCenter();
                Atom* l7 = cl7->getCenter();
                Atom* l8 = cl8->getCenter();
                Atom* l9 = cl9->getCenter();
                Atom* l10 = cl10->getCenter();
                Atom* l11 = cl11->getCenter();
                // insert the 8 new hexahedrons
                r->addStructure(newHexa(f2, l9, f4,  a, l4, a5, l5, f5));
                r->addStructure(newHexa(l8, f2,  a, f1, a4, l4, f5, l7));
                r->addStructure(newHexa(l0, a1, l1, f0, f2, l9, f4,  a));
                r->addStructure(newHexa(a0, l0, f0, l3, l8, f2,  a, f1));
                r->addStructure(newHexa(a, f4, l10, f3, f5, l5, a6, l6));
                r->addStructure(newHexa(f1,  a, f3, l11, l7, f5, l6, a7));
                r->addStructure(newHexa(f0, l1, a2, l2,  a, f4, l10, f3));
                r->addStructure(newHexa(l3, f0, l2, a3, f1,  a, f3, l11));
                break;
            }

            default:
                break;
        }
    }

    // return the SC
    return r;
}


// -------------------- newAtomSC ------------------------
StructuralComponent* newAtomSC() {
    StructuralComponent* na = new StructuralComponent(nullptr, "new atoms");

    for (unsigned int i = 0; i < newAtoms.size(); i++) {
        na->addStructure(newAtoms[i]);
    }

    // return the SC
    return na;
}

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

    if (argc != 2) {
        cout << "Usage:" << endl;
        cout << "\t" << argv[0] << " file.pml" << endl;
        cout << "subdivide structural component 'Elements' (each tetra is subdivided in 4 sub-tetra, each hexa is subdivided in 4 sub-hexa), output to: file-refined.pml" << endl;
        cout << "PML " << PhysicalModel::VERSION << endl;
        exit(-1);
    }

    try {
        // read the pml
        string filename(argv[1]);
        pm = new PhysicalModel(filename.c_str());

        // find the structural component "Elements" in the exclusive components
        Component* c = pm->getComponentByName("Elements");
        if (!c) {
            cout << "Cannot find any component named \"Elements\". Exiting" << endl;
            exit(-1);
        }

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

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

        // now, we have the right component, let's do some work...
        StructuralComponent* refined = refine(sc);

        // add the new SC to the informative component
        if (refined) {
            pm->getInformativeComponents()->addSubComponent(refined);
            // add another informative component with all new atoms
            pm->getInformativeComponents()->addSubComponent(newAtomSC());
        }

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

        cout << "-> saved as " << filename << endl;

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

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