/**
 *
 * @file src/parser/PajeParser/ParserDefinitionPaje.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Arthur Redondy
 * @author Mathieu Faverge
 * @author Pascal Noisette
 * @author Olivier Lagrasse
 * @author Augustin Degomme
 * @author Luigi Cannarozzo
 *
 * @date 2024-07-17
 */
#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;

#if defined WIN32 && !defined(__MINGW32__)
#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;

    _nbFieldNames = FIELDNAME_SIZE;
    _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
        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 trid;
    char *defname = line->_tokens[2];
    char *tridstr = line->_tokens[3];

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

    // The id is missing
    if (tridstr == nullptr) {
        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, "%d", &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 == (int)_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 == (int)_EventDefs.size());
        return;
    }

#ifdef DBG_PARSER_PAJE
    PajeDef::print(&_FieldNames, &_EventDefs[_defid]);
#endif
    _defid++;
    assert(_defid == (int)_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 == nullptr) {
        Error::set(Error::VITE_ERR_FIELD_NAME_MISSING, line->_id, Error::VITE_ERRCODE_ERROR);
        return;
    }

    // If fieldtype is not defined
    if (fieldtype == nullptr) {
        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
        // In case there are more than the default value
        if ((int)_FieldNames.size() <= _nbFieldNames) {
            _FieldNames.resize(2 * _nbFieldNames);
        }
        _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 nullptr;
    }
    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;
}
