/*****************************************************************************
 * $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>
#include <math.h>
#include <limits.h>


bool isIn(StructuralComponent* sc, Atom* a) {
    unsigned int i = 0;
    bool found = false;
    while (i < sc->getNumberOfStructures() && !found) {
        found = (sc->getStructure(i) == a);
        i++;
    }
    return found;
}

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

    if (argc != 3 && argc != 4) {
        cout << "Usage:" << endl;
        cout << "\t" << argv[0] << " file.pml \"c1\" \"c2\"" << endl;
        cout << "\t\tOnly select cells of component c1 of file.pml where all atoms are also in component c2, output to: file-extracted.pml (c2 is either component of file.pml or a separated csv file)" << endl;
        cout << "\t" << argv[0] << " file.pml \"c\"" << endl;
        cout << "\t\tSelect component c of file.pml, output to: component.pml (pseudo pml)" << endl;
        cout << "PML " << PhysicalModel::VERSION << endl;
        exit(-1);
    }

    try {
        // read the pml
        string filename(argv[1]);
        cout << "-> please wait while reading " << filename << " for extraction of ";
        string c1(argv[2]);
        string c2;
        if (argc == 3) {
            cout << "component " << c1 << endl;
        }
        else if (argc == 4) {
            c2 = argv[3];
            cout << "all cells in " << c1 << " having all atoms in " << c2 << endl;
        }

        PhysicalModel* pm = new PhysicalModel(filename.c_str());

        cout << "-> get " << c1 << endl;

        // get the component
        Component* cpt1 = pm->getComponentByName(c1);
        if (!cpt1) {
            throw PMLAbortException("No component named " + c1);
        }

        if (argc == 3) {
            // save cpt
            string cptOutName(c1);
            cptOutName += ".pml";
            cout << "-> saving " << cptOutName << endl;

            ofstream cptOut(cptOutName.c_str());
            cpt1->xmlPrint(cptOut);
            cptOut.close();
        }
        else if (argc == 4) {
            cout << "-> get " << c2 << endl;

            // atom list of c2
            StructuralComponent* atom2;
            atom2 = new StructuralComponent(nullptr);

            // get the cell and build atom2
            Component* cpt2 = pm->getComponentByName(c2);
            if (!cpt2) {
                // try to open a file name c2 as csv
                cout << "-> cannot find component " << c2 << " trying to open file " << c2 << endl;

                // open the file
                ifstream atomList(c2.c_str());

                while (!atomList.eof()) {
                    char l[200000];
                    // read the next line
                    if (atomList.getline(l, 199999)) {
                        // analyze
                        std::stringstream line(l);
                        unsigned int id;
                        while ((line >> id)) {
                            Atom* a = pm->getAtom(id);
                            if (a) {
                                atom2->addStructureIfNotIn(a);
                            }
                        }
                    }
                }
                if (atom2->getNumberOfStructures() > 0) {
                    cout << "-> found " << atom2->getNumberOfStructures() << " atoms in file " << c2 << endl;
                }
                else {
                    throw PMLAbortException("Cannot find any atoms in file " + c2);
                }

            }
            else  {
                // cpt2 is found, just get all atoms in it
                for (unsigned int i = 0; i < cpt2->getNumberOfCells(); i++) {
                    Cell* c = cpt2->getCell(i);
                    for (unsigned int j = 0; j < c->getNumberOfStructures(); j++) {
                        atom2->addStructureIfNotIn(c->getStructure(j));
                    }
                }
                cout << "-> found " << atom2->getNumberOfStructures() << " atoms in component " << c2 << endl;
            }

            // create the new component
            StructuralComponent* extracted;
            extracted = new StructuralComponent(pm, c1 + "/" + c2 + " extracted");

            // create atom list of atoms in cell
            cout << "-> please wait while extracting from " << c1 << " (" << cpt1->getNumberOfCells() << " cells)..." << endl;

            unsigned int ok = 0;
            // check each cell and all atoms in the cell
            for (unsigned int i = 0; i < cpt1->getNumberOfCells(); i++) {
                Cell* c = cpt1->getCell(i);
                unsigned int j = 0;
                bool isOk = true;
                while (j < c->getNumberOfStructures() && isOk) {
                    isOk = isOk && isIn(atom2, (Atom*)c->getStructure(j));
                    j++;
                }
                if (isOk) {
                    ok++;
                    // copy the cell
                    Cell* newCell = new Cell(pm, c->getType());
                    for (unsigned int j = 0; j < c->getNumberOfStructures(); j++) {
                        newCell->addStructureIfNotIn(c->getStructure(j));
                    }
                    extracted->addStructureIfNotIn(newCell);
                    //cout << '\r' << c->Structure::getName() << endl;
                }
                cout << '\r' << (i + 1) << "/" << ok;
            }
            cout << endl << "-> found " << ok << " matching cells, " << cpt1->getNumberOfCells() - ok  << " excluded" << endl;

            // add the new SC to the informative component
            pm->getInformativeComponents()->addSubComponent(extracted);

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

            cout << "-> please wait while saving " << filename << " " << endl;

            ofstream outputFile(filename.c_str());
            pm->setName(pm->getName() + " " + c1 + "/" + c2 + " Extracted");
            // 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;
    }
}
