/*****************************************************************************
 * $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/StructuralComponent.h>
#include <pml/MultiComponent.h>

// the input .obj file
PhysicalModel* pm;
string pmlFile;
string meshFile;
bool reorder;

// -------------------- ProgramArg ------------------------
// Code inspired from ProgVals "Thinking in C++, 2nd Edition, Volume 2", chapter 4
// by Bruce Eckel & Chuck Allison, (c) 2001 MindView, Inc.
// Available at www.BruceEckel.com.
// Program values can be changed by command lineclass ProgVals
#include <map>
#include <iostream>
#include <string>

class ProgramArg : public std::map<std::string, std::string> {
public:
    ProgramArg(std::string defaults[][2], unsigned int sz) {
        for (unsigned int i = 0; i < sz; i++) {
            insert(std::pair<const std::string, std::string&>(defaults[i][0], defaults[i][1]));
        }
    };

    void parse(int argc, char* argv[], std::string usage, int offset = 1) {
        for (int i = offset; i < argc; i++) {
            string flag(argv[i]);
            unsigned int equal = flag.find('=');
            if (equal == string::npos) {
                cerr << "Command line error: " << argv[i] << endl << usage << endl;
                continue; // Next argument
            }
            string name = flag.substr(0, equal);
            string value = flag.substr(equal + 1);
            if (find(name) == end()) {
                cerr << name << endl << usage << endl;
                continue; // Next argument
            }
            operator[](name) = value;
        }
    };

    void print(std::ostream& out = std::cout) {
        out << "Argument values:" << endl;
        for (iterator it = begin(); it != end(); ++it) {
            out << (*it).first << " = " << (*it).second << endl;
        }
    };
};

string defaultsArg[][2] = {
    { "-f", "none" },
    { "-o", "pml2ghs3d-output.mesh" },
    { "-reorder", "false"},
};

const char* usage = "usage:\n"
                    "pml2ghs3d -f=document.pml [-o=output] [-reorder=false]\n"
                    "Transform a PML document containing an 'Elements' (list of triangles) exclusive structural component to a .mesh (readable in GHS3D), and reorder the index if reorder is true (needed if atom id are not successive number)\n"
                    "(Note no space around '=')\n"
                    "Where the flags can be any of: \n"
                    "f (input pml document), o (output .mesh file name), reorder (true or false, default is false) \n";

// global ProgramArgument
ProgramArg argVal(defaultsArg, sizeof defaultsArg / sizeof* defaultsArg);

// -------------------- processArg ------------------------
void processArg() {
    pmlFile = argVal["-f"];
    meshFile = argVal["-o"];
    reorder = (argVal["-reorder"] == "false") ? false : true;
    if (pmlFile == "none") {
        cerr << "Argument error: -f is mandatory" << usage << endl;
        exit(-1);
    }
}

// -------------------- printArg ------------------------
void printArg() {
    cout << "pml2ghs3d generating " << meshFile << " from " << pmlFile << ", with" << (reorder ? "" : "out") << " reordering" << endl;
    cout << endl;
}

// -------------------- 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);
    //argVal.print();

    processArg();
    printArg();

    try {
        pm = new PhysicalModel(pmlFile.c_str());

        //-- create the .mesh
        std::ofstream meshStream;
        meshStream.open(meshFile.c_str());
        if (!meshStream.is_open()) {
            cerr << "Error: cannot open file " << meshFile << endl;
            exit(1);
        }

        // write the vertices
        meshStream << " MeshVersionFormatted" << endl;
        meshStream << " Dimension" << endl;
        meshStream << " 3" << endl;
        meshStream << " Vertices" << endl;
        meshStream << " " << pm->getNumberOfAtoms() << endl;

        StructuralComponent* sc = pm->getAtoms();

        Atom* a;
        double pos[3];
        meshStream.setf(ios::fixed, ios::floatfield); // set fixed floating format
        meshStream.precision(6);
        map<unsigned int, unsigned int> atomIdToNodeId;
        for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
            a = (Atom*) sc->getStructure(i);
            a->getPosition(pos);
            meshStream << "\t\t" << pos[0] << "   " << pos[1] << "       \t\t\t " << pos[2] << "                     0\t\t\t\t\t\t" << endl;
            atomIdToNodeId.insert(pair<unsigned int, unsigned int>(a->getIndex(), i + 1));
        }

        meshStream << endl;
        meshStream << " Triangles" << endl;

        //-- convert the triangles
        Component* cpt = pm->getComponentByName("Elements");
        if (!cpt) {
            cerr << "Cannot find any component named \"Elements\". Exiting" << endl;
            exit(-1);
        }

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

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

        // now, we have the right component, let's do some work...
        meshStream << " " << sc->getNumberOfStructures() << endl;

        Cell* c;
        map<unsigned int, unsigned int>::iterator result;
        for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
            c = (Cell*) sc->getCell(i);
            if (c->getType() == StructureProperties::TRIANGLE) {
                if (!reorder) {
                    meshStream  << c->getStructure(0)->getIndex() + 1 << " "
                                << c->getStructure(1)->getIndex() + 1 << " "
                                << c->getStructure(2)->getIndex() + 1
                                << " 1" << endl;
                }
                else {
                    for (unsigned int j = 0; j < 3; j++) {
                        result = atomIdToNodeId.find(c->getStructure(j)->getIndex());
                        if (result == atomIdToNodeId.end()) {
                            cerr << "Cell #" << c->getIndex() << ": cannot find atom#" << c->getStructure(j)->getIndex() << " in the map. Exiting." << endl;
                            exit(-4);
                        }
                        else {
                            meshStream  << result->second << " ";
                        }
                    }
                    meshStream << " 1" << endl;
                }
            }
        }
        meshStream << endl;
        meshStream << " End" << endl;
        meshStream << endl;

        meshStream.close();

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