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

// obj format description: http://www.cs.huji.ac.il/~danix/modeling/Format_Obj.html

#include <iostream>
#include <string>
using namespace std;

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

// the input .obj file
std::ifstream objStream;
PhysicalModel* pm;
unsigned lineNr;
bool eof;

// a .obj line
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 = !(objStream.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;
            }
        }
    }

}

// -------------------- getVector ------------------------
// get the double 3D vector in the current data
void getVector(double d[3]) {
    stringstream data(currentLine.data, std::stringstream::in);

    for (unsigned int i = 0; i < 3; i++) {
        data >> d[i];
    }
}

// -------------------- getFacetType ------------------------
// return 3 for triangular facets and 4 for quadrangular facets, 0 if there is an error
// current data is something like "vertexId/textureId/normalId vertexId/textureId/normalId..."
unsigned int getFacetType() {
    int count = 0;
    unsigned int space = 0;

    do {
        space = currentLine.data.find(' ', space + 1);
        if (space != string::npos) {
            count++;
        }
    }
    while (space != string::npos);

    return count + 1;
}

// -------------------- getVertex ------------------------
// get the vertex list {i0, j0, k0} in a facet directive data "i0/j0/k0 i1/j1/k1 i2/j2/k2"
void getVertex(unsigned int size, int v[]) {
    stringstream data(currentLine.data, std::stringstream::in);
    string buff;

    for (unsigned int i = 0; i < size; i++) {
        data >> buff; // buff is of the form "4/4" or "2/33/32" or "4"
        unsigned int slash = buff.find('/');
        //if (slash != string::npos) {
        v[i] = atoi(buff.substr(0, slash).c_str());
        //}
    }
}


// -------------------- vertexToAtoms ------------------------
StructuralComponent* vertexToAtoms() {
    StructuralComponent* atoms = new StructuralComponent(nullptr, "Atoms");
    double pos[3];
    unsigned int atomIndex;

    // very important: start at 1 to have the same index system
    atomIndex = 1;

    // go to the first v directive
    while (!eof && currentLine.directive != "v") {
        getNext();
    }

    do {
        if (currentLine.ok && currentLine.directive == "v") {
            getVector(pos);
            atoms->addStructure(new Atom(nullptr, atomIndex++, pos));
        }
        getNext();
    }
    while (!eof);

    cout << "Found atom #1 to #" << atomIndex << endl;

    return atoms;
}

// -------------------- facetsToSC ------------------------
void facetsToSC(MultiComponent* mother) {
    StructuralComponent* group = new StructuralComponent(nullptr, "Facets");
    int* vertex;
    Cell* c;
    unsigned int type;

    // go to the first f directive
    while (!eof && currentLine.directive != "f" && currentLine.directive != "g" && currentLine.directive != "o") {
        getNext();
    }

    // process f directives
    do {
        if (currentLine.ok && (currentLine.directive == "g" || currentLine.directive == "o")) {
            // insert the current group if exists
            if (group->getNumberOfStructures() > 0) {
                mother->addSubComponent(group);
            }
            // create a new group
            group = new StructuralComponent(nullptr, currentLine.data);
        }
        if (currentLine.ok && currentLine.directive == "f") {
            // create cell depending on type
            type = getFacetType();
            switch (type) {
                case 3:
                    c = new Cell(nullptr, StructureProperties::TRIANGLE);
                    break;
                case 4:
                    c = new Cell(nullptr, StructureProperties::QUAD);
                    break;
                default:
                    c = nullptr;
                    cerr << "line " << lineNr << ": error: cannot manage facet using " << type << " vertices" << endl;
                    break;
            }

            if (c != nullptr) {
                // get atom indexes
                vertex = new int[type];
                getVertex(type, vertex);

                // add atoms
                for (unsigned int i = 0; i < type; i++) {
                    Atom* a = pm->getAtom(vertex[i]);
                    if (a != nullptr) {
                        c->addStructure(a);
                    }
                    else {
                        cerr << "line " << lineNr << ": error: cannot find vertex #" << vertex[i] << endl;
                    }
                }

                // add facet to current group
                group->addStructure(c);

                delete [] vertex;
            }
        }

        // go to the next directive
        getNext();
    }
    while (!eof);

    // insert the current group if exists
    if (group->getNumberOfStructures() > 0) {
        mother->addSubComponent(group);
    }

    cout << "Found " << mother->getNumberOfSubComponents() << " groups, total " << mother->getNumberOfCells() << " facets" << endl;
}

// -------------------- init ------------------------
void init() {
    currentLine.ok = false;
    eof = false;
    lineNr = 0;
}


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

    if (argc != 2) {
        cout << "Usage:" << endl;
        cout << "\t" << argv[0] << " file.obj" << endl;
        cout << "if file.obj is a valid Wavefront OBJ format, then a corresponding file.pml is created" << endl;
        cout << "PML " << PhysicalModel::VERSION << endl;
        exit(-1);
    }

    try {

        // open the obj file
        string filename(argv[1]);
        objStream.open(filename.c_str());
        if (!objStream.is_open()) {
            cerr << "Error: cannot open file " << filename << endl;
            exit(1);
        }
        init();

        // create a new physical model
        pm = new PhysicalModel();
        pm->setName("PML for " + filename);

        // first pass: read the vertex, and build atoms
        pm->setAtoms(vertexToAtoms());

        // rewind
        objStream.clear(); // clear the ios::eof flag, etc...
        objStream.seekg(0, ios::beg); // rewind
        init();

        // second pass: read the facets and build the corresponding SC
        pm->setExclusiveComponents(new MultiComponent(nullptr, "Exclusive Components"));
        facetsToSC(pm->getExclusiveComponents());
        // close input
        objStream.close();

        // save the result in the pml file
        unsigned int pLast = filename.rfind(".");
        if (pLast != string::npos) {
            filename.erase(pLast);
            filename += ".pml";
        }
        else {
            filename += ".pml";
        }
        ofstream outputFile(filename.c_str());
        pm->xmlPrint(outputFile);

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