/**
 *
 * @file src/interface/Interval_select.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Johnny Jazeix
 * @author Augustin Degomme
 * @author Mathieu Faverge
 *
 * @date 2024-07-17
 */
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <vector>

/* -- */
#include "common/common.hpp"
#include "common/Info.hpp"
#include "common/Message.hpp"
/* -- */
#include "interface/Interface_graphic.hpp"
#include "interface/resource.hpp"
/* -- */
#include "core/Core.hpp"
/* -- */
#include "trace/values/Values.hpp"
#include "trace/EntityValue.hpp"
#include "trace/EntityTypes.hpp"
#include "trace/Entitys.hpp"
#include "trace/tree/Interval.hpp"
#include "trace/Container.hpp"
/* -- */
#include <QKeyEvent>
#include <QDoubleSpinBox>

#include "interface/Interval_select.hpp"
#include "interface/RangeSliderWidget.hpp"

#include <limits>

using namespace std;

Interval_select::Interval_select(Interface_graphic *interface_graphic) :
    QDialog(), _interface_graphic(interface_graphic) {
    setupUi(this);

    // Make the window non resizable
    setFixedSize(this->width(), this->height());

    // Initialise range slider
    _range_slider.setParent(this);
    _range_slider.setGeometry(65, 140, 400, 20);
    _range_slider.setObjectName(QString::fromStdString("rangeSlider"));

    // The slider goes from 0 to 1000 for smoother cursor movements
    // The slider values are then converted to trace time values inside the functions
    _range_slider.setMaximum(1000);

    connect(&_range_slider, &RangeSliderWidget::sliderMoved, this, &Interval_select::on_sliderMoved);
    connect(apply_button, &QPushButton::clicked, this, &Interval_select::apply_settings);
    connect(close_button, &QPushButton::clicked, this, &QWidget::hide);

    // Saving this value prevents it to be computed again several times in the code.
    _range_span = _range_slider.maximum() - _range_slider.minimum();
    _max_possible_value = 0.;
    _is_slider_moving = false;
}

Interval_select::~Interval_select() = default;

void Interval_select::init() {

    min_value->setText(QString().setNum(Info::Entity::x_min));
    max_value->setText(QString().setNum(Info::Entity::x_max));
    _max_possible_value = Info::Entity::x_max;

    minSpinBox->setMinimum(Info::Entity::x_min);
    minSpinBox->setMaximum(INT32_MAX);
    maxSpinBox->setMinimum(Info::Entity::x_min);
    maxSpinBox->setMaximum(INT32_MAX);

    double step = 0.001 * (Info::Entity::x_max - Info::Entity::x_min); // make steps of .1% for spinboxes
    minSpinBox->setSingleStep(step);
    maxSpinBox->setSingleStep(step);

    update_values();
}

void Interval_select::update_values() {

    if (_interface_graphic->get_trace() != nullptr) {
        Element_pos _min = Info::Render::_x_min;
        Element_pos _max = Info::Render::_x_max;

        // choose which value to load the actual position from (can change if we are loading from a splitted file set)
        if (Info::Splitter::preview == true) {
            _min = Info::Entity::x_min;
            _max = Info::Entity::x_max;
        }
        else if (Info::Splitter::load_splitted == true) {
            Info::Render::_x_min = Info::Splitter::_x_min;
            Info::Render::_x_max = Info::Splitter::_x_max;
            auto_refresh_box->setChecked(false); // set the checkbox to false because reload is expensive for splitted mode
            _min = Info::Splitter::_x_min;
            _max = Info::Splitter::_x_max;
        }

        // Set the values of SpinBox and Slider
        // Block signals to prevent events firing when updating values
        bool v = minSpinBox->blockSignals(true);
        minSpinBox->setValue(_min);
        minSpinBox->blockSignals(v);

        v = maxSpinBox->blockSignals(true);
        maxSpinBox->setValue(_max);
        if (!_is_slider_moving) {
            max_value->setText(QString().setNum(max(maxSpinBox->value(), (double)Info::Entity::x_max)));
            _max_possible_value = max(maxSpinBox->value(), (double)Info::Entity::x_max);
        }
        maxSpinBox->blockSignals(v);

        v = _range_slider.blockSignals(true);
        _range_slider.setLow(int(_min * _range_span / (_max_possible_value - maxSpinBox->minimum())));
        _range_slider.setHigh(int(_max * _range_span / (_max_possible_value - maxSpinBox->minimum())));
        _range_slider.blockSignals(v);
    }
}

void Interval_select::on_minSpinBox_valueChanged(double value) {
    /**
     * This method can also change the maxSpinBox value on large trace
     * This is due to scaled float precision error in the apply_zoom_box function (called by apply_settings)
     */

    QPalette myPalette(minSpinBox->palette());

    if (value < maxSpinBox->value()) {
        // Change the value of the slider
        bool v = _range_slider.blockSignals(true);
        _range_slider.setLow(int(value * _range_span / (_max_possible_value - maxSpinBox->minimum())));
        _range_slider.blockSignals(v);

        myPalette.setColor(QPalette::Active, QPalette::Text, Qt::black);
        myPalette.setColor(QPalette::Active, QPalette::HighlightedText, Qt::white);

        if (auto_refresh_box->isChecked()) {
            apply_settings();
        }
        else {
            apply_button->setEnabled(true);
        }
    }
    else {
        // Set the text to red if min >= max
        myPalette.setColor(QPalette::Active, QPalette::Text, Qt::red);
        myPalette.setColor(QPalette::Active, QPalette::HighlightedText, Qt::red);
    }

    minSpinBox->setPalette(myPalette);
    maxSpinBox->setPalette(myPalette);
}

void Interval_select::on_maxSpinBox_valueChanged(double value) {
    /**
     * This method can also change the minSpinBox value on large trace
     * This is due to scaled float precision error in the apply_zoom_box function (called by apply_settings)
     */
    QPalette myPalette(minSpinBox->palette());

    if (value > minSpinBox->value()) {

        max_value->setText(QString().setNum(max(maxSpinBox->value(), (double)Info::Entity::x_max)));
        _max_possible_value = max(maxSpinBox->value(), (double)Info::Entity::x_max);

        bool v = _range_slider.blockSignals(true);
        _range_slider.setHigh(int(value * _range_span / (_max_possible_value - maxSpinBox->minimum())));
        _range_slider.blockSignals(v);

        myPalette.setColor(QPalette::Active, QPalette::Text, Qt::black);
        myPalette.setColor(QPalette::Active, QPalette::HighlightedText, Qt::white);

        if (auto_refresh_box->isChecked()) {
            apply_settings();
        }
        else {
            apply_button->setEnabled(true);
        }
    }
    else {
        // Set the text to red if max <= min
        myPalette.setColor(QPalette::Active, QPalette::Text, Qt::red);
        myPalette.setColor(QPalette::Active, QPalette::HighlightedText, Qt::red);
    }

    minSpinBox->setPalette(myPalette);
    maxSpinBox->setPalette(myPalette);
}

void Interval_select::on_sliderMoved(int low, int high) {

    // If the SpinBox value is changed directly it will launch a valueChanged signal
    // thus changing the value of the slider creating an infinite loop
    // The signal is block to prevent these infinite loops
    bool v = minSpinBox->blockSignals(true);
    minSpinBox->setValue(low * (_max_possible_value - maxSpinBox->minimum()) / _range_span);
    minSpinBox->blockSignals(v);

    v = maxSpinBox->blockSignals(true);
    maxSpinBox->setValue(high * (_max_possible_value - maxSpinBox->minimum()) / _range_span);
    maxSpinBox->blockSignals(v);

    if (auto_refresh_box->isChecked()) {
        // apply_settings refresh the scroll bar which calls update_values
        // This is because we want to update the values when the scroll bar is changed by the user
        // But this changes _max_possible_value
        // A bool is then set so _max_possible_value is not changed when slider is moved
        _is_slider_moving = true;
        apply_settings();
        _is_slider_moving = false;
        return;
    }

    apply_button->setEnabled(true);
}

void Interval_select::apply_settings() {

    if (minSpinBox->value() != maxSpinBox->value()) {

        // reload data from disk if needed
#if defined(USE_ITC) && defined(BOOST_SERIALIZE)
        Info::Splitter::_x_min = minSpinBox->value();
        Info::Splitter::_x_max = maxSpinBox->value();
        _interface_graphic->get_trace()->updateTrace(new Interval(minSpinBox->value(), maxSpinBox->value()));
#endif

        _interface_graphic->render_zoom_on_interval(minSpinBox->value(), maxSpinBox->value());
    }

    apply_button->setEnabled(false);
}

/*
 * Restore min/max values
 */
void Interval_select::on_reset_button_clicked() {
    Info::Render::_x_min = Info::Entity::x_min;
    Info::Render::_x_max = Info::Entity::x_max;
    max_value->setText(QString().setNum(Info::Entity::x_max));
    _max_possible_value = Info::Entity::x_max;
    update_values();

    if (auto_refresh_box->isChecked()) {
        apply_settings();
    }
    else {
        apply_button->setEnabled(true);
    }
}

void Interval_select::on_auto_refresh_box_stateChanged() {
    // If the auto_refresh checkbox becomes checked and the apply button is active
    // (meaning that some changes have not been applied)
    // then these changes are applied
    // When there is no pending modification, apply_settings is not called
    if (auto_refresh_box->checkState() == Qt::CheckState::Checked && apply_button->isEnabled()) {
        apply_settings();
    }
}

#include "moc_Interval_select.cpp"
