/**
 *
 * @file src/render/Render_abstract.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Lucas Guedon
 *
 * @date 2024-07-17
 */
/*!
 *\file Render_abstract.cpp
 */

#include <stack>
#include <sstream>
/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
/* -- */
#include "common/Message.hpp"
/* -- */
#include "render/Render_abstract.hpp"
/* -- */
using namespace std;

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

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

const float Render_abstract::_MIN_X_ZOOM_VALUE = 0.01f;

Render_abstract::Render_abstract(Interface_graphic *interface_graphic) :
    _interface_graphic(interface_graphic), _layout(nullptr),
    _scroll_margin_x((Info::Render::width - _default_entity_x_translate) / 2), _scroll_margin_y((Info::Render::height - _ruler_height) / 2) {

    /* init main information about the scene and the widget size */

    _ruler_distance = 5;
    _key_scrolling = false;
    _selection_rectangle_alpha = 0.5;
}

/***********************************
 *
 *
 *
 * Scaling and scrolling functions.
 *
 *
 *
 **********************************/

bool Render_abstract::apply_zoom_box(Element_pos x_min, Element_pos x_max, Element_pos y_min, Element_pos y_max) {

    // If zoom is too large, return
    if (render_to_trace_x(screen_to_render_x(x_max)) - render_to_trace_x(screen_to_render_x(x_min)) < Info::Entity::x_max / _MAX_ZOOM_VALUE) {
        // The render is updated to remove the rectangle of the zoom box
        update_render();
        return false;
    }

    Element_pos x_middle_selection;
    Element_pos y_middle_selection;
    Element_pos x_scaled_middle_selection;
    Element_pos y_scaled_middle_selection;
    Element_pos x_distance_between_state_origin_and_render_middle;
    Element_pos y_distance_between_state_origin_and_render_middle;
    /*
     * Now, we try to zoom on the selection rectangle. To perform this, the left of the selection rectangle must be fit with the left
     * of the render area. Idem for the right, the top and the bottom.
     *
     * Thus, we need to know the difference the scale between the rectangle width and height and the render area width and height.
     * Results are given by the scale: Info::Screen::width/(x_max - x_min) for the horizontal dimension.
     *
     * Then, our selection rectangle is scaled. Nevertheless, it should not be centered inside the render area. So, we work out
     * the correct x and y translation to make both the render area middle and the selection rectangle middle fit.
     * There are 3 steps for the middle x position :
     *  - First, find distance between the scaled selection rectangle middle and the state origin.
     *  - Second, find the distance between the state origin and the render area middle.
     *  - Finally, translate the scaled selection middle to the difference.
     */

    /*
     * Work out the horizontal middle of the selection area (the mean value of x_min and x_max, same for y)
     * Note: mouse positions are converted to render area coordinate with screen_to_render_**() methods.
     */
    x_middle_selection = screen_to_render_x((x_min + x_max) / 2);
    y_middle_selection = screen_to_render_y((y_min + y_max) / 2);

    /*
     * 1st step:
     *
     * Work out the new selection middle position after applying the scale.
     */
    x_scaled_middle_selection = (x_middle_selection + _x_state_translate - _default_entity_x_translate) * ((Info::Screen::width * (1 - _x_scale_container_state)) / ((x_max - x_min)));
    y_scaled_middle_selection = (y_middle_selection + _y_state_translate - _ruler_height) * ((Info::Screen::height - render_to_screen_y(_ruler_height)) / ((y_max - y_min)));

    /*
     * 2nd step:
     *
     * Work out the distance between the state origin and the render area middle (Info::Render::width/2).
     */
    x_distance_between_state_origin_and_render_middle = _x_state_translate + Info::Render::width * _x_scale_container_state - _default_entity_x_translate + Info::Render::width / 2 - _default_entity_x_translate / 2;
    y_distance_between_state_origin_and_render_middle = _y_state_translate + (Info::Render::height - _ruler_height) / 2;

    /*
     * 3rd step:
     *
     * Translate entities.
     */
    _x_state_translate += x_scaled_middle_selection - x_distance_between_state_origin_and_render_middle;
    _y_state_translate += y_scaled_middle_selection - y_distance_between_state_origin_and_render_middle;

    /*
     * Finally, perform the scale.
     */

    /* NOTE: do not use replace_scale() because the translate will be also modified */
    _x_state_scale *= (Info::Render::width * (1 - _x_scale_container_state)) / screen_to_render_x(x_max - x_min);

    _y_state_scale *= (Info::Render::height - _ruler_height) / screen_to_render_y(y_max - y_min);

    /* Now, check if zoom is not too much */
    update_visible_interval_value();

#if defined(USE_ITC) && defined(BOOST_SERIALIZE)
    if (Info::Splitter::preview == true && Info::Render::_key_ctrl == true) {
        // we are in preview mode, we have to load the actual data from the serialized files
        Info::Splitter::_x_min = Info::Render::_x_min_visible;
        Info::Splitter::_x_max = Info::Render::_x_max_visible;
        //  Info::Splitter::preview=false;
        _interface_graphic->get_console()->load_data(_interface_graphic->get_trace());
        Info::Render::_key_ctrl = false;
        // return;
    }
#endif

    _layout->set_zoom_box_x_value((int)(100 * _x_state_scale));
    _layout->set_zoom_box_y_value((int)(100 * _y_state_scale));
    /* Update render */
    update_render();
    refresh_scroll_bars();
    return true;
}

void Render_abstract::apply_zoom_on_interval(const Element_pos &begin, const Element_pos &end) {

    Element_pos x_min = render_to_screen_x(trace_to_render_x(begin));
    Element_pos x_max = render_to_screen_x(trace_to_render_x(end));
    Element_pos y_min = render_to_screen_y(_ruler_height + _ruler_y);
    Element_pos y_max = Info::Screen::height;

    apply_zoom_box(x_min, x_max, y_min, y_max);
}

void Render_abstract::change_scale_x(const Element_pos &scale_coeff) {

    const Element_pos new_scale = _x_state_scale * (1 + scale_coeff * 0.05); /* 5% scale */

    replace_scale_x(new_scale);
}

void Render_abstract::change_scale_y(const Element_pos &scale_coeff) {

    const Element_pos new_scale = _y_state_scale * (1 + scale_coeff * 0.05); /* 5% scale */

    if (new_scale < 1.0)
        _y_state_scale = 1.0;
    else
        replace_scale_y(new_scale);
}

void Render_abstract::replace_scale_x(Element_pos new_scale) {

    // Clamp scale value
    if (new_scale < _MIN_X_ZOOM_VALUE) {
        new_scale = _MIN_X_ZOOM_VALUE;
    }
    else if (new_scale > _MAX_ZOOM_VALUE) {
        new_scale = _MAX_ZOOM_VALUE;
    }

    // If the value is the same as the last one it does not need to be changed
    if (_x_state_scale == new_scale) {
        return;
    }

    /*
     * Reajust the entity translation to recenter the same point previously under the mouse pointer
     *
     * Work out the distance between the point in the middle Widget and the state origin.
     * Then multiply it by the scale coefficient ( (new_scale / old_scale) ) less 1.
     * 1 corresponding to the original translation of the point from the state origin.
     * Finally, translate the result.
     *
     */
    _x_state_translate = (_x_state_translate + _scroll_margin_x) * (new_scale / _x_state_scale) - _scroll_margin_x;
    _x_state_scale = new_scale;

    _layout->set_zoom_box_x_value((int)(100 * _x_state_scale));
    update_render();
    refresh_scroll_bars();
}

void Render_abstract::replace_scale_y(const Element_pos &new_scale) {

    if (new_scale > 0.0) {
        /*
         * Reajust the entity translation to recenter the same point previously under the mouse pointer
         *
         * Work out the distance between the point in the middle Widget and the state origin.
         * Then multiply it by the scale coefficient ( (new_scale / old_scale) ) less 1.
         * 1 corresponding to the original translation of the point from the state origin.
         * Finally, translate the result.
         *
         */
        _y_state_translate = (_y_state_translate + _scroll_margin_y) * (new_scale / _y_state_scale) - _scroll_margin_y;
        _y_state_scale = new_scale;

        _layout->set_zoom_box_y_value((int)(100 * _y_state_scale));
        update_render();
        refresh_scroll_bars();
    }
}

void Render_abstract::change_translate_x(const Element_pos &translate) {
    replace_translate_x(_x_state_translate + translate);
}

void Render_abstract::change_translate_y(const Element_pos &translate) {
    replace_translate_y(_y_state_translate + translate);
}

void Render_abstract::replace_translate_x(const Element_pos &new_translate) {
    if (new_translate < -_scroll_margin_x) {
        _x_state_translate = -_scroll_margin_x;
    }
    else if (new_translate > max(_scroll_margin_x + (_x_state_scale - 1) * (Info::Render::width - _default_entity_x_translate), 0.f)) {
        _x_state_translate = max(_scroll_margin_x + (_x_state_scale - 1) * (Info::Render::width - _default_entity_x_translate), 0.f);
    }
    else {
        _x_state_translate = new_translate;
    }

    if (_key_scrolling) {
        refresh_scroll_bars();
        _key_scrolling = false;
    }
    update_render();
}

void Render_abstract::replace_translate_y(const Element_pos &new_translate) {
    if (new_translate < -_scroll_margin_y) {
        _y_state_translate = -_scroll_margin_y;
    }
    else if (new_translate > _scroll_margin_y + (_y_state_scale - 1) * (Info::Render::height - _ruler_height)) {
        _y_state_translate = _scroll_margin_y + (_y_state_scale - 1) * (Info::Render::height - _ruler_height);
    }
    else {
        _y_state_translate = new_translate;
    }

    if (_key_scrolling) {
        refresh_scroll_bars();
        _key_scrolling = false;
    }
    update_render();
}

void Render_abstract::registered_translate(int id) {

    switch (id) {

    case Info::Render::X_TRACE_BEGINNING: /* show the beginning entities */
        _x_state_translate = -((Element_pos)Info::Render::width - _default_entity_x_translate) / 2.0;
        break;
    case Info::Render::Y_TRACE_BEGINNING: /* show the beginning entities */
        _y_state_translate = -((Element_pos)Info::Render::height - _ruler_height) / 2.0;
        break;
    case Info::Render::X_TRACE_ENDING: /* show the ending entities */
        _x_state_translate = max(_scroll_margin_x + (_x_state_scale - 1) * (Info::Render::width - _default_entity_x_translate), 0.f);
        break;
    case Info::Render::Y_TRACE_ENDING: /* show the ending entities */
        _y_state_translate = _scroll_margin_y + (_y_state_scale - 1) * (Info::Render::height - _ruler_height);
        break;
    case Info::Render::X_TRACE_ENTIRE: /* show the entire entities */
        replace_scale_x(1);
        replace_translate_x(0);
        break;
    case Info::Render::Y_TRACE_ENTIRE: /* show the entire entities */
        replace_scale_y(1);
        replace_translate_y(0);
        break;
    default:
        message << "Unknown registered translate" << Message::endw;
    }

    refresh_scroll_bars();
    update_render();
}

void Render_abstract::refresh_scroll_bars() {
    _layout->refresh_scroll_bars(_x_state_translate, _y_state_translate);
}

void Render_abstract::change_container_scale_x(const int &view_size) {

    _x_scale_container_state = 0.01 * view_size;
    update_render();
}

void Render_abstract::set_layout(RenderLayout *layout) {
    _layout = layout;
}

const int &Render_abstract::get_scroll_margin_x() const {
    return _scroll_margin_x;
}

const int &Render_abstract::get_scroll_margin_y() const {
    return _scroll_margin_y;
}