/*****************************************************************************
 * $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>
#include <math.h>
using namespace std;

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

#include "../ansys2pml/programarg.h"

// global parameters (command line arguments)
string outputPML;
int subdiv;
PhysicalModel* pm;
unsigned int atomIndex;
StructuralComponent* elemSC;
string shapeType;
vector <double*> vertices;
vector <unsigned int*> faces;

// coordinates of one of the icosahedron vertex
#define X 0.525731112119133696
#define Z 0.850650808352039932

// icosahedron  vertices
double icosahedronVertex[12][3] = {
    { -X, 0.0, Z}, {X, 0.0, Z}, { -X, 0.0, -Z}, {X, 0.0, -Z},
    {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},
    {Z, X, 0.0}, { -Z, X, 0.0}, {Z, -X, 0.0}, { -Z, -X, 0.0}
};

// icosahedron faces
unsigned int icosahedronFace[20][3] = {
    {1, 4, 0}, {4, 9, 0}, {4, 5, 9}, {8, 5, 4}, {1, 8, 4},
    {1, 10, 8}, {10, 3, 8}, {8, 3, 5}, {3, 2, 5}, {3, 7, 2},
    {3, 10, 7}, {10, 6, 7}, {6, 11, 7}, {6, 0, 11}, {6, 1, 0},
    {10, 1, 6}, {11, 0, 9}, {2, 11, 9}, {5, 2, 9}, {11, 2, 7}
};

// coordinates of the cube vertices
#define C 0.5773502691896257645

// cube vertices
double cubeVertex[8][3] = {
    {C, C, -C}, {C, C, C}, {C, -C, C}, {C, -C, -C},
    { -C, C, -C}, { -C, C, C}, { -C, -C, C}, { -C, -C, -C}
};

// cube faces
unsigned int cubeFace[6][4] = {
    {0, 1, 2, 3}, {0, 4, 5, 1}, {1, 5, 6, 2},
    {4, 0, 3, 7}, {5, 4, 7, 6}, {2, 6, 7, 3}
};

// -------------------- processArg ------------------------
void processArg() {
    outputPML = argVal["-o"];
    subdiv = atoi(argVal["-l"].c_str());
    shapeType = argVal["-t"];
    if (shapeType == "ico") {
        for (unsigned int i = 0; i < 12; i++) {
            vertices.push_back(icosahedronVertex[i]);
        }
        for (unsigned int i = 0; i < 20; i++) {
            faces.push_back(icosahedronFace[i]);
        }
    }
    if (shapeType == "cube") {
        for (unsigned int i = 0; i < 8; i++) {
            vertices.push_back(cubeVertex[i]);
        }
        for (unsigned int i = 0; i < 6; i++) {
            faces.push_back(cubeFace[i]);
        }
    }
}

// -------------------- printArg ------------------------
void printArg() {
    cout << "gensphere generating " << outputPML << " using " << subdiv << " level" << "of shape " << shapeType << endl;
    cout << "The idea of subdvision is taken from the book of Frank Nielsen \"Visual Computing: Geometry, Graphics, and Vision\" , ISBN 1-58450-427-7, see http://www.charlesriver.com/visualcomputing" << endl;
}

// -------------------- newAtom ------------------------
Atom* newAtom(double posA[3]) {
    // look for a similar atom (similar position)
    StructuralComponent* atoms = pm->getAtoms();
    bool found = false;
    unsigned int i = 0;
    while (i < atoms->getNumberOfStructures() && !found) {
        double pos[3];
        ((Atom*)atoms->getStructure(i))->getPosition(pos);
        found = (sqrt((posA[0] - pos[0]) * (posA[0] - pos[0]) + (posA[1] - pos[1]) * (posA[1] - pos[1]) + (posA[2] - pos[2]) * (posA[2] - pos[2])) < 1e-6);
        i++;
    }
    if (!found) {
        // new atom
        Atom* a = new Atom(pm, atomIndex, posA);
        pm->addAtom(a);
        atomIndex++;
        return a;
    }
    else {
        return ((Atom*)atoms->getStructure(i - 1));
    }
}

// -------------------- normalize ------------------------
// Vertices should belong to the unit sphere.
// so we perform normalization
void normalize(double v[3]) {
    double d = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    if (d > 1.0e-6) {
        v[0] /= d;
        v[1] /= d;
        v[2] /= d;
    }
}

// -------------------- subdivide (3) ------------------------
// Recursively subdivide the sphere
void subdivide(Atom* a1, Atom* a2, Atom* a3, int depth) {
    double v1[3], v2[3], v3[3];
    a1->getPosition(v1);
    a2->getPosition(v2);
    a3->getPosition(v3);

    if (depth == 0) {
        normalize(v1);
        a1->setPosition(v1);
        normalize(v2);
        a2->setPosition(v2);
        normalize(v3);
        a3->setPosition(v3);

        // insert a new cell
        Cell* c = new Cell(nullptr, StructureProperties::TRIANGLE);

        // add atoms
        c->addStructure(a1);
        c->addStructure(a2);
        c->addStructure(a3);

        // add the cell to the informative and exclusive component
        elemSC->addStructure(c);

    }
    else {
        double v12[3], v23[3], v31[3];
        // midpoint
        for (unsigned int i = 0; i < 3; i++) {
            v12[i] = (v1[i] + v2[i]) / 2.0;
            v23[i] = (v2[i] + v3[i]) / 2.0;
            v31[i] = (v3[i] + v1[i]) / 2.0;
        }

        // lift midpoints on the sphere
        normalize(v12);
        normalize(v23);
        normalize(v31);

        // insert them in the atoms
        Atom* a12 = newAtom(v12);
        Atom* a23 = newAtom(v23);
        Atom* a31 = newAtom(v31);

        // subdivide new triangles in 4
        subdivide(a1, a12, a31, depth - 1);
        subdivide(a2, a23, a12, depth - 1);
        subdivide(a3, a31, a23, depth - 1);
        subdivide(a12, a23, a31, depth - 1);
    }
}

// -------------------- subdivide (4) ------------------------
// Recursively subdivide the sphere
void subdivide(Atom* a1, Atom* a2, Atom* a3, Atom* a4, int depth) {
    double v1[3], v2[3], v3[3], v4[3];
    a1->getPosition(v1);
    a2->getPosition(v2);
    a3->getPosition(v3);
    a4->getPosition(v4);

    if (depth == 0) {
        normalize(v1);
        a1->setPosition(v1);
        normalize(v2);
        a2->setPosition(v2);
        normalize(v3);
        a3->setPosition(v3);
        normalize(v4);
        a4->setPosition(v4);

        // insert a new cell
        Cell* c = new Cell(nullptr, StructureProperties::QUAD);

        // add atoms
        c->addStructure(a1);
        c->addStructure(a2);
        c->addStructure(a3);
        c->addStructure(a4);

        // add the cell to the informative and exclusive component
        elemSC->addStructure(c);

    }
    else {
        double v12[3], v23[3], v34[3], v41[3], vc[3];
        // midpoint
        for (unsigned int i = 0; i < 3; i++) {
            v12[i] = (v1[i] + v2[i]) / 2.0;
            v23[i] = (v2[i] + v3[i]) / 2.0;
            v34[i] = (v3[i] + v4[i]) / 2.0;
            v41[i] = (v4[i] + v1[i]) / 2.0;
            vc[i] = (v1[i] + v2[i] + v3[i] + v4[i]) / 4.0;
        }

        // lift midpoints on the sphere
        normalize(v12);
        normalize(v23);
        normalize(v34);
        normalize(v41);
        normalize(vc);

        // insert them in the atoms
        Atom* a12 = newAtom(v12);
        Atom* a23 = newAtom(v23);
        Atom* a34 = newAtom(v34);
        Atom* a41 = newAtom(v41);
        Atom* ac = newAtom(vc);

        // subdivide new quad in 4
        subdivide(a1, a12,  ac, a41, depth - 1);
        subdivide(a12,  a2, a23,  ac, depth - 1);
        subdivide(ac, a23,  a3, a34, depth - 1);
        subdivide(a41,  ac, a34,  a4, depth - 1);
    }
}


// -------------------- main ------------------------
int main(int argc, char** argv) {
    // Initialize and parse command line values
    // before any code that uses pvals is called:
    argVal.parse(argc, argv, usage);

    processArg();
    printArg();

    try {

        // create a new physical model
        pm = new PhysicalModel();
        pm->setName("synthetic sphere subdiv " + argVal["-l"]);
        pm->setInformativeComponents(new MultiComponent(nullptr, "Informative Components"));

        // create the new atom SC
        StructuralComponent* atoms = new StructuralComponent(pm, "Atoms");
        atomIndex = 0;

        // starts with the the initial shape
        for (unsigned int i = 0; i < vertices.size(); i++) {
            Atom* a = new Atom(pm, atomIndex, vertices[i]);
            atoms->addStructure(a);
            atomIndex++;
        }

        // add the atom list to the pml
        pm->setAtoms(atoms);

        // create the new element SC
        elemSC = new StructuralComponent(nullptr, "Elements");

        // subdivide each face of the triangle
        for (unsigned int i = 0; i < faces.size(); i++) {
            if (shapeType == "ico") {
                subdivide(pm->getAtom(faces[i][0]),
                          pm->getAtom(faces[i][1]),
                          pm->getAtom(faces[i][2]),
                          subdiv);
            }
            if (shapeType == "cube") {
                subdivide(pm->getAtom(faces[i][0]),
                          pm->getAtom(faces[i][1]),
                          pm->getAtom(faces[i][2]),
                          pm->getAtom(faces[i][3]),
                          subdiv);
            }
        }

        // add the element list to the pml
        pm->setExclusiveComponents(new MultiComponent(nullptr, "Exclusive Components"));
        pm->getExclusiveComponents()->addSubComponent(elemSC);

        // save the result in the pml file
        ofstream outputFile(outputPML.c_str());
        pm->xmlPrint(outputFile);

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