#include "TimingLine.hh"

#include <QPainter>
#include <QGraphicsScene>

#include "TimingUtils.hh"

using namespace Vivy;

#define CONNECT_SEP(sep)                                                                           \
    connect(sep, &TimingSeparator::positionChanged, this, &TimingLine::timingSeparatorHasChanged); \
    connect(sep, &TimingSeparator::enterPress, this, &TimingLine::sepEnterPress);                  \
    connect(sep, &TimingSeparator::exitPress, this, &TimingLine::sepExitPress);

TimingLine::TimingLine(Ass::Line *lineptr, int index, QGraphicsItem *parent)
    : QGraphicsObject(parent)
    , line(*lineptr)
    , lineIndex(index)
{
    setPos(TimingUtils::posFromMs(int(line.getStart()) * 10), TimingUtils::axisHeight());
    int currentTime = 0;
    int endSyl      = 0;
    int i;

    TimingSeparator *timingSeparatorStart =
        new TimingSeparator(currentTime, 0, TimingSeparator::SeparatorStyle::Start, this);
    seps.append(timingSeparatorStart);
    CONNECT_SEP(timingSeparatorStart);

    QVector<Ass::Syl> syls = line.getContent();
    for (i = 0; i < syls.size(); ++i) {
        endSyl = currentTime + 10 * int(syls.at(i).getDuration());

        TimingSyl *timingSyl = new TimingSyl(syls.at(i), currentTime, this);
        timingSyls.append(timingSyl);

        if (i != 0) {
            TimingSeparator *timingSeparator =
                new TimingSeparator(currentTime, i, TimingSeparator::SeparatorStyle::Middle, this);
            seps.append(timingSeparator);
            CONNECT_SEP(timingSeparator);
        }

        currentTime = endSyl;
    }

    TimingSeparator *timingSeparatorEnd =
        new TimingSeparator(currentTime, i, TimingSeparator::SeparatorStyle::End, this);
    seps.append(timingSeparatorEnd);
    CONNECT_SEP(timingSeparatorEnd);
}
#undef CONNECT_SEP

QRectF
TimingLine::boundingRect() const
{
    return QRectF(tempOffset, 0,
                  TimingUtils::posFromMs(int(10 * (line.getEnd() - line.getStart()))),
                  TimingUtils::audioHeight());
}

void
TimingLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->fillRect(QRectF(tempOffset, 0,
                             TimingUtils::posFromMs(int(10 * (line.getEnd() - line.getStart()))),
                             TimingUtils::audioHeight()),
                      QColor(0, 255, 255, 50));
}

void
TimingLine::timingSeparatorHasChanged(int sylIndex, qreal x)
{
}

void
TimingLine::sepEnterPress(int sepIndex)
{
}

void
TimingLine::sepExitPress(int sepIndex)
{
    if (tempOffset) {
        prepareGeometryChange();
        if (sepIndex == 0) {
            moveBy(tempOffset, 0);

            timingSyls[sepIndex]->setPos(0, 0);
            seps[sepIndex]->silentlySetPos(0, 0);

            for (int i = 1; i < timingSyls.size(); i++) {
                timingSyls[i]->moveBy(-tempOffset, 0);
            }
            for (int i = 1; i < seps.size(); i++) {
                seps[i]->silentlyMoveBy(-tempOffset, 0);
            }

            line.setStart(quint64(TimingUtils::msFromPos(mapToScene(0, 0).x()) / 10));
        }
    }

    tempOffset = 0;
}

qreal
TimingLine::requestMove(int sepIndex, qreal x)
{
    QRectF sceneRect       = mapRectFromScene(scene()->sceneRect());
    QVector<Ass::Syl> syls = line.getContent();

    qreal given = x;
    qreal mini, maxi;

    if (sepIndex <= 0) {
        prepareGeometryChange();

        mini = sceneRect.left();
        maxi = sepIndex < syls.size() - 1 ? timingSyls[sepIndex + 1]->pos().x()
                                          : TimingUtils::posFromMs(int(line.getDuration()) * 10);

        given      = qBound(mini, given, maxi);
        tempOffset = given;

        quint64 dur1 = quint64(TimingUtils::msFromPos(int(seps[1]->pos().x() - given)) / 10);
        syls[0].setDuration(dur1);
        timingSyls[0]->setLen(dur1);
        timingSyls[0]->setPos(given, 0);
        line.setStart(quint64(TimingUtils::msFromPos(mapToScene(seps[0]->pos().x(), 0).x()) / 10));
    }

    else if (sepIndex >= syls.size()) {
        prepareGeometryChange();

        mini  = timingSyls[sepIndex - 1]->pos().x();
        maxi  = sceneRect.right();
        given = qBound(mini, given, maxi);

        quint64 dur2 =
            quint64(TimingUtils::msFromPos(int(given - timingSyls[sepIndex - 1]->pos().x())) / 10);
        syls[sepIndex - 1].setDuration(dur2);
        timingSyls[sepIndex - 1]->setLen(dur2);
        line.setEnd(line.getStart() + quint64(TimingUtils::msFromPos(int(given)) / 10));
    }

    else {
        mini  = timingSyls[sepIndex - 1]->pos().x();
        maxi  = sepIndex < syls.size() - 1 ? timingSyls[sepIndex + 1]->pos().x()
                                           : TimingUtils::posFromMs(int(line.getDuration() * 10));
        given = qBound(mini, given, maxi);

        quint64 sumDur = syls[sepIndex].getDuration() + syls[sepIndex - 1].getDuration();
        quint64 dur1   = quint64(
              TimingUtils::msFromPos(int(given) - int(timingSyls[sepIndex - 1]->pos().x())) / 10);
        dur1         = qMin(dur1, sumDur);
        quint64 dur2 = sumDur - dur1;

        syls[sepIndex - 1].setDuration(dur1);
        syls[sepIndex].setDuration(dur2);

        timingSyls[sepIndex - 1]->setLen(dur1);
        timingSyls[sepIndex]->setPos(given, 0);
        timingSyls[sepIndex]->setLen(dur2);
    }

    line.setContent(syls);

    if (given) {
        emit lineChanged(lineIndex, std::make_shared<Ass::Line>(line));
    }

    return given;
}