/**
 *
 * @file src/trace/TraceBuilderThread.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Philippe Swartvagher
 * @author Augustin Degomme
 *
 * @date 2024-07-17
 */
#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, /* TODO: EntityValue */ NULL, 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;
}

#include "moc_TraceBuilderThread.cpp"
