/**
 *
 * @file src/render/opengl/Render_opengl.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Arthur Chevalier
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Francois Trahay
 * @author Thibault Soucarre
 * @author Thomas Herault
 * @author Kevin Coulomb
 * @author Philippe Swartvagher
 * @author Lucas Guedon
 * @author Augustin Gauchet
 * @author Olivier Lagrasse
 * @author Augustin Degomme
 * @author Luigi Cannarozzo
 *
 * @date 2024-07-17
 */
/*!
 *\file Render_opengl.cpp
 */

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

#include <sstream>
#include <cmath>
/* -- */
#ifdef __APPLE__
#include <OpenGL/glu.h>
#else
#include <GL/glu.h>
#endif
/* -- */
#include <QFile> // For loading the wait image
#include <QDate>
#include <QTimer>
#include <QEventLoop>
/* -- */
#include "interface/resource.hpp"
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
#include "common/Message.hpp"
/* -- */
#include "render/Ruler.hpp"
#include "render/opengl/Render_opengl.hpp"
#include "render/GanttDiagram.hpp"
/* -- */
#include "trace/DrawTrace.hpp"
#include "trace/EntityValue.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] = { 0 };
static double sin_table[NB_STEPS] = { 0 };

const int Render_opengl::DRAWING_TIMER_DEFAULT = 10;

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

Render_opengl::Render_opengl(Interface_graphic *interface_graphic, const QGLFormat &format) :
    Hook_event(this, interface_graphic, format) {
    _draw_container = false;
    _draw_state = false;
    _draw_ruler = false;
    _draw_arrow = false;
    _draw_event = false;

    _list_containers = 0;
    _list_states = 0;
    _list_counters = 0;
    _list_arrows = 0;

    _red = 0.;
    _green = 0.;
    _blue = 0.;

    _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;
    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();
}

QWidget *Render_opengl::get_render_widget() {
    return this;
}

QImage Render_opengl::grab_frame_buffer() {
    return grabFrameBuffer(true); /* true = with alpha channel */
}

/***********************************
 *
 * 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();
    _interface_graphic->get_console()->waitGUIInit->quit();
}

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

    /* update information 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 occurred: ").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(QStringLiteral("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(QStringLiteral("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(QString::fromStdString(_texts[i].value), 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);

    //  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_x(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(QStringLiteral(":/img/img/logo") + QDate::currentDate().toString(QStringLiteral("MMdd")) + QStringLiteral(".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(QStringLiteral(":/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() {

    update_visible_interval_value();

    // Draw blue rect
    glBegin(GL_QUADS);
    {
        glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
        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();

    // Draw black rect
    glBegin(GL_QUADS);
    {
        glColor4f(0.0, 0.0, 0.0, 1.0);
        glVertex3d(0, 0, _z_ruler_over);
        glVertex3d(0, 3, _z_ruler_over);
        glVertex3d(Info::Render::width, 3, _z_ruler_over);
        glVertex3d(Info::Render::width, 0, _z_ruler_over);
    }
    glEnd();

    // Draw graduation lines
    set_color(1.0, 1.0, 1.0); // White

    QFont arial_font = QFont(QStringLiteral("Arial"), 10);
    std::ostringstream buf_txt;

    const Element_pos offset_x = _default_entity_x_translate;

    const Element_pos graduation_diff = Ruler::get_graduation_diff(render_to_trace_x(0), render_to_trace_x(Info::Render::width));
    const Element_pos grad_div_by_5 = graduation_diff / 5;

    const Element_pos first_grad_pos = Info::Render::_x_min_visible - fmodf(Info::Render::_x_min_visible, graduation_diff);
    const Element_pos last_grad_pos = Info::Render::_x_max_visible - fmodf(Info::Render::_x_max_visible, graduation_diff);

    const Element_pos offset_y = _ruler_y + _ruler_height + 3;

    for (Element_pos i = first_grad_pos; i < last_grad_pos + graduation_diff; i += graduation_diff) {
        Element_pos j = (i + grad_div_by_5);
        for (int loop_index = 0; loop_index < 4; loop_index++) {
            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, offset_y + 2.5, _z_ruler);
            j += grad_div_by_5;
        }
        draw_line(trace_to_render_x(i) + offset_x, offset_y,
                  trace_to_render_x(i) + offset_x, offset_y + 5, _z_ruler);
    }

    arial_font.setPointSize(14);

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

    renderText(render_to_screen_x(0),
               render_to_screen_y(3),
               QString::fromStdString(buf_txt.str()),
               arial_font);

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

    renderText(render_to_screen_x(Info::Render::width) - 130,
               render_to_screen_y(3),
               QString::fromStdString(buf_txt.str()),
               arial_font);

    // Display common part
    const std::string common_part_string = Ruler::get_common_part_string(Info::Render::_x_min_visible, Info::Render::_x_max_visible);
    buf_txt.str("");
    buf_txt << common_part_string << "-- ";

    renderText(render_to_screen_x(Info::Render::width / 2),
               render_to_screen_y(3),
               QString::fromStdString(buf_txt.str()),
               arial_font);

    // Display visible duration
    buf_txt.str("");
    buf_txt << "(duration : "
            << Info::Render::_x_max_visible - Info::Render::_x_min_visible
            << ")";

    renderText(render_to_screen_x(Info::Render::width / 4),
               render_to_screen_y(3),
               QString::fromStdString(buf_txt.str()),
               arial_font);

    // Display graduation text

    // Check the number of digits after the floating point
    const std::size_t length_after_point = max(0, -int(floor(log10(graduation_diff))));

    for (Element_pos i = first_grad_pos; i <= last_grad_pos; i += graduation_diff) {
        renderText(render_to_screen_x(trace_to_render_x(i) + 1),
                   render_to_screen_y(8),
                   QString::fromStdString(Ruler::get_variable_part(i, common_part_string, length_after_point)),
                   arial_font);
    }

    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 size, 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.size = size;
    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 {
        const Element_pos d = render_to_trace_x(screen_to_render_x(_mouse_x));
        set_vertical_line(d);
        DrawTrace buf;
        std::map<long int, double> var_map = _layout->get_trace()->update_text_variable_values(d);
        buf.draw_text_variable_values(_layout->get_render_area(), &var_map);
    }
    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(QStringLiteral("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();
}

#include "moc_Render_opengl.cpp"
