/**
 *
 * @file src/trace/DrawTrace.hpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Olivier Lagrasse
 * @author Mathieu Faverge
 * @author Francois Trahay
 * @author Thibault Soucarre
 * @author Kevin Coulomb
 * @author Philippe Swartvagher
 * @author Luca Bourroux
 * @author Augustin Degomme
 * @author Clement Vuchener
 *
 * @date 2024-07-17
 */
/*!
 *\file DrawTrace.hpp
 */

#ifndef DRAW_TRACE_HPP
#define DRAW_TRACE_HPP

#include <cmath>
/* -- */
#include "trace/Container.hpp"
#include "trace/DrawTree.hpp"
#include "trace/EntityValue.hpp"
#include "trace/Event.hpp"
#include "trace/StateChange.hpp"
#include "trace/Trace.hpp"
#include "trace/Variable.hpp"
#include "common/Message.hpp"

class Container;
class EntityValue;
class Interface_graphic;
class Interval;
class Trace;
class State;
class Variable;

/*
 * Theses constants can not be put as static const float because it is a template and there binary representation is not normed by the C++ langage.
 */

/*!
 * \def _DRAWING_CONTAINER_HEIGHT_DEFAULT
 * \brief The default height for basic containers.
 */
#define _DRAWING_CONTAINER_HEIGHT_DEFAULT 1.2f

/*!
 * \def _DRAWING_CONTAINER_WIDTH_DEFAULT
 * \brief The default width for containers.
 */
#define _DRAWING_CONTAINER_WIDTH_DEFAULT 2.5f

/*!
 * \def _DRAWING_CONTAINER_H_SPACE_DEFAULT
 * \brief The default horizontal space between containers.
 */
#define _DRAWING_CONTAINER_H_SPACE_DEFAULT 0.1f

/*!
 * \def _DRAWING_CONTAINER_V_SPACE_DEFAULT
 * \brief The default vertical space between containers.
 */
#define _DRAWING_CONTAINER_V_SPACE_DEFAULT 0.2f

/*!
 * \def _DRAWING_STATE_HEIGHT_DEFAULT
 * \brief The default height for states.
 */
#define _DRAWING_STATE_HEIGHT_DEFAULT 1.2f

/*!
 * \def _DRAWING_STATE_V_SPACE_DEFAULT
 * \brief The default vertical space between states.
 */
#define _DRAWING_STATE_V_SPACE_DEFAULT 0.2f

/*!
 * \class DrawTrace
 * \brief Browse the trace and call back T drawing methods
 */
/*template<class T>*/
class DrawTrace
{

protected:
    /*!
     * \brief Containers with states or events
     */
    std::list<const Container *> _entity_containers;

    /*!
     * \brief Containers with links
     */
    std::list<const Container *> _link_containers;

    /*!
     * \brief Containers with variables
     */
    std::list<const Container *> _variable_containers;

    std::map<const Container *, Element_pos, std::less<const Container *>> _container_positions;
    std::map<const Container *, Element_pos, std::less<const Container *>> _container_sizes;
    std::map<const Variable *, Element_pos> _var_positions;

    // Geometrical information about the trace shape.
    /*!
     * \brief  _container_width width of the container
     */
    Element_pos _container_width;
    /*!
     * \brief _container_height height of the container
     */
    Element_pos _container_height;
    /*!
     * \brief _container_h_space Horizontal space beetween 2 containers
     */
    Element_pos _container_h_space;
    /*!
     * \brief _container_v_space Vertical space between 2 containers
     */
    Element_pos _container_v_space;
    /*!
     * \brief _state_height Height of the state
     */
    Element_pos _state_height;
    /*!
     * \brief _state_v_space Vertical space between 2 states
     */
    Element_pos _state_v_space;

public:
    /***********************************
     *
     * Constructor and destructor.
     *
     **********************************/

    /*!
     * \brief The default constructor
     */
    DrawTrace();

    /*!
     * \brief The destructor
     */
    virtual ~DrawTrace();

    /***********************************
     *
     * Building functions.
     *
     **********************************/

    /*!
     * \fn build(T* draw_object, Trace* trace)
     * \brief The trace building function that do not draw all the containers but only a part.
     * \param draw_object the kind of object which will be drawn (OpenGL, SVG...).
     * \param trace the trace data.
     */
    template <class T>
    void build(T *draw_object, Trace *trace) {
        Info::Container::y_max = 0; // reset the vertical zoom when rebuilding
        double zoom = trace->get_filter();
        Interval *interval; // = trace->get_interval_constrained();
        // clear entities
        _container_positions.clear();
        _container_sizes.clear();
        _entity_containers.clear();
        _link_containers.clear();
        _variable_containers.clear();

        thread_local std::vector<const Container *> local_container;
        local_container.clear();
        std::vector<const Container *> *container = trace->get_selected_container();
        if ((container == nullptr) || container->empty()) {
            if (!container)
                container = &local_container;
            container->clear();

            trace->set_filter(0);

            std::stack<Container *> containers;
            const Container::Vector *root_containers = trace->get_view_root_containers();
            if (root_containers->empty())
                root_containers = trace->get_root_containers();
            for (const auto &root_container: *root_containers) {
                containers.push(root_container);
                container->push_back(root_container);
            }
            add(container, &containers);
        }
        // Adding the parent containers if not added yet
        else {
            bool ended = false;
            std::vector<const Container *> store;
            store.clear();
            while (!ended) {
                ended = true;
                for (std::vector<const Container *>::const_iterator it = container->begin();
                     it != container->end();
                     it++) {

                    if (*it && (*it)->get_parent())
                        if (!is_in_set((*it)->get_parent(), container)) {
                            store.push_back((*it)->get_parent());
                            ended = false;
                        }
                } // end for
                // TODO Warning the same container should be added more than 1 time
                for (std::vector<const Container *>::const_iterator it = store.begin();
                     it != store.end();
                     it++) {
                    if (!is_in_set(*it, container))
                        container->push_back(*it);
                }
                store.clear();
            } // end while
        } // end else

        interval = trace->get_interval_constrained();

        draw_object->start_draw();

        draw_object->start_draw_containers();
        browse_container_tree(draw_object, trace, container);
        draw_object->end_draw_containers();
        browse_entities(draw_object, zoom, interval, container);

        draw_object->end_draw();
    }

    /***********************************
     *
     * Browsing functions.
     *
     **********************************/
    /*
     * \fn browse_container_tree
     * \brief Function that browses the containers of the trace argument that
     * are in the set container and make them painted with a T object
     */
    template <class T>
    inline void browse_container_tree(T *draw_object, Trace *trace, std::vector<const Container *> *container) {
        /*** Drawing containers ***/
        Element_pos y = 0.;

        const Container::Vector *root_containers = trace->get_view_root_containers();
        if (root_containers->empty())
            root_containers = trace->get_root_containers();

        if (root_containers->empty()) {
            *Message::get_instance() << QObject::tr("There is no container. The trace can not be drawn.").toStdString() << Message::endw;
        }
        else {
            for (const auto &root_container: *root_containers)
                // Use the fact that if a container is selected, then it implies that all his ancestors are
                if (is_in_set(root_container, container))
                    y += browse_container(draw_object, root_container, 0., y, _container_width, 0., container) + _container_v_space;
        }
    } /* end browse_container_tree */

    /*
     * \fn browse_container
     * \brief Recursive function that browse a container to draw it if it is in
     * the set of container with a T painting object in position, knowing the
     * current depth in the tree
     */
    template <class T>
    Element_pos browse_container(T *draw_object, const Container *container, Element_pos x, Element_pos y,
                                 Element_pos w, Element_pos h, std::vector<const Container *> *set_container) {
        // int size = 0;

        // Draw children:
        // we want to display only children meant to be displayed
        const Container::Vector *children = container->get_view_children();
        if (children->empty())
            children = container->get_children();
        /*if(!children.empty())
         h = 0.;*/
        for (const auto &i: *children) {
            h += browse_container(draw_object, i, x + _container_width + _container_h_space, y + h, _container_width, 0., set_container) + _container_v_space;
        }

        // h -= _container_v_space;
        /*if(is_in_set(*i,set_container))
         size += browse_container(draw_object, (*i), position+size, depth+1,set_container);*/

        // 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
        {
            h += _container_height + _container_v_space;
            _entity_containers.push_back(container);
        }

        // Store the position to draw links
        _container_positions[container] = y;
        _container_sizes[container] = h;

        // Use one line for each variable
        if (container->get_variable_number() > 0) {
            _variable_containers.push_back(container);
            h += (_container_height + _container_v_space) * container->get_variable_number();
        }

        if (h < _container_height) // Minimum size
            h = _container_height;

        // Push containers with links to draw
        if (!container->get_links()->empty())
            _link_containers.push_back(container);

        h -= _container_v_space;

        // Draw this container
        draw_container(draw_object, x, y, w, h, container->get_Name().to_string());

        return h;
    }

    /*!
     * \brief Draw a container
     * \param draw_object Object that contains drawing methods to call
     * \param position Starting line of the container
     * \param size Height of the container in number of line
     * \param depth Depth of container in the tree
     * \param text Name of the container
     */
    template <class T>
    inline void draw_container(T *draw_object, Element_pos x, Element_pos y, Element_pos w, Element_pos h, const std::string &text) {
        // Element_pos x      = depth   *(_container_width +_container_h_space) + _container_h_space/2;
        // Element_pos y      = position*(_container_height+_container_v_space) + _container_v_space/2;
        // Element_pos height = size    *(_container_height+_container_v_space) - _container_v_space;
        draw_object->draw_container(x, y + _container_v_space / 2, w, h, text);
        // draw_object->draw_container_text(x, y+h/2, text);
    }

    /*!
     * \brief Browse the states list and draw them
     */
    template <class T>
    inline void browse_entities(T *draw_object, double zoom, Interval *interval, std::vector<const Container *> *set_container) {
        //////////////////////////////////////////////////////////
        const Container *container;
        StateChange::Tree *state_tree;
        Event::Tree *event_tree;
        const Link::Vector *link_list;
        Link *link;
        const std::map<VariableType *, Variable *> *variable_map;
        Variable *var;
        const std::list<std::pair<Date, Double>> *variable_values;
        Element_pos position;
        std::map<std::string, Value *>::const_iterator field;
        Element_pos lvl_zoom;

        if (zoom >= 0)
            lvl_zoom = zoom;
        else
            lvl_zoom = 0;

        draw_object->start_draw_states();
        draw_object->start_draw_events();
        for (std::list<const Container *>::const_iterator c = _entity_containers.begin();
             c != _entity_containers.end();
             c++) {
            if (is_in_set(*c, set_container)) {
                container = *c;

                position = _container_positions[container] + _container_sizes[container] - (_container_height + _container_v_space);

                state_tree = container->get_states();
                event_tree = container->get_events();
#ifdef USE_ITC
                if (state_tree != NULL) {
#endif
                    if (!state_tree->empty()) {
                        // printf("drawing states for %s\n", container->get_Name().to_string().c_str());
                        //  Browse states
                        DrawTree<T, StateChange>(draw_object, position, lvl_zoom,
                                                 _container_height, _container_v_space, _state_height, _state_v_space)
                            .draw_tree(state_tree, *interval);
                    }
#ifdef USE_ITC
                }
                if (event_tree != NULL) {
#endif
                    if (!event_tree->empty()) {
                        // printf("drawing events for %s\n", container->get_Name().to_string().c_str());
                        //  Browse events
                        DrawTree<T, Event>(draw_object, position, lvl_zoom,
                                           _container_height, _container_v_space, _state_height, _state_v_space)
                            .draw_tree(event_tree, *interval);
                    }
#ifdef USE_ITC
                }
#endif
            }
        } /* end for (!_stack_states.empty()) */

        draw_object->end_draw_events();
        draw_object->end_draw_states();

        draw_object->start_draw_arrows();

        for (std::list<const Container *>::const_iterator c = _link_containers.begin();
             c != _link_containers.end();
             c++) {
            if (is_in_set(*c, set_container)) {
                container = *c;

                // Browse links
                link_list = container->get_links();
                for (const auto &it: *link_list) {
                    link = it;
                    EntityValue *value = link->get_value();
                    assert(value);
                    const Color *color = value->get_used_color();
                    assert(color);
                    if (link_is_in_set(link, set_container, interval))
                        draw_link(draw_object, link, color, value);
                    // /*bool display=false;
                    //  color=NULL;
                    //  if(!Session::get_use_palette("link_types")) display = true;
                    //  else{
                    //  if (link->get_type()){
                    //  Palette *lt = Session::get_palette("link_types", Session::get_current_palette("link_types"));

                    //  color = lt->get_color(link->get_type()->get_name().to_string());
                    //  if(color) display=true;
                    //  */
                    // /*for(std::list<std::string>::const_iterator it2= link_types->get_->begin();
                    //  it2!= link_types->end();
                    //  it2++ ){
                    //  if(*it2 ==  link->get_type()->get_name().to_string()){
                    //  display=true;
                    //  break;

                    //  }
                    //  } */

                    // //}
                    // /*}
                    //  if(display &&
                    //  link_is_in_set(link,set_container,interval)){

                    //  // Search the color
                    //  if (color==NULL &&
                    //  link->get_type()&&
                    //  link->get_type()->get_extra_fields()!=NULL &&
                    //  !link->get_type()->get_extra_fields()->empty() &&
                    //  ((field = link->get_type()->get_extra_fields()->find(std::string("Color"))) != link->get_type()->get_extra_fields()->end()))
                    //  /* Call the object link drawing function with the link color */
                    // /*                  color = (const Color *)(*field).second;

                    //  draw_link(draw_object, link, color );
                    //  }
                    //  }/* end for */
                } // end for
            } /* end while (!_stack_states.empty()) */
        }

        draw_object->end_draw_arrows();
        draw_object->start_draw_counter();

        for (std::list<const Container *>::const_iterator c = _variable_containers.begin();
             c != _variable_containers.end();
             c++) {

            if (is_in_set(*c, set_container)) {

                container = *c;
                position = _container_positions[container] + _container_sizes[container];

                // Browse variables
                variable_map = container->get_variables();

                for (const auto &i: *variable_map) {

                    var = i.second;
                    double min = var->get_min().get_value();
                    double max = var->get_max().get_value();
                    variable_values = var->get_values();

                    double first_value = 0.;
                    double second_value = 0.;

                    // draw_object->draw_text_value((long int)var,0.0, (position+1)*(_container_height+_container_v_space) - _container_v_space/2 - 0.5*_container_height);
                    draw_object->draw_text_value((long int)var, 0.0, position + 0.5 * _container_height + _container_v_space / 2);
                    _var_positions[var] = position;
                    draw_variable_value(draw_object, 0.0, 0.0, position);
                    for (const auto &variable_value: *variable_values) {

                        /* Call the object state drawing function.
                         We pass the first value if correspond to the beginning */
                        first_value = variable_value.first.get_value();

                        // min and max can be equal if all values are equal (ie 0) or if there is only one value for the variable
                        if (fabs(max - min) < std::numeric_limits<double>::epsilon()) {
                            second_value = 0;
                        }
                        else {
                            second_value = (variable_value.second.get_value() - min) / (max - min);
                        }

                        if (!(first_value == 0. && second_value == 0.)) {
                            draw_variable_value(draw_object, first_value, second_value, position);
                        }
                    }
                    draw_variable_value(draw_object, 0.0, 0.0, position);
                    position += _container_height + _container_v_space; // One line was used
                } /* end for */
            }
        } // end for

        draw_object->end_draw_counter();
    }

    /*!
     * \brief Draw a point of a variable curve
     * \param draw_object Object that contains the drawing methods
     * \param time Time of the point
     * \param value Value of of the variable (between 0.0 and 1.0)
     * \param position Line where the variable is drawn
     */
    template <class T>
    inline void draw_variable_value(T *draw_object, double time, double value, Element_pos position) {
        Element_pos y = position + (_container_height + _container_v_space) - _container_v_space / 2 - value * _container_height;
        draw_object->draw_counter(time, y);
    }

    /*!
     * \brief Draw a link
     * \param draw_object Object that contains the drawing methods
     * \param starttime Time of the start of the link
     * \param endtime Time of the end of the link
     * \param start Line of the start of the link
     * \param end Line of the end of the link
     * \param color The link color
     */
    template <class T>
    inline void draw_link(T *draw_object, const Link *link, const Color *color, EntityValue *value) {
        double starttime = link->get_start_time().get_value();
        double endtime = link->get_end_time().get_value();
        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()];

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

        if (color != nullptr)
            draw_object->draw_arrow(starttime, endtime, y1, y2, color->get_red(), color->get_green(), color->get_blue(), value);
        else /* Draw white link */
            draw_object->draw_arrow(starttime, endtime, y1, y2, 1.0, 1.0, 1.0, value);
    }

    /*
     * \brief Assuming someone has clicked in (x, y), display the description corresponding to the item clicked
     */
    void display_information(const Interface_graphic *window, const Trace *trace, double x, double y, double d);

    const Container *search_container_by_position(const Container *container, Element_pos &y);

    /*
     * \fn browse_container(T* draw_object, const Container *container, int position, int depth)
     * \brief Recursive function that browse a container to draw it with a T painting object in position, knowing the current depth in the tree
     */
    int calc_container_positions(const Container *container, int position);

    /*!
     * \brief Tries to find all links passing by x and y in the container
     */
    void fill_link_list(const Container *container, const Element_pos &x, const Element_pos &y, const Element_pos &d, Link::Vector *link_list);

    /*
     * \brief Returns the state at the time x
     */
    const State *find_state(const Container *container, Element_pos x);

    bool link_is_in_set(Link *link, std::vector<const Container *> *set_container, Interval *interval);

    bool is_in_set(const Container *c, std::vector<const Container *> *set_container);

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

    template <class T>
    void draw_text_variable_values(T *render, std::map<long int, double> *var_map) {
        std::map<long int, double>::const_iterator it = var_map->begin();
        const std::map<long int, double>::const_iterator it_end = var_map->end();

        while (it != it_end) {
            render->draw_text_value((*it).first, (*it).second, -1);
            it++;
        }
    }
};
#endif
