/**
 *
 * @file src/trace/IntervalOfContainer.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Augustin Degomme
 * @author Thibault Soucarre
 *
 * @date 2024-07-17
 */
#include <iostream>
#include <string>
#include <map>
#include <list>
#include <vector>
#include <stack>
#include <algorithm>

#include "common/common.hpp"
#include "common/Info.hpp"
#include <sstream>
#include <cstring>
#include <iostream>
#include <fstream>
#include "common/Message.hpp"
/* -- */
#include "trace/values/Values.hpp"
#include "trace/values/Date.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Event.hpp"
#include "trace/tree/Interval.hpp"
#include "trace/tree/Node.hpp"
#include "trace/tree/BinaryTree.hpp"

#include "trace/State.hpp"
#include "trace/StateChange.hpp"
#include "trace/Variable.hpp"
#include "trace/Event.hpp"
#include "trace/Link.hpp"

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

#ifdef USE_ITC

#if defined(BOOST_SERIALIZE)
// necessary, otherwise BOOST_VERSION is not set and portable_archive gets lost
#include <boost/version.hpp>
#include <boost/archive/tmpdir.hpp>
#include <trace/portable_archive_exception.hpp>
#include <trace/portable_iarchive.hpp>
#include <trace/portable_oarchive.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/assume_abstract.hpp>
#include "boost/serialization/map.hpp"
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/split_member.hpp>

#ifdef BOOST_GZIP
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#endif
#endif

#include "trace/Serializer_values.hpp"
#include "trace/Serializer_types.hpp"
#include "trace/Serializer_structs.hpp"
#include "trace/IntervalOfContainer.hpp"
#include <limits>
#include <float.h>
using namespace std;

IntervalOfContainer::IntervalOfContainer() :
    _loaded(1), _beginning(0.0), _end(0.0), _statechanges(NULL), _n_statechanges(0), _states(NULL), _n_states(0), _states_values_map(NULL), _events(NULL), _n_events(0), _links(NULL), _n_links(0), _variables(NULL), _n_variables(0) {
}

IntervalOfContainer::~IntervalOfContainer() {

    if (_loaded) {
        if (_n_statechanges != 0) {
            for (int i = 0; i < _n_statechanges; i++)
                _statechanges[i].clear();

            free(_statechanges);
            _statechanges = NULL;
        }

        if (_n_states != 0) {
            for (int i = 0; i < _n_states; i++)
                _states[i].clear();

            free(_states);
            _states = NULL;
        }

        if (_n_events != 0) {
            for (int i = 0; i < _n_events; i++)
                _events[i].clear();
            free(_events);
            _events = NULL;
        }

        if (_n_links != 0) {
            for (int i = 0; i < _n_links; i++)
                _links[i].clear();
            free(_links);
            _links = NULL;
        }

        if (_n_variables != 0) {
            for (int i = 0; i < _n_variables; i++)
                _variables[i].clear();

            free(_variables);
            _variables = NULL;
        }
    }
}

#if defined(BOOST_SERIALIZE)
void IntervalOfContainer::unload() {
    if (_n_statechanges != 0) {
        free(_statechanges);
        _statechanges = NULL;
    }

    if (_n_states != 0) {
        for (int i = 0; i < _n_states; i++)
            _states[i].clear();

        free(_states);
        _states = NULL;
    }

    if (_n_events != 0) {
        for (int i = 0; i < _n_events; i++)
            _events[i].clear();
        free(_events);
        _events = NULL;
    }

    if (_n_links != 0) {
        for (int i = 0; i < _n_links; i++)
            _links[i].clear();

        free(_links);
        _links = NULL;
    }

    if (_n_variables != 0) {
        for (int i = 0; i < _n_variables; i++)
            _variables[i].clear();

        free(_variables);
        _variables = NULL;
    }

    _loaded = false;
}

#endif

Date IntervalOfContainer::get_beginning() {
    return _beginning;
}

Date IntervalOfContainer::get_end() {
    return _end;
}
std::map<EntityValue *, double> *IntervalOfContainer::get_states_values_map() {
    return _states_values_map;
}

State *IntervalOfContainer::add_state(Date start, Date end, StateType *type, EntityValue *value, Container *container, std::map<std::string, Value *> opt) {

    if (_n_states == 0) {

        _states = (State *)malloc(sizeof(State) * (_n_states + N_EVENTS_PER_INTERVAL + 2)); // as we have two states per statechange, we need one more at he end (which will be shared with the next itc)
        _states_values_map = new std::map<EntityValue *, double>();
    }
#if defined(BOOST_SERIALIZE)
    // set an unique id for the EntityValue (the use of bimaps inside entityvalues guarantees its unicity)
    Serializer<EntityValue>::Instance().setUid(value);
#endif

    // create a State inside the previously allocated array (just call the constructor)
    new (&_states[_n_states]) State(start, end, type, container, value, opt);
    //_states[_n_states].set_state_values( start, end, type, value, container, opt);
    _n_states++;

    (*_states_values_map)[value] += (end - start);

    return &(_states[_n_states - 1]);
}

bool IntervalOfContainer::add_statechange(Date time, State *left, State *right) {
    if (_n_statechanges == N_EVENTS_PER_INTERVAL)
        return false;
    if (_n_statechanges == 0 || time < _beginning)
        _beginning = time;
    if (time > _end)
        _end = time;

    if (_statechanges == NULL)
        _statechanges = (StateChange *)malloc(sizeof(StateChange) * N_EVENTS_PER_INTERVAL);
    new (&(_statechanges[_n_statechanges])) StateChange(time, left, right);
    // set_statechange_values(& _statechanges[_n_statechanges],time, left, right);
    _n_statechanges++;
    return true;
}

void IntervalOfContainer::add_event(Date time, EventType *type, Container *cont, EntityValue *value, map<string, Value *> opt) {
    // if (time<_beginning)_beginning=time;
    if (_beginning.get_value() == 0.0 || time < _beginning)
        _beginning = time;
    if (time > _end)
        _end = time;
    if (_n_events == 0 || _n_events % N_EVENTS_PER_INTERVAL == 0)
        _events = (Event *)realloc(_events, sizeof(Event) * (_n_events + N_EVENTS_PER_INTERVAL));

#if defined(BOOST_SERIALIZE)
    // set an unique id for the EntityValue (the use of bimaps inside entityvalues guarantees its unicity)
    Serializer<EntityValue>::Instance().setUid(value);
#endif

    new (&(_events[_n_events])) Event(time, type, cont, value, opt);
    // set_event_values(&_events[_n_events], time, type, cont, value, opt);
    _n_events++;
}

void IntervalOfContainer::set_variable(Container *container, VariableType *type) {

    if (_n_variables == 0 || _n_variables % N_EVENTS_PER_INTERVAL == 0)
        _variables = (Variable *)realloc(_variables, sizeof(Variable) * (_n_variables + N_EVENTS_PER_INTERVAL));
    new (&(_variables[_n_variables])) Variable(container, type);
    _n_variables++;
}

void IntervalOfContainer::set_variables(std::map<VariableType *, Variable *> *map_var) {

    _variables = (Variable *)malloc(sizeof(Variable) * map_var->size());
    memset(_variables, 0, sizeof(Variable) * map_var->size());
    _n_variables = map_var->size();
    int i = 0;
    map<VariableType *, Variable *>::const_iterator it_end = map_var->end();
    for (map<VariableType *, Variable *>::iterator it = map_var->end(); it != it_end; ++it) {
        _variables[i] = *(*it).second;
        i++;
    }
}

bool IntervalOfContainer::add_link(Date start, Date end, LinkType *type, Container *container, Container *source, Container *destination, EntityValue *value, std::map<std::string, Value *> opt) {

    if (_n_links == N_EVENTS_PER_INTERVAL)
        return false;
    Date min, max;
    if (start < end) {
        min = start;
        max = end;
    }
    else {
        min = end;
        max = start;
    }
    if (_beginning.get_value() == 0.0 || min < _beginning)
        _beginning = min;
    if (max > _end)
        _end = max;
    if (_n_links == 0 || _n_links % N_EVENTS_PER_INTERVAL == 0)
        _links = (Link *)realloc(_links, sizeof(Link) * (_n_links + N_EVENTS_PER_INTERVAL));
#if defined(BOOST_SERIALIZE)
    // set an unique id for the EntityValue (the use of bimaps inside entityvalues guarantees its unicity)
    Serializer<EntityValue>::Instance().setUid(value);
#endif
    new (&_links[_n_links]) Link(start, end, type, container, source, destination, value, opt);
    // set_link_values(&_links[_n_links],   start,end, type, container, source, destination, value, opt);
    _n_links++;
    return true;
}

#if defined(BOOST_SERIALIZE)
bool IntervalOfContainer::dump_on_disk(const char *filename) {
#ifdef BOOST_GZIP
    // make an archive
    std::ofstream ofs(filename, std::ios::out | std::ios::binary);
    // std::ofstream ofs(filename, std::ios::out);
    {
        boost::iostreams::filtering_streambuf<boost::iostreams::output> out;
        out.push(boost::iostreams::gzip_compressor());
        out.push(ofs);
        // boost::archive::binary_oarchive oa(out);
        // boost::archive::text_oarchive oa(out);
        eos::portable_oarchive oa(out);
        QT_TRY {
            oa << *this;
        }
        QT_CATCH(...) {
            printf("fail ! %s", filename);
        }
    }
    return true;
#else
    std::ofstream ofs(filename);
    if (ofs.fail()) {
        printf("file ofs fail ! %s\n", filename);
        return false;
    }
    {
        // eos::portable_oarchive oa(ofs);
        boost::archive::text_oarchive oa(ofs);
        QT_TRY {
            oa << *this;
        }
        QT_CATCH(const eos::portable_archive_exception &e) {
            printf("fail while dumping serialized file ! %s %s\n", e.what(), filename);
            return false;
        }
        QT_CATCH(const exception &e) {
            printf("fail while dumping serialized file ! %s %s\n", e.what(), filename);

            return false;
        }
    }
    return true;
#endif
}

bool IntervalOfContainer::retrieve(const char *filename) {
    // open the archive
#ifdef BOOST_GZIP
    IntervalOfContainer *itc = this;
    std::ifstream ifs(filename, std::ios::in | std::ios::binary);
    // std::ifstream ifs(filename, std::ios::in);
    assert(ifs.good()); // XXX catch if file not found
    boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
    in.push(boost::iostreams::gzip_decompressor());
    in.push(ifs);
    // boost::archive::binary_iarchive ia(in);
    // boost::archive::text_iarchive ia(in);
    eos::portable_iarchive ia(in);
    ia >> *itc;
    return true;
#else
    IntervalOfContainer *itc = this;
    std::ifstream ifs(filename);
    if (ifs.fail())
        return false;
    boost::archive::text_iarchive ia(ifs);
    // eos::portable_iarchive ia(ifs);
    //  restore the schedule from the archive
    QT_TRY {
        ia >> *itc;
    }
    QT_CATCH(eos::portable_archive_exception e) {
        printf("fail while restoring serialized file ! %s %s\n", e.what(), filename);

        return false;
    }
    return true;
#endif
}
#endif

#endif
