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

#include <pml/PhysicalModel.h>
#include <pml/StructuralComponent.h>
#include <lml/Loads.h>
#include <lml/Translation.h>

string srcFile;
string destFile;
string output;

// -------------------- 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] = {
    { "-src", "none" },
    { "-dest", "none" },
    { "-o", "pmldiff-output.lml" },
};

const char* usage = "usage:\n"
                    "pmldiff -src=f1.pml -dest=f2.pml [-o=diff.lml]\n"
                    "Create a LML document which contains the translations needed to transform f1.pml to f2.pml\n"
                    "(Note no space around '=')\n"
                    "Where mandatory flags are: \n"
                    "src (source pml document), dest (destination pml document)\n"
                    "The optional o flag allows one to specify the resulting LML file name\n";

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

// -------------------- processArg ------------------------
void processArg() {
    srcFile = argVal["-src"];
    destFile = argVal["-dest"];
    output = argVal["-o"];
    if (srcFile == "none" || destFile == "none") {
        cerr << "Argument error: -src and -dest flags are mandatory" << usage << endl;
        exit(-1);
    }
}

// -------------------- printArg ------------------------
void printArg() {
    cout << "pml2diff generating " << output << ", tranformation of " << srcFile << " to " << destFile << 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 {
        PhysicalModel* src = new PhysicalModel(srcFile.c_str());
        PhysicalModel* dest = new PhysicalModel(destFile.c_str());

        //-- create the loads
        Loads* transform = new Loads();

        //-- compute the differences
        StructuralComponent* srcA = src->getAtoms();
        Atom* a1, *a2;
        double srcPos[3];
        double destPos[3];
        double translation[3];
        double value;

        for (unsigned int i = 0; i < srcA->getNumberOfStructures(); i++) {
            // get the src position
            a1 = (Atom*) srcA->getStructure(i);
            a1->getPosition(srcPos);
            // get the dest position
            if ((a2 = dest->getAtom(a1->getIndex())) == nullptr) {
                cerr << "Error cannot find atom #" << a1->getIndex() << " in " << destFile << endl;
                exit(1);
            }
            a2->getPosition(destPos);
            // compute the translation
            Translation* t = new Translation();
            value = 0.0;
            for (unsigned int j = 0; j < 3; j++) {
                translation[j] = destPos[j] - srcPos[j];
                value += translation[j] * translation[j];
            }
            value = sqrt(value);
            // if atoms are not at the same place
            if (value > 1e-12) {
                // normalize translation
                for (unsigned int j = 0; j < 3; j++) {
                    translation[j] /= value;
                }

                t->setDirection(translation[0], translation[1], translation[2]);
                t->addTarget(a1->getIndex());
                t->addValueEvent(0.0, 0.0);
                t->addValueEvent(value, 1.0);

                transform->addLoad(t);
            }
        }

        // write the transformation in a file
        std::ofstream loadStream;
        loadStream.open(output.c_str());
        if (!loadStream.is_open()) {
            cerr << "Error: cannot open file " << output << endl;
            exit(1);
        }
        transform->xmlPrint(loadStream);
        loadStream << "<!-- this file (" << output << ") was generated by pml2diff and is the tranformation from " << srcFile << " to " << destFile << " -->" << endl;
        loadStream.close();


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