/**
 *
 * @file src/trace/DrawTrace.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 *
 * @date 2024-07-17
 */
#include <cassert>
#include <cmath>
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
/* -- */
#include <QObject>
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
#include "common/Log.hpp"
#include "common/Message.hpp"
/* -- */
#include "interface/Interface_graphic.hpp"
/* -- */
#include "trace/values/Values.hpp"
/* -- */
#include "trace/tree/Interval.hpp"
#include "trace/tree/Node.hpp"
#include "trace/tree/BinaryTree.hpp"
/* -- */
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/EntityValue.hpp"
#include "trace/Trace.hpp"
#include "trace/DrawTree.hpp"
// #include "trace/values/Value.hpp"
#include "trace/DrawTrace.hpp"

DrawTrace::DrawTrace() {
    _container_width = _DRAWING_CONTAINER_WIDTH_DEFAULT;
    _container_height = _DRAWING_CONTAINER_HEIGHT_DEFAULT;
    _container_h_space = _DRAWING_CONTAINER_H_SPACE_DEFAULT;
    _container_v_space = _DRAWING_CONTAINER_V_SPACE_DEFAULT;

    _state_height = _DRAWING_STATE_HEIGHT_DEFAULT;
    _state_v_space = _DRAWING_STATE_V_SPACE_DEFAULT;
}

DrawTrace::~DrawTrace() = default;

void print_extra_fields(const std::string &name, const std::map<std::string, Value *> *extra_fields, std::ostream *message_buffer) {
    if (extra_fields != nullptr && !extra_fields->empty()) {
        *message_buffer << "<em>" << name << " extra fields</em><br />";
        for (const auto &extra_field: *extra_fields)
            *message_buffer << "<strong>" << extra_field.first << ":</strong> " << extra_field.second->to_string() << "<br />";
    }
}

void display_link_info(const Link *link, const Interface_graphic *window, const int &index) {
    std::stringstream message_buffer;
    message_buffer.str("");
    message_buffer << "<center><strong>Link</strong></center>"
                   << "<strong>Value:</strong> " << link->get_value()->get_name() << "<br />"
                   << "<strong>Source:</strong> " << link->get_source()->get_Name().to_string() << "<br />"
                   << "<strong>Destination:</strong> " << link->get_destination()->get_Name().to_string() << "<br />"
                   << "<strong>Type:</strong> " << link->get_type()->get_name() << "<br />"
                   << "<strong>Date:</strong> " << link->get_start_time().to_string() << " - " << link->get_end_time().to_string() << "<br />"
                   << "<strong>Duration:</strong> " << link->get_duration() << "<br />";
    print_extra_fields("Link", link->get_extra_fields(), &message_buffer);
    print_extra_fields("Value", link->get_value()->get_extra_fields(), &message_buffer);
    print_extra_fields("Type", link->get_type()->get_extra_fields(), &message_buffer);
    window->selection_information("Link " + std::to_string(index), message_buffer.str());
}

void display_event_info(const Event *event, const Interface_graphic *window, const int &index) {
    std::stringstream message_buffer;
    message_buffer.str("");
    message_buffer << "<center><strong>Event</strong></center>"
                   << "<strong>Value:</strong> " << event->get_value()->get_name() << "<br />"
                   << "<strong>Container:</strong> " << event->get_container()->get_Name().to_string() << "<br />"
                   << "<strong>Type:</strong> " << event->get_type()->get_name() << "<br />"
                   << "<strong>Date:</strong> " << event->get_time().to_string() << "<br />";
    print_extra_fields("Event", event->get_extra_fields(), &message_buffer);
    print_extra_fields("Value", event->get_value()->get_extra_fields(), &message_buffer);
    print_extra_fields("Type", event->get_type()->get_extra_fields(), &message_buffer);
    window->selection_information("Event " + std::to_string(index), message_buffer.str());
}
void display_state_info(const State *state, const Interface_graphic *window) {
    std::stringstream message_buffer;
    message_buffer.str("");
    message_buffer << "<center><strong>State</strong></center>"
                   << "<strong>Value:</strong> " << state->get_value()->get_name() << "<br />"
                   << "<strong>Container:</strong> " << state->get_container()->get_Name().to_string() << "<br />"
                   << "<strong>Type:</strong> " << state->get_type()->get_name() << "<br />"
                   << "<strong>Date:</strong> " << state->get_start_time().to_string() << " - " << state->get_end_time().to_string() << "<br />"
                   << "<strong>Duration:</strong> " << state->get_duration() << "<br />";
    print_extra_fields("State", state->get_extra_fields(), &message_buffer);
    print_extra_fields("Value", state->get_value()->get_extra_fields(), &message_buffer);
    print_extra_fields("Type", state->get_type()->get_extra_fields(), &message_buffer);
    window->selection_information("State", message_buffer.str());
}
void display_variable_info(const Variable *variable, const Times &x, const Interface_graphic *window) {
    std::stringstream message_buffer;
    message_buffer.str("");
    message_buffer << "<center><strong>Variable</strong></center>"
                   << "<strong>Container:</strong> " << variable->get_container()->get_Name().to_string() << "<br />"
                   << "<strong>Type:</strong> " << variable->get_type()->get_name() << "<br />"
                   << "<strong>Value:</strong> " << variable->get_value_at(x) << "<br />"
                   << "<strong>Value:</strong> " << variable->get_value_at(x) << "<br />"
                   << "<strong>Min:</strong> " << variable->get_min().get_value() << "<br />"
                   << "<strong>Max:</strong> " << variable->get_max().get_value() << "<br />";
    print_extra_fields("Type", variable->get_type()->get_extra_fields(), &message_buffer);
    window->selection_information("Variable", message_buffer.str());
}

void DrawTrace::fill_link_list(const Container *container, const Element_pos &x, const Element_pos &y, const Element_pos &accuracy, Link::Vector *link_list) {
    const Link::Vector *container_links;
    Link *link;
    double a, b, c; // Equation: ax + by + c = 0
    double x1, x2, y1, y2;

    if (!container)
        return;

    // Browse links
    container_links = container->get_links();
    for (const auto &it: *container_links) {
        link = it;

        double srcpos = _container_positions[link->get_source()];
        double dstpos = _container_positions[link->get_destination()];
        double srcsize = _container_sizes[link->get_source()];
        double dstsize = _container_sizes[link->get_destination()];

        x1 = link->get_start_time().get_value();
        x2 = link->get_end_time().get_value();

        y1 = (srcpos + 0.5 * srcsize) * (_container_height + _container_v_space);
        y2 = (dstpos + 0.5 * dstsize) * (_container_height + _container_v_space);

        // Test if (x, y) is in the counding box made by the link ends
        if (((x1 - accuracy <= x && x <= x2 + accuracy) || (x2 - accuracy <= x && x <= x1 + accuracy)) && ((y1 - accuracy <= y && y <= y2 + accuracy) || (y2 - accuracy <= y && y <= y1 + accuracy))) {
            a = y1 - y2;
            b = x2 - x1;
            c = -(a * x1 + b * y1);

            double e = a * x + b * y + c;

            // Test the distance from (x, y) to the link
            if (e * e / (a * a + b * b) < accuracy) {
                link_list->push_back(link);
            }
        }
    }

    return;
}

void fill_event_list(const Node<Event> *node, const Element_pos &x, const Element_pos &d, std::list<const Event *> *event_list) {

    if (nullptr == node) {
        return;
    }

    const Element_pos t = node->get_element()->get_time().get_value();

    if (t < x) {
        if (x - t < d) {
            fill_event_list(node->get_left_child(), x, d, event_list);
            event_list->push_back(node->get_element());
        }
        fill_event_list(node->get_right_child(), x, d, event_list);
        return;
    }

    fill_event_list(node->get_left_child(), x, d, event_list);
    if (t - x < d) {
        event_list->push_back(node->get_element());
        fill_event_list(node->get_right_child(), x, d, event_list);
    }
    return;
}

void DrawTrace::display_information(const Interface_graphic *window, const Trace *trace, double x, double y, double d) {
    const Container *container = nullptr;
    const Container *ancestor = nullptr;

    Link::Vector link_list;
    std::list<const Event *> event_list;
    const State *state;
    const Variable *variable;

    window->selection_information_clear();

    // find container needs to know the position of each container
    Element_pos yr = y;
    const Container::Vector *root_containers = trace->get_view_root_containers();
    if (root_containers->empty())
        root_containers = trace->get_root_containers();
    if (!root_containers->empty())
        for (const auto &root_container: *root_containers)
            if ((container = search_container_by_position(root_container, yr)))
                break;

    // If the clic is out
    if (!container)
        return;

    // Calculate the container positions
    int position = 0;
    for (const auto &root_container: *root_containers) {
        position += calc_container_positions(root_container, position);
    }
    // Now browsing for the events and states of the container root
    // Verification if there is events or a state in the container
#ifndef USE_ITC
    if ((!container->get_events()->empty() || !container->get_states()->empty()) && yr < _container_height + _container_v_space) {
#else
    if (((container->get_states() != NULL && !container->get_states()->empty()) || (container->get_events() != NULL && !container->get_events()->empty())) && yr < _container_height + _container_v_space) {
#endif
        if ((state = find_state(container, x))) {
            display_state_info(state, window);
        }
        if (!Info::Render::_no_events) {
            if (container->get_events() != nullptr) {
                fill_event_list(container->get_events()->get_root(), x, d, &event_list);
                int i = 1;
                for (auto const &event: event_list) {
                    display_event_info(event, window, i);
                    i++;
                }
            }
        }
    }
    // Else search for a variable
    else {
#ifdef USE_ITC
        if ((container->get_states() != NULL && !container->get_states()->empty()) || (container->get_events() != NULL && !container->get_events()->empty()))
#else
        if (!container->get_events()->empty() || !container->get_states()->empty())
#endif
            yr -= _container_height + _container_v_space;
        const std::map<VariableType *, Variable *> *variable_map = container->get_variables();
        std::map<VariableType *, Variable *>::const_iterator i = variable_map->begin();
        while (yr > _container_height + _container_v_space) {
            yr -= _container_height + _container_v_space;
            i++;
        }
        if (i != variable_map->end()) {
            variable = (*i).second;
            display_variable_info(variable, (Times)x, window);
        }
    }

    // Last we browse to find links
    if (!Info::Render::_no_arrows) {
        ancestor = container;
        while (ancestor) {
            fill_link_list(ancestor, x, y, d, &link_list);
            ancestor = ancestor->get_parent();
        }
        int i = 1;
        for (auto const &link: link_list) {
            display_link_info(link, window, i);
            i++;
        }
    }
    window->selection_information_show();
    return;
}

const Container *DrawTrace::search_container_by_position(const Container *container, Element_pos &y) {
    const Container *result;
    // Search if the result is a descendant
    const Container::Vector *children = container->get_view_children(); // we want to display only children meant to be displayed
    if (children->empty())
        children = container->get_children();
    for (const auto &i: *children) {
        if ((result = search_container_by_position(i, y)) != nullptr)
            return result;
    }

    // Calculate the size of the container (without its children)
    int size = 0;
#ifdef USE_ITC
    if ((container->get_states() != NULL && !container->get_states()->empty()) || (container->get_events() != NULL && !container->get_events()->empty()))
#else
    if (!container->get_states()->empty() || !container->get_events()->empty())
#endif
        size++;
    if (container->get_variable_number() > 0)
        size += container->get_variable_number();

    if (children->empty() && size < 1) // Minimum size
        size = 1;

    // Test if the position is in this container
    if (y < size * (_container_height + _container_v_space))
        return container;
    else
        y -= size * (_container_height + _container_v_space);

    // The position is outside this container
    return nullptr;
}

int DrawTrace::calc_container_positions(const Container *container, int position) {
    int size = 0;

    // Draw children
    const Container::Vector *children = container->get_view_children(); // we want to display only children meant to be displayed
    if (children->empty())
        children = container->get_children();
    for (const auto &i: *children) {
        size += calc_container_positions(i, position + size);
    }

    // Store the position to draw links
    _container_positions[container] = position; // First line after children
    _container_sizes[container] = size; // First line after children

    // Use one line for states and events
#ifdef USE_ITC
    if ((container->get_states() != NULL && !container->get_states()->empty()) || (container->get_events() != NULL && !container->get_events()->empty())) {
#else
    if (!container->get_states()->empty() || !container->get_events()->empty()) {
#endif
        size++;
    }

    // Use one line for each variable
    if (container->get_variable_number() > 0) {
        size += container->get_variable_number();
    }

    return size;
}

const State *DrawTrace::find_state(const Container *container, Element_pos x) {
    if (!container || container->get_states() == nullptr)
        return nullptr;

    Node<StateChange> *node = container->get_states()->get_root();

    while (node) {
        Element_pos t = node->get_element()->get_time().get_value();
        if (x < t) {
            if (node->get_element()->get_left_state() && node->get_element()->get_left_state()->get_start_time().get_value() < x)
                return node->get_element()->get_left_state();
            node = node->get_left_child();
        }
        else {
            if (node->get_element()->get_right_state() && x < node->get_element()->get_right_state()->get_end_time().get_value())
                return node->get_element()->get_right_state();
            node = node->get_right_child();
        }
    }

    return nullptr;
}

bool DrawTrace::link_is_in_set(Link *link, std::vector<const Container *> *set_container, Interval *interval) {
    const Container *src = link->get_source();
    const Container *dest = link->get_destination();
    for (unsigned int i = 0; i < set_container->size(); i++) {
        if (src == (*set_container)[i]) {
            for (const auto &j: *set_container)
                if (dest == j)
                    if (
                        (interval->_left.get_value() < link->get_start_time().get_value() && interval->_right.get_value() > link->get_end_time().get_value()) || (interval->_left.get_value() < link->get_end_time().get_value() && interval->_right.get_value() > link->get_start_time().get_value()))
                        return true;
        }
    }
    return false;
}

bool DrawTrace::is_in_set(const Container *c, std::vector<const Container *> *set_container) {
    if (!c || !set_container)
        return false;
    for (const auto &i: *set_container)
        if (c == i)
            return true;
    return false;
}

void DrawTrace::add(std::vector<const Container *> *container, std::stack<Container *> *containers) {

    while (!containers->empty()) {
        const Container *c = containers->top();
        containers->pop();
        const Container::Vector *children = c->get_view_children(); // we want to display only children meant to be displayed
        if (children->empty())
            children = c->get_children();
        for (const auto &i: *children) {
            containers->push(i);
            container->push_back(i);
        }
        add(container, containers);
    }
}
