/*
 ** 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 Render_opengl.cpp
 */

#ifdef _WIN32
	#include <Windows.h>
#endif

#include <sstream>
#include <cmath>
/* -- */
#include <GL/glu.h>
/* -- */
#include <QFile> // For loading the wait image
#include <QDate>
#include <QTimer>
#include <QEventLoop>
/* -- */
#include "interface/resource.hpp"
#include "interface/Interface.hpp"
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
#include "common/Message.hpp"
/* -- */
#include "render/Ruler.hpp"
#include "render/Render_opengl.hpp"
#include "render/GanttDiagram.hpp"
/* -- */
#include "core/Core.hpp"

using namespace std;


#define message *Message::get_instance() << "(" << __FILE__ << " l." << __LINE__ << "): "
#define NB_STEPS 20


const double angle       = M_PI/2.0;
const double delta_angle = 2.0*M_PI/NB_STEPS;
const double radius = .5;


template <int N> void Render_opengl::cos_table_builder(double table[]){
    cos_table_builder<N-1>(table);
    table[N-1]=cos(angle+delta_angle*(N-1))*radius;
}

template <> void Render_opengl::cos_table_builder<0>(double table[]){
    table[0]=cos(angle*radius);
}

template <int N> void Render_opengl::sin_table_builder(double table[]){
    sin_table_builder<N-1>(table);
    table[N-1]=sin(angle+delta_angle*(N-1))*(radius);
}

template <> void Render_opengl::sin_table_builder<0>(double table[]){
    table[0]=sin(angle)*(radius);
}

static double cos_table[NB_STEPS];
static double sin_table[NB_STEPS];



const int Render_opengl::DRAWING_TIMER_DEFAULT = 10;

/***********************************
 *
 *
 *
 * Constructor and destructor.
 *
 *
 *
 **********************************/

Render_opengl::Render_opengl(Core* core, QWidget *parent, const QGLFormat& format)
    : Hook_event(this, core, parent, format)
{
    _arrows.clear();
    _circles.clear();
    _texts.clear();
    _variable_texts.clear();
    /* init the wait animation */
    _wait_list      = 0;
    _wait_angle     = 0.0f; /* begin with 0 rad angle */
    _wait_timer     = nullptr;
    _wait_spf       = DRAWING_TIMER_DEFAULT;/* DRAWING_TIMER_DEFAULT milliseconds per frame */
    /* _minimap_widget = NULL;
     _minimap_image  = NULL;*/

#ifdef SPINNING_LOGO
    _wait_timer = new QTimer(this);
    connect(_wait_timer, SIGNAL(timeout()), _render_instance, SLOT(updateGL()));
    _wait_timer->start(_wait_spf);
#endif // SPINNING_LOGO
    vertical_line=0;
    vertical_line=0;
    setAutoFillBackground(false);


    cos_table_builder<NB_STEPS>(cos_table);
    sin_table_builder<NB_STEPS>(sin_table);

}

void Render_opengl::release()
{
    /* Release the Rabbit and ruler lists */
    if (glIsList(_wait_list) == GL_TRUE)
        glDeleteLists(_wait_list, 1);

    /* Release timer (for Rabbit rotate) */
    if (_wait_timer != nullptr){
        delete _wait_timer;
        _wait_timer=nullptr;
    }

    //release_minimap();
}

/***********************************
 *
 * Default QGLWidget functions.
 *
 **********************************/

void  Render_opengl::initializeGL() {
    glClearColor(0.5f, 0.5f, 0.55f, 1.0f);

    glEnable(GL_DEPTH_TEST);

    glClearStencil(0);

    _wait_list = draw_wait();
    _core->waitGUIInit->quit();
}

void  Render_opengl::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);

    /* update informations about widget size */
    Info::Screen::width  = width;
    Info::Screen::height = height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if(DRAWING_STATE_WAITING == _state){// A wait is drawn
        glOrtho(-50, 50, -50, 50, 0, 1000);
    }else if (DRAWING_STATE_DRAWING == _state){// A trace is drawn
        glOrtho(0, Info::Render::width, 0, Info::Render::height, 0, -1000);
    }else{
        message << tr("Undefined value for the drawing state attribute - Render area").toStdString() << Message::ende;
    }

    glMatrixMode(GL_MODELVIEW);
}


void  Render_opengl::paintGL(){
    resizeGL(Render_opengl::QGLWidget::width(), Render_opengl::QGLWidget::height());
    Render_opengl::QGLWidget::setFocus(Qt::ActiveWindowFocusReason);/* give the focus to the render area for mouse and keyboard events */
    makeCurrent();
    //static int arrows_drawn=0;
    glClearDepth(1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();

    if(DRAWING_STATE_WAITING == _state){/* A wait is drawn */

        /* turn around y axis */
        _wait_angle+=0.1f;
        if (_wait_angle>=360) _wait_angle=0.0f;

        glPushMatrix();

        glScalef(15, 15, 0);

#ifdef SPINNING_LOGO
        glRotatef(-_wait_angle,0, 1, 0);
#endif // SPINNING_LOGO

        glCallList(_wait_list);

        glPopMatrix();


    }else if (DRAWING_STATE_DRAWING == _state){/* A trace is drawn */
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glPushMatrix();
        {
            glTranslated(0.0, Info::Render::height, 100.0);
            glRotated(180.0, 1.0, 0.0, 0.0);

            glPushMatrix();

            {
                glTranslatef(0.0f, _ruler_y + _ruler_height -_y_state_translate,  _z_container);
                glScalef(_x_scale_container_state/0.20, _y_state_scale, 1.0);

                if (glIsList(_list_containers) == GL_FALSE)
                    *Message::get_instance() << tr("ERROR LIST not exist for containers.").toStdString() << Message::ende;
                else
                    glCallList(_list_containers);

            }
            glPopMatrix();

            glPushMatrix();
            {
                glTranslated(_default_entity_x_translate - _x_state_translate, _ruler_y + _ruler_height - _y_state_translate,  _z_state);
                glScalef(_x_state_scale, _y_state_scale, 1);

                if (glIsList(_list_states) == GL_FALSE)
                    *Message::get_instance() << tr("ERROR LIST not exist for states.").toStdString() << Message::ende;
                else{
                    glCallList(_list_states);
                    glCallList(_list_counters);
                    // if( (false == Info::Render::_no_arrows) && arrows_drawn!=0 ) glCallList(_list_arrows);
                }
            }
            glPopMatrix();

            _minimap.update( ( ( (_x_state_translate - _default_entity_x_translate)/ (Info::Render::width*_x_state_scale) ) +  (_default_entity_x_translate/Info::Render::width) )*_minimap.width(),
                             ( (_y_state_translate - _ruler_height) / (Info::Render::height*_y_state_scale) + (_ruler_height/Info::Render::height) )*_minimap.height(),
                             _minimap.width()/_x_state_scale,
                             _minimap.height()/_y_state_scale
                             );
            //initialize the minimap if it hasn't been done yet
            if(!_minimap.is_initialized()){
                QImage buf = grabFrameBuffer(true);
                _minimap.init(buf);
            }
            if(false == Info::Render::_no_arrows){
                draw_stored_arrows();
            }



            if (false == Info::Render::_no_events)/* display events */
                draw_stored_circles();



            /* Untranslate ruler */
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            call_ruler();
            glDisable(GL_BLEND);

            if (Info::Render::_vertical_line){
                draw_vertical_line();
            }

        }
        glPopMatrix();

        glPushMatrix();
        {
            glTranslated(0.0,  0.0, 100.0);/* not accurate */

            if (_mouse_pressed && (Info::Render::_key_ctrl == false) && !_mouse_pressed_inside_container){


                glTranslated(0.0, 0.0, _z_container_under);

                Element_pos old_x, old_y, new_x, new_y;



                old_x = screen_to_render_x(_mouse_x);
                old_y = Info::Render::height - screen_to_render_y(_mouse_y);

                new_x = screen_to_render_x(_new_mouse_x);
                new_y = Info::Render::height -  screen_to_render_y(_new_mouse_y);

#ifdef DEBUG_MODE_RENDER_OPENGL

                cerr << __FILE__ << " l." << __LINE__ << ":" << endl;
                cerr << "Selection rectangle position: (" << old_x << ", " << old_y << ") - (" << new_x << ", " << new_y << ")" << endl;
#endif


                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                /* Square for selection */
                glBegin(GL_QUADS);
                {
                    glColor4f(0.9f, 1.0f, 0.9f, _selection_rectangle_alpha);glVertex2d(old_x, old_y);
                    glColor4f(0.9f, 0.9f, 0.9f, _selection_rectangle_alpha);glVertex2d(old_x, new_y);
                    glColor4f(0.9f, 0.9f, 0.9f, _selection_rectangle_alpha);glVertex2d(new_x, new_y);
                    glColor4f(0.9f, 0.9f, 0.9f, _selection_rectangle_alpha);glVertex2d(new_x, old_y);
                }
                glEnd();

                glDisable(GL_BLEND);

            }/* end  if (true==_mouse_pressed) */
        }
        glPopMatrix();



    }else{
        message << tr("Undefined value for the drawing state attribute - Render area").toStdString() << Message::ende;
    }

    glFlush();

    /* Check the errors */
    GLenum glerror;
    glerror = glGetError();
    if(glerror != GL_NO_ERROR)
        message << tr("Render area : the following OpengGL error occured: ").toStdString() << (char *)gluErrorString(glerror) << Message::endw;

    if(DRAWING_STATE_WAITING == _state)/* A wait is drawn, do not take car about the ruler drawing */
        return;

    QFont arial_font = QFont("Arial", 10);
    qglColor(Qt::white);

    const QFontMetrics metric(arial_font);

    //we calculate the height of the interline we want : max height of the font + 1 pixel to avoid overlapping (metric.height() returns a bigger value, a bit too much)
    int height = metric.tightBoundingRect("fg").height()+1;

    /* Draw container text */
    const unsigned int texts_size = _texts.size();
    std::map<Element_pos,Element_pos> previous_by_column;


    //int skipped,displayed=0;
    for (unsigned int i=0 ; i<texts_size ; i++){



        if (trace_to_render_y(_texts[i].y) + 0.5 < 9) continue;/* Do not display text if it is on the ruler area */

        //check if ye are not too close to another container to properly display the text
        std::map<Element_pos,Element_pos>::const_iterator it = previous_by_column.find(_texts[i].x);
        const std::map<Element_pos,Element_pos>::const_iterator it_end = previous_by_column.end();

        if (it==it_end || render_to_screen_y(trace_to_render_y(_texts[i].y)) - render_to_screen_y(trace_to_render_y((*it).second))> height ){
            const QString text_elided = metric.elidedText(_texts[i].value.c_str(), Qt::ElideRight, _x_scale_container_state*Info::Screen::width/(Info::Trace::depth+1.));
            renderText ( render_to_screen_x(_texts[i].x * _x_scale_container_state/0.20),
                         render_to_screen_y( trace_to_render_y(_texts[i].y) + 0.5),
                         text_elided,
                         arial_font);

            //push only displayed values in the map
            previous_by_column[_texts[i].x]=_texts[i].y;
            //displayed++;
        }//else{skipped++;}
    }

    // printf("skipped %d displayed %d\n", skipped, displayed);


    /* Draw ruler text */
    std::ostringstream buf_txt;
    Element_pos graduation_diff;
    Element_pos coeff_prefix;
    graduation_diff = Ruler::get_graduation_diff(Info::Render::_x_min_visible, Info::Render::_x_max_visible);
    coeff_prefix    = Ruler::get_coeff_for_common_prefix(Info::Render::_x_min_visible, Info::Render::_x_max_visible);

    arial_font.setPointSize(14);

    buf_txt.str("");/* flush the buffer */
    buf_txt << "min: " << (double)Info::Render::_x_min_visible;

    renderText (render_to_screen_x(trace_to_render_x(Info::Render::_x_min_visible)),
                render_to_screen_y(3),
                buf_txt.str().c_str(),
                arial_font);

    buf_txt.str("");/* flush the buffer */
    buf_txt << "max: " << (double)Info::Render::_x_max_visible;

    renderText (render_to_screen_x(trace_to_render_x(Info::Render::_x_max_visible))-130,
                render_to_screen_y(3),
                buf_txt.str().c_str(),
                arial_font);

    buf_txt.str("");
    buf_txt << Ruler::get_common_part_string(Info::Render::_x_min_visible, coeff_prefix) << "--";

    renderText (render_to_screen_x(
                    trace_to_render_x(
                        (Info::Render::_x_min_visible +
                         Info::Render::_x_max_visible) / 2)),
                render_to_screen_y(3),
                buf_txt.str().c_str(),
                arial_font);

    arial_font.setPointSize(10);

    for (Element_pos i = Info::Render::_x_min_visible ;
         i < Info::Render::_x_max_visible ;
         i+=graduation_diff){

        buf_txt.str("");/* flush the buffer */
        buf_txt << Ruler::get_variable_part(i, coeff_prefix, 2);

        renderText (render_to_screen_x(trace_to_render_x(i)+1),
                    render_to_screen_y(8),
                    buf_txt.str().c_str(),
                    arial_font);

    }

    //  update_minimap();





    //initialize the minimap if it hasn't been done yet
    if(!_minimap.is_initialized()){
        QImage buf = grabFrameBuffer(true);
        _minimap.init(buf);
    }

}





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



bool Render_opengl::build(){

    _state = DRAWING_STATE_DRAWING;/* change the drawing state */

    /* disable some OpenGL features to enhance the rendering */
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);

    replace_scale(1);/* for states scaling */
    init_geometry();/* Initialize geometry */

#ifdef SPINNING_LOGO
    if (_wait_timer != NULL){
        _wait_timer->stop();
        delete(_wait_timer);
        _wait_timer = NULL;
    }
#endif // SPINNING_LOGO


    if (nullptr == _render_instance)
        return true;


    // updateGL();
    //   QImage buf = grabFrameBuffer(true);
    // _minimap.init(buf);
    //updateGL();


    // QImage buf = grabFrameBuffer(true);
    // _minimap.init(buf);
    // updateGL();

    return true;//_render_instance->display_build();
}



bool Render_opengl::unbuild(){

    /**********************
     *
     * Init OpenGL features
     *
     **********************/

    /* enable some OpenGL features*/
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glEnable(GL_TEXTURE_2D);

    glEnable(GL_BLEND);/* enable blending for the alpha color */
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glColor3f(1.0, 1.0, 1.0);/* init color to white */


    /*****************************
     *
     * Init render area attributes
     *
     *****************************/

    _state = DRAWING_STATE_WAITING;/* change the drawing state */

    _wait_angle=0.0f;/* begin with 0 rad angle */


    /* clear lists to store container texts */
    _text_pos.clear();
    _text_value.clear();

    _arrows.clear();
    _circles.clear();
    _texts.clear();

    /* empty the selection stack */
    // while(false == _previous_selection.empty())
    //   _previous_selection.pop();

    if (nullptr == _render_instance)
        return true;

    /* Now, timer is set */
#ifdef SPINNING_LOGO
    if (_wait_timer == NULL){
        _wait_angle = 0.0f;

        _wait_timer = new QTimer(this);
        connect(_wait_timer, SIGNAL(timeout()), _render_instance, SLOT(updateGL()));
        _wait_timer->start(_wait_spf);
    }
#endif // SPINNING_LOGO
    _minimap.release();

    init_geometry();/* Initialize geometry */

    return true;//_render_instance->display_unbuild();
}





/***********************************
 *
 *
 *
 * Drawing function for the wait screen.
 *
 *
 *
 **********************************/



GLuint Render_opengl::draw_wait() {
    GLuint object;
    GLuint texture;

    object = glGenLists(1);/* create the list */

    if (object == 0)
        message << tr("Error when creating list").toStdString() << Message::endw;


    glGenTextures(1, &texture);/* create the texture and link it with the list previously created */

    QFile texture_file(QString(":/img/img/logo") +  QDate::currentDate().toString("MMdd") + QString(".png"));

    if (true == texture_file.exists())/* The texture exists */
        texture = bindTexture(QPixmap(texture_file.fileName()), GL_TEXTURE_2D);
    else/* use the default picture */
        texture = bindTexture(QPixmap(":/img/img/logo.png"), GL_TEXTURE_2D);

    glNewList(object, GL_COMPILE);/* open the list */
    {
        glBindTexture(GL_TEXTURE_2D, texture);/* load texture for drawing */

        glBegin(GL_QUADS);/* draw a square */
        {
            glTexCoord2d(0,0); glVertex2f(-1, -1);
            glTexCoord2d(1,0); glVertex2f(1, -1);
            glTexCoord2d(1,1); glVertex2f(1, 1);
            glTexCoord2d(0,1); glVertex2f(-1, 1);
        }
        glEnd();


    }
    glEndList();/* close the list */

    /* apply some parameters on the texture */
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glEnable(GL_TEXTURE_2D);

    glEnable(GL_BLEND);/* enable blending for the alpha color */
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


    /* Now, timer is set */
    if (_wait_timer == nullptr){
        _wait_angle = 0.0f;
        /*
         _wait_timer = new QTimer(this);
         connect(_wait_timer, SIGNAL(timeout()), _render_instance, SLOT(updateGL()));
         _wait_timer->start(_wait_spf);*/
    }

    return object;
}


void Render_opengl::call_ruler(){
    Element_pos graduation_diff;
    //Element_pos coeff_prefix;
    const Element_pos offset_x = _default_entity_x_translate;
    const Element_pos offset_y = _ruler_y + _ruler_height+3.5;

    update_visible_interval_value();

    graduation_diff = Ruler::get_graduation_diff(Info::Render::_x_min_visible, Info::Render::_x_max_visible);
    // coeff_prefix    = Ruler::get_coeff_for_common_prefix(Info::Render::_x_min_visible, Info::Render::_x_max_visible);

    set_color(1.0, 1.0, 1.0);

    for (Element_pos i = Info::Render::_x_min_visible ;
         i < Info::Render::_x_max_visible ;
         i+=graduation_diff){

        const Element_pos grad_div_by_5 = graduation_diff/5;
        for (Element_pos j = (i+grad_div_by_5) ; j<(i+graduation_diff) ; j+= grad_div_by_5){

            if (j>=Info::Render::_x_max_visible)
                break;

            draw_line( trace_to_render_x(j) + offset_x, offset_y ,
                       trace_to_render_x(j) + offset_x, 2+offset_y, _z_ruler);
        }

        if (i>=Info::Render::_x_max_visible)
            break;

        draw_line( trace_to_render_x(i) + offset_x, offset_y-1 ,
                   trace_to_render_x(i) + offset_x, 4+offset_y, _z_ruler);
    }

    glBegin(GL_QUADS);
    {
        glColor4f(0.0f, 0.0f, 1.0f, 0.8f);
        glVertex3d(trace_to_render_x(Info::Render::_x_min_visible), 3, _z_ruler_under );
        glVertex3d(trace_to_render_x(Info::Render::_x_min_visible), 8, _z_ruler_under );
        glVertex3d(trace_to_render_x(Info::Render::_x_max_visible), 8, _z_ruler_under );
        glVertex3d(trace_to_render_x(Info::Render::_x_max_visible), 3, _z_ruler_under );
    }
    glEnd();

    glBegin(GL_QUADS);
    {
        glColor4f(0.0, 0.0, 0.0, 1.0);
        glVertex3d(trace_to_render_x(Info::Render::_x_min_visible), 0, _z_ruler_over );
        glVertex3d(trace_to_render_x(Info::Render::_x_min_visible), 3, _z_ruler_over );
        glVertex3d(trace_to_render_x(Info::Render::_x_max_visible), 3, _z_ruler_over );
        glVertex3d(trace_to_render_x(Info::Render::_x_max_visible), 0, _z_ruler_over );
    }
    glEnd();

    return;
}



void Render_opengl::set_color(float r, float g, float b){
    _red = r;
    _green = g;
    _blue = b;
}

void Render_opengl::draw_text_value(long int id,double text, double y){
    if(y!=-1){
        Variable_text_ buf;
        buf.y=y;
        buf.value=text;
        // printf("adding %f at %f for %p\n", text, y, (void*)id);
        _variable_texts[id]=buf;
    }else{
        //it's an update
        Variable_text_ buf=_variable_texts[id];
        buf.value=text;
        //printf("updating %f at %f for %p\n", text, y, (void*)id);
        _variable_texts[id]=buf;
    }
}

void Render_opengl::draw_text(const Element_pos x, const Element_pos y, const Element_pos, const std::string s){
    if (_draw_ruler) return;/* do not draw text for ruler */
    if (_draw_state) message << "Fatal error! Cannot put text in state lists!" << Message::ende;

    Container_text_ buf;

    buf.x     = x;
    buf.y     = render_to_trace_y(y);/* Cancel previous transformation. */
    buf.value = s;

    _texts.push_back(buf);
}


void Render_opengl::draw_quad(Element_pos x, Element_pos y, Element_pos /*z*/, Element_pos w, Element_pos h){

    Element_pos offset_x;
    const Element_pos offset_y = -_ruler_y - _ruler_height;

    offset_x = 0;

    if (!_draw_container)
        offset_x = -_default_entity_x_translate;


    glBegin(GL_QUADS);
    {
        glColor3f(_red, _green, _blue);

        glVertex2d(x + offset_x   , y + offset_y);
        glVertex2d(x + offset_x   , y + h + offset_y);


        if (Info::Render::_shaded_states){/* State color is shaded */
            glColor3f(_red*0.6, _green*0.6, _blue*0.6);

            glVertex2d(x + w + offset_x, y + h + offset_y);
            glVertex2d(x + w + offset_x, y + offset_y);
        }else{/* State color must be uniform */
            glColor3f(_red, _green, _blue);

            /* Multiply by 0.98 to allow distinction between two States with the same color. Temporary solution
             before using a border.
             */
            glVertex2d(x + w*0.98 + offset_x, y + h + offset_y);
            glVertex2d(x + w*0.98 + offset_x, y + offset_y);
        }
    }
    glEnd();
}

void Render_opengl::draw_triangle(Element_pos, Element_pos,
                                  Element_pos, Element_pos ){
}

void Render_opengl::draw_line(Element_pos x1, Element_pos y1, Element_pos x2, Element_pos y2, Element_pos z){

    const Element_pos offset_x = -_default_entity_x_translate;
    const Element_pos offset_y = -_ruler_y - _ruler_height;

    if (_draw_state) message << "Fatal error! Cannot put line in state lists!" << Message::ende;


    glBegin(GL_LINES);
    {
        glColor3f(_red, _green, _blue);
        glVertex3d(x1 + offset_x, y1 + offset_y, z);
        glVertex3d(x2 + offset_x, y2 + offset_y, z);
    }
    glEnd();
}

void Render_opengl::draw_circle(Element_pos /*x*/, Element_pos /*y*/, Element_pos /*z*/, Element_pos /*r*/){
}


void Render_opengl::start_draw(){

    _draw_container = false;
    _draw_state     = false;
    _draw_ruler     = false;
    _draw_arrow     = false;
    _draw_event     = false;

    init_geometry();/* Initialize geometry */

}

void Render_opengl::start_draw_containers(){
    _list_containers = glGenLists(1);/* create the list */
    if (_list_containers == 0) {
        *Message::get_instance() << tr("Error when creating containers list.").toStdString() << Message::endw;
    }

    _draw_container = true;
    glNewList(_list_containers, GL_COMPILE);/* open the list */
}

void Render_opengl::draw_container(const Element_pos, const Element_pos, const Element_pos, const Element_pos ){

}

void Render_opengl::draw_container_text(const Element_pos, const Element_pos, const std::string &){
}

void Render_opengl::end_draw_containers(){
    _draw_container = false;
    glEndList();/* close the list */
}

void Render_opengl::start_draw_states(){
    _list_states = glGenLists(1);/* create the list */
    if (_list_states == 0) {
        *Message::get_instance() << tr("Error when creating states list.").toStdString() << Message::endw;
    }

    _draw_state = true;
    glNewList(_list_states, GL_COMPILE);/* open the list */
}

void Render_opengl::draw_state( const Element_pos x,
                                const Element_pos y,
                                const Element_pos z,
                                const Element_pos w,
                                const Element_pos h,
                                EntityValue *value )
{
    if ((value == nullptr) || (value->get_visible() == true) ) {
        draw_quad( x, y, z, w, h );
    }
}

void Render_opengl::end_draw_states(){
    glEndList();/* close the states list */
    _draw_state = false;
}

void Render_opengl::start_draw_arrows(){
    _draw_arrow = true;
}

void Render_opengl::draw_arrow(const Element_pos start_time,
                               const Element_pos end_time,
                               const Element_pos start_height,
                               const Element_pos end_height,
                               const Element_col red,
                               const Element_col green,
                               const Element_col blue,
                               EntityValue *value)
{
    Arrow_ buf;
    const Element_pos offset_x = -_default_entity_x_translate;
    const Element_pos offset_y = -_ruler_y - _ruler_height;

    if ((value == nullptr) || (value->get_visible() == true) ) {
        buf.start_time   = start_time   + offset_x;
        buf.end_time     = end_time     + offset_x;
        buf.start_height = start_height + offset_y;
        buf.end_height   = end_height   + offset_y;
        buf.red          = red;
        buf.green        = green;
        buf.blue         = blue;

        _arrows.push_back(buf);
    }
}

void Render_opengl::end_draw_arrows(){
    _draw_arrow = false;
}

void Render_opengl::start_draw_events(){
    _draw_event = true;
}

void Render_opengl::draw_event(const Element_pos time,
                               const Element_pos height,
                               const Element_pos container_height,
                               EntityValue *value)
{
    Event_ buf;
    const Element_pos offset_x = -_default_entity_x_translate;
    const Element_pos offset_y = -_ruler_y - _ruler_height;

    if ((value == nullptr) || (value->get_visible() == true) ) {
        buf.time             = time   + offset_x;
        buf.height           = height + offset_y;
        buf.container_height = -container_height/2   ;
        buf.red              = _red;
        buf.green            = _green;
        buf.blue             = _blue;

        _circles.push_back(buf);
    }
}


void Render_opengl::end_draw_events(){
    _draw_event = false;
}

void Render_opengl::start_draw_counter(){
    _list_counters = glGenLists(1);/* create the list */
    if (_list_counters == 0) {
        *Message::get_instance() << tr("Error when creating counters list").toStdString() << Message::ende;
    }

    glNewList(_list_counters, GL_COMPILE);/* open the list */
}

void Render_opengl::draw_counter(const Element_pos, const Element_pos ){
}

void Render_opengl::end_draw_counter(){
    glEndList();/* close the list */
}

void Render_opengl::start_ruler(){
    _draw_ruler = true;
}

void Render_opengl::end_ruler(){
    _draw_ruler = false;
}

void Render_opengl::end_draw(){
    //printf("sizeof eventlist : %d %d %d %d %d %d\n",_circles.size(), _circles.size()*sizeof(Event_),_arrows.size(),_arrows.size()*sizeof(Arrow_), _texts.size(), _texts.size()*sizeof(Container_text_));
}


void Render_opengl::draw_stored_texts(){
}

void Render_opengl::draw_stored_arrows(){

    /* Only draw triangle. Lines are already in a display list */
    const Element_pos coeff = 180.0/M_PI;
    Element_pos angle;
    Element_pos start_time, end_time, start_height, end_height;
    Element_pos scaled_start_time, scaled_end_time, scaled_start_height, scaled_end_height;



    const unsigned int arrow_size = _arrows.size();
    for (unsigned int i=0 ; i<arrow_size ; i++){

        start_time   = _arrows[i].start_time;// + _x_state_scale*_x_state_translate;
        end_time     = _arrows[i].end_time;// + _x_state_scale*_x_state_translate;
        start_height = _arrows[i].start_height;
        end_height   = _arrows[i].end_height;

        glColor3f(_arrows[i].red, _arrows[i].green, _arrows[i].blue);

        scaled_start_time   = start_time   * _x_state_scale;
        scaled_end_time     = end_time     * _x_state_scale;
        scaled_start_height = start_height * _y_state_scale;
        scaled_end_height   = end_height   * _y_state_scale;

        glPushMatrix();
        {

            glTranslated(_default_entity_x_translate + _x_state_scale*end_time - _x_state_translate,
                         _ruler_y + _ruler_height + _y_state_scale*end_height - _y_state_translate,
                         _z_arrow);/*

                                    glTranslated( _x_state_scale*end_time,
                                    _y_state_scale*end_height,
                                    0);*/

            if (start_time != end_time){
                angle = atan2((scaled_end_height - scaled_start_height), (scaled_end_time - scaled_start_time))*coeff;/* arc tangent */
                glRotated(angle, 0, 0, 1);
            }
            else
                glRotated(90, 0, 0, 1);/* vertical alignment */

            glBegin(GL_TRIANGLES);
            {
                // glColor3d(_red, _green, _blue);
                glVertex2d(0.0, 0.0);
                glVertex2d(-1.2, -0.4);
                glVertex2d(-1.2, 0.4);
            }
            glEnd();

        }
        glPopMatrix();
    }

    for (unsigned int i=0 ; i<arrow_size ; i++){

        start_time   = _arrows[i].start_time;// + _x_state_scale*_x_state_translate;
        end_time     = _arrows[i].end_time;// + _x_state_scale*_x_state_translate;
        start_height = _arrows[i].start_height;
        end_height   = _arrows[i].end_height;

        glColor3f(_arrows[i].red, _arrows[i].green, _arrows[i].blue);

        glPushMatrix();
        {

            glTranslated(_default_entity_x_translate - _x_state_translate,
                         _ruler_y + _ruler_height - _y_state_translate,
                         _z_arrow);
            glScaled(_x_state_scale, _y_state_scale, 1.0);

            glBegin(GL_LINES);
            {
                glVertex2d(start_time, start_height);
                glVertex2d(end_time , end_height  );
            }
            glEnd();
        }
        glPopMatrix();
    }
}


void Render_opengl::draw_stored_circles(){


    //tables to store values used for circles drawings, with run-time data (zoom, size of the window), using pre-generated tables
    double cos_table2[NB_STEPS];
    double sin_table2[NB_STEPS];

    for(int j = 0 ; j < NB_STEPS ; j ++){
        cos_table2[j]=  cos_table[j]/_x_state_scale;
        sin_table2[j]= (sin_table[j]*Info::Screen::width)/(_y_state_scale*Info::Screen::height);
    }

    const unsigned int size = _circles.size();

    for (unsigned int i=0 ; i<size ; i++){

        glPushMatrix();
        {

            glColor3f(1.0 - _circles[i].red, 1.0 - _circles[i].green, 1.0 - _circles[i].blue);

            glTranslated(_default_entity_x_translate - _x_state_translate,
                         _ruler_y + _ruler_height - _y_state_translate,
                         _z_arrow);
            glScalef(_x_state_scale, _y_state_scale, 1.0);



            // Draw the outer circle

            glBegin(GL_POLYGON);
            {
                for(int j = 0 ; j < NB_STEPS ; j ++){
                    glVertex2d(_circles[i].time + cos_table2[j],
                               _circles[i].height + sin_table2[j]);
                }
            }
            glEnd();
        }
        glPopMatrix();
    }

    for (unsigned int i=0 ; i<size ; i++){

        glPushMatrix();
        {


            glColor3f(_circles[i].red, _circles[i].green, _circles[i].blue);
            glTranslated(_default_entity_x_translate - _x_state_translate,
                         _ruler_y + _ruler_height - _y_state_translate,
                         _z_arrow);
            glScalef(_x_state_scale, _y_state_scale, 1.0);

            // Draw the sub-circle
            glBegin(GL_POLYGON);
            {
                for(int j = 0 ; j < NB_STEPS ; j ++){
                    glVertex3d(_circles[i].time + cos_table2[j]/1.2,
                               _circles[i].height + sin_table2[j]/1.2,
                               0.1);
                }
            }
            glEnd();
        }
        glPopMatrix();
    }

    //draw the line in another loop for better performance
    for (unsigned int i=0 ; i<size ; i++){

        glPushMatrix();
        {

            glColor3f(_circles[i].red, _circles[i].green, _circles[i].blue);
            glTranslated(_default_entity_x_translate - _x_state_translate,
                         _ruler_y + _ruler_height - _y_state_translate,
                         _z_arrow);
            glScalef(_x_state_scale, _y_state_scale, 1.0);

            glLineWidth( 3.0 );
            //Draw the line
            glBegin(GL_LINES);
            {
                glVertex2d(_circles[i].time, _circles[i].height-_circles[i].container_height);
                glVertex2d(_circles[i].time, _circles[i].height);

            }
            glEnd();
        }
        glPopMatrix();
    }

    glLineWidth( 1.0 );
}

void Render_opengl::update_vertical_line(){


    if (_mouse_pressed_inside_container)
        set_vertical_line(0);
    else {
        set_vertical_line(render_to_trace_x( screen_to_render_x(_mouse_x)));
        Element_pos d = render_to_trace_x( screen_to_render_x(_mouse_x));
        _core->launch_action(_core->_STATE_UPDATE_VARVALUES, &d);//update the value of the variables to display
    }
    updateGL();
}

Element_pos Render_opengl::get_vertical_line(){
    return vertical_line;
}

void Render_opengl::set_vertical_line(Element_pos new_coord){
    if(new_coord==vertical_line)vertical_line=0;
    else vertical_line=new_coord;

}


void Render_opengl::draw_vertical_line(){

    if(vertical_line==0)return;
    glPushMatrix();
    {
        glLineWidth( 1.0 );
        /* Draw the line */
        glBegin(GL_LINES);
        {
            glColor3d(1.0,0.0,0.0);
            glVertex2d( trace_to_render_x(vertical_line),0);
            glVertex2d( trace_to_render_x(vertical_line),Info::Render::height);
        }
        glEnd();
    }
    glPopMatrix();
    QFont arial_font = QFont("Arial", 10);
    const QFontMetrics metric(arial_font);
    QString t =QString().setNum(vertical_line);
    // draw time
    renderText ( render_to_screen_x(trace_to_render_x(vertical_line))-metric.size(Qt::TextSingleLine,t).width() -5,
                 metric.height()-1,
                 t,
                 arial_font);


    //draw texts for variables
    std::map<long int, Variable_text_>::const_iterator it=_variable_texts.begin();
    const std::map<long int, Variable_text_>::const_iterator it_end=_variable_texts.end();

    for(;it!=it_end;it++){
        renderText ( render_to_screen_x(trace_to_render_x(vertical_line))+3,
                     render_to_screen_y( trace_to_render_y((*it).second.y) + 0.5),
                     QString().setNum((*it).second.value),
                     arial_font);

    }
}

// void Render_opengl::create_minimap(const int width, const int height){

//     _minimap_widget = new QLabel;
//     _minimap_image = new QImage;

//     if (!_minimap_widget || !_minimap_image){
//         message << "Cannot allocate memory for minimap" << Message::endw;
//         return;
//     }

//     paintGL();

//     *_minimap_image = grabFrameBuffer(true);

//     //    _default_entity_x_translate = 20; /* TODO: use constant value define in config file */
//     /*_ruler_height               = 8.5;
//       _ruler_y                    = 0.0;*/

//     //  *_minimap_image = _minimap_image->scaled(QSize(width, height), Qt::IgnoreAspectRatio);
//     _minimap_widget->resize(width, height);
//     _minimap_widget->hide();/* Do not display it */
//     //  update_minimap();
// }

// void Render_opengl::update_minimap(){
//     QPixmap buf;
//     static int i = 0;

//     if (!_minimap_image)
//         return;

//     if (!_minimap_widget)
//         return;

//     buf = QPixmap::fromImage(*_minimap_image);

//     if (buf.isNull())
//         return;

//     QPainter painter;
//     QPen pen;
//     painter.begin(&buf);

//     pen.setColor(Qt::red);
//     pen.setWidth(4);

//     painter.setPen(pen);
//     painter.setBrush(QBrush(QColor::fromRgbF(1, 1, 0, 0.5)));

//     painter.drawRect ( ( ( (_x_state_translate - _default_entity_x_translate)/ (Info::Render::width*_x_state_scale) ) +  (_default_entity_x_translate/Info::Render::width) )*_minimap_image->width(),
//                       //render_to_screen_x(trace_to_render_x(Info::Render::_x_min_visible))*200/Info::Screen::width,
//                        ( (_y_state_translate - _ruler_height) / (Info::Render::height*_y_state_scale) + (_ruler_height/Info::Render::height))*_minimap_image->height(),
//                        _minimap_image->width()/_x_state_scale,//render_to_screen_x(trace_to_render_x(Info::Render::_x_max_visible - Info::Render::_x_min_visible)),
//                        _minimap_image->height()/_y_state_scale);
//     painter.end();


//     _minimap_widget->setPixmap(buf);
//     _minimap_widget->setScaledContents(true);

//     if (!_minimap_widget->pixmap())
//         message << tr("No Pixmap set for the minimap").toStdString() << Message::endw;
// }


void Render_opengl::show_minimap(){

    //if (state)
    _minimap.show();
    /*    else
     _minimap.hide();*/
    // if (!_minimap_widget) return;

    // if (state)
    //     _minimap_widget->show();
    // else
    //     _minimap_widget->hide();
}


// void Render_opengl::release_minimap(){

//     if (_minimap_image){
//         delete _minimap_image;
//         _minimap_image = NULL;
//     }

//     if (_minimap_widget)
//         _minimap_widget->close();

//     /* Qt will release automatically _minimap_widget */
// }


void Render_opengl::clear_arrow (){
    _arrows.clear();
}

void Render_opengl::clear_text (){
    _texts.clear();
    _variable_texts.clear();
}
