/**
 *
 * @file src/trace/DrawTree.hpp
 *
 * @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 Thibault Soucarre
 * @author Kevin Coulomb
 * @author Olivier Lagrasse
 * @author Luigi Cannarozzo
 * @author Clement Vuchener
 *
 * @date 2024-07-17
 */
/*!
 *\file DrawTree.hpp
 */

#ifndef DRAWTREE_HPP
#define DRAWTREE_HPP

#include "trace/EntityValue.hpp"
#include "trace/Trace.hpp"

/*!
 * \class DrawTree
 * \brief Class called to draw a binary tree of type E  using a D painting object
 */
class Interval;
template <typename E>
class Node;
template <typename E>
class BinaryTree;

template <class D, class E>
class DrawTree
{
private:
    D *_draw_object;
    Element_pos _position;
    double _min_size;

    Element_pos _container_height;
    Element_pos _container_v_space;

    Element_pos _state_height;
    Element_pos _state_v_space;

public:
    /*!
     *\brief Default constructor
     */
    DrawTree(D *draw_object, Element_pos position, double min_size,
             Element_pos container_height, Element_pos container_v_space,
             Element_pos state_height, Element_pos state_v_space) :
        _draw_object(draw_object),
        _position(position), _min_size(min_size),
        _container_height(container_height), _container_v_space(container_v_space),
        _state_height(state_height), _state_v_space(state_v_space) {
    }

    /*!
     * \fn draw_tree( BinaryTree<E> * tree, const Interval &I)
     * \brief Function called to draw on ViTE the elements of a tree
     * \param tree  : The tree we want to draw
     * \param I : The interval we have to draw the tree
     */
    void draw_tree(BinaryTree<E> *tree, const Interval &I) {
        Node<E> *node = tree->get_root();
        bool b = false;
        if (node)
            browse_tree(I, node, b);
    }

    /*!
     * \fn browse_tree(const Interval &I, Node<E> *node, bool &b)
     * \brief Function called to draw on ViTE a node
     * \param I : The interval we have to draw node
     * \param node : The node we want to draw
     */
    void browse_tree(const Interval &I, Node<E> *node, bool &b);

    /*!
     * \fn draw_state(double starttime, double endtime,  double r, double g, double b)
     * \brief Draw a state
     * \param starttime Time when the state begins
     * \param endtime Time when the state ends
     * \param r Red value of the state color
     * \param g Green value of the state color
     * \param b Blue value of the state color
     */
    inline void draw_state(double starttime, double endtime, double r, double g, double b, EntityValue *value) {
        Element_pos y = _position + _container_v_space / 2;
        _draw_object->draw_state(starttime, endtime, y, _state_height, r, g, b, value);
    }

    /*!
     * \fn draw_event(double time, double r, double g, double b)
     * \brief Draw an event
     * \param time Time of the event
     * \param r The red component of the color
     * \param g The green component of the color
     * \param b The blue component of the color
     */
    inline void draw_event(double time, double r, double g, double b, EntityValue *value) {
        Element_pos y = _position + _container_v_space / 2;
        _draw_object->draw_event(time, y, _state_height, r, g, b, value);
    }

    /*!
     * \fn draw_busy(Interval *I)
     * \brief Function that display a purple square in the interval selected
     */
    inline void draw_busy(Interval *I) {
        draw_state(I->_left.get_value(), I->_right.get_value(), 0.2, 0.2, 0.2, nullptr);
    }
};

/*!
 * \struct DrawNode
 * \brief Structure to draw a node of type E with a painting object of type D
 */
template <class D, class E>
struct DrawNode
{
    /*!
     * \fn draw_node(DrawTree<D, E> *draw, Node<E> *node, bool &b, const Interval *i)
     * \brief To draw a node of type E with a painting object of type D
     */
    static void draw_node(DrawTree<D, E> *draw, Node<E> *node, bool &b, const Interval *i) { }
};

/*!
 * \struct DrawNode
 * \brief Draw a Node<Event> with a D painting object
 */
template <class D>
struct DrawNode<D, Event>
{
    /*!
     * \fn draw_node(DrawTree<D, Event> *draw, Node<Event> *node, bool &b, const Interval *i)
     * \brief To draw a node of type Event with a painting object of type D
     */
    static void draw_node(DrawTree<D, Event> *draw, Node<Event> *node, bool &b, const Interval *i) {
        // Just to avoid warning at the compilation, these 2 parameters are only useful in draw_node<stateChange>
        b = true;
        i++;

        const Event *event = node->get_element();
        EntityValue *value = event->get_value();
        const Color *color = value->get_used_color();
        double time = event->get_time().get_value();
        draw->draw_event(time, color->get_red(), color->get_green(), color->get_blue(), value);
    }
};

/*!
 * \struct DrawNode
 * \brief Draw a Node<StateChange> with a D painting object
 */
template <class D>
struct DrawNode<D, StateChange>
{
    static void draw_node(DrawTree<D, StateChange> *draw, Node<StateChange> *node, bool &b, const Interval *interval) {
        // Part to display the first state

        if (!b) {
            b = true;
            //                Node<StateChange> n = *node;
            //                n.get_element()->get_left_state()->set_left_state(interval->_left);
            if (/*node &
                  node->get_element() &*/
                node->get_element()->get_left_state()) {
                const State *state = node->get_element()->get_left_state();
                EntityValue *value = state->get_value();
                const Color *color = value->get_used_color();
                draw->draw_state(interval->_left.get_value(), state->get_end_time().get_value(),
                                 color->get_red(), color->get_green(), color->get_blue(), value);
                //}
            }
        }

        if (!node->get_element()->get_right_state())
            return;
        const State *state = node->get_element()->get_right_state();
        EntityValue *value = state->get_value();
        const Color *color = value->get_used_color();
        double max;
        if (state->get_end_time().get_value() > interval->_right.get_value())
            max = interval->_right.get_value();
        else
            max = state->get_end_time().get_value();
        draw->draw_state(state->get_start_time().get_value(), max,
                         color->get_red(), color->get_green(), color->get_blue(), state->get_value());
    }
};

template <class D, class E>
/*!
 *\fn DrawTree<D, E>::browse_tree(const Interval &I, Node<E> * node, bool &b)
 *\brief Function that browses a tree to display
 *\param I The interval we have to display node
 *\param node The node in the tree we want to display
 */
void DrawTree<D, E>::browse_tree(const Interval &I, Node<E> *node, bool &b) {

    if (!node)
        return;

    bool displayed = false; // To remember if node has already been displayed
    int n_children;
    Interval *interval = NULL; // To avoid resources leak

    // If the node has 2 children
    if (node->get_right_child())
        n_children = 2;
    // Else if only a left child
    else if (node->get_left_child())
        n_children = 1;
    // Else no child
    else
        n_children = 0;

    // If the node is in the interval
    Date time = node->get_element()->get_time();
    if (time <= I._right && time >= I._left) {

        // If the node can be displayed (interval is wide enough)
        if (I._right - I._left > _min_size) {
            // Launching Recursively in children
            if (n_children >= 1) // left
                browse_tree(Interval(I._left, time),
                            node->get_left_child(),
                            b);
            if (n_children >= 2) // right
                browse_tree(Interval(time, I._right),
                            node->get_right_child(),
                            b);

            // If node's left son has a conflict on it's left side
            if (n_children >= 1 && node->get_left_child()->_left_interval) {
                // Setting the node's left interval as busy
                node->_left_interval = new Interval(node->get_left_child()->_left_interval->_left,
                                                    node->get_left_child()->_left_interval->_right);
            }
            // Else no problem on the left (if a leaf, treated later)
            else {
                node->_left_interval = nullptr;
            }

            // Looking for conflict to display the node
            if (n_children >= 2 && node->get_left_child()->_right_interval && node->get_right_child()->_left_interval) {
                interval = new Interval(node->get_left_child()->_right_interval->_left,
                                        node->get_right_child()->_left_interval->_right);
                // Drawing a crowded area
                draw_busy(interval);
                displayed = true;
                delete interval;
                delete node->get_left_child()->_right_interval;
                delete node->get_right_child()->_left_interval;
                node->get_right_child()->_left_interval = nullptr;
                node->get_left_child()->_right_interval = nullptr;
            }

            // Else if problem possible with the left child : it's right interval
            else if (n_children >= 1 && node->get_left_child()->_right_interval) {
                interval = new Interval(node->get_left_child()->_right_interval->_left, time);
                draw_busy(interval);
                delete interval;
                delete node->get_left_child()->_right_interval;
                node->get_left_child()->_right_interval = nullptr;
            }
            // Else if problem possible with the right child : it's left interval
            else if (n_children >= 2 && node->get_right_child()->_left_interval) {
                displayed = true;
                interval = new Interval(time, node->get_right_child()->_left_interval->_right);
                draw_busy(interval);
                delete interval;
                delete node->get_right_child()->_left_interval;
                node->get_right_child()->_left_interval = nullptr;
            }

            // If node's right son has a conflict on it's right side
            if (n_children >= 2 && node->get_right_child()->_right_interval) {
                // Setting the node's right interval as busy
                node->_right_interval = node->get_right_child()->_right_interval;
            }
            // Else no problem on the right (if a leaf, treated after)
            else {
                node->_right_interval = nullptr;
            }

            // Treating the special case of a leaf
            if (n_children == 0) {
                // If too close of the previous node
                if (time - I._left < _min_size) {
                    node->_left_interval = new Interval(I._left, time);
                    node->_right_interval = nullptr;
                }
                // If too close of the following node
                if (I._right - time < _min_size) {
                    node->_right_interval = new Interval(std::move(time), I._right);
                    node->_left_interval = nullptr;
                }
            }

            // Making sure node has been displayed
            if (!displayed) {
                DrawNode<D, E>::draw_node(this, node, b, &I);
            }
        } // end if has enough space

        else {
            // Cannot display node so the busy intervals are the same and value I
            node->_left_interval = new Interval(I);
            node->_right_interval = new Interval(I);
        }
    } // end if is in the interval
    else {
        // If node is after the interval
        if (n_children >= 1 && node->get_element()->get_time() > I._right) {
            browse_tree(I, node->get_left_child(), b);
            node->_left_interval = node->get_left_child()->_left_interval;
            node->_right_interval = nullptr;
        }
        // Else he is before
        else if (n_children >= 2) {
            browse_tree(I, node->get_right_child(), b);
            node->_left_interval = nullptr;
            node->_right_interval = node->get_right_child()->_right_interval;
        }
    } // end else is in the interval
}

#endif
