/**
 *
 * @file src/interface/RangeSliderWidget.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 *
 * @date 2024-07-17
 */
/*
 * This code is mostly inspired from :
 * https://github.com/sleepbysleep/range_slider_for_Qt5_and_PyQt5/blob/master/qt_range_slider/range_slider.h
 * Originated from
 * https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html
 * Modification refered from
 * https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
 */

#include <QSlider>
#include <QStyle>
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOption>
#include <QPalette>
#include <QApplication>

#include "RangeSliderWidget.hpp"

RangeSliderWidget::RangeSliderWidget(Qt::Orientation ot, QWidget *parent) :
    QSlider(ot, parent) {
    this->lowLimit = this->minimum();
    this->highLimit = this->maximum();
    this->click_position = 0;
    this->active_handle = NO_HANDLE;
    this->setMouseTracking(true);
}

int RangeSliderWidget::low() {
    return this->lowLimit;
}

void RangeSliderWidget::setLow(int low_limit) {
    this->lowLimit = low_limit;
    update();
}

int RangeSliderWidget::high() {
    return this->highLimit;
}

void RangeSliderWidget::setHigh(int high_limit) {
    this->highLimit = high_limit;
    update();
}

inline int pick(const RangeSliderWidget *slider, QPoint const &pt) { return slider->orientation() == Qt::Horizontal ? pt.x() : pt.y(); }

int RangeSliderWidget::pixelPosToRangeValue(int pos) {
    QStyleOptionSlider opt;
    this->initStyleOption(&opt);
    QStyle *style = QApplication::style();

    QRect gr = style->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
    QRect sr = style->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);

    int slider_length, slider_min, slider_max;
    if (this->orientation() == Qt::Horizontal) {
        slider_length = sr.width();
        slider_min = gr.x();
        slider_max = gr.right() - slider_length + 1;
    }
    else {
        slider_length = sr.height();
        slider_min = gr.y();
        slider_max = gr.bottom() - slider_length + 1;
    }

    return style->sliderValueFromPosition(this->minimum(), this->maximum(), pos - slider_min, slider_max - slider_min, opt.upsideDown);
}

// based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp
void RangeSliderWidget::paintEvent(QPaintEvent *ev) {
    QPainter painter(this);
    QStyleOptionSlider opt;

    // Draw groove
    this->initStyleOption(&opt);
    opt.sliderValue = 0;
    opt.sliderPosition = 0;
    opt.subControls = QStyle::SC_SliderGroove;
    this->style()->drawComplexControl(QStyle::CC_Slider, &opt, &painter, this);
    QRect groove = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);

    // Draw span
    opt.sliderPosition = this->lowLimit;
    QRect low_rect = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
    opt.sliderPosition = this->highLimit;
    QRect high_rect = this->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);

    int low_pos = pick(this, low_rect.center());
    int high_pos = pick(this, high_rect.center()) - high_rect.width() / 2;
    int min_pos = std::min(low_pos, high_pos);
    int max_pos = std::max(low_pos, high_pos);

    QPoint c = QRect(low_rect.center(), high_rect.center()).center();
    QRect span_rect;
    if (opt.orientation == Qt::Horizontal) {
        span_rect = QRect(QPoint(min_pos, c.y() - 2), QPoint(max_pos, c.y() + 2));
    }
    else {
        span_rect = QRect(QPoint(min_pos, c.x() - 2), QPoint(max_pos, c.x() + 2));
    }

    QColor highlight = this->palette().color(QPalette::Highlight);
    painter.setBrush(QBrush(highlight));
    painter.setPen(QPen(highlight, 0));
    painter.drawRect(span_rect.intersected(groove));

    opt.subControls = QStyle::SC_SliderHandle;

    // Draw low handle
    opt.sliderPosition = this->lowLimit - low_rect.width();
    opt.sliderValue = this->lowLimit - low_rect.width();
    this->style()->drawComplexControl(QStyle::CC_Slider, &opt, &painter, this);

    // Draw high handle
    opt.sliderPosition = this->highLimit;
    opt.sliderValue = this->highLimit;
    this->style()->drawComplexControl(QStyle::CC_Slider, &opt, &painter, this);
}

void RangeSliderWidget::mousePressEvent(QMouseEvent *ev) {

    // Only LeftButton press is considered
    if (ev->button() != Qt::LeftButton) {
        ev->ignore();
        return;
    }

    ev->accept();

    if (NO_HANDLE != this->active_handle) {
        this->setSliderDown(true);
    }
    else {
        this->click_position = this->pixelPosToRangeValue(pick(this, ev->pos()));
    }

    this->triggerAction(this->SliderMove);
    this->setRepeatAction(this->SliderNoAction);
}

void RangeSliderWidget::mouseMoveEvent(QMouseEvent *ev) {

    ev->accept();

    // If clicked is not pressed, check where the mouse is to change cursor
    if (ev->buttons() != Qt::LeftButton) {

        QStyle *style = QApplication::style();
        QStyle::SubControl hit;
        QStyleOptionSlider opt;

        this->initStyleOption(&opt);
        this->active_handle = NO_HANDLE;

        // Test if click is on the low handle
        opt.sliderPosition = this->lowLimit;
        hit = style->hitTestComplexControl(QStyle::CC_Slider, &opt, ev->pos(), this);
        if (hit == QStyle::SC_SliderHandle) {
            this->active_handle = LOW_HANDLE;
            setCursor(Qt::PointingHandCursor);
            return;
        }

        // Test if the click is on the high handle
        opt.sliderPosition = this->highLimit;
        hit = style->hitTestComplexControl(QStyle::CC_Slider, &opt, ev->pos(), this);
        if (hit == QStyle::SC_SliderHandle) {
            this->active_handle = HIGH_HANDLE;
            setCursor(Qt::PointingHandCursor);
            return;
        }

        if (orientation() == Qt::Orientation::Vertical) {
            setCursor(Qt::SizeVerCursor);
            return;
        }

        setCursor(Qt::SizeHorCursor);
        return;
    }

    // Depending on where the mouse is, move the corresponding part of the slider

    int new_pos = this->pixelPosToRangeValue(pick(this, ev->pos()));
    QStyleOptionSlider opt;
    this->initStyleOption(&opt);

    int offset, diff;

    // Move the entire range
    // The range can be moved when the click is outside the blue part
    // This can cause a shift between the mouse and the range making
    // it difficult to move the range to the extrema
    if (NO_HANDLE == this->active_handle) {
        offset = new_pos - this->click_position;
        this->highLimit += offset;
        this->lowLimit += offset;

        // Keep the range between the bounds of the slider
        if (this->lowLimit < this->minimum()) {
            diff = this->minimum() - this->lowLimit;
            this->lowLimit += diff;
            this->highLimit += diff;
        }
        if (this->highLimit > this->maximum()) {
            diff = this->maximum() - this->highLimit;
            this->lowLimit += diff;
            this->highLimit += diff;
        }
    }
    else if (LOW_HANDLE == this->active_handle) {
        // The low handle can't go on the right of the high handle
        if (new_pos >= this->highLimit) {
            new_pos = this->highLimit - 1;
        }
        this->lowLimit = new_pos;
    }
    else {
        // The high handle can't go on the left of the high handle
        if (new_pos <= this->lowLimit) {
            new_pos = this->lowLimit + 1;
        }
        this->highLimit = new_pos;
    }

    this->click_position = new_pos;
    this->update();
    Q_EMIT this->sliderMoved(this->lowLimit, this->highLimit);
}
