/*
 ** This file is part of the ViTE project.
 **
 ** This software is governed by the CeCILL-A license under French law
 ** and abiding by the rules of distribution of free software. You can
 ** use, modify and/or redistribute the software under the terms of the
 ** CeCILL-A license as circulated by CEA, CNRS and INRIA at the following
 ** URL: "http://www.cecill.info".
 **
 ** As a counterpart to the access to the source code and rights to copy,
 ** modify and redistribute granted by the license, users are provided
 ** only with a limited warranty and the software's author, the holder of
 ** the economic rights, and the successive licensors have only limited
 ** liability.
 **
 ** In this respect, the user's attention is drawn to the risks associated
 ** with loading, using, modifying and/or developing or reproducing the
 ** software by the user in light of its specific status of free software,
 ** that may mean that it is complicated to manipulate, and that also
 ** therefore means that it is reserved for developers and experienced
 ** professionals having in-depth computer knowledge. Users are therefore
 ** encouraged to load and test the software's suitability as regards
 ** their requirements in conditions enabling the security of their
 ** systems and/or data to be ensured and, more generally, to use and
 ** operate it in the same conditions as regards security.
 **
 ** The fact that you are presently reading this means that you have had
 ** knowledge of the CeCILL-A license and that you accept its terms.
 **
 **
 ** ViTE developers are (for version 0.* to 1.0):
 **
 **        - COULOMB Kevin
 **        - FAVERGE Mathieu
 **        - JAZEIX Johnny
 **        - LAGRASSE Olivier
 **        - MARCOUEILLE Jule
 **        - NOISETTE Pascal
 **        - REDONDY Arthur
 **        - VUCHENER Clément
 **
 */
#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( name, 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);

        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, 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;
    }
}
