/*
 ** 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 <assert.h>
#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
#include <map>
#include <set>
#include <queue>
/* -- */
#include "common/Errors.hpp"
/* -- */
#include "parser/PajeParser/PajeFileManager.hpp" // temporary
#include "parser/PajeParser/PajeDefinition.hpp"
#include "parser/PajeParser/ParserDefinitionPaje.hpp"
/* -- */
using namespace std;

#ifdef WIN32
#define sscanf sscanf_s
#define sprintf sprintf_s
#endif

#define _OUT_A_DEFINITION 0
#define _IN_A_DEFINITION  1

#define INSERT_FNAME(name, allowed)					\
    {									\
        _FieldNames[_PajeFN_##name]._name    = #name;			\
        _FieldNames[_PajeFN_##name]._id      = _PajeFN_##name;		\
        _FieldNames[_PajeFN_##name]._code    = CODE(_PajeFN_##name);	\
        _FieldNames[_PajeFN_##name]._allowed = allowed;			\
        i++;								\
    }

#define INSERT_AVAILABLE_DEF(name, required)                            \
    {                                                                   \
        PajeEventDef _evdef;                                            \
        _evdef._id =  _##name;                                          \
        _evdef._fdrequired = required;                                  \
        _AvailableEvDef.insert(pair<string, PajeEventDef>(#name, _evdef)); \
    }

std::map<std::string, ParserDefinitionPaje::PajeEventDef> ParserDefinitionPaje::_AvailableEvDef;
std::map<std::string, int> ParserDefinitionPaje::_FTypeStr2Code;
bool                       ParserDefinitionPaje::_initialized = false;
int                        ParserDefinitionPaje::_nbparsers   = 0;

ParserDefinitionPaje::ParserDefinitionPaje() {
    int i;

    _state = _OUT_A_DEFINITION;
    _defid = 0;
    _nbparsers++;

    if ( !_initialized ) {
        _initialized = true;

        /* Should be changed into a singleton structure */

        // Initialize the list of types available for field in EventDef declaration
        // This list connect the available types for event definition to a unique bitmask
        _FTypeStr2Code.insert(pair<string, int>("int"   ,_FieldType_Int    ));
        _FTypeStr2Code.insert(pair<string, int>("hex"   ,_FieldType_Hex    ));
        _FTypeStr2Code.insert(pair<string, int>("date"  ,_FieldType_Date   ));
        _FTypeStr2Code.insert(pair<string, int>("double",_FieldType_Double ));
        _FTypeStr2Code.insert(pair<string, int>("string",_FieldType_String ));
        _FTypeStr2Code.insert(pair<string, int>("color" ,_FieldType_Color  ));

        // Initialize the list of Field's names mandatory for each EventDef declaration
        i = 0;
        _FieldNames.resize( FIELDNAME_SIZEMAX );
        INSERT_FNAME(Time,                _FieldType_Date                    );
        INSERT_FNAME(Name,                _FieldType_Int | _FieldType_String );
        INSERT_FNAME(Alias,               _FieldType_Int | _FieldType_String );
        INSERT_FNAME(Type,                _FieldType_Int | _FieldType_String );
        INSERT_FNAME(Container,           _FieldType_Int | _FieldType_String );
        INSERT_FNAME(StartContainerType,  _FieldType_Int | _FieldType_String );
        INSERT_FNAME(EndContainerType,    _FieldType_Int | _FieldType_String );
        INSERT_FNAME(StartContainer,      _FieldType_Int | _FieldType_String );
        INSERT_FNAME(EndContainer,        _FieldType_Int | _FieldType_String );
        INSERT_FNAME(Color,               _FieldType_Color );
        INSERT_FNAME(Value,               _FieldType_Int | _FieldType_String | _FieldType_Double);
        INSERT_FNAME(Key,                 _FieldType_Int | _FieldType_String );
        INSERT_FNAME(File,                _FieldType_String );
        INSERT_FNAME(Line,                _FieldType_Int | _FieldType_String );

        // Initialize the map between the field names and the associated id
        _nbFieldNames = FIELDNAME_SIZE;
        for(i=0; i<FIELDNAME_SIZE; i++) {
            _FNameStr2Id.insert(pair<string, int>(_FieldNames[i]._name, i));
        }

        // Former types no longer used in Paje format
        _FNameStr2Id.insert(pair<string, int>("ContainerType"      , _PajeFN_ContainerType       ));
        _FNameStr2Id.insert(pair<string, int>("EntityType"         , _PajeFN_EntityType          ));
        _FNameStr2Id.insert(pair<string, int>("SourceContainerType", _PajeFN_SourceContainerType ));
        _FNameStr2Id.insert(pair<string, int>("DestContainerType"  , _PajeFN_DestContainerType   ));
        _FNameStr2Id.insert(pair<string, int>("SourceContainer"    , _PajeFN_SourceContainer     ));
        _FNameStr2Id.insert(pair<string, int>("DestContainer"      , _PajeFN_DestContainer       ));

        // Initialize the list of Events available
        INSERT_AVAILABLE_DEF(PajeDefineContainerType, CODE2(Name) | CODE2(Alias) | CODE2(Type) );
        INSERT_AVAILABLE_DEF(PajeDefineEventType    , CODE2(Name) | CODE2(Alias) | CODE2(Type) );
        INSERT_AVAILABLE_DEF(PajeDefineStateType    , CODE2(Name) | CODE2(Alias) | CODE2(Type) );
        INSERT_AVAILABLE_DEF(PajeDefineVariableType , CODE2(Name) | CODE2(Alias) | CODE2(Type) );
        INSERT_AVAILABLE_DEF(PajeDefineEntityValue  , CODE2(Name) | CODE2(Alias) | CODE2(Type)    );
        INSERT_AVAILABLE_DEF(PajeDestroyContainer   , CODE2(Name) | CODE2(Alias) | CODE2(Time) | CODE2(Type) );
        INSERT_AVAILABLE_DEF(PajeCreateContainer    , CODE2(Name) | CODE2(Alias) | CODE2(Time) | CODE2(Type) | CODE2(Container) );
        INSERT_AVAILABLE_DEF(PajeDefineLinkType     , CODE2(Name) | CODE2(Alias) | CODE2(Type) | CODE2(StartContainerType) | CODE2(EndContainerType) );
        INSERT_AVAILABLE_DEF(PajeSetState           , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajePushState          , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajePopState           , CODE2(Time) | CODE2(Type)  | CODE2(Container) );
        INSERT_AVAILABLE_DEF(PajeResetState         , CODE2(Time) | CODE2(Type)  | CODE2(Container) );
        INSERT_AVAILABLE_DEF(PajeNewEvent           , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajeSetVariable        , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajeAddVariable        , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajeSubVariable        , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) );
        INSERT_AVAILABLE_DEF(PajeStartLink          , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) | CODE2(Key) | CODE2(StartContainer) );
        INSERT_AVAILABLE_DEF(PajeEndLink            , CODE2(Time) | CODE2(Type)  | CODE2(Container) | CODE2(Value) | CODE2(Key) | CODE2(EndContainer) );

    }
}

ParserDefinitionPaje::~ParserDefinitionPaje() {
    int i;

    // Free the string to store extra names
    for( i=FIELDNAME_SIZE ; i<_nbFieldNames; i++) {
        delete[] _FieldNames[i]._name;
    }
    _FieldNames.clear();
    _EventDefs.clear();
    _FNameStr2Id.clear();
    _EvDefTrId2Id.clear();

    _nbparsers--;
    if ( (_nbparsers == 0) && _initialized ) {
        _FTypeStr2Code.clear();
        _AvailableEvDef.clear();
        _initialized = false;
    }
}

void ParserDefinitionPaje::enter_definition(const PajeLine_t *line){
    PajeDefinition def;
    int   evtdefid, trid;
    char *defname = line->_tokens[2];
    char *tridstr = line->_tokens[3];

    // The name is missing
    if(defname == NULL) {
        Error::set(Error::VITE_ERR_EXPECT_NAME_DEF, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // The id is missing
    if(tridstr == NULL) {
        Error::set(Error::VITE_ERR_EXPECT_ID_DEF, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // If we are already trying to define an event,
    // we close it before starting a new one
    if(_state == _IN_A_DEFINITION){
        Error::set(Error::VITE_ERR_EXPECT_END_DEF, line->_id, Error::VITE_ERRCODE_WARNING);
        leave_definition(line);
    }

    // Get the trid of the definition in the trace (trid)
    if(sscanf(tridstr, "%u", &trid) != 1){
        Error::set(Error::VITE_ERR_EXPECT_ID_DEF, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // Try to find the name
    if ( _AvailableEvDef.find(defname) == _AvailableEvDef.end() ) {
        Error::set(Error::VITE_ERR_UNKNOWN_EVENT_DEF, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // Check if it is the first time we see this Event
    if ( _EvDefTrId2Id.find(trid) != _EvDefTrId2Id.end() ) {
        Error::set(Error::VITE_ERR_EVENT_ALREADY_DEF, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // Extra tokens are not supported
    if(line->_nbtks > 4){
        Error::set(Error::VITE_ERR_EXTRA_TOKEN, line->_id, Error::VITE_ERRCODE_WARNING);
    }

    // Everything is ok, we initialize the currentEvtDef
    assert(_defid == _EventDefs.size());

    def._name = defname;
    def._id   = _AvailableEvDef[defname]._id;
    def._trid = trid;
    def._fdpresent  = 0;
    def._fdrequired = _AvailableEvDef[defname]._fdrequired;

    _EventDefs.push_back( def );
    _state = _IN_A_DEFINITION;
    _EvDefTrId2Id.insert(pair<int, int>(trid, _defid));

    return;
}

void ParserDefinitionPaje::leave_definition(const PajeLine_t *line){

    // We can't end a definition if we are not in one !!!
    if(_state != _IN_A_DEFINITION){
        Error::set(Error::VITE_ERR_EXPECT_EVENT_DEF, line->_id, Error::VITE_ERRCODE_WARNING);
        return;
    }

    _state = _OUT_A_DEFINITION;

    // Check that the event has been correctly defined
    if( !(PajeDef::check_definition(&_EventDefs[_defid])) ) {
        Error::set(Error::VITE_ERR_EVENT_NOT_CORRECT
                   + PajeDef::print_string(&_FieldNames, &_EventDefs[_defid]),
                   line->_id, Error::VITE_ERRCODE_ERROR);

        // We remove the definition
        _EvDefTrId2Id.erase(_EventDefs[_defid]._trid);
        _EventDefs.pop_back();
        assert( _defid == _EventDefs.size() );
        return;
    }

#ifdef DBG_PARSER_PAJE
    PajeDef::print(&_FieldNames,&_EventDefs[_defid]);
#endif
    _defid++;
    assert( _defid == _EventDefs.size() );
}

void ParserDefinitionPaje::add_field_to_definition(const PajeLine_t *line){
    char *fieldname = line->_tokens[1];
    char *fieldtype = line->_tokens[2];
    int   idname;
    int   idtype;
    Field fd;

    // Check we are currently defining an event
    if (_state == _OUT_A_DEFINITION) {
        Error::set(Error::VITE_ERR_EXPECT_EVENT_DEF,   line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // If fieldtype is not defined
    if (fieldname == NULL) {
        Error::set(Error::VITE_ERR_FIELD_NAME_MISSING, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // If fieldtype is not defined
    if (fieldtype == NULL) {
        Error::set(Error::VITE_ERR_FIELD_TYPE_MISSING, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    idtype = _FTypeStr2Code[fieldtype];

    // Type unknown
    if ( idtype  == 0 ) {
        Error::set(Error::VITE_ERR_FIELD_TYPE_UNKNOWN + fieldtype, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // Name unknown, we have to store it
    if ( _FNameStr2Id.find(fieldname) == _FNameStr2Id.end() ) {
        int   size  = strlen(fieldname)+1;
        char *newfn = new char[size];
#ifndef WIN32
        strcpy(newfn, fieldname);
#else
        strcpy_s(newfn, size, fieldname);
#endif

        _FieldNames[_nbFieldNames]._name    = newfn;
        _FieldNames[_nbFieldNames]._id      = _nbFieldNames;
        _FieldNames[_nbFieldNames]._code    = (1 << _nbFieldNames);
        _FieldNames[_nbFieldNames]._allowed = idtype;
        idname = _nbFieldNames;
        _nbFieldNames++;
    } else {
        idname =_FNameStr2Id[fieldname];
    }

    // check if type is allowed
    if ( !( idtype & _FieldNames[idname]._allowed ) ) {
        Error::set(Error::VITE_ERR_FIELD_NOT_ALLOWED + "(" + fieldname + "," + fieldtype + ")", line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // Extra tokens are not supported
    if(line->_nbtks > 3){
        Error::set(Error::VITE_ERR_EXTRA_TOKEN, line->_id, Error::VITE_ERRCODE_WARNING);
    }

    // We had the field and his type to the definition
    fd._idname = idname;
    fd._idtype = idtype;
    _EventDefs[_defid]._fields.push_back( fd );
    _EventDefs[_defid]._fdpresent |= _FieldNames[idname]._code;
}


void ParserDefinitionPaje::store_definition(const PajeLine_t *line){
    string token = line->_tokens[1];

    // We need almost two tokens '%' and "[End]EventDef"
    if(line->_nbtks < 2){
        Error::set(Error::VITE_ERR_EMPTY_DEF, line->_id, Error::VITE_ERRCODE_WARNING);
        return;
    }

    // We start a definition
    if (token.compare("EventDef") == 0){
        enter_definition(line);
    }
    // We stop a definition
    else if (token.compare("EndEventDef") == 0) {
        leave_definition(line);
        if(line->_nbtks > 2){
            Error::set(Error::VITE_ERR_EXTRA_TOKEN, line->_id, Error::VITE_ERRCODE_WARNING);
        }
    }
    // Add a field to the definition
    else{
        add_field_to_definition(line);
    }

}

PajeDefinition *ParserDefinitionPaje::getDefFromTrid(int trid) {
    if ( _EvDefTrId2Id.find(trid) == _EvDefTrId2Id.end() ) {
        return NULL;
    } else {
        return &(_EventDefs[_EvDefTrId2Id[trid]]);
    }
}

void ParserDefinitionPaje::print_definitions() {
    map<int, int>::iterator it     = _EvDefTrId2Id.begin();
    map<int, int>::iterator it_end = _EvDefTrId2Id.end();

    for (; it != it_end ; ++ it){

        cout << "###### " << (*it).first << endl;
        // (*it).second represents the current definition
        PajeDef::print(&_FieldNames, &_EventDefs[(*it).second]);
    }
}

int ParserDefinitionPaje::definitions_number() const {
    return _EvDefTrId2Id.size();
}

const vector<PajeFieldName> *ParserDefinitionPaje::get_FieldNames() const {
    return &_FieldNames;
}
