/*
** This file is part of the ViTE project.
**
** This software is governed by the CeCILL-A license under French law
** and abiding by the rules of distribution of free software. You can
** use, modify and/or redistribute the software under the terms of the
** CeCILL-A license as circulated by CEA, CNRS and INRIA at the following
** URL: "http://www.cecill.info".
**
** As a counterpart to the access to the source code and rights to copy,
** modify and redistribute granted by the license, users are provided
** only with a limited warranty and the software's author, the holder of
** the economic rights, and the successive licensors have only limited
** liability.
**
** In this respect, the user's attention is drawn to the risks associated
** with loading, using, modifying and/or developing or reproducing the
** software by the user in light of its specific status of free software,
** that may mean that it is complicated to manipulate, and that also
** therefore means that it is reserved for developers and experienced
** professionals having in-depth computer knowledge. Users are therefore
** encouraged to load and test the software's suitability as regards
** their requirements in conditions enabling the security of their
** systems and/or data to be ensured and, more generally, to use and
** operate it in the same conditions as regards security.
**
** The fact that you are presently reading this means that you have had
** knowledge of the CeCILL-A license and that you accept its terms.
**
**
** ViTE developers are (for version 0.* to 1.0):
**
**        - COULOMB Kevin
**        - FAVERGE Mathieu
**        - JAZEIX Johnny
**        - LAGRASSE Olivier
**        - MARCOUEILLE Jule
**        - NOISETTE Pascal
**        - REDONDY Arthur
**        - VUCHENER Clément
**
*/
/*!
 *\file DrawCounter.hpp
 */

#ifndef DRAW_COUNTER_HPP
#define DRAW_COUNTER_HPP

#include "DrawStats.hpp"

#define _MAX_HEIGHT_AXIS_DEFAULT _HEIGHT_FOR_ONE_CONTAINER_DEFAULT - 50
#define _MAX_WIDTH_AXIS_DEFAULT  350
/*!
 * \class DrawCounter
 * \brief Browse the stats and call back T drawing methods
 */
template<class T>
class DrawCounter : public DrawStats<T> {
private:
    int _nb_variables_already_print;

    // Origin of the graph
    double _start_x;
    double _start_y;

    // Geometrical informations about the stats shape.
    double _size_for_one_variable;
    double _pos_x_container_name;
    double _pos_y_container_name;
    double _x_axis_length;
    double _y_axis_length;

public:
    /*
     * \brief The default constructor
     */
    DrawCounter() {
        this->_size_for_one_variable  = _HEIGHT_FOR_ONE_CONTAINER_DEFAULT;
        _x_axis_length                = _MAX_WIDTH_AXIS_DEFAULT;
        _y_axis_length                = _MAX_HEIGHT_AXIS_DEFAULT;
        this->_pos_x_container_name   = _POS_X_CONTAINER_NAME;
        this->_pos_y_container_name   = 10;
        _nb_variables_already_print   = 0;
        _start_x                      = _START_HISTOGRAM_X_DEFAULT;
        _start_y                      = 20.;
    }


    /*!
     * \brief The destructor
     */
    ~DrawCounter() override = default;


    /*!
     * \fn build(T* draw_object, std::vector<Container *> containers_to_print)
     * \brief The trace building function.
     * \param draw_object the kind of object which will be drawn (OpenGL, SVG...).
     * \param containers_to_print the container's data.
     */
    void build(T* draw_object, std::vector<Container *> containers_to_print) override {
        int nbcont;

        draw_object->clear();

        this->_containers_to_print = containers_to_print;

        nbcont = this->_containers_to_print.size();

        draw_object->start_draw();
        draw_object->set_total_width(draw_object->width());

        // For each container
        for(int i = 0 ; i < nbcont ; i ++) {
            const std::map<VariableType *, Variable *> *variables = this->_containers_to_print[i]->get_variables();
            // We print each variable
            for(const auto &variable : *variables) {
                draw_diagram(draw_object, i, variable.second);
                _nb_variables_already_print ++;
            }
        }

        draw_object->set_total_height((_nb_variables_already_print-1)*this->_size_for_one_variable);

        this->end_draw(draw_object);
    }

    /*!
     * \fn draw_diagram(T* draw_object, const int container_id, const Variable *var)
     * \brief Print the i-th diagram
     * \param draw_object the kind of object which will be drawn (OpenGL, SVG...).
     * \param container_id the id of the container.
     * \param var the variable printed
     */
    void draw_diagram(T* draw_object, const int container_id, const Variable *var) {
        int         pos_x, pos_y;
        std::string container_name;
        double      duration;

        duration = this->_end_time - this->_start_time;

        // Position for the origin of the graph
        pos_x = _start_x;
        pos_y = _start_y - this->_size_for_one_variable * _nb_variables_already_print;

        // Draw the container name
        container_name = this->_containers_to_print[container_id]->get_Name().to_string();
        draw_object->draw_text(this->_pos_x_container_name,
                               this->_pos_y_container_name + 30 - pos_y,
                               container_name);
        // Draw the variable name
        draw_object->draw_text(this->_pos_x_container_name,
                               this->_pos_y_container_name + 50 - pos_y,
                               var->get_type()->get_name());

        // Draw the axis
        draw_object->draw_axis(pos_x,
                               pos_y,
                               _x_axis_length,
                               _y_axis_length);

        // Draw the legend axis
        // Time unit
        draw_object->draw_text(pos_x+_x_axis_length+10.,
                               _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-pos_y,
                               "time unit");
        // Variable unit
        if(var->get_extra_fields() != nullptr && var->get_extra_fields()->find(std::string("unit")) != var->get_extra_fields()->end()) {
            const String *var_unit = (const String *)var->get_extra_fields()->find(std::string("unit"))->second;
            draw_object->draw_text(pos_x,
                                   _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-(pos_y+_y_axis_length+10.) ,
                                   "("+var_unit->to_string()+")");
        }
        else {
            draw_object->draw_text(pos_x,
                                   _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-(pos_y+_y_axis_length+10.) ,
                                   "no unit");
        }

        // Draw some values for the axis
        // Formula : duration*(length/axis_length) + start_time;
        const double min_value = var->get_min().get_value();
        const double max_value = var->get_max().get_value();
        if(max_value != min_value) {
            const int nb_values = 6;
            for(int i = 0 ; i <= nb_values ; i ++) {
                // x value -> time
                draw_object->draw_text(pos_x+i*_x_axis_length/nb_values,
                                       _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-(pos_y-15.) ,
                                       QString::number(duration*(double)i/(double)nb_values+this->_start_time, 'f', 3).toStdString());
                draw_object->draw_vertical_line(pos_x+i*_x_axis_length/nb_values,
                                                pos_y-5,
                                                5);
                // y value -> counter
                draw_object->draw_text(pos_x-30. ,
                                       _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-(pos_y+i*_y_axis_length/nb_values),
                                       QString::number((max_value-min_value)*i/(double)nb_values+min_value, 'f', 1).toStdString());
                draw_object->draw_horizontal_line(pos_x-5,
                                                  pos_y+i*_y_axis_length/nb_values,
                                                  5);
            }
        }
        else {
            // Only one in the middle
            draw_object->draw_text(pos_x-30. ,
                                   _HEIGHT_FOR_ONE_CONTAINER_DEFAULT-(pos_y+_y_axis_length/2),
                                   QString::number(max_value, 'f', 1).toStdString());
            draw_object->draw_horizontal_line(pos_x-5,
                                              pos_y+_y_axis_length/2,
                                              5);
        }

        // Draw the variable
        // Formula : x = (time-begin)*(x_length)/duration + decalage
        const std::list<std::pair<Date, Double> > *values = var->get_values();
        double x          = 0. ,
               y          = 0. ,
               prev_x     = pos_x,
               prev_y     = pos_y;
        double median     = 0.;
        double time       = 0.,
               prev_time  = this->_start_time;
        double value      = 0.,
               prev_value = min_value;

        bool is_last_value = false; // If we get a time bigger than the end_time

        if(max_value != min_value) {
          for (const auto & it : *values) {
                time  = it.first.get_value();
                value = it.second.get_value();

                if(time < this->_start_time) {
                    continue;
                }
                if(time >= this->_end_time) {
                    time = this->_end_time;
                    is_last_value = true;
                }

                x = (time-this->_start_time)*_x_axis_length/duration + pos_x;
                y = (value-min_value)*_y_axis_length/(max_value-min_value) + pos_y;
                draw_object->draw_line(prev_x, prev_y, x, prev_y);
                draw_object->draw_line(x, prev_y, x, y);
                median += prev_value*(time-prev_time);
                prev_x = x;
                prev_y = y;
                prev_time = time;
                prev_value = value;
                if(is_last_value) {
                    break;
                }
            }

            draw_object->draw_horizontal_line(x, y,
                                              pos_x+_x_axis_length-x);

            median /= duration;


//             std::cout << "min : " << min_value <<  " max : " << max_value << " Median : " << median << std::endl;
//             // Draw the median
//             draw_object->draw_horizontal_line(pos_x,
//                                               pos_y+_y_axis_length*(median-min_value)/(max_value-min_value),
//                                               _x_axis_length);
        }
        else {
            // Draw the median
            draw_object->draw_horizontal_line(pos_x,
                                              pos_y+_y_axis_length/2,
                                              _x_axis_length);
        }
    }

};

#endif
