/*****************************************************************************
 * $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$
 ****************************************************************************/
// Reference includes
#include "Reference.h"

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

// Monitor includes
#include "MonitorFactory.h"

// MML includes
#include <MonitoringModel.hxx>

// PML includes
#include <pml/CellProperties.h>

#include <cstring>


// -------------------- constructor --------------------
Reference::Reference(mml::Reference reference, MonitoringManager* monitoringManager) :
    target{""} {

    if (reference.target().present()) {
        target = reference.target().get();
    }

    this->monitoringManager = monitoringManager;
    this->mmlOutFile = monitoringManager->getmmlFileFolder() + reference.document();
    xml_schema::properties props;
    //props.no_namespace_schema_location(MONITORIN_XSD);//TODO define montorout
    mmlOut = mml::monitoringOut(mmlOutFile.c_str(), xml_schema::flags::dont_validate);
    mml::MonitoringOut::time_sequence& ts(mmlOut->time());

    for (mml::MonitoringOut::time_iterator i(ts.begin()); i != ts.end(); ++i) {
        timeStep* step = new timeStep;
        step->time = (*i).value(); //TODO time paramete 2double
        mml::TimeStep::monitor_sequence& monit((*i).monitor());

        for (mml::TimeStep::monitor_iterator it(monit.begin()); it != monit.end(); ++it) {
            Monitor* m = MonitorFactory::createMonitor(&(*it), monitoringManager);
#if (_MSC_VER == 1700) // Visual Studio 2012 only => function signature has changed, since VS2012
            step->monitorsMap.insert(std::pair(m->getTypeName(), m));
#else
            step->monitorsMap.insert(std::pair<std::string, Monitor*>(m->getTypeName(), m));
#endif
        }

        data.push_back(step);
    }

    // change final step time to infinity
    data.back()->time = std::numeric_limits< double >::infinity();

    std::string refpath = mmlOutFile;
    std::string folder;

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

    if (slashPlace) {
        folder = refpath.substr(0, slashPlace + 1);
    }
    else {
        folder = "";
    }

    pml = new PhysicalModel((folder + mmlOut->pmlFile()).c_str());
    CurrentIndex = 0;

}

// -------------------- destructor --------------------
Reference::~Reference() {
    for (unsigned int i = 0; i < data.size(); i++) {
        timeStep* ts = data[i];

        for (std::multimap<std::string, Monitor*>::iterator it = ts->monitorsMap.begin() ; it != ts->monitorsMap.end(); ++it) {
            delete it->second;
        }

        ts->monitorsMap.clear();
        delete ts;
    }

    data.clear();

    delete pml;
}

// -------------------- getNearest --------------------
bool Reference::getNearest(double pos[3], double time, double ref[3]) {
    double temp[3];
    double min = 0;
    double realTime;
    int i = 0;
    bool success = false;
    AtomIterator it = AtomIterator(pml, target);

    for (it.begin(); !it.end(); it.next()) {
        if (getMonitoredData("Position", time, it.currentAtom()->getIndex(), realTime, temp)) {
            success = true;
            double d = distance(pos, temp);

            if (i == 0 || d < min) {
                min = d;
                ref[0] = temp[0];
                ref[1] = temp[1];
                ref[2] = temp[2];
                i++;
            }
        }
    }

    return success;
}

// -------------------- getNearest --------------------
bool Reference::getNearest(double pos[3], double ref[3]) {
    double temp[3];
    double min = 0;
    int i = 0;
    AtomIterator it = AtomIterator(pml, target);

    for (it.begin(); !it.end(); it.next()) {
        it.currentAtom()->getPosition(temp);
        double d = distance(pos, temp);

        if (i == 0 || d < min) {
            min = d;
            ref[0] = temp[0];
            ref[1] = temp[1];
            ref[2] = temp[2];
            i++;
        }
    }

    return i > 0;
}


// -------------------- getMonitoredData --------------------
bool Reference::getMonitoredData(std::string type, double ref[]) {
// TODO
    return false;
}

// return a struct with time and ref...?
// -------------------- getMonitoredData --------------------
bool Reference::getMonitoredData(std::string type, double time, double& realTime, double ref[]) {
    unsigned int i = CurrentIndex;
    bool timeFound = false;

    while (i < data.size() &&  !timeFound) {
        if (data[i]->time >= time) {
            timeFound = true;
        }
        else {
            i++;
        }
    }

    CurrentIndex = i;
    // we have found a time (which is infinity if final step)
    realTime = data[i]->time;
    // we now search the rigth monitor
    std::multimap<std::string, Monitor*>::iterator iter = data[i]->monitorsMap.find(type);

    if (iter == data[i]->monitorsMap.end()) {
        // no monitors found
        std::cerr << "no monitor " << type << " found (time: " << time << " )"  << std::endl;
        return false;
    }
    else {
        (*iter).second->getValueType();

        switch ((*iter).second->getValueType()) {
            case Monitor::SCALAR:
                ref[0] = (*iter).second->getValue(0);
                return true;
                break;
            default:
                std::cerr << "monitor " << type <<  " not compatible with getMonitoredData without index parameter" << std::endl;
                return false;
        }
    }

    return false;
}

// -------------------- getMonitoredData --------------------
bool Reference::getMonitoredData(std::string type, double time, int index, double& realTime, double ref[]) {
    unsigned int i = CurrentIndex;
    bool timeFound = false;

    while (i < data.size() && !timeFound) {
        if (data[i]->time >= time) {
            timeFound = true;
        }
        else {
            i++;
        }
    }

    CurrentIndex = i;
    // we have found a time (which is infinity if final step)
    realTime = data[i]->time;
    // we now search the rigth monitor
    std::multimap<std::string, Monitor*>::iterator iter = data[i]->monitorsMap.find(type);
    std::multimap<std::string, Monitor*>::iterator lastElem;

    if (iter == data[i]->monitorsMap.end()) {
        // no monitors found
        std::cerr << "no monitor " << type << " found (time: " << time << " )"  << std::endl;
        return false;
    }
    else {
        lastElem = data[i]->monitorsMap.upper_bound(type); //last elem of "type" key in map
        bool indFound = false;

        while (iter != lastElem && !indFound) { // ieration element of "type" key in map, stop when an index is found
            // looking for rigth index in this monitor
            indFound = (iter->second->getValuesOfIndex(index, ref));
            ++iter;
        }

        if (indFound) {
            return true;
        }
        else {
            std::cerr << "no data for index " << index << " in monitor " << type << " (time: " << time << ")" << std::endl;
            return false;
        }
    }

}

// -------------------- getDistanceToTriangularMesh --------------------
bool Reference::getDistanceToTriangularMesh(double pos[3], double& dist) {
    //look for nearest atom
    Atom* a = nullptr;
    double temp[3];
    dist = -1;
    AtomIterator it = AtomIterator(pml, target);

    for (it.begin(); !it.end(); it.next()) {
        it.currentAtom()->getPosition(temp);
        double d = distance(pos, temp);

        if (d < dist || dist == -1) {
            dist = d;
            a = it.currentAtom();
        }
    }

    if (a) {
        //heuristic. look cell containing the nearest atom and compute distance to triangle
        std::vector<StructuralComponent*> comps = a->getAllStructuralComponents();

        for (unsigned int i = 0; i < comps.size(); i++) {
            StructuralComponent* str = comps[i];
            Cell* c = dynamic_cast<Cell*>(str);

            if (c) {
                if (c->getProperties()->getType() == StructureProperties::TRIANGLE) {
                    double v1[3];
                    double v2[3];
                    double v3[3];
                    ((Atom*)(c->getStructure(0)))->getPosition(v1);
                    ((Atom*)(c->getStructure(1)))->getPosition(v2);
                    ((Atom*)(c->getStructure(2)))->getPosition(v3);
                    double d = distanceToTrianglePlane(pos, v1, v2, v3);

                    if (d < dist) {
                        dist = d;
                    }
                }
            }
        }
    }

    return (dist != -1);

}


// -------------------- toString --------------------
std::string Reference::toString() {
    return mmlOutFile;
}
