/**
 *
 * @file src/parser/PajeParser/ParserEventPaje.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Francois Trahay
 * @author Thibault Soucarre
 * @author Philippe Swartvagher
 * @author Olivier Lagrasse
 * @author Augustin Degomme
 * @author Luigi Cannarozzo
 *
 * @date 2024-07-17
 */
#include <string>
#include <map>
#include <set>
#include <queue>
#include <list>
/* -- */
#include "common/Errors.hpp"
/* -- */
#include "trace/values/Values.hpp"
#include "trace/EntityValue.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Trace.hpp"
/* -- */
#include <fstream>
#include <sstream>
#include "parser/PajeParser/PajeFileManager.hpp" // temporary
#include "parser/PajeParser/PajeDefinition.hpp"
#include "parser/PajeParser/ParserDefinitionPaje.hpp"
#include "parser/PajeParser/ParserEventPaje.hpp"
/* -- */
#if defined WIN32 && !defined(__MINGW32__)
#define sscanf sscanf_s
#endif

using namespace std;

ParserEventPaje::ParserEventPaje(ParserDefinitionPaje *defs) {
    _Definitions = defs;
}

ParserEventPaje::~ParserEventPaje() {
    _containers.clear();
}

void ParserEventPaje::store_event(const PajeLine *line,
                                  Trace &trace) {
    string fvalue;
    string alias;
    string name;
    String type;
    String start_container_type;
    String end_container_type;
    Date time;
    String container;
    String value_string;
    Double value_double;
    String start_container;
    String end_container;
    String key;
    map<std::string, Value *> extra_fields;

    const vector<PajeFieldName> *FNames = _Definitions->get_FieldNames();
    vector<Field> *fields;
    PajeDefinition *def;
    int i, trid;
    int defsize;
    int idname, idtype;

    // We check if we have an event identifier
    if (sscanf(line->_tokens[0], "%d", &trid) != 1) {
        Error::set(Error::VITE_ERR_EXPECT_ID_DEF, line->_id, Error::VITE_ERRCODE_WARNING);
        return;
    }

    // We check if the trid is available
    def = _Definitions->getDefFromTrid(trid);
    if (def == nullptr) {
        stringstream s;
        s << Error::VITE_ERR_UNKNOWN_ID_DEF << trid;
        Error::set(s.str(), line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    fields = &(def->_fields);
    defsize = fields->size();

    // We check if we have enough data for this event
    if (defsize > (line->_nbtks - 1)) {
        Error::set(Error::VITE_ERR_LINE_TOO_SHORT_EVENT, line->_id,
                   Error::VITE_ERRCODE_WARNING);
        return;
    }

    // Warning if we have extra data
    if (defsize < (line->_nbtks - 1)) {
        Error::set(Error::VITE_ERR_EXTRA_TOKEN, line->_id, Error::VITE_ERRCODE_WARNING);
    }

    // Dispatch the tokens in the good fields
    for (i = 0; i < defsize; i++) {

        fvalue = line->_tokens[i + 1];
        idname = (*fields)[i]._idname;
        idtype = (*fields)[i]._idtype;

        // Store the fvalue in the correct field
        switch (idname) {
        case _PajeFN_Alias:
            alias = fvalue;
            break;

        case _PajeFN_Name:
            name = fvalue;
            break;

        case _PajeFN_Type:
            type = fvalue;
            break;

        case _PajeFN_StartContainerType:
            start_container_type = fvalue;
            break;

        case _PajeFN_EndContainerType:
            end_container_type = fvalue;
            break;

        case _PajeFN_Time:
            time = fvalue;
            if (!time.is_correct()) {
                Error::set(Error::VITE_ERR_INCOMPATIBLE_VALUE_IN_EVENT + fvalue + " (expecting a \"date\")",
                           line->_id,
                           Error::VITE_ERRCODE_WARNING);
                return;
            }
            break;

        case _PajeFN_Container:
            container = fvalue;
            break;

        case _PajeFN_Value:
            if (idtype == _FieldType_Double) {
                value_double = fvalue;

                if (!value_double.is_correct()) {
                    Error::set(Error::VITE_ERR_INCOMPATIBLE_VALUE_IN_EVENT + fvalue + " (expecting a \"double\")",
                               line->_id, Error::VITE_ERRCODE_WARNING);
                    return;
                }
            }
            else {
                value_string = fvalue;
            }
            break;

        case _PajeFN_StartContainer:
            start_container = fvalue;
            break;

        case _PajeFN_EndContainer:
            end_container = fvalue;
            break;

        case _PajeFN_Key:
            key = fvalue;
            break;

        default:
            Value *value = nullptr;
            switch (idtype) {
            case _FieldType_String:
                value = new String(fvalue);
                break;

            case _FieldType_Double:
                value = new Double(fvalue);
                break;

            case _FieldType_Hex:
                value = new Hex(fvalue);
                break;

            case _FieldType_Date:
                value = new Date(fvalue);
                break;

            case _FieldType_Int:
                value = new Integer(fvalue);
                break;

            case _FieldType_Color:
                value = new Color(fvalue);
                break;

            default:
                Error::set(Error::VITE_ERR_FIELD_TYPE_UNKNOWN, line->_id, Error::VITE_ERRCODE_WARNING);
                return;
            }

            //          if(!value->is_correct()) { // Check if the value is correct or not
            //              Error::set(Error::VITE_ERR_INCOMPATIBLE_VALUE_IN_EVENT + fvalue + " (expecting a \"" + ftype + "\")",
            //                         line->_id, Error::VITE_ERRCODE_WARNING);
            //              return;
            //          }

            extra_fields[(*FNames)[idname]._name] = value;
        }
    }

    if ((alias != "") && (name == "")) {
        name = alias;
    }
    if ((name != "") && (alias == "")) {
        alias = name;
    }

    Name alias_name(std::move(name), std::move(alias));

    switch (def->_id) {
    case _PajeDefineContainerType: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        if ((temp_container_type == nullptr) && (type.to_string() != "0")) {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_container_type(alias_name, temp_container_type, extra_fields);
        }
    } break;

    case _PajeCreateContainer: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        Container *temp_container = trace.search_container(container);
        if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.create_container(time, alias_name, temp_container_type, temp_container, extra_fields);
            // We store the container in the map
            _containers[alias_name.to_string()] = trace.search_container(alias_name.to_string());
        }
    } break;

    case _PajeDestroyContainer: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        Container *temp_container = trace.search_container(alias_name.to_string());
        if (temp_container == nullptr && alias_name.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + alias_name.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.destroy_container(time, temp_container, temp_container_type, extra_fields);
        }
    } break;

    case _PajeDefineEventType: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_event_type(alias_name, temp_container_type, extra_fields);
        }
    } break;

    case _PajeDefineStateType: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_state_type(alias_name, temp_container_type, extra_fields);
        }
    } break;

    case _PajeDefineVariableType: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_variable_type(alias_name, temp_container_type, extra_fields);
        }
    } break;

    case _PajeDefineLinkType: {
        ContainerType *temp_container_type = trace.search_container_type(type);
        ContainerType *temp_start_container_type = trace.search_container_type(start_container_type);
        ContainerType *temp_end_container_type = trace.search_container_type(end_container_type);
        if (temp_container_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_start_container_type == nullptr && start_container_type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + start_container_type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_end_container_type == nullptr && end_container_type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + end_container_type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_link_type(alias_name, temp_container_type, temp_start_container_type, temp_end_container_type, extra_fields);
        }
    } break;

    case _PajeDefineEntityValue: {
        EntityType *temp_entity_type = trace.search_entity_type(type);
        if (temp_entity_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_ENTITY_TYPE + type.to_string(), line->_id,
                       Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.define_entity_value(alias_name, temp_entity_type, extra_fields);
        }
    } break;

    case _PajeSetState: {
        StateType *temp_state_type = trace.search_state_type(type);
        EntityValue *temp_entity_value = trace.search_entity_value(value_string, temp_state_type);

        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_state_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.set_state(time, temp_state_type, temp_container, temp_entity_value, extra_fields);
        }
    } break;

    case _PajePushState: {
        StateType *temp_state_type = trace.search_state_type(type);
        EntityValue *temp_entity_value = trace.search_entity_value(value_string, temp_state_type);

        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_state_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.push_state(time, temp_state_type, temp_container, temp_entity_value, extra_fields);
        }
    } break;

    case _PajePopState: {
        StateType *temp_state_type = trace.search_state_type(type);
        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_state_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.pop_state(time, temp_state_type, temp_container, extra_fields);
        }
    } break;

    case _PajeResetState: {
        StateType *temp_state_type = trace.search_state_type(type);
        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_state_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.reset_state(time, temp_state_type, temp_container, extra_fields);
        }
    } break;

    case _PajeNewEvent: {
        EventType *temp_event_type = trace.search_event_type(type);
        Container *temp_container = nullptr;

        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_event_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_EVENT_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.new_event(time, temp_event_type, temp_container, value_string, extra_fields);
        }
    } break;

    case _PajeSetVariable: {
        VariableType *temp_variable_type = trace.search_variable_type(type);

        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_variable_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.set_variable(time, temp_variable_type, temp_container, value_double, extra_fields);
        }
    } break;

    case _PajeAddVariable: {
        VariableType *temp_variable_type = trace.search_variable_type(type);
        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_variable_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.add_variable(time, temp_variable_type, temp_container, value_double, extra_fields);
        }
    } break;

    case _PajeSubVariable: {
        VariableType *temp_variable_type = trace.search_variable_type(type);
        Container *temp_container = nullptr;
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }

        if (temp_variable_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.sub_variable(time, temp_variable_type, temp_container, value_double, extra_fields);
        }
    } break;

    case _PajeStartLink: {
        LinkType *temp_link_type = trace.search_link_type(type);
        EntityValue *temp_entity_value = trace.search_entity_value(value_string, temp_link_type);

        Container *temp_container = nullptr;
        Container *temp_start_container = nullptr;
        // temp_container
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }
        // temp_start_container
        if (_containers.find(start_container) != _containers.end()) {
            temp_start_container = _containers[start_container];
        }
        else {
            temp_start_container = trace.search_container(start_container);
            _containers[start_container] = temp_start_container;
        }

        if ((temp_link_type == nullptr) || (type.to_string() == "0")) {
            Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if ((temp_container == nullptr) || (container.to_string() == "0")) {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if ((temp_start_container == nullptr) || (start_container.to_string() == "0")) {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + start_container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.start_link(time, temp_link_type, temp_container, temp_start_container, temp_entity_value, key, extra_fields);
        }
    } break;

    case _PajeEndLink: {
        LinkType *temp_link_type = trace.search_link_type(type);
        Container *temp_container = nullptr;
        Container *temp_end_container = nullptr;

        // temp_container
        if (_containers.find(container) != _containers.end()) {
            temp_container = _containers[container];
        }
        else {
            temp_container = trace.search_container(container);
            _containers[container] = temp_container;
        }
        // temp_end_container
        if (_containers.find(end_container) != _containers.end()) {
            temp_end_container = _containers[end_container];
        }
        else {
            temp_end_container = trace.search_container(end_container);
            _containers[end_container] = temp_end_container;
        }

        if (temp_link_type == nullptr && type.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + type.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_container == nullptr && container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else if (temp_end_container == nullptr && end_container.to_string() != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + end_container.to_string(), line->_id, Error::VITE_ERRCODE_ERROR);
        }
        else {
            trace.end_link(time, temp_link_type, temp_container, temp_end_container, key, extra_fields);
        }
    } break;

    default:
        Error::set(Error::VITE_ERR_UNKNOWN_EVENT_DEF + def->_name, line->_id, Error::VITE_ERRCODE_WARNING);
        return;
    }
}
