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

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>

// -------------------- isIn ------------------------
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;
}

// -------------------- 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] = {
    { "-simul", "" },
    { "-real", "" },
    { "-init", ""},
    { "-select", ""},
};

const char* usage = "usage:\n"
                    "renError -simul=simulation.pml -real=real.pml -init=init.pml [-select=\"name\"]\n"
                    "(Note no space around '=')\n"
                    "Mandatory options: \n"
                    "-simul    pml document of the simulated results\n"
                    "-real     pml document of the real results\n"
                    "-init     pml document of the initial positions\n"
                    "Optional option: \n"
                    "-select   name of the structural component or cell to export as csv\n"
                    "          (default = all atoms)\n"
                    "\nCompute the Relative Energy Norm error (average, min, max, std-dev)"
                    ;

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

// arguments
string simul;
string real;
string initial;
string selected;

// -------------------- processArg ------------------------
void processArg() {
    simul = argVal["-simul"];
    real = argVal["-real"];
    initial = argVal["-init"];
    selected = argVal["-select"];

    if (simul == "") {
        cerr << "Argument error: -simul argument is mandatory" << endl << usage << endl;
        exit(-1);
    }

    if (real == "") {
        cerr << "Argument error: -real argument is mandatory" << endl << usage << endl;
        exit(-1);
    }

    if (initial == "") {
        cerr << "Argument error: -init argument is mandatory" << endl << usage << endl;
        exit(-1);
    }

}

// -------------------- printArg ------------------------
void printArg() {
    cout << "renError is computing the Relative Energy Norm Error for ";

    if (selected != "") {
        cout << "component/cell " << selected;
    }
    else {
        cout << "all atoms";
    }
    cout << " in " <<  simul << " over " << real << " (using initial positions in " << initial << ")" << 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 {
        // read the pmls
        cout << "-> please wait while reading " << simul << endl;
        PhysicalModel* simulPM = new PhysicalModel(simul.c_str());

        StructuralComponent* simulA = nullptr;
        cout << "-> get ";
        if (selected == "") {
            cout << "all atoms" << endl;
            simulA = simulPM->getAtoms();
        }
        else {
            cout << selected << endl;
            Component* cpt = simulPM->getComponentByName(selected);
            if (cpt && (cpt->isInstanceOf("StructuralComponent") || cpt->isInstanceOf("Cell"))) {
                simulA = ((StructuralComponent*) cpt)->getAtoms();
            }
            else {
                // try harder: look for a cell
                Structure* s = simulPM->getStructureByName(selected);
                if (s && s->isInstanceOf("Cell")) {
                    simulA = ((Cell*) s)->getAtoms();
                }
                else {
                    throw PMLAbortException("No structural components/cells named " + selected);
                }
            }
        }

        cout << "-> please wait while reading " << real << endl;
        PhysicalModel* realPM = new PhysicalModel(real.c_str());
        cout << "-> please wait while reading " << initial << endl;
        PhysicalModel* initialPM = new PhysicalModel(initial.c_str());

        // for all selected atoms compute the error
        double min = numeric_limits<double>::max(), max = -1.0;
        double average = 0.0, stddev;
        double sumSquare = 0.0;
        double pos[3], posReal[3], pos0[3];
        double val;

        for (unsigned int i = 0; i < simulA->getNumberOfStructures(); i++) {
            Atom* a = (Atom*) simulA->getStructure(i);
            a->getPosition(pos);
            realPM->getAtom(a->getIndex())->getPosition(posReal);
            initialPM->getAtom(a->getIndex())->getPosition(pos0);

            double simulatedToReal = sqrt((pos[0] - posReal[0]) * (pos[0] - posReal[0])
                                          + (pos[1] - posReal[1]) * (pos[1] - posReal[1])
                                          + (pos[2] - posReal[2]) * (pos[2] - posReal[2]));
            double totalDisplacement = sqrt((pos0[0] - posReal[0]) * (pos0[0] - posReal[0])
                                            + (pos0[1] - posReal[1]) * (pos0[1] - posReal[1])
                                            + (pos0[2] - posReal[2]) * (pos0[2] - posReal[2]));

            if (totalDisplacement > 1e-10) {
                val = simulatedToReal / totalDisplacement;
            }
            else {
                val = simulatedToReal;    // error is 0% if simulatedToReal=0
            }

            if (val < min) {
                min = val;
            }

            if (val > max) {
                max = val;
            }

            sumSquare += (val * val);

            average += val;
        }

        average /= ((double) simulA->getNumberOfStructures());
        sumSquare /= ((double) simulA->getNumberOfStructures());
        stddev = sqrt(sumSquare - average * average);

        // print values
        cout << "Average: " << average << endl;
        cout << "Std-dev: " << stddev << endl;
        cout << "Max: " << max << endl;
        cout << "Min: " << min << endl;

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