/**
 *
 * @file src/render/Hook_event.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Nolan Bredel
 * @author Mathieu Faverge
 * @author Francois Trahay
 * @author Thibault Soucarre
 * @author Lucas Guedon
 * @author Augustin Gauchet
 * @author Olivier Lagrasse
 * @author Augustin Degomme
 *
 * @date 2024-07-17
 */
/*!
 *\file Hook_event.cpp
 */

#include <stack>
#include <sstream>
/* -- */
#include <QMessageBox>
#include <QMouseEvent>
#include <QGLWidget>
#include <QTimer>
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
/* -- */
#include "core/Core.hpp"
/* -- */
#include "common/Message.hpp"
/* -- */
#include "render/Geometry.hpp"
#include "render/Render.hpp"
#include "render/opengl/Render_opengl.hpp"
#include "render/Hook_event.hpp"
/* -- */
using namespace std;

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

#ifdef WIN32
#define getTimeClick() GetDoubleClickTime()
#else
#define getTimeClick() 200 /*                                            \
 #include "X11/Intrinsic.h"                                              \
 #include <QX11Info>                                                     \
                                                                       \ \
 #define getTimeClick() try{                                             \
 XtGetMultiClickTime(((QWidget*)this->parent())->x11Info().display());   \
 }catch(char* e){                                                        \
 printf("%s \n", e);                                                     \
 }*/
#endif

const int Hook_event::DRAWING_STATE_WAITING = 1;
const int Hook_event::DRAWING_STATE_DRAWING = 2;
const int Hook_event::_ctrl_scroll_factor = 10;
const int Hook_event::_ctrl_zoom_factor = 3;

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

Hook_event::Hook_event(Render *render_instance, Interface_graphic *interface_graphic, const QGLFormat &format) :
    QGLWidget(format, interface_graphic), Render_abstract(interface_graphic) { // interface_graphic is used as the parent of this QGLWidget

    if (!QGLFormat::hasOpenGL()) {
        QMessageBox::information(nullptr, tr("ViTE: Fatal OpenGL error"), tr("This system does not support OpenGL."));
    }

    /*   if (!QGLFormat::hasOpenGLOverlays () ) {
        message <<  tr("Render OpenGL: overlay is not supported!").toStdString() << Message::endw;
        }*/

    //  makeCurrent();/* need to "enable" glew */

    //     glew_code = glewInit();/* use for VBO and shaders */

    //     if(GLEW_OK != glew_code){
    //         message << "Cannot initialize GLEW: " << glewGetErrorString(glew_code) << Message::ende;
    //     }

    //     /*Check if VBO is supported */
    //     if (GL_FALSE == glewIsSupported("GL_ARB_vertex_buffer_object")){
    //         message << "VBO OpenGL extension is not supported by your graphic card." << Message::ende;
    //     }

    Info::Screen::width = width(); /* get the widget visibled width (in pixel)  */
    Info::Screen::height = height(); /* get the widget visibled height (in pixel) */

    _render_instance = render_instance;

    _state = DRAWING_STATE_WAITING; /* At the beginning, no trace is drawing */

    /* init main information about OpenGL scene and QGLWidget size */

    _mouse_x = 0;
    _mouse_y = 0;
    _new_mouse_x = 0;
    _new_mouse_y = 0;
    _mouse_pressed = false;
    _mouse_pressed_inside_container = false;
    _mouse_pressed_inside_ruler = false;

    _minimum_distance_for_selection = 5; /* 5 pixels */

    /* Info init */
    // Info::Render::_key_alt = false;/* no CTRL key pushed */

    setAutoFillBackground(false);

    setMouseTracking(true); /* to catch mouse events */
    setFocusPolicy(Qt::StrongFocus); /* to catch keybord events (focus by tabbing or clicking) */

    // initialize the timer for single click handling
    _timer = new QTimer(this);
    _timer->setSingleShot(true);
    _connected = false;
}

Hook_event::~Hook_event() = default;

/***********************************
 *
 *
 *
 * Scaling and scrolling functions.
 *
 *
 *
 **********************************/
void Hook_event::mousePressEvent(QMouseEvent *event) {
    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING) {
        return;
    }

    /* If a right click was triggered, just restore the previous zoom */
    if (Qt::RightButton == event->button()) {

        /* Just a special case: if a zoom box is currently drawing, the right click is used
           to cancel the drawing */
        if (true == _mouse_pressed) {
            _mouse_pressed = false;
            _mouse_pressed_inside_container = false;
            _mouse_pressed_inside_ruler = false;

            update_render();
            return;
        }

        /* if there is no previous zoom box registered, return */
        if (true == _previous_selection.empty())
            return;

        /* restore the previous values */
        _x_state_scale = _previous_selection.top().x_scale;
        _y_state_scale = _previous_selection.top().y_scale;
        _x_state_translate = _previous_selection.top().x_translate;
        _y_state_translate = _previous_selection.top().y_translate;

        /* remove the previous value */
        _previous_selection.pop();

        refresh_scroll_bars();
        update_render();

#ifdef DEBUG_MODE_RENDER_AREA_

        /* if there is no previous zoom box registered, return */
        if (true == _previous_selection.empty())
            return;

        cerr << __FILE__ << " l." << __LINE__ << ":" << endl;
        cerr << _previous_selection.top().x_scale << " "
             << _previous_selection.top().y_scale << " "
             << _previous_selection.top().x_translate << " "
             << _previous_selection.top().y_translate << endl
             << endl;
#endif

        _layout->set_zoom_box_x_value((int)(100 * _x_state_scale));
        _layout->set_zoom_box_y_value((int)(100 * _y_state_scale));

        /* Then, return */
        return;
    }

    /* else, registered current X and Y mouse position to draw a zoom box */

    _mouse_x = event->x();
    _new_mouse_x = _mouse_x;

    /* user had clicked on the container */
    if (screen_to_render_x(_mouse_x) < _x_scale_container_state * Info::Render::width) {
        _mouse_pressed_inside_container = true;
        _mouse_x = render_to_screen_x(_x_scale_container_state * Info::Render::width);
    }

    _mouse_y = event->y();
    _new_mouse_y = _mouse_y;

    /* user had clicked on the ruler */
    if (screen_to_render_y(_mouse_y) <= (_ruler_height + _ruler_y)) {
        _mouse_pressed_inside_ruler = true;
        _mouse_y = render_to_screen_y(_ruler_height + _ruler_y);
    }

    _mouse_pressed = true;
}

void Hook_event::mouseDoubleClickEvent(QMouseEvent *event) {

    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING) {
        return;
    }

    Element_pos x_result, y_result; /* The click coordinates for the Data Structure. */

    if (_timer->isActive()) {
        _timer->stop();
    }

    if (Qt::LeftButton == event->button()) {
        x_result = render_to_trace_x(screen_to_render_x(_mouse_x));
        y_result = render_to_trace_y(screen_to_render_y(_mouse_y));

        Info::Render::_info_x = x_result;
        Info::Render::_info_y = y_result;
        Info::Render::_info_accurate = 1 / coeff_trace_render_x();
        _interface_graphic->render_display_information();

        _mouse_pressed = false;
        update_render();
    }
}

void Hook_event::mouseMoveEvent(QMouseEvent *event) {
    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING || !_mouse_pressed) {
        return;
    }

    if (event->modifiers() == Qt::CTRL) {
        setCursor(Qt::SizeAllCursor);
        Info::Render::_key_ctrl = true;
    }

    if (Info::Render::_key_ctrl) {
        change_translate_x(screen_to_render_x(_mouse_x - _new_mouse_x));
        change_translate_y(screen_to_render_y(_mouse_y - _new_mouse_y));

        _mouse_x = _new_mouse_x;
        _mouse_y = _new_mouse_y;

        refresh_scroll_bars();
    }
    // if (_mouse_pressed_inside_container)
    //     _new_mouse_x = Info::Screen::width;
    // else
    _new_mouse_x = event->x();

    if (_mouse_pressed_inside_ruler)
        _new_mouse_y = Info::Screen::height;
    else
        _new_mouse_y = event->y();

    update_render();
}

void Hook_event::mouseReleaseEvent(QMouseEvent *event) {
    // if the timer has not been connected yet, connect it (there is a problem with QT signal/slots and genericty, and the type of this isn't right in the constructor)
    if (!_connected) {
        // connect(_timer, SIGNAL(timeout()), (Hook_event *) this, SLOT(update_vertical_line()));
        connect(_timer, &QTimer::timeout, this, [=]() {
            update_vertical_line();
        });
        _connected = true;
    }

    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING || (_mouse_pressed && Info::Render::_key_ctrl)) {
        _mouse_pressed = false;
        return;
    }

    Element_pos invert_buf_x;
    Element_pos invert_buf_y;

    Selection_ selection_stack_buf;

    /* If a right click was triggered, just restore the previous zoom */
    if (Qt::RightButton == event->button())
        return;

    if (_mouse_pressed_inside_container) {
        Element_pos y1 = render_to_trace_y(screen_to_render_y(_mouse_y));
        Element_pos x1 = screen_to_render_x(_new_mouse_x) / (_x_scale_container_state * Info::Render::width);
        Element_pos y2 = render_to_trace_y(screen_to_render_y(_new_mouse_y));
        Element_pos x2 = screen_to_render_x(_new_mouse_x) / (_x_scale_container_state * Info::Render::width);
        _interface_graphic->switch_container(x1, x2, y1, y2);

        _mouse_pressed = false;
        _mouse_pressed_inside_container = false;
    }
    else {
        if (_new_mouse_x < _mouse_x) {
            invert_buf_x = _mouse_x;
            _mouse_x = _new_mouse_x;
            _new_mouse_x = (int)invert_buf_x;
        }

        if (_new_mouse_y < _mouse_y) {
            invert_buf_y = _mouse_y;
            _mouse_y = _new_mouse_y;
            _new_mouse_y = (int)invert_buf_y;
        }

        /*
         * When the mouse is released:
         *
         * First, check if there is a significant difference between mouse coordinates. Prevent bad manipulations.
         */

        if (false == _mouse_pressed)
            return;

        if (((_new_mouse_x - _mouse_x) < _minimum_distance_for_selection)
            && ((_new_mouse_y - _mouse_y) < _minimum_distance_for_selection)) { /* selection is too thin to draw a box. So, it must be a user click to display entity information */
            // start the timer to catch a click or double click
            _timer->start(getTimeClick());

            _mouse_pressed = false;
            _mouse_pressed_inside_container = false;
            _mouse_pressed_inside_ruler = false;
            update_render();
            return; /* escape */
        }

        /* Thin box in the ruler: ignore */
        if (_mouse_pressed_inside_ruler && ((_new_mouse_x - _mouse_x) < _minimum_distance_for_selection)) {

            // start the timer to catch a click or double click
            _timer->start(getTimeClick());

            _mouse_pressed = false;
            _mouse_pressed_inside_ruler = false;
            update_render();
            return;
        }

        /* Thin box in the containers: ignore */
        if (_mouse_pressed_inside_container && ((_new_mouse_y - _mouse_y) < _minimum_distance_for_selection)) {

            // hide the vertical line if we press inside a container
            _render_instance->set_vertical_line(0);

            _render_instance->update_vertical_line();
            // start the timer to catch a click or double click

            _mouse_pressed = false;
            _mouse_pressed_inside_container = false;
            update_render();
            return;
        }

        /*
         * Now, user was drawing a box. Zoom in it!
         */

        /* Register this position which will be used by a right clic */
        selection_stack_buf.x_scale = _x_state_scale;
        selection_stack_buf.y_scale = _y_state_scale;
        selection_stack_buf.x_translate = _x_state_translate;
        selection_stack_buf.y_translate = _y_state_translate;

        _mouse_pressed = false;
        _mouse_pressed_inside_container = false;
        _mouse_pressed_inside_ruler = false;

        // If zoom box was applied we push the last saved zoom state
        if (apply_zoom_box(_mouse_x, _new_mouse_x, _mouse_y, _new_mouse_y)) {
            _previous_selection.push(selection_stack_buf);
        };

#ifdef DEBUG_MODE_RENDER_AREA_

        cerr << __FILE__ << " l." << __LINE__ << ":" << endl;
        cerr << _previous_selection.top().x_scale << " "
             << _previous_selection.top().y_scale << " "
             << _previous_selection.top().x_translate << " "
             << _previous_selection.top().y_translate << endl
             << endl;
#endif
    }
}

void Hook_event::wheelEvent(QWheelEvent *event) {

    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING) {
        return;
    }

    QPoint num_degrees = event->angleDelta() / 8;

    if (!num_degrees.isNull()) {
        QPoint num_steps = num_degrees / 15;
        Element_pos scale_coeff = num_steps.y(); // Take the vertical wheel info

#if defined(HAVE_QT5_15)
        _mouse_x = event->globalPosition().x();
#else
        _mouse_x = event->globalPos().x();
#endif
        int ctrl_factor = 1;

        if (event->modifiers() == Qt::CTRL)
            ctrl_factor *= _ctrl_zoom_factor;

        //   cerr << Info::Render::_key_alt << endl;
        if (true == Info::Render::_key_alt) { /* Zoom on height */
            change_scale_y(scale_coeff);
        }
        else { /* Zoom on time */
            change_scale_x(scale_coeff * ctrl_factor);
        }
    }

    event->accept(); /* accept the event */
}

void Hook_event::keyPressEvent(QKeyEvent *event) {

    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING) {
        return;
    }

    int ctrl_factor = 1;

    if (event->key() == Qt::Key_Alt) {
        //    cerr << "Push" << endl;
        Info::Render::_key_alt = true;
        _interface_graphic->set_axis_icon(true);
    }

    if (event->key() == Qt::Key_Control) {
        ctrl_factor *= _ctrl_scroll_factor;
        setCursor(Qt::SizeAllCursor);
        Info::Render::_key_ctrl = true;
    }

    switch (event->key()) {
    case Qt::Key_Left:
        /*
         * Key 'left' pressed.
         */
        change_translate_x(-1 * ctrl_factor);
        break;
    case Qt::Key_Right:
        /*
         * Key 'right' pressed.
         */
        change_translate_x(1 * ctrl_factor);
        break;
    case Qt::Key_Up:
        /*
         * Key 'up' pressed.
         */
        change_translate_y(-1 * ctrl_factor);
        break;
    case Qt::Key_Down:
        /*
         * Key 'down' pressed.
         */
        change_translate_y(1 * ctrl_factor);
        break;
    case Qt::Key_PageUp:
        /*
         * Key 'Page Up' pressed.
         */
        if (true == Info::Render::_key_alt)
            change_translate_y(-Info::Render::height);
        else
            change_translate_x(Info::Render::width);
        break;
    case Qt::Key_PageDown:
        /*
         * Key 'Page Down' pressed.
         */
        if (true == Info::Render::_key_alt)
            change_translate_y(Info::Render::height);
        else
            change_translate_x(-Info::Render::width);
        break;

    default:
        /*
         * Unknow key pressed.
         */
        break;
    }

    _key_scrolling = true;
    event->accept(); /* accept the event */
}

void Hook_event::keyReleaseEvent(QKeyEvent *event) {

    /* Do nothing if no trace is loaded */
    if (_state == DRAWING_STATE_WAITING) {
        return;
    }

    if (event->key() == Qt::Key_Alt) {
        Info::Render::_key_alt = false;
        _interface_graphic->set_axis_icon(false);
    }

    if (event->key() == Qt::Key_Control) {
        setCursor(Qt::ArrowCursor);
        Info::Render::_key_ctrl = false;
    }
}

void Hook_event::update_render() {
    updateGL();
}
