/*
** 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 <sstream>
#include <string>
#include <map>
#include <queue>
#include <list>
/* -- */
#include <otf.h>
/* -- */
#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 <QWaitCondition>
#include <QSemaphore>
#include <QObject>
#include <QMutex>
/* -- */
#include "parser/OTFParser/ParserDefinitionOTF.hpp"
#include "parser/OTFParser/mt_ParserEventOTF.hpp"
#include "parser/OTFParser/OTFTraceBuilderThread.hpp"
/* -- */
using namespace std;


map <const String, Container *, String::less_than> OTFTraceBuilderThread::_containers;
std::map<uint32_t, Marker > OTFTraceBuilderThread::_marker;


OTFTraceBuilderThread::OTFTraceBuilderThread(QWaitCondition* cond,  QSemaphore * freeSlots, QMutex* mutex):_cond(cond), _freeSlots(freeSlots), _mutex(mutex){
}


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

  void OTFTraceBuilderThread::build_trace(int n_structs, OTF_Trace_builder_struct* tb_struct){
       for(int i=0; i<n_structs; i++){
           tb_struct[i].func(&tb_struct[i]);
                }
       _freeSlots->release();
       delete[] tb_struct;



       }


void  OTFTraceBuilderThread::build_finished(){
                //locks the mutex and automatically unlocks it when going out of scope
                QMutexLocker locker(_mutex);
        _is_finished=true;
            _cond->wakeAll();
}

int OTFTraceBuilderThread::handler_begin_process(OTF_Trace_builder_struct* tb_struct) {
    Trace *t = (Trace *)tb_struct->trace;

    Process current_process = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    Process process_parent = ParserDefinitionOTF::get_process_by_id(current_process._parent);
    ProcessGroup process_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);

    map<string, Value *> extra_fields;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();
    Name n = Name(current_process._name);

    ContainerType *process_container_type = t->search_container_type(String(process_group._name));
    Container *parent_container = t->search_container(String(process_parent._name));

    if(t->search_container(String(current_process._name))) {
        return OTF_RETURN_OK; // Already created so we quit the function
    }

    if(process_container_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + process_group._name, Error::VITE_ERRCODE_ERROR);
    }
    if(parent_container == 0 && process_parent._name != "0") {
        // Create the parent process
        tb_struct->process=current_process._parent;
        handler_begin_process(tb_struct);
        parent_container = t->search_container(String(process_parent._name));
    }

    t->create_container(d, n, process_container_type, parent_container, extra_fields);

    // We store the container in the map
    _containers[n.to_string()] = t->search_container(n.to_string());

    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_end_process (OTF_Trace_builder_struct* tb_struct) {
    Trace *t = (Trace *)tb_struct->trace;

    Process process = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    ProcessGroup process_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);

    map<string, Value *> extra_fields;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Container *temp_container = NULL;
    ContainerType *temp_container_type = t->search_container_type(String(process_group._name));

    const String process_name = String(process._name);
    if(_containers.find(process_name) != _containers.end()) {
        temp_container = _containers[process_name];
    }
    else {
        temp_container = t->search_container(process_name);
        _containers[process_name] = temp_container;
    }

    if(temp_container_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + process_group._name, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == 0 && process._name != "0") {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + process._name, Error::VITE_ERRCODE_ERROR);
    }

    t->destroy_container(d, temp_container, temp_container_type, extra_fields);
    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_enter (OTF_Trace_builder_struct* tb_struct) {
    Trace *t = (Trace *)tb_struct->trace;

    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Function &temp_function = ParserDefinitionOTF::get_function_by_id(tb_struct->process);
    const FunctionGroup temp_function_group = ParserDefinitionOTF::get_function_group_by_id(temp_function._func_group);
    Process temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process2);
    const ProcessGroup process_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process2);

    const String function_name = String(temp_function._name);
    const String function_group_name = String(temp_function_group._name);
    const String proc_name = String(temp_proc._name);

    Container *temp_container = NULL;
    ContainerType *temp_container_type = t->search_container_type(String(process_group._name));

    StateType *temp_state_type = t->search_state_type(function_group_name);
    EntityValue *temp_value = NULL;
    map<string, Value *> extra_fields;

    if(_containers.find(proc_name) != _containers.end()) {
        temp_container = _containers[proc_name];
    }
    else {
        temp_container = t->search_container(proc_name);
        _containers[proc_name] = temp_container;
    }

    if(temp_state_type == 0) {
        Name name_temp(temp_function_group._name);
        t->define_state_type(name_temp, temp_container_type, extra_fields);
        temp_state_type = t->search_state_type(function_group_name);
    }

    if(!temp_function._is_defined) {
        temp_function._is_defined = true;

        Name name_temp(temp_function._name);
        map<string, Value *> opt;

        /* Optional fields */
        FileLine source_file_locator;
        FileSource source_file;

        if(temp_function._file_source != 0) {
            source_file_locator = ParserDefinitionOTF::get_fileline_by_id(temp_function._file_source);
            source_file = ParserDefinitionOTF::get_filesource_by_id(source_file_locator._file_id);
        }

        if(source_file._name != "") {
            opt["File name"] = new String(source_file._name);
            opt["Line"] = new Integer(source_file_locator._line_number);
        }

        opt["Color"] = ParserDefinitionOTF::get_color(tb_struct->process);

        t->define_entity_value(name_temp, t->search_entity_type(function_group_name), opt);
    }

    temp_value = t->search_entity_value(function_name, temp_state_type);


    if(temp_container == NULL && temp_proc._name != "0") {
        // Creation of the container if not already done with beginProcess

        tb_struct->process=tb_struct->process2;
        handler_begin_process(tb_struct);
        temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process2);
        temp_container = t->search_container(proc_name);
        temp_container_type = t->search_container_type(proc_name);
    }

    // Check if nothing is empty
    if(temp_state_type == 0 && temp_function_group._name != "0") {
        Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + temp_function_group._name, Error::VITE_ERRCODE_ERROR);
    }
    if(temp_container == 0 && temp_proc._name != "0") {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + temp_function_group._name, Error::VITE_ERRCODE_ERROR);
    }
    if(temp_value == 0 && temp_proc._name != "0") {
        Error::set(Error::VITE_ERR_UNKNOWN_ENTITY_VALUE + temp_proc._name, Error::VITE_ERRCODE_ERROR);
    }

    t->set_state(d, temp_state_type, temp_container, temp_value, extra_fields);
    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_leave (OTF_Trace_builder_struct*) {
    //Trace *t = (Trace *)trace;

    //Date d = (double)time/(double)ParserDefinitionOTF::get_ticks_per_second();

    //Function temp_function = ParserDefinitionOTF::get_function_by_id(func_id);
    //Process temp_proc = ParserDefinitionOTF::get_process_by_id(proc_id);

    //StateType *temp_type = t->search_state_type(String(temp_function._name));
    //Container *temp_container = t->search_container(String(temp_proc._name));
    //EventType *temp_event_type = t->search_event_type(String(temp_function._name));
    //EntityValue *temp_value = t->search_entity_value(String(temp_function._name), temp_event_type);
    //map<string, Value *> extra_fields;

    //t->set_state(d, temp_type, temp_container, temp_value, extra_fields);
    //t->pop_state(d, temp_type, temp_container, extra_fields);

    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_counter(OTF_Trace_builder_struct* tb_struct) {
    Trace *t = (Trace *)tb_struct->trace;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Counter temp_counter = ParserDefinitionOTF::get_counter_by_id(tb_struct->process2);
    Process temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    CounterGroup temp_counter_group = ParserDefinitionOTF::get_counter_group_by_id(temp_counter._counter_group);
    Container *temp_container = NULL;
    map<string, Value *> extra_fields;

    String counter_group_name = String(temp_counter_group._name);

    VariableType *temp_variable_type = t->search_variable_type(counter_group_name);
    ProcessGroup process_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);
    ContainerType *temp_container_type = t->search_container_type(String(process_group._name));

    const String temp_proc_name = String(temp_proc._name);

    if(_containers.find(temp_proc_name) != _containers.end()) {
        temp_container = _containers[temp_proc_name];
    }
    else {
        temp_container = t->search_container(temp_proc_name);
        _containers[temp_proc_name] = temp_container;
    }

    if(temp_variable_type == 0) {
        Name name_temp(temp_counter_group._name);
        t->define_variable_type(name_temp, temp_container_type, extra_fields);
        temp_variable_type = t->search_variable_type(counter_group_name);
    }

    if(temp_container == NULL && temp_proc._name != "0") {
        // Creation of the container if not already done with beginProcess

        handler_begin_process(tb_struct);
        temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
        temp_container = t->search_container(temp_proc_name);
    }

    // Check if nothing is empty
    if(temp_variable_type == 0 && temp_counter_group._name != "0") {
        Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + temp_counter_group._name, Error::VITE_ERRCODE_ERROR);
    }
    if(temp_container == 0 && temp_proc._name != "0") {
            Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + temp_counter_group._name, Error::VITE_ERRCODE_ERROR);
    }

    t->set_variable(d, temp_variable_type, temp_container, tb_struct->value, extra_fields);
    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_defmarker(OTF_Trace_builder_struct* tb_struct) {
    Marker temp = {tb_struct->text, tb_struct->process, tb_struct->type};
    OTFTraceBuilderThread::_marker[tb_struct->process2] = temp;
    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_marker(OTF_Trace_builder_struct* tb_struct) {
    Trace *t = (Trace *)tb_struct->trace;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Marker temp_marker = _marker[tb_struct->process2];
    Process temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    Container *temp_container = NULL;
    map<string, Value *> extra_fields;

    String str_eventType(temp_marker._name);
    String str_event(tb_struct->text);
    Name n(tb_struct->text);
    EventType *event_type = t->search_event_type(str_eventType);

    ProcessGroup process_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);
    ContainerType *temp_container_type = t->search_container_type(String(process_group._name));

    const String temp_proc_name = String(temp_proc._name);

    if(_containers.find(temp_proc_name) != _containers.end()) {
        temp_container = _containers[temp_proc_name];
    }
    else {
        temp_container = t->search_container(temp_proc_name);
        _containers[temp_proc_name] = temp_container;
    }

    if(event_type == 0) {
        Name name_temp(temp_marker._name);
        t->define_event_type(name_temp, temp_container_type, extra_fields);
        event_type = t->search_event_type(str_eventType);
    }

    if(temp_container == NULL && temp_proc._name != "0") {
        // Creation of the container if not already done with beginProcess
        handler_begin_process(tb_struct);
        temp_proc = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
        temp_container = t->search_container(temp_proc_name);
    }

    // Check if nothing is empty
    if(event_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_EVENT_TYPE + n.to_string(), Error::VITE_ERRCODE_ERROR);
    }
    if(temp_container == 0 && temp_proc._name != "0") {
        //Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + temp_counter_group._name, Error::VITE_ERRCODE_ERROR);
    }

    t->new_event(d, event_type, temp_container, str_event, extra_fields);
    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_send_message (OTF_Trace_builder_struct* tb_struct ) {
    // We define the LinkType if not exist and we store the time and other fields
    Trace *t  = (Trace *)tb_struct->trace;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Process temp_sender = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    Process temp_receiver = ParserDefinitionOTF::get_process_by_id(tb_struct->process2);
    // The sender process may have no ancestor, so let's say that his ancestor is himself
    Process temp_ancestor = temp_sender;
    ProcessGroup sender_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);
    ProcessGroup receiver_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process2);
    ProcessGroup ancestor_group = ParserDefinitionOTF::get_processgroup_by_process(temp_sender._parent);

    String sender_string = String(temp_sender._name);
    String ancestor_string = String(temp_ancestor._name);
    String receiver_string = String(temp_receiver._name);

    String sender_group_string = String(sender_group._name);
    String ancestor_group_string = String(ancestor_group._name);
    String receiver_group_string = String(receiver_group._name);

    /* Value */
    string name = temp_sender._name + " to " + temp_receiver._name;
    String name_string = String(name);

    ostringstream link_type_oss;
    link_type_oss << tb_struct->type;
    String link_type_string = String(link_type_oss.str());

    Name name_temp = Name(name);
    LinkType *link_type = t->search_link_type(link_type_string);

    Container *source_container = NULL;
    Container *ancestor_container = NULL;

    ContainerType *source_type = t->search_container_type(sender_group_string);
    ContainerType *destination_type = t->search_container_type(receiver_group_string);
    ContainerType *ancestor_type = t->search_container_type(ancestor_group_string);

    map<string, Value *> opt;

    if(_containers.find(sender_string) != _containers.end()) {
        source_container = _containers[sender_string];
    }
    else {
        source_container = t->search_container(sender_string);
        _containers[sender_string] = source_container;
    }

    if(_containers.find(ancestor_string) != _containers.end() && !_containers.empty()) {
        // found
        ancestor_container = _containers[ancestor_string];
    }
    else {
        // receiver not found
        ancestor_container = t->search_container(ancestor_string);
        if(ancestor_container) {
            _containers[ancestor_string] = ancestor_container;
      }
    }

    if(ancestor_type == 0) {
        // No ancestor
        ancestor_type = source_type;
    }

    if(ancestor_container == 0) {
        // No ancestor
        ancestor_container = source_container;
    }

    if(link_type == 0) {
        Name link_name = Name(link_type_oss.str());
        t->define_link_type(link_name, ancestor_type, destination_type, destination_type, opt);
        link_type = t->search_link_type(link_type_string);
    }

    // Check if nothing is empty
    if(source_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + temp_sender._name, Error::VITE_ERRCODE_ERROR);
    }
    if(destination_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + temp_receiver._name, Error::VITE_ERRCODE_ERROR);
    }
    if(ancestor_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + temp_ancestor._name, Error::VITE_ERRCODE_ERROR);
    }
    if(link_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + link_type_oss.str(), Error::VITE_ERRCODE_ERROR);
    }

    /* Creation of the optional fields */
    if(tb_struct->group != 0) {
        ProcessGroup proc_group = ParserDefinitionOTF::get_processgroup_by_id(tb_struct->group);
        String *proc_group_string = new String(proc_group._name);
        opt["ProcessGroup"] = proc_group_string;
    }

    if(tb_struct->length != 0) {
        Integer *length_int = new Integer(tb_struct->length);
        opt["Length"] = length_int;
    }

    if(tb_struct->source != 0) {
        FileLine source_file_locator;
        FileSource source_file;
        source_file_locator = ParserDefinitionOTF::get_fileline_by_id(tb_struct->source);
        source_file = ParserDefinitionOTF::get_filesource_by_id(source_file_locator._file_id);
        if(source_file._name != "") {
            opt["File name"] = new String(source_file._name);
            opt["Line"] = new Integer(source_file_locator._line_number);
        }
    }

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

    return OTF_RETURN_OK;
}

int OTFTraceBuilderThread::handler_receive_message (OTF_Trace_builder_struct* tb_struct) {

    Trace *t  = (Trace *)tb_struct->trace;
    Date d = (double)tb_struct->time/(double)ParserDefinitionOTF::get_ticks_per_second();

    Process temp_sender = ParserDefinitionOTF::get_process_by_id(tb_struct->process2);
    Process temp_receiver = ParserDefinitionOTF::get_process_by_id(tb_struct->process);
    // The sender process may have no ancestor, so let's say that his ancestor is himself
    Process temp_ancestor = temp_sender;
    String sender_string = String(temp_sender._name);
    String ancestor_string = String(temp_ancestor._name);
    String receiver_string = String(temp_receiver._name);

    ProcessGroup sender_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process2);
    ProcessGroup receiver_group = ParserDefinitionOTF::get_processgroup_by_process(tb_struct->process);
    ProcessGroup ancestor_group = ParserDefinitionOTF::get_processgroup_by_process(temp_sender._parent);

    String sender_group_string = String(sender_group._name);
    String ancestor_group_string = String(ancestor_group._name);
    String receiver_group_string = String(receiver_group._name);

    /* Value */
    string name = temp_sender._name + " to " + temp_receiver._name;

    String name_string = String(name);

    ostringstream link_type_oss;
    link_type_oss << tb_struct->type;
    String link_type_string = String(link_type_oss.str());

    Name name_temp = Name(name);
    LinkType *link_type = t->search_link_type(link_type_string);

    Container *destination_cont = NULL;
    Container *ancestor_cont = NULL;
    Container *sender_cont = NULL;

    ContainerType *destination_type = t->search_container_type(receiver_group_string);
    ContainerType *ancestor_type = t->search_container_type(ancestor_group_string);
    ContainerType *sender_type = t->search_container_type(sender_group_string);

    map<string, Value *> opt;

    if(_containers.find(receiver_string) != _containers.end()) {
        destination_cont = _containers[receiver_string];
    }
    else {
        destination_cont = t->search_container(receiver_string);
        _containers[receiver_string] = destination_cont;
    }

    if(_containers.find(ancestor_string) != _containers.end() && !_containers.empty()) {
        ancestor_cont = _containers[ancestor_string];
    }
    else {
        ancestor_cont = t->search_container(ancestor_string);
        if(ancestor_cont)
            _containers[ancestor_string] = ancestor_cont;
    }

    if(ancestor_type == 0) {
        // No ancestor
        ancestor_type = sender_type;
    }

    if(ancestor_cont == 0) {
        // No ancestor
        ancestor_cont = sender_cont;
    }

    if(link_type == 0) {
        Name link_name = Name(link_type_oss.str());
        t->define_link_type(link_name, ancestor_type, destination_type, destination_type, opt);
        link_type = t->search_link_type(link_type_string);
    }

    // Check if nothing is empty
    if(destination_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + temp_receiver._name, Error::VITE_ERRCODE_ERROR);
    }
    if(ancestor_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + temp_ancestor._name, Error::VITE_ERRCODE_ERROR);
    }
    if(link_type == 0) {
        Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + link_type_oss.str(), Error::VITE_ERRCODE_ERROR);
    }

    /* Creation of the optional fields */
    if(tb_struct->group != 0) {
        ProcessGroup proc_group = ParserDefinitionOTF::get_processgroup_by_id(tb_struct->group);
        String *proc_group_string = new String(proc_group._name);
        opt["ProcessGroup"] = proc_group_string;
    }

    if(tb_struct->length != 0) {
        Integer *length_int = new Integer(tb_struct->length);
        opt["Length"] = length_int;
    }

    if(tb_struct->source != 0) {
        FileLine source_file_locator;
        FileSource source_file;
        source_file_locator = ParserDefinitionOTF::get_fileline_by_id(tb_struct->source);
        source_file = ParserDefinitionOTF::get_filesource_by_id(source_file_locator._file_id);
        if(source_file._name != "") {
            opt["File name"] = new String(source_file._name);
            opt["Line"] = new Integer(source_file_locator._line_number);
        }
    }

    t->end_link(d, link_type, ancestor_cont, destination_cont, name_string, opt);
    return OTF_RETURN_OK;
}
