#include "stfslider.h"
#include <cmath>
#include <QPainter>
#include <QPaintEvent>
#include <QPainterPath>
#include <QToolTip>

static float clamp(float x)
{
    return std::min(std::max(x, 0.0f), 1.0f);
}

STFSlider::STFSlider(const QColor &color, QWidget *parent) : QWidget(parent)
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setMinimumWidth(100);
    setMouseTracking(true);

    if(color == Qt::white)
    {
        setMaximumHeight(16);
        setMinimumHeight(16);
    }
    else
    {
        setMaximumHeight(10);
        setMinimumHeight(10);
    }
    m_blackPoint = 0;
    m_midPoint = 0.5;
    m_whitePoint = 1;
    m_grabbed = -1;
    m_fineTune = false;
    m_color = color;
    if(color == Qt::blue || color == Qt::red)
        m_threshold = 1.1f;
    else if(color == Qt::green)
        m_threshold = 0.8f;
    else
        m_threshold = 0.4f;
    setToolTip(tr("Press Shift for fine tuning"));
}

float STFSlider::blackPoint() const
{
    return m_blackPoint;
}

float STFSlider::midPoint() const
{
    return m_midPoint;
}

float STFSlider::whitePoint() const
{
    return m_whitePoint;
}

void STFSlider::setMTFParams(float low, float mid, float high)
{
    m_blackPoint = clamp(low);
    m_midPoint = clamp(mid);
    m_whitePoint = clamp(high);
    update();
}

void STFSlider::orientationChanged(Qt::Orientations orientation)
{
    m_orientation = orientation;
    if(m_orientation == Qt::Horizontal)
    {
        if(m_color == Qt::white)
        {
            setMaximumSize(QWIDGETSIZE_MAX, 16);
            setMinimumSize(16, 16);
        }
        else
        {
            setMaximumSize(QWIDGETSIZE_MAX, 10);
            setMinimumSize(10, 10);
        }
    }
    else
    {
        if(m_color == Qt::white)
        {
            setMaximumSize(16, QWIDGETSIZE_MAX);
            setMinimumSize(16, 16);
        }
        else
        {
            setMaximumSize(10, QWIDGETSIZE_MAX);
            setMinimumSize(10, 10);
        }
    }
}

void STFSlider::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QRect rect = event->rect();
    qreal w = rect.width() - 1;
    qreal h = rect.height();
    if(m_orientation == Qt::Vertical)
    {
        rect = rect.transposed();
        painter.rotate(90);
        w = rect.width() - 1;
        h = rect.height();
        painter.translate(0, -h);
    }
    QLinearGradient gradient(rect.topLeft(), rect.topRight());
    gradient.setColorAt(0, Qt::black);
    for(int i=1; i<=32; i++)
    {
        qreal p = i/32.0f;
        qreal c = std::pow(p, 1.0/2.2)*255;
        gradient.setColorAt(p, QColor(m_color.redF()*c, m_color.greenF()*c, m_color.blueF()*c));
    }

    QPainterPath tick(QPointF(0, 0));
    tick.lineTo(0, h - 5);
    tick.lineTo(-5, h);
    tick.lineTo(5, h);
    tick.lineTo(0, h - 5);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);
    painter.drawRect(rect);

    auto drawTick = [&](qreal p)
    {
        painter.setPen(p < m_threshold ? Qt::white : Qt::black);
        painter.resetTransform();
        if(m_orientation == Qt::Vertical)
        {
            painter.rotate(90);
            painter.translate(0, -h);
        }
        painter.translate(w*p, 0);
        painter.drawPath(tick);
    };

    painter.setBrush(Qt::NoBrush);
    drawTick(m_blackPoint);
    drawTick(m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint);
    drawTick(m_whitePoint);
}

void STFSlider::mouseMoveEvent(QMouseEvent *event)
{
    qreal x,w;
    if(m_orientation == Qt::Horizontal)
    {
        x = event->position().x();
        w = width();
    }
    else
    {
        x = event->position().y();
        w = height();
    }

    if(std::abs(m_blackPoint*w - x) < 5 ||
            std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5 ||
            std::abs(m_whitePoint*w - x) < 5)
        setCursor(m_orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor);
    else
        unsetCursor();

    qreal xw = x/w;
    if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
    {
        m_fineTune = true;
        m_fineTuneX = xw;
    }
    if(!(event->modifiers() & Qt::ShiftModifier) && m_fineTune)
        m_fineTune = false;

    if(m_fineTune)
    {
        xw = m_fineTuneX + (xw - m_fineTuneX) * 0.2;
    }

    switch(m_grabbed)
    {
    case 0:
        m_blackPoint = clamp(xw);
        m_whitePoint = std::max(m_whitePoint, m_blackPoint);
        QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_blackPoint), this);
        break;
    case 1:
        m_midPoint = (xw - m_blackPoint) / (m_whitePoint - m_blackPoint);
        m_midPoint = clamp(m_midPoint);
        QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_midPoint), this);
        break;
    case 2:
        m_whitePoint = clamp(xw);
        m_blackPoint = std::min(m_blackPoint, m_whitePoint);
        QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_whitePoint), this);
        break;
    }
    if(m_grabbed >= 0)
    {
        emit paramChanged(m_blackPoint, midPoint(), m_whitePoint);
        update();
    }
}

void STFSlider::mousePressEvent(QMouseEvent *event)
{
    qreal x,w;
    if(m_orientation == Qt::Horizontal)
    {
        x = event->position().x();
        w = width();
    }
    else
    {
        x = event->position().y();
        w = height();
    }

    if(event->modifiers() & Qt::ShiftModifier)
    {
        m_fineTune = true;
        m_fineTuneX = x/w;
    }

    if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5)
        m_grabbed = 1;
    else if(std::abs(m_blackPoint*w - x) < 5)
        m_grabbed = 0;
    else if(std::abs(m_whitePoint*w - x) < 5)
        m_grabbed = 2;
    else
        m_grabbed = -1;
}

void STFSlider::mouseReleaseEvent(QMouseEvent *)
{
    m_grabbed = -1;
    m_fineTune = false;
    emit paramChanged(m_blackPoint, midPoint(), m_whitePoint);
}
