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

// Monitor includes
#include "MonitoringManager.h"
#include "MonitorFactory.h"

#include <iostream>
#include <string>
#include <fstream>

// Tools includes
#include "Chrono.h"
#include "Tools.h"

// Simulator includes
#include "SimulatorFactory.h"

using namespace std;

// -------------------- constructor --------------------
MonitoringManager::MonitoringManager(const char* mml) {
    // TODO use the CamiTK Root Dir to determine where is the schema
    // in the meantime: do not validate!
    //xml_schema::properties props;
    //props.no_namespace_schema_location(MONITORIN_XSD);
    //mmlIn = mml::monitoringIn(mml,0,props);
    mmlIn = mml::monitoringIn(mml, xml_schema::flags::dont_validate);
    string mmlPath = mml;

    size_t slashPlace = mmlPath.find_last_of("/");

    if (slashPlace) {
        this->mmlFileFolder = mmlPath.substr(0, slashPlace + 1);
    }
    else {
        this->mmlFileFolder = "";
    }

    //TODO working for relative paths but for absolute ones?
    this->mmlFileName = mml;

    this->dt = timeParameter2double(mmlIn->dt());
    this->refresh = timeParameter2double(mmlIn->refresh());
    this->step = 1;
    this->currentTime = 0;
    this->computingTime = 0;
    this->stepComputingTime = 0;
    this->simulatorName = mmlIn->simulator();

    this->lml = nullptr;

    if (isLmlPresent()) {
        this->lmlFileName = mmlFileFolder + mmlIn->lmlFile().get();
    }

    buildLoads();

    this->pml = nullptr;
    this->initPml = nullptr;
    this->oldPositionPointer = nullptr;
    this->simul = nullptr;

    // building physical model and simulators
    // depend if pml file or simulator file is present
    if (isPmlPresent()) {
        this->pmlFileName = mmlFileFolder + mmlIn->pmlFile().get();
    }

    /*  else {
      #include <QFileInfo>

        // check if the file exists
        QFileInfo fi=QFileInfo(mml);
        QString guessPMLFilename = QFileInfo(fi).absolutePath() + "/" + QFileInfo(fi).baseName() + ".pml";
        if (QFileInfo(guessPMLFilename).exists()) {
            this->pmlFileName=guessPMLFilename.toStdString();
        }

      }
    */
    buildPmlAndSimulator();

    buildMonitors();
}

// -------------------- destructor --------------------

MonitoringManager::~MonitoringManager() {
    while (!monitors.empty()) {
        delete monitors.back();
        monitors.pop_back();
    }

    while (!times.empty()) {
        delete times.back();
        times.pop_back();
    }

    if (lml && ownLml) {
        delete lml;
    }

    if (initPml) {
        delete initPml;
    }

    if (pml && ownPml) {
        delete pml;
    }
}


// -------------------- writeOutput --------------------
void MonitoringManager::writeOutput(const char* filename) {
    mml::MonitoringOut mml(getPmlFileName());

    mml::MonitoringOut::time_sequence& ts(mml.time());

    for (unsigned int i = 0; i < times.size(); i++) {
        ts.push_back(*(times[i]));
    }

    xml_schema::namespace_infomap map;
    map[""].name = "";
    map[""].schema = "";

    ofstream ofs(filename);
    mml::monitoringOut(ofs, mml, map);

    // TODO a specific monitor called "PML" should be created instead...
    string finalPMLFileName;
    finalPMLFileName = getPmlFileName();
    string::size_type pLast = finalPMLFileName.rfind(".");

    if (pLast != string::npos) {
        finalPMLFileName.erase(pLast);
        finalPMLFileName += "-final.pml";
    }

    ofstream finalPML(finalPMLFileName.c_str());
    pml->xmlPrint(finalPML);
}

// -------------------- writeCsv --------------------
void MonitoringManager::writeCsv(const char* fileName) {
    ofstream out(fileName, ios::out | ios::trunc);

    if (out) {

        for (unsigned int i = 0; i < monitors.size(); i++) {
            unsigned int index = monitors[i]->getIndex();
            out << "monitor: " << monitors[i]->getTypeName() << " index: " << monitors[i]->getIndex() << endl;

            for (unsigned int j = 0; j < times.size(); j++) {
                out << times[j]->value() << " / ";
                mml::TimeStep::monitor_sequence& monit(times[j]->monitor());

                for (mml::TimeStep::monitor_iterator it(monit.begin()); it != monit.end(); ++it) {
                    if ((*it).index() == index) {
                        out << (*it).data() << std::endl;
                    }
                }
            }

            out << std::endl;
        }

        out.close();
    }
    else {
        cerr << "file opening error" << endl;
    }

}

// -------------------- saveMonitors --------------------
void MonitoringManager::saveMonitors() {
    for (unsigned int i = 0; i < monitors.size(); i++) {
        monitors[i]->calculate();
    }

    mml::TimeStep* t = new mml::TimeStep(currentTime, "s");

    if (mmlIn->stoppingCriteria().present()) {
        t->stoppingCriteria(mmlIn->stoppingCriteria());
    }

    mml::TimeStep::monitor_sequence& ms(t->monitor());

    if (mmlIn->monitors().present()) {
        mml::Monitors::monitor_sequence& monit(mmlIn->monitors().get().monitor());

        for (mml::Monitors::monitor_iterator i(monit.begin()); i != monit.end(); ++i) {
            if ((timeParameter2double(i->startAt()) <= currentTime) && (timeParameter2double(i->stopAt()) >= currentTime)) {
                ms.push_back(*i);
            }
        }
    }

    times.push_back(t);
}

// -------------------- getDt --------------------
double MonitoringManager::getDt() {
    return dt;
}

// -------------------- getRefresh --------------------
double MonitoringManager::getRefresh() {
    return refresh;
}

// -------------------- getCurrentTime --------------------
double MonitoringManager::getCurrentTime() {
    return currentTime;
}

// -------------------- getCurrentStep --------------------
double MonitoringManager::getCurrentStep() {
    return step;
}

// -------------------- getStepComputingTime --------------------
double MonitoringManager::getStepComputingTime() {
    return stepComputingTime;
}


// -------------------- getComputingTime --------------------
double MonitoringManager::getComputingTime() {
    return computingTime;
}

// -------------------- getInitPml --------------------
PhysicalModel* MonitoringManager::getInitPml() {
    return initPml;
}

// -------------------- getPml --------------------
PhysicalModel* MonitoringManager::getPml() {
    return pml;
}

// -------------------- takePml --------------------
PhysicalModel* MonitoringManager::takePml() {
    ownPml = false;
    return getPml();
}

// -------------------- getLml --------------------
Loads* MonitoringManager::getLml() {
    return lml;
}

// -------------------- takeLml --------------------
Loads* MonitoringManager::takeLml() {
    ownLml = false;
    return getLml();
}

// -------------------- getPmlFileName --------------------
std::string MonitoringManager::getPmlFileName() {
    return pmlFileName;
}

// -------------------- getLmlFileName --------------------
string MonitoringManager::getLmlFileName() {
    return lmlFileName;
}

// -------------------- getMmlFileName --------------------
string MonitoringManager::getMmlFileName() {
    return mmlFileName;
}

// -------------------- getmmlFileFolder --------------------
string MonitoringManager::getmmlFileFolder() {
    return mmlFileFolder;
}


// -------------------- getSimulator --------------------
Simulator* MonitoringManager::getSimulator() {
    return simul;
}



// -------------------- setCurrentTime --------------------
void MonitoringManager::setCurrentTime(double time) {
    this->currentTime = time;
}

// -------------------- updateCurrentTime --------------------

void MonitoringManager::updateCurrentTime() {
    this->currentTime += dt;
}

// -------------------- updateComputingTime --------------------

void MonitoringManager::updateComputingTime() {
    this->computingTime += stepComputingTime;
}

// -------------------- incStep --------------------
void MonitoringManager::incStep() {
    step++;
}

// -------------------- getOldPosition --------------------
void MonitoringManager::getOldPosition(double pos[3], unsigned int index) {
    // we use the current pml to find the position of the pointer for the old positionPtr

    // look for atom  #index in the atoms
    bool found = false;
    unsigned int i = 0;

    while (i < pml->getAtoms()->getNumberOfStructures() && !found) {
        Atom* a = dynamic_cast<Atom*>(pml->getAtoms()->getStructure(i));
        found = (a->getIndex() == index);
        i++;
    }

    if (found) {
        // the memory allocated to atom #index is (i-1) * 3 (because the position are stored consequently, using a double for x, y and z
        double* positionPtr = oldPositionPointer + (i - 1) * 3;
        pos[0] = positionPtr[0];
        pos[1] = positionPtr[1];
        pos[2] = positionPtr[2];
    }
    else {
        pos = nullptr;
    }
}

// -------------------- storeOldPositions --------------------
void MonitoringManager::storeOldPositions() {
    memcpy(oldPositionPointer, pml->getPositionPointer(), 3 * this->pml->getAtoms()->getNumberOfStructures()*sizeof(double));
}

// -------------------- addMonitor --------------------
void MonitoringManager::addMonitor(Monitor* monitor) {
    monitors.push_back(monitor);
}

// -------------------- deleteMonitor --------------------
void MonitoringManager::deleteMonitor(const unsigned int i) {
    std::vector <Monitor*>::iterator it;
    it = monitors.begin() + i;
    monitors.erase(it);
}

// -------------------- getMonitor --------------------
Monitor* MonitoringManager::getMonitor(const unsigned int i) const {
    if (i < monitors.size()) {
        return monitors[i];
    }
    else {
        return nullptr;
    }
}

// -------------------- numberOfMonitor --------------------
unsigned int MonitoringManager::numberOfMonitor() const {
    return monitors.size();
}

// -------------------- ChangeDt --------------------
void MonitoringManager::setDt(double dt) {
    this->dt = dt;
    mmlIn->dt().value(dt);
}

// -------------------- ChangeRefresh --------------------
void MonitoringManager::setRefresh(double refresh) {
    this->refresh = refresh;
    mmlIn->refresh().value(refresh);
}

// -------------------- ChangeLml --------------------
void MonitoringManager::setLmlFileName(const char* file) {
    this->lmlFileName = file;
    mmlIn->lmlFile(file);
}

// -------------------- ChangePml --------------------
void MonitoringManager::setPmlFileName(const char* file) {
    this->pmlFileName = file;
    mmlIn->pmlFile(file);
}

// -------------------- saveMmlIn --------------------
void MonitoringManager::saveMmlIn(const char* fileName) {
    //-- save mml
    xml_schema::namespace_infomap map;
    map[""].name = "";
    map[""].schema = ""; //TODO use correct path of XML Schema (what about an URL?), this would be great: one could validate the file before instantiating/unmarshalling

    ofstream ofs(fileName);
    mml::monitoringIn(ofs, *mmlIn, map);
}

// -------------------- simulate --------------------
void MonitoringManager::simulate() {
    init();
    doMove();

    while (!checkStop()) {
        std::cout << currentTime << std::endl;
        doMove();
    }

    saveMonitors();
    writeOutput("Output.mml");
    cout << "computingTime: " << computingTime << endl;
    cout << "avr step: " << computingTime / step << endl;
    cout << "Ended at time t=" << currentTime << endl;
}

// -------------------- rewind--------------------
void MonitoringManager::rewind() {
    step = 1;
    currentTime = 0;
    computingTime = 0;
    stepComputingTime = 0;

    while (!times.empty()) {
        delete times.back();
        times.pop_back();
    }

    //re-initialise manager
    init();
}


// -------------------- reload--------------------
void MonitoringManager::reload(bool delPml) {
    step = 1;
    currentTime = 0;
    computingTime = 0;
    stepComputingTime = 0;

    while (!times.empty()) {
        delete times.back();
        times.pop_back();
    }

    buildLoads();
    buildPmlAndSimulator(delPml);

    //re-initialise manager
    init();
}

// -------------------- isPmlPresent --------------------
bool MonitoringManager::isPmlPresent() {
    return mmlIn->pmlFile().present();
}

// -------------------- isLmlPresent --------------------
bool MonitoringManager::isLmlPresent() {
    return mmlIn->lmlFile().present();
}

// -------------------- buildLoads --------------------
void MonitoringManager::buildLoads() {
    if (lml && ownLml) {
        delete lml;
        lml = nullptr;
    }

    // building loads if present
    if (isLmlPresent()) {
        this->lml = new Loads(lmlFileName.c_str());
        ownLml = true;
    }
    else {
        this->lmlFileName = "";
        this->lml = nullptr;
        ownLml = false;
    }
}

// -------------------- buildMonitors --------------------
void MonitoringManager::buildMonitors() {
    // creating monitors
    if (mmlIn->monitors().present()) {
        mml::Monitors::monitor_sequence& monit(mmlIn->monitors().get().monitor());

        for (mml::Monitors::monitor_iterator i(monit.begin()); i != monit.end(); ++i) {
            monitors.push_back(MonitorFactory::createMonitor(&(*i), this));
        }
    }
}

// -------------------- buildPmlAndSimulator --------------------
void MonitoringManager::buildPmlAndSimulator(bool delPml) {
    if (pml && delPml && ownPml) {
        delete pml;
        pml = nullptr;
    }

    if (initPml) {
        delete initPml;
        initPml = nullptr;
    }

    if (oldPositionPointer) {
        delete oldPositionPointer;
        oldPositionPointer = nullptr;
    }

    if (simul) {
        delete simul;
        simul = nullptr;
    }

    if (isPmlPresent()) {
        buildPmlAndSimulatorfromPml();
    }
    else {
        buildPmlAndSimulatorfromSimulator();
    }
}

// -------------------- buildPmlAndSimulatorfromPml --------------------
void MonitoringManager::buildPmlAndSimulatorfromPml() {
    this->pml = new PhysicalModel(pmlFileName.c_str());
    ownPml = true;
    this->initPml = new PhysicalModel(pmlFileName.c_str());
    this->oldPositionPointer = new double[3 * this->pml->getAtoms()->getNumberOfStructures()];
    this->simul = SimulatorFactory::getInstance()->createSimulator(simulatorName.c_str(), this);
}

// -------------------- buildPmlAndSimulatorfromSimulator --------------------
void MonitoringManager::buildPmlAndSimulatorfromSimulator() {

    std::string simulatorFile = "";
    // relative or absolute path
    if (mmlIn->simulatorFile().get()[0] != '/') {
        simulatorFile = mmlFileFolder;
    }
    simulatorFile += mmlIn->simulatorFile().get();

    // change extension
    std::string::size_type dotPos = simulatorFile.rfind('.');

    if (dotPos != std::string::npos) {
        this->pmlFileName = simulatorFile.substr(0, dotPos) + ".pml";
    }
    else {
        this->pmlFileName = simulatorFile + ".pml";
    }

    // create simulator and pml file
    this->simul = SimulatorFactory::getInstance()->createSimulator(simulatorName.c_str(), this, simulatorFile.c_str());
    simul->createPml(simulatorFile.c_str(), pmlFileName.c_str());
    this->pml = new PhysicalModel(pmlFileName.c_str());
    ownPml = true;
    this->initPml = new PhysicalModel(pmlFileName.c_str());
    this->oldPositionPointer = new double[3 * this->pml->getAtoms()->getNumberOfStructures()];
}

