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

#include "ValueEvent.h"
#include "Translation.h"
#include "ForceUnit.h"
#include "RotationUnit.h"
#include "PressureUnit.h"
#include "AccelerationUnit.h"
#include "LoadsVersion.h"
#include "Direction.h"

// lmlschema stuffs
#include <Loads.hxx>
#include <Load.hxx>
#include <Force.hxx>
#include <Translation.hxx>
#include <Rotation.hxx>
#include <Pressure.hxx>
#include <ValueEvent.hxx>
#include <Acceleration.hxx>
#include <iostream>

using namespace std;
// ------------------ constructor ------------------
Loads::Loads(std::string fileName) {
    xmlRead(fileName);
}

// ------------------ destructor ------------------
Loads::~Loads() {
    // std::vector method clear() does not delete each elements individually
    // this has to be done manually...

    // ... so here it is...

    // clear all elements from the array
    for (std::vector<Load*>::iterator it = loads.begin(); it != loads.end(); ++it) {
        delete *it;    // free the element from memory
    }
    // finally, clear all elements from the array
    loads.clear();
}

// ------------------ xmlRead ------------------
void Loads::xmlRead(string filename) {
    // Use XSD with the lmlschema library to read the loads described in the xml file.
    unique_ptr<load::Loads> xmlloads = load::loads(filename, xml_schema::flags::dont_validate);


    // Construct the Loads object by iterating over the lmlschema library objects
    load::Loads::load_sequence ls = xmlloads->load();
    for (load::Loads::load_iterator li = ls.begin(); li != ls.end(); ++li) {
        Load* l = nullptr;
        load::Load* xmlload = & (*li);

        // get the load type and unit
        if (load::Translation* xml_t = dynamic_cast<load::Translation*>(xmlload)) {
            l = Load::LoadFactory("Translation");
            string unit = xml_t->unit().data();
            if (unit.compare("m") == 0) {
                l->setUnit(TranslationUnit::M());
            }
            else if (unit.compare("mm") == 0) {
                l->setUnit(TranslationUnit::MM());
            }
            else if (unit.compare("microm") == 0) {
                l->setUnit(TranslationUnit::MICRO_M());
            }
            else if (unit.compare("nm") == 0) {
                l->setUnit(TranslationUnit::NM());
            }

        }
        else if (load::Force* xml_f = dynamic_cast<load::Force*>(xmlload)) {
            l = Load::LoadFactory("Force");
            string unit = xml_f->unit().data();
            if (unit.compare("N") == 0) {
                l->setUnit(ForceUnit::N());
            }
            else if (unit.compare("kN") == 0) {
                l->setUnit(ForceUnit::KN());
            }
            else if (unit.compare("pN") == 0) {
                l->setUnit(ForceUnit::PN());
            }

        }
        else if (load::Rotation* xml_r = dynamic_cast<load::Rotation*>(xmlload)) {
            l = Load::LoadFactory("Rotation");
            string unit = xml_r->unit().data();
            if (unit.compare("radians") == 0) {
                l->setUnit(RotationUnit::RAD());
            }
            else if (unit.compare("degrees") == 0) {
                l->setUnit(RotationUnit::DEG());
            }

        }
        else if (load::Pressure* xml_p = dynamic_cast<load::Pressure*>(xmlload)) {
            l = Load::LoadFactory("Pressure");
            string unit = xml_p->unit().data();
            if (unit.compare("Pa") == 0) {
                l->setUnit(PressureUnit::PA());
            }
            if (unit.compare("kPa") == 0) {
                l->setUnit(PressureUnit::KPA());
            }
            if (unit.compare("mmHg") == 0) {
                l->setUnit(PressureUnit::MMHG());
            }

        }
        else if (load::Acceleration* xml_a = dynamic_cast<load::Acceleration*>(xmlload)) {
            l = Load::LoadFactory("Acceleration");
            string unit = xml_a->unit().data();
            if (unit.compare("m/s^2") == 0) {
                l->setUnit(AccelerationUnit::MSm2());
            }
            if (unit.compare("cm/s^2") == 0) {
                l->setUnit(AccelerationUnit::CMSm2());
            }
            if (unit.compare("mm/s^2") == 0) {
                l->setUnit(AccelerationUnit::MMSm2());
            }

        }

        if (l) {
            // direction
            Direction direction = Direction();
            if (xmlload->direction().toward().present()) {
                direction.setToward(xmlload->direction().toward().get());
            }
            if (xmlload->direction().x().present()) {
                direction.setX(atof(xmlload->direction().x().get().c_str()));
            }
            if (xmlload->direction().y().present()) {
                direction.setY(atof(xmlload->direction().y().get().c_str()));
            }
            if (xmlload->direction().z().present()) {
                direction.setZ(atof(xmlload->direction().z().get().c_str()));
            }
            l->setDirection(direction);

            // target
            l->addTarget(xmlload->appliedTo().data());

            // value event
            load::Load::valueEvent_sequence ves = xmlload->valueEvent();
            for (load::Load::valueEvent_iterator vei = ves.begin(); vei != ves.end(); vei++) {
                load::Load::valueEvent_type ve = *vei;
                l->addEvent(new ValueEvent(ve.value(), ve.date()));
            }

            addLoad(l);
        }
    }
}

// ------------------ xmlPrint ------------------
/// print the prolog of the xml file
void  Loads::xmlPrint(std::ostream& o) const {
    o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
    o << "<!-- physical model load file -->" << std::endl;
    o << "<loads xmlns='http://www-timc.imag.fr/load'" << std::endl;
    o << "       xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" << std::endl;

    unsigned int i;
    Load* currentL;
    for (i = 0; i < numberOfLoads(); i++) {
        currentL = getLoad(i);
        currentL->xmlPrint(o);  // o << (*currentL) doesn't work !!!;
        o << std::endl;
    }

    o << "</loads>" << std::endl;
}

// ------------------ ansysPrint ------------------
// Print an ansys translation of the load list (not everything is implemented)
void  Loads::ansysPrint(std::ostream& o) const {
    o << "! -------------------------------------------- " << std::endl;
    o << "! translated from an physical model load file " << std::endl;
    o << "! -------------------------------------------- " << std::endl << std::endl;
    unsigned int i;
    Load* currentL;
    for (i = 0; i < numberOfLoads(); i++) {
        currentL = getLoad(i);
        o << "! -- Load #" << i << " (" << currentL->getType() << ")" << std::endl;
        currentL->ansysPrint(o);
        o << std::endl;
    }

    o << "! --- end of all selections ---" << std::endl;
    o << "ALLSEL" << std::endl;

}

// ------------------ operator << ------------------
std::ostream& operator << (std::ostream& o, const Loads l) {
    o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
    o << "<!-- physical model load file -->" << std::endl;
    o << "<loads xmlns='http://www-timc.imag.fr/load'" << std::endl;
    o << "       xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" << std::endl;

    unsigned int i;
    Load* currentL;
    for (i = 0; i < l.numberOfLoads(); i++) {
        currentL = l.getLoad(i);
        o << (* currentL) << std::endl;
    }

    o << "</loads>" << std::endl;

    return o;
}

// ------------------ addLoad ------------------
void Loads::addLoad(Load* ld) {
    loads.push_back(ld);
}

// ------------------ getLoad ------------------
Load* Loads::getLoad(const unsigned int i) const {
    if (i < loads.size()) {
        return loads[i];
    }
    else {
        return nullptr;
    }
}

// ------------------ numberOfLoads ------------------
unsigned int Loads::numberOfLoads() const {
    return (unsigned int) loads.size();  // if there are more than MAX_INT loads, too bad (that's over 2,147,483,647 anyway...)
}

// ------------------ deleteLoad ------------------
void Loads::deleteLoad(const unsigned int index) {
    std::vector <Load*>::iterator it;
    it = loads.begin() + index;
    loads.erase(it);
}

// ------------------ getFirstEventDate ------------------
/// get the first event date present in the list of loads
double Loads::getFirstEventDate() {
    double dateMin = -1.0;
    bool foundOne = false;
    ValueEvent* ev;

    for (auto& load : loads) {
        // as events are sorted by date, test only the first event
        // of each load
        ev = load->getValueEvent(0);
        if (ev && ((foundOne && ev->getDate() < dateMin) || !foundOne)) {
            dateMin = ev->getDate();
            foundOne = true;
        }
    }

    if (foundOne) {
        return dateMin;
    }
    else {
        return -1.0;
    }
}

// ------------------ getLastEventDate ------------------
/// get the last event date present in the list of loads
double Loads::getLastEventDate() {
    double dateMax = -1.0;
    ValueEvent* ev;

    for (auto& load : loads) {
        // as events are sorted by date, test only the last event
        // of each load
        ev = load->getValueEvent(load->numberOfValueEvents() - 1);
        if (ev && ev->getDate() > dateMax) {
            dateMax = ev->getDate();
        }
    }

    return dateMax;
}

