#include <iostream>
#include <stdio.h>
#include <string>
#include <queue>
#include <map>

#include <QWaitCondition>
#include <QObject>
#include <QThread>
/* -- */

#include "trace/values/Values.hpp"
#include "trace/EntityValue.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/Trace.hpp"
#include "trace/tree/Node.hpp"
#include "trace/tree/BinaryTree.hpp"
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp" /* Dirty, should be remove */
#include "common/Message.hpp"
#include "common/Errors.hpp"
/* -- */
#include "trace/tree/Interval.hpp"
#include <trace/TraceBuilderThread.hpp>

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

void TraceBuilderThread::build_trace(int n_structs, 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  TraceBuilderThread::build_finished(){
    //locks the mutex and automatically unlocks it when going out of scope
    QMutexLocker locker(_mutex);
    _is_finished=true;
    _cond->wakeAll();
}

void TraceBuilderThread::define_container_type(Trace_builder_struct* tb_struct){
    ContainerType *temp_container_type = tb_struct->_trace->search_container_type(tb_struct->type);
    if( (temp_container_type == NULL) && (tb_struct->type.to_string() != "0") ){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        tb_struct->_trace->define_container_type(tb_struct->alias, temp_container_type, tb_struct->extra_fields);
    }
}

void TraceBuilderThread::create_container(Trace_builder_struct* tb_struct){
    ContainerType *temp_container_type = tb_struct->_trace->search_container_type(tb_struct->type);
    Container *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {

        temp_container =( (*it).second);
    }
    else {
        temp_container =tb_struct->_trace->search_container(tb_struct->container);
    }

    if(temp_container_type == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        if(tb_struct->extra_fields["FileName"]) {
            if(tb_struct->_parser) {
                tb_struct->_parser->parse(tb_struct->extra_fields["FileName"]->to_string(),
                                          *tb_struct->_trace);
            }else {
                // This should never happen.
                // TODO: raise an error ?
            }
        }
        Container* new_cont = tb_struct->_trace->create_container(tb_struct->time, tb_struct->alias, temp_container_type, temp_container, tb_struct->extra_fields);
        // We store the container in the map
        (*tb_struct->_containers)[String(tb_struct->alias.get_alias())] = new_cont;//tb_struct->_trace->search_container(tb_struct->alias.to_string());


    }
}





void TraceBuilderThread::destroy_container(Trace_builder_struct* tb_struct){

    Container *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( String(tb_struct->alias.get_alias()));
    if( it != (*tb_struct->_containers).end()) {

        temp_container =( (*it).second);
    }
    else {
        temp_container =tb_struct->_trace->search_container(tb_struct->alias.to_string());
    }

    //Container *temp_container = tb_struct->_trace->search_container(tb_struct->alias.to_string());
    ContainerType *temp_container_type = tb_struct->_trace->search_container_type(tb_struct->type);
    if(temp_container == NULL && tb_struct->alias.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->alias.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->destroy_container(tb_struct->time, temp_container, temp_container_type, tb_struct->extra_fields);
    }

}


void TraceBuilderThread::define_event_type(Trace_builder_struct* tb_struct){
    ContainerType *temp_container_type = tb_struct->_trace->search_container_type( tb_struct->type);
    if(temp_container_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->define_event_type( tb_struct->alias, temp_container_type, tb_struct->extra_fields);
    }

}


void TraceBuilderThread::define_state_type(Trace_builder_struct* tb_struct){

    ContainerType *temp_container_type = tb_struct->_trace->search_container_type( tb_struct->type);
    if(temp_container_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->define_state_type( tb_struct->alias, temp_container_type,tb_struct->extra_fields);
    }
}


void TraceBuilderThread::define_variable_type(Trace_builder_struct* tb_struct){

    ContainerType *temp_container_type = tb_struct->_trace->search_container_type( tb_struct->type);
    if(temp_container_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->define_variable_type( tb_struct->alias, temp_container_type,tb_struct->extra_fields);
    }



}


void TraceBuilderThread::define_link_type(Trace_builder_struct* tb_struct){

    ContainerType *temp_container_type = tb_struct->_trace->search_container_type( tb_struct->type);
    ContainerType *temp_start_container_type = tb_struct->_trace->search_container_type(tb_struct->start_container_type);
    ContainerType *temp_end_container_type = tb_struct->_trace->search_container_type(tb_struct->end_container_type);
    if(temp_container_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_start_container_type == NULL && tb_struct->start_container_type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->start_container_type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_end_container_type == NULL && tb_struct->end_container_type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE + tb_struct->end_container_type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->define_link_type( tb_struct->alias, temp_container_type, temp_start_container_type, temp_end_container_type,tb_struct->extra_fields);
    }
}


void TraceBuilderThread::define_entity_value(Trace_builder_struct* tb_struct) {

    EntityType *temp_entity_type = tb_struct->_trace->search_entity_type(tb_struct->type);
    if(temp_entity_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_ENTITY_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(extra_fields==NULL)extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->define_entity_value( tb_struct->alias, temp_entity_type,tb_struct->extra_fields);
    }
}


void TraceBuilderThread::set_state(Trace_builder_struct* tb_struct){

    StateType   *temp_state_type   = tb_struct->_trace->search_state_type( tb_struct->type);

    EntityValue *temp_entity_value = tb_struct->_trace->search_entity_value(tb_struct->value_string, temp_state_type);

    Container   *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_state_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        tb_struct->_trace->set_state( tb_struct->time, temp_state_type, temp_container, temp_entity_value,tb_struct->extra_fields);
    }

}


void TraceBuilderThread::push_state(Trace_builder_struct* tb_struct){

    StateType   *temp_state_type = tb_struct->_trace->search_state_type( tb_struct->type);
    EntityValue *temp_entity_value = tb_struct->_trace->search_entity_value(tb_struct->value_string, temp_state_type);

    Container   *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_state_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        tb_struct->_trace->push_state( tb_struct->time, temp_state_type, temp_container, temp_entity_value,tb_struct->extra_fields);
    }

}


void TraceBuilderThread::pop_state(Trace_builder_struct* tb_struct){

    StateType *temp_state_type =  tb_struct->_trace->search_state_type( tb_struct->type);
    Container *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container =  tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_state_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_STATE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->pop_state( tb_struct->time, temp_state_type, temp_container,tb_struct->extra_fields);
    }
}


void TraceBuilderThread::new_event(Trace_builder_struct* tb_struct){

    EventType   *temp_event_type = tb_struct->_trace->search_event_type( tb_struct->type);
    Container   *temp_container  = NULL;

    Container_map::const_iterator it;
    it = (*tb_struct->_containers).find( tb_struct->container);

    if( it != (*tb_struct->_containers).end()) {
        temp_container = (*it).second;
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container );
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if( (temp_event_type == NULL) && (tb_struct->type.to_string() != "0") )
    {
        Error::set( Error::VITE_ERR_UNKNOWN_EVENT_TYPE + tb_struct->type.to_string(),
                    tb_struct->_id, Error::VITE_ERRCODE_ERROR );
    }
    else if( (temp_container == NULL) && (tb_struct->container.to_string() != "0") )
    {
        Error::set( Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(),
                    tb_struct->_id, Error::VITE_ERRCODE_ERROR );
    }
    else{
        tb_struct->_trace->new_event( tb_struct->time, temp_event_type,
                                      temp_container, tb_struct->value_string,
                                      tb_struct->extra_fields );
    }
}


void TraceBuilderThread::set_variable(Trace_builder_struct* tb_struct){

    VariableType *temp_variable_type = tb_struct->_trace->search_variable_type( tb_struct->type);

    Container    *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_variable_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->set_variable( tb_struct->time, temp_variable_type, temp_container, tb_struct->value_double,tb_struct->extra_fields);
    }

}


void TraceBuilderThread::add_variable(Trace_builder_struct* tb_struct){

    VariableType *temp_variable_type = tb_struct->_trace->search_variable_type( tb_struct->type);
    Container    *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_variable_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->add_variable( tb_struct->time, temp_variable_type, temp_container, tb_struct->value_double,tb_struct->extra_fields);
    }

}



void TraceBuilderThread::sub_variable(Trace_builder_struct* tb_struct){

    VariableType *temp_variable_type =  tb_struct->_trace->search_variable_type( tb_struct->type);
    Container    *temp_container = NULL;
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container =  tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }

    if(temp_variable_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->sub_variable( tb_struct->time, temp_variable_type, temp_container, tb_struct->value_double,tb_struct->extra_fields);
    }
}





void TraceBuilderThread::start_link(Trace_builder_struct* tb_struct){

    LinkType    *temp_link_type = tb_struct->_trace->search_link_type( tb_struct->type);

    Container   *temp_container = NULL;
    Container   *temp_start_container = NULL;
    // temp_container
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }
    // temp_start_container
    if((*tb_struct->_containers).find(tb_struct->start_container) != (*tb_struct->_containers).end()) {
        temp_start_container = (*tb_struct->_containers)[tb_struct->start_container];
    }
    else {
        temp_start_container = tb_struct->_trace->search_container(tb_struct->start_container);
        (*tb_struct->_containers)[tb_struct->start_container] = temp_start_container;
    }

    if(temp_container == NULL && tb_struct->container.to_string() == "0"){
        temp_container= tb_struct->_trace->get_root_containers()->front();
    }

    if(temp_link_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_start_container == NULL && tb_struct->start_container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->start_container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        // if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->start_link( tb_struct->time, temp_link_type, temp_container, temp_start_container, tb_struct->key,tb_struct->extra_fields);
    }




}

void TraceBuilderThread::end_link(Trace_builder_struct* tb_struct){
    LinkType    *temp_link_type = tb_struct->_trace->search_link_type( tb_struct->type);

    Container   *temp_container = NULL;
    Container   *temp_end_container = NULL;
    // temp_container
    Container_map::const_iterator it=(*tb_struct->_containers).find( tb_struct->container);
    if( it != (*tb_struct->_containers).end()) {
        temp_container =( (*it).second);
    }
    else {
        temp_container = tb_struct->_trace->search_container( tb_struct->container);
        (*tb_struct->_containers)[tb_struct->container] = temp_container;
    }
    // temp_end_container
    if((*tb_struct->_containers).find(tb_struct->end_container) != (*tb_struct->_containers).end()) {
        temp_end_container = (*tb_struct->_containers)[tb_struct->end_container];
    }
    else {
        temp_end_container = tb_struct->_trace->search_container(tb_struct->end_container);
        (*tb_struct->_containers)[tb_struct->end_container] = temp_end_container;
    }

    //if message father is the root container, assign it to our first root container
    if(temp_container == NULL && tb_struct->container.to_string() == "0"){
        temp_container= tb_struct->_trace->get_root_containers()->front();
    }

    if(temp_link_type == NULL && tb_struct->type.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_LINK_TYPE + tb_struct->type.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_container == NULL && tb_struct->container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else if(temp_end_container == NULL && tb_struct->end_container.to_string() != "0"){
        Error::set(Error::VITE_ERR_UNKNOWN_CONTAINER + tb_struct->end_container.to_string(), tb_struct->_id, Error::VITE_ERRCODE_ERROR);
    }
    else{
        //if(tb_struct->extra_fields==NULL)tb_struct->extra_fields=new map<std::string, Value *>();
        tb_struct->_trace->end_link( tb_struct->time, temp_link_type, temp_container, temp_end_container, tb_struct->key,tb_struct->extra_fields);
    }
}

bool TraceBuilderThread::is_finished(){
    return _is_finished;
}
