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

#include "programarg.h"

// global parameters (command line arguments)
string outputPML;
vector <string> nodeFileNames;
vector <string> elemFileNames;
StructureProperties::GeometricType type;

// maps to get the correspondance between atom id and node file id
namespace std {
typedef std::pair<unsigned int, Atom*> AtomIndexPair;
typedef map <unsigned int, Atom*> AtomIndexMap;
typedef map <unsigned int, Atom*> ::iterator AtomIndexMapIterator;
}

// one map for each node file
vector <AtomIndexMap> maps;

// the input node.txt file
class InputFile {
public:
    string fileName;
    std::ifstream stream;
    unsigned lineNr;
    bool eof;
    unsigned int id;

    InputFile(string f, unsigned int index) : fileName(f), id(index) {
        open();
    };

    ~InputFile() {
        stream.close();
    }

    void init() {
        eof = false;
        lineNr = 0;
    }

    void open() {
        stream.open(fileName.c_str());
        if (!stream.is_open()) {
            cerr << "Error: cannot open file " << fileName << endl;
            exit(1);
        }
        init();
    }
};

// a node file line
// NUM X Y Z, where NUM is the node number
typedef struct {
    int id;
    double pos[3];
} NodeLine;

NodeLine currentNodeLine;

// an elem file line
/* read two lines
 * For tetrahedron : node id are 1,2,3 and 5
 * Elem Id is the last nr of the second lines
 */
typedef struct {
    int node[4];
    int type;
    int id;
} TetraElemLine;

TetraElemLine currentElemLine;

// -------------------- getNextNode ------------------------
void getNextNode(InputFile& nodes) {
    char l[200];

    if (!nodes.eof) {
        // read the next line
        nodes.eof = !(nodes.stream.getline(l, 199));
        nodes.lineNr++;
        // analyze
        stringstream line(l);
        // if the node id is found
        if ((line >> currentNodeLine.id)) {
            for (unsigned int i = 0; i < 3; i++) {
                if (!(line >> currentNodeLine.pos[i])) {
                    cerr << "Error in " << nodes.fileName << " line " << nodes.lineNr << " node #" << currentNodeLine.id << ": cannot find coordinate " << i + 1 << " (set to 0.0)" << endl;
                    currentNodeLine.pos[i] = 0.0;
                }
            }
        }
    }
}

// -------------------- getNextElem ------------------------
void getNextElem(InputFile& elems) {
    char l[200];
    int data[14];

    if (!elems.eof) {
        // read the first line
        elems.eof = !(elems.stream.getline(l, 199));
        elems.lineNr++;
        stringstream line2(l);
        for (unsigned int i = 0; i < 14; i++) {
            line2 >> data[i];
        }

        // mat is the 10th value
        currentElemLine.type = data[9];

        if (type == StructureProperties::TETRAHEDRON || currentElemLine.type == 2) {
            // node id is the 1st,2nd,3rd and 5th integer
            currentElemLine.node[0] = data[0];
            currentElemLine.node[1] = data[1];
            currentElemLine.node[2] = data[2];
            currentElemLine.node[3] = data[4];
        }
        if (type == StructureProperties::QUAD) {
            // node id are 6th, 7th, 8th and 9th integer
            currentElemLine.node[0] = data[6];
            currentElemLine.node[1] = data[7];
            currentElemLine.node[2] = data[8];
            currentElemLine.node[3] = data[9];
        }

        // elem id is the last data
        currentElemLine.id = data[13];

        // read the second line, no data there
        if (type == StructureProperties::TETRAHEDRON) {
            elems.eof = !(elems.stream.getline(l, 199));
            elems.lineNr++;
        }
    }
}

// -------------------- nodesToAtoms ------------------------
StructuralComponent* nodesToAtoms(StructuralComponent* atoms, unsigned int* atomIndex, InputFile& nodes) {
    // create a new map
    AtomIndexMap m;

    // create the corresponding SC
    StructuralComponent* nodeSC = new StructuralComponent(nullptr, nodes.fileName);

    cout << nodes.fileName << "=> atom #" << *atomIndex;
    // go to the first node line
    getNextNode(nodes);

    do {
        // currentNodeLine.id becomes atomIndex
        Atom* a = new Atom(nullptr, (*atomIndex), currentNodeLine.pos);
        atoms->addStructure(a);
        (*atomIndex)++;

        // add the atom to the SC
        nodeSC->addStructure(a);

        // add the local index
        m.insert(AtomIndexPair(currentNodeLine.id, a));

        // next one!
        getNextNode(nodes);
    }
    while (!nodes.eof);

    cout << " to atom #" << (*atomIndex) - 1 << endl;

    // add the map to the list
    maps.push_back(m);

    return nodeSC;
}


// -------------------- elemToSC ------------------------
StructuralComponent* elemToSC(StructuralComponent* elemSC, InputFile& elems) {
    StructuralComponent* infoSC = new StructuralComponent(nullptr, elems.fileName + " elements");
    Cell* c;

    // go to the first elem directive
    getNextElem(elems);

    // process f directives
    do {
        if (type == StructureProperties::TETRAHEDRON || currentElemLine.type == 2) {
            // create tetrahedron cell
            c = new Cell(nullptr, StructureProperties::TETRAHEDRON, currentElemLine.id);

            if (c != nullptr) {
                // add atoms
                for (unsigned int i = 0; i < 4; i++) {
                    // get the atom from the corresponding map
                    AtomIndexMapIterator mapIt;
                    mapIt = maps[elems.id].find(currentElemLine.node[i]);
                    Atom* a = (mapIt == maps[elems.id].end()) ? nullptr : mapIt->second;

                    if (a != nullptr) {
                        c->addStructure(a);
                    }
                    else {
                        cerr << "Error in " << elems.fileName << ", line " << elems.lineNr << ": cannot find atom #" << currentElemLine.node[i] << endl;
                    }
                }

                // add the cell to the informative and exclusive component
                elemSC->addStructure(c);
                infoSC->addStructure(c);
            }
        }
        else if (type == StructureProperties::QUAD) {
            // create quad cell
            c = new Cell(nullptr, StructureProperties::QUAD, currentElemLine.id);

            if (c != nullptr) {
                // add atoms
                for (unsigned int i = 0; i < 4; i++) {
                    // get the atom from the corresponding map
                    AtomIndexMapIterator mapIt;
                    mapIt = maps[elems.id].find(currentElemLine.node[i]);
                    Atom* a = (mapIt == maps[elems.id].end()) ? nullptr : mapIt->second;

                    if (a != nullptr) {
                        c->addStructure(a);
                    }
                    else {
                        cerr << "Error in " << elems.fileName << ", line " << elems.lineNr << ": cannot find atom #" << currentElemLine.node[i] << endl;
                    }
                }

                // add the cell to the informative and exclusive component
                elemSC->addStructure(c);
                infoSC->addStructure(c);
            }
        }
        else {
            cerr << "Warning in " << elems.fileName << ", line " << elems.lineNr << ": type is unknown (" << currentElemLine.type << ")" << endl;
        }

        // go to the next directive
        getNextElem(elems);
    }
    while (!elems.eof);

    cout << elems.fileName << " => " << infoSC->getNumberOfStructures() << " elements" << endl;
    return infoSC;
}

// -------------------- processArg ------------------------
void processArg() {

    if (argVal["-n"] == "none") {
        cerr << "Argument error: -n is mandatory" << usage << endl;
        exit(-1);
    }

    stringstream nodeNames(argVal["-n"]);
    char line[200];
    bool eof = false;
    while (!eof) {
        eof = !(nodeNames.getline(line, 199, ','));
        if (line[0] != '\0') {
            nodeFileNames.push_back(line);
        }
    }

    stringstream elemNames(argVal["-e"]);
    if (argVal["-e"] != "none") {
        eof = false;
        while (!eof) {
            eof = !(elemNames.getline(line, 199, ','));
            if (line[0] != '\0') {
                elemFileNames.push_back(line);
            }
        }
        if (elemFileNames.size() != nodeFileNames.size()) {
            cerr << "Warning: the number of element files (" << elemFileNames.size() << " and node files (" << nodeFileNames.size() << " are differents" << endl;
        }
    }

    outputPML = argVal["-o"];

    type = StructureProperties::toType(argVal["-t"]);
}

// -------------------- printArg ------------------------
void printArg() {
    cout << "ansys2pml generating " << outputPML << " from node files ";
    for (unsigned int i = 0; i < nodeFileNames.size(); i++) {
        cout << nodeFileNames[i];
        if (i != nodeFileNames.size() - 1) {
            cout << ",";
        }
    }
    cout << " and elem files ";
    for (unsigned int i = 0; i < elemFileNames.size(); i++) {
        cout << elemFileNames[i];
        if (i != elemFileNames.size() - 1) {
            cout << ",";
        }
    }
    cout << endl;
    cout << "Elements are of type " << StructureProperties::toString(type) << 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);

    processArg();
    printArg();

    try {

        // create a new physical model
        PhysicalModel* pm = new PhysicalModel();
        pm->setName("PML for " + argVal["-n"]);
        pm->setInformativeComponents(new MultiComponent(nullptr, "Informative Components"));

        // create the new atom SC
        StructuralComponent* atoms = new StructuralComponent(nullptr, "Atoms");
        // very important: start at 1 to have the same index system
        unsigned int atomIndex = 1;

        // open the node files
        for (unsigned int i = 0; i < nodeFileNames.size(); i++) {
            InputFile nodes(nodeFileNames[i], i);
            StructuralComponent* nodeSC = nodesToAtoms(atoms, &atomIndex, nodes);
            // add the nodeSC to the pml
            pm->getInformativeComponents()->addSubComponent(nodeSC);
        }
        // add the atom list to the pml
        pm->setAtoms(atoms);

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

        // open the elem files
        for (unsigned int i = 0; i < elemFileNames.size(); i++) {
            InputFile elems(elemFileNames[i], i);
            StructuralComponent* yetAnotherSC = elemToSC(elemSC, elems);
            // add the yetAnotherSC to the pml
            pm->getInformativeComponents()->addSubComponent(yetAnotherSC);
        }
        // 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;
    }
}
