/*
** 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 <cstdlib>
#include <cstdio>
#include <sstream>
#include <iostream>
#include <string>
#include <map>
#include <list>
/* -- */
#include <TAU_tf.h>
/* -- */
#include "trace/values/Values.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Trace.hpp"
/* -- */
#include "parser/Parser.hpp"
#include "parser/TauParser/ParserTau.hpp"
/* -- */

using namespace std;

double ParserTau::_clock_period = 1.;
map <string, Tau::Container>  ParserTau::_containers;
map <int,    Tau::State>      ParserTau::_states;
map <int,    Tau::StateGroup> ParserTau::_states_groups;
map <int,    Tau::Event>      ParserTau::_events;

ParserTau::ParserTau() {};
ParserTau::ParserTau(const std::string &filename) : Parser(filename) {};
ParserTau::~ParserTau() {};

void ParserTau::parse(Trace &trace, bool finish_trace_after_parse) {

    const string base_name = _file_to_parse.substr(0, _file_to_parse.find_last_of('.'));
    const string file_trc = base_name + ".trc";
    const string file_edf = base_name + ".edf";

    int records_read = 0;

    Ttf_FileHandleT fh = Ttf_OpenFileForInput(file_trc.c_str(), file_edf.c_str());

    if(!fh) {
        cerr << "unable to open files : " << file_trc << " and " << file_edf << endl;
        return;
    }
    
    /* Fill the tau callback struct */
    _callbacks.UserData = &trace;
    _callbacks.DefClkPeriod  = ParserTau::set_timer_resolution;
    _callbacks.DefThread     = ParserTau::def_process;
    _callbacks.DefStateGroup = ParserTau::def_state_group;
    _callbacks.DefState      = ParserTau::def_state;
    _callbacks.DefUserEvent  = ParserTau::def_event;
    _callbacks.EventTrigger  = ParserTau::event_triggered;
    _callbacks.EndTrace      = ParserTau::end_trace;
    _callbacks.EnterState    = ParserTau::enter_state;
    _callbacks.LeaveState    = ParserTau::leave_state;
    _callbacks.SendMessage   = ParserTau::send_message;
    _callbacks.RecvMessage   = ParserTau::receive_message;

    /* Creation of the ContainerType. Just one? */
    // Create the container type
    Name name("threads", "");
    map<std::string, Value *> extra_fields;
    trace.define_container_type(name, NULL, extra_fields);

    /* Beginning of the reading */
    do {
        records_read = Ttf_ReadNumEvents(fh, _callbacks, 10000);
        cout <<"Read "<<records_read<<" records"<<endl;
    }
    while ((records_read > 0)//  && !(_is_finished || _is_canceled)
           );

    Ttf_CloseFile(fh);

    finish();

    if(finish_trace_after_parse) { // true by default
        trace.finish();
    }

}

int ParserTau::set_timer_resolution(void */*trace*/, double clkPeriod) { 
    _clock_period = clkPeriod;
    //printf("Clockperiod %lf\n", _clock_period);
    return 0;
}

int ParserTau::def_process(void */*trace*/, unsigned int nodeToken, unsigned int threadToken, const char *threadName) {
    //printf("DefThread nid %d tid %d, thread name %s\n", nodeToken, threadToken, threadName);
    ostringstream oss;
    oss << nodeToken << "-" << threadToken; 
    _containers[oss.str()] = Tau::Container(nodeToken, threadToken, threadName);    
    return 0;
}

int ParserTau::def_state(void */*trace*/, unsigned int stateToken, const char *stateName, unsigned int stateGroupToken) {
    //printf("DefState stateid %d stateName %s stategroup id %d\n", stateToken, stateName, stateGroupToken);
    _states[stateToken] = Tau::State(stateGroupToken, stateToken, stateName);
    return 0;
}

int ParserTau::def_state_group(void */*trace*/, unsigned int stateGroupToken, const char *stateGroupName) {
    //printf("StateGroup groupid %d, group name %s\n", stateGroupToken, stateGroupName);
    _states_groups[stateGroupToken] = Tau::StateGroup(stateGroupToken, stateGroupName);
    return 0;
}

int ParserTau::def_event(void */*trace*/, unsigned int userEventToken, const char *userEventName, int monotonicallyIncreasing) {
    //printf("DefUserEvent event id %d user event name %s, monotonically increasing = %d\n", userEventToken, userEventName, monotonicallyIncreasing);
    _events[userEventToken] = Tau::Event(userEventToken, userEventName, monotonicallyIncreasing);
    return 0;
}


int ParserTau::enter_state (void *trace, double time, unsigned int nodeid, unsigned int tid, unsigned int stateid) {
    Trace *t = (Trace *)trace;
    
    map<string, Value *> extra_fields;

    Date d = time;

    /* Recuperation of tau elements */
    Tau::State &current_state = _states[stateid];
    const Tau::StateGroup &current_state_group = _states_groups[current_state._state_group];

    ostringstream oss;
    oss << nodeid << "-" << tid;
    Tau::Container &current_proc = _containers[oss.str()];

    /* Look for the correspondances in the Trace module */
    const String state_name = String(current_state._name);
    const String state_group_name = String(current_state_group._name);
    
    Name name_container(current_proc._name, "");

    Container *current_container = t->search_container(current_proc._name);
    Container *parent_container = NULL; /* Always in Tau or set to nodeId? -> can be a tree */

    ContainerType *process_container_type = t->search_container_type(String("threads"));
    StateType *state_type = t->search_state_type(state_group_name);

    EntityValue *entity_value = NULL;

    /* Creation of the containerType if not exist */
    if(!process_container_type) {
        Name container_type_name("threads", "");
        t->define_container_type(container_type_name, NULL, extra_fields);
        process_container_type = t->search_container_type(String("threads"));
    }

    /* Creation of the container if not exist */
    if(!current_proc._is_created) {
        t->create_container(d, name_container, process_container_type, parent_container, extra_fields);
        current_container = t->search_container(current_proc._name);
        current_proc._is_created = true;
    }

    /* Creation of the stategroup (StateType) if not exist */
    if(!state_type) {
        Name state_type_name(current_state_group._name, "");
        t->define_state_type(state_type_name, process_container_type, extra_fields);
        state_type = t->search_state_type(current_state_group._name);
    }

    /* Creation of the state if not exist */
    if(!current_state._is_created) {
        current_state._is_created = true;
        Name name_temp(current_state._name, "");
        map<string, Value *> opt;
        t->define_entity_value(name_temp, t->search_entity_type(state_group_name), opt);
    }

    entity_value = t->search_entity_value(state_name, state_type);

    t->push_state(d, state_type, current_container, entity_value, extra_fields);

    //printf("Entered state %d time %g nid %d tid %d\n", stateid, time, nodeid, tid);
    return 0;
}

int ParserTau::leave_state (void *trace, double time, unsigned int nid, unsigned int tid, unsigned int stateid) {
    Trace *t = (Trace *)trace;
    
    Date d = time;

    /* Recuperation of tau elements */
    ostringstream oss;
    oss << nid << "-" << tid;
    const Tau::Container &current_proc = _containers[oss.str()];

    const Tau::State &current_state = _states[stateid];
    const String state_name = String(current_state._name);

    /* Look for the correspondances in the Trace module */
    Container *current_container = t->search_container(current_proc._name);

    StateType *state_type = t->search_state_type(state_name);
    map<string, Value *> extra_fields;

    t->pop_state(d, state_type, current_container, extra_fields);
    //printf("Leaving state %d time %g nid %d tid %d\n", stateid, time, nid, tid);
    return 0;
}

int ParserTau::event_triggered(void *trace, double time, unsigned int nodeToken, unsigned int threadToken, unsigned int userEventToken, long long userEventValue) {
    Trace *t = (Trace *)trace;

    Date d = time;

    /* Recuperation of tau elements */
    ostringstream oss;
    oss << nodeToken << "-" << threadToken;
    const Tau::Container &current_proc = _containers[oss.str()];

    const Tau::Event &current_event = _events[userEventToken];

    /* Look for the correspondances in the Trace module */
    const String event_name = String(current_event._name);
    
    ostringstream oss2;
    oss2 << userEventValue;
    const String value_string(oss2.str());
    
    Container *current_container = t->search_container(current_proc._name);

    EventType *event_type = t->search_event_type(event_name);
    map<string, Value *> opt;

    /* Creation if needed */
    if(event_type == NULL) {
        Name event_type_name(current_event._name, "");
        ContainerType *container_type = t->search_container_type(String("threads"));
        t->define_event_type(event_type_name, container_type, opt);
        event_type = t->search_event_type(event_name);
    }


    EntityValue *entity_value = t->search_entity_value(value_string, event_type);

    t->new_event(d, event_type, current_container, entity_value, opt);

    //printf("EventTrigger: time %g, nid %d tid %d event id %d triggered value %lld \n", time, nodeToken, threadToken, userEventToken, userEventValue);
    return 0;
}

int ParserTau::send_message (void *trace, double time, unsigned int sourceNodeToken, unsigned int sourceThreadToken, unsigned int destinationNodeToken, unsigned int destinationThreadToken, unsigned int messageSize, unsigned int messageTag, unsigned int messageComm) {
    Trace *t  = (Trace *)trace;
    Date d = time;

    ostringstream oss_source;
    oss_source << sourceNodeToken << "-" << sourceThreadToken;
    Tau::Container &source_proc = _containers[oss_source.str()];

    ostringstream oss_destination;
    oss_destination << destinationNodeToken << "-" << destinationThreadToken;
    Tau::Container &destination_proc = _containers[oss_destination.str()];

    ostringstream current_link;
    current_link  << oss_source.str() << " to " << oss_destination.str();

    String source_string = String(source_proc._name);
    String destination_string = String(destination_proc._name);

    Container *source_container = t->search_container(source_proc._name);
    Container *ancestor_container = source_container; // Should be a common ancestor of both containers...

    ContainerType *container_type = t->search_container_type(String("threads"));

    /* LinkType */
    LinkType *link_type = t->search_link_type(current_link.str());

    /* Value of the message */
    ostringstream oss_value;
    oss_value << messageComm;
    String value_str(oss_value.str());

    /* Creation if needed */
    if(link_type == NULL) {
        Name link_type_name(current_link.str(), "");
        map<string, Value *> opt;
        t->define_link_type(link_type_name, container_type, container_type, container_type, opt);
        link_type = t->search_link_type(current_link.str());
    }

    EntityValue *value = t->search_entity_value(value_str, link_type);

    /* Key of the message */
    ostringstream oss_key;
    oss_key << messageTag;
    String key(oss_key.str());

    map<string, Value *> opt;

    t->start_link(d, link_type, ancestor_container, source_container, value, key, opt);

    printf("SendMessage: time %g, source nid %d tid %d, destination nid %d tid %d, size %d, tag %d\n", time, sourceNodeToken, sourceThreadToken, destinationNodeToken, destinationThreadToken, messageSize, messageTag);
    return 0;
}

int ParserTau::receive_message (void *trace, double time, unsigned int sourceNodeToken, unsigned int sourceThreadToken, unsigned int destinationNodeToken, unsigned int destinationThreadToken, unsigned int messageSize, unsigned int messageTag, unsigned int messageComm) {
    Trace *t  = (Trace *)trace;
    Date d = time;

    ostringstream oss_source;
    oss_source << sourceNodeToken << "-" << sourceThreadToken;
    Tau::Container &source_proc = _containers[oss_source.str()];

    ostringstream oss_destination;
    oss_destination << destinationNodeToken << "-" << destinationThreadToken;
    Tau::Container &destination_proc = _containers[oss_destination.str()];

    ostringstream current_link;
    current_link  << oss_source.str() << " to " << oss_destination.str();

    String source_string = String(source_proc._name);
    String destination_string = String(destination_proc._name);

    Container *destination_container = t->search_container(destination_proc._name);
    Container *ancestor_container = t->search_container(source_proc._name); // Should be an ancestor of both containers from the Pajé norm but impossible here (no parent in common)

    ContainerType *container_type = t->search_container_type(String("threads"));

    /* LinkType */
    LinkType *link_type =  t->search_link_type(current_link.str());

    /* Value of the message */
    ostringstream oss_value;
    oss_value << messageComm;
    String value_str(oss_value.str());

    /* Creation if needed */
    if(link_type == NULL) {
        Name link_type_name(current_link.str(), "");
        map<string, Value *> opt;
        t->define_link_type(link_type_name, container_type, container_type, container_type, opt);
        link_type = t->search_link_type(current_link.str());
    }

    EntityValue *value = t->search_entity_value(value_str, link_type);

    /* Key of the message */
    ostringstream oss_key;
    oss_key << messageTag;
    String key(oss_key.str());

    map<string, Value *> opt;

    t->end_link(d, link_type, ancestor_container, destination_container, value, key, opt);

    printf("RecvMessage: time %g, source nid %d tid %d, destination nid %d tid %d, size %d, tag %d\n", time, sourceNodeToken, sourceThreadToken, destinationNodeToken, destinationThreadToken, messageSize, messageTag);
    return 0;
}

int ParserTau::end_trace(void *trace, unsigned int nodeToken, unsigned int threadToken) {
    Trace *t  = (Trace *)trace;
    ostringstream oss;
    oss << nodeToken << "-" << threadToken;
    Tau::Container &current_proc = _containers[oss.str()];

    Date time = t->get_max_date();
    Container *current_container = t->search_container(current_proc._name);
    ContainerType *container_type = t->search_container_type(String("threads"));
    map<string, Value *> opt;
    t->destroy_container(time, current_container, container_type, opt);

    //printf("EndTrace nid %d tid %d\n", nodeToken, threadToken);
    return 0;
}

float ParserTau::get_percent_loaded() const {
    //return ParserEvent::get_size_loaded();
    return 0;
}

