#include "scrolllistwidget.h"

ScrollListWidget::ScrollListWidget(QWidget* parent) : QWidget(parent)
{
    //initialize list container and timer
    container = new ScrollListContainer(this);
    container->move(0, 0);
    container->resize(this->width(), 3);
    getCord = new QTimer;
    getCord->setSingleShot(true);
    rfrshView = new QTimer;
    getCord->setSingleShot(true);

    indicator = new ScrollListIndicator(this);
    indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height()));
    indicator->move(this->width() - indicator->width() - 3, 0);

    this->setMouseTracking(true);
    container->setMouseTracking(true);
    indicator->setMouseTracking(true);

    bounce = new QPropertyAnimation(container, "pos");

    QObject::connect(getCord, SIGNAL(timeout()), this, SLOT(updateSpd()));
    QObject::connect(rfrshView, SIGNAL(timeout()), this, SLOT(scrollContainer()));
    QObject::connect(indicator, SIGNAL(scrollPage(int)), this, SLOT(scrollIndicator(int)));
}

void ScrollListWidget::paintEvent(QPaintEvent* event) {
    container->resize(this->width(), container->height());
    if (container->height() > this->height() && container->y() < this->height() - container->height() && curSpd == 0 && bounce->state() == QAbstractAnimation::Stopped)
        container->move(container->x(), this->height() - container->height());
    if (container->height() <= this->height()) {
        container->move(container->x(), 0);
        indicator->hide();
    }
    else {
        indicator->show();
    }
    indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height()));
    indicator->move(this->width() - indicator->width() - 3, -container->y() * this->height() / container->height());
}

void ScrollListWidget::mousePressEvent(QMouseEvent* event) {
    if (container->height() > this->height()) {
        if (container->y() <= 0 && container->y() + container->height() >= this->height())
            pressed = true;
        lastY = event->pos().y();
    }
    getCord->stop();
    rfrshView->stop();
    curSpd = 0;
    outOfEdge = false;
    moveStored = 0;
    nextMove = 1;
}

void ScrollListWidget::mouseMoveEvent(QMouseEvent* event) {
    setCursor(Qt::ArrowCursor);
    if (pressed) {
        //start scroll
        if (!getCord->isActive() && event->pos().y() - lastY != 0) {
            //start 30ms timer
            getCord->start(30);
            strtY = event->pos().y();
        }
        if (container->y() <= 0 && container->y() + container->height() >= this->height())
            container->move(container->x(), container->y() + event->pos().y() - lastY);
        else {
            if (!outOfEdge) {
                bfEdgeY = event->pos().y();
                container->move(container->x(), container->y() + event->pos().y() - lastY);
                outOfEdge = true;
            }
            else {
                int pos = container->y() >= 0 ? 1 : -1;
                int dp = event->pos().y() - bfEdgeY;
                if (dp == 0) {
                    outOfEdge = false;
                    nextMove = 1;
                    moveStored = 0;
                    if (container->y() >= 0)
                        container->move(container->x(), 0);
                    else
                        container->move(container->x(), this->height() - container->height());
                }
                else if (dp / abs(dp) != pos) {
                    outOfEdge = false;
                    container->move(container->x(), this->y() + event->pos().y() - bfEdgeY);
                    nextMove = 1;
                    moveStored = 0;
                }
                else {
                    while (abs(moveStored) + nextMove <= abs(event->pos().y() - bfEdgeY)) {
                        moveStored += nextMove * pos;
                        container->move(container->x(), container->y() + pos);
                        nextMove++;
                    }
                    while (nextMove > 1 && abs(moveStored) > abs(event->pos().y() - bfEdgeY)) {
                        nextMove--;
                        moveStored -= nextMove * pos;
                        container->move(container->x(), container->y() - pos);
                    }
                    if (moveStored == 0) {
                        outOfEdge = false;
                        if (container->y() >= 0)
                            container->move(container->x(), 0);
                        else
                            container->move(container->x(), this->height() - container->height());
                        nextMove = 1;
                        moveStored = 0;
                    }
                }
            }
        }
        lastY = event->pos().y();
    }
}

void ScrollListWidget::mouseReleaseEvent(QMouseEvent* event) {
    //start scrolling
    if (container->y() > 0 || container->y() + container->height() < this->height())
        bounceBack();
    else
        rfrshView->start(30);
    pressed = false;
}

void ScrollListWidget::bounceBack() {
    rfrshView->stop();
    getCord->stop();
    bounce->setDuration(500);
    bounce->setStartValue(container->pos());
    if (container->y() > 0)
        bounce->setEndValue(QPoint(container->x(), 0));
    else
        bounce->setEndValue(QPoint(container->x(), this->height() - container->height()));
    bounce->setEasingCurve(QEasingCurve::OutQuad);
    bounce->start();
}

void ScrollListWidget::scrollContainer() {
    //scroll
    if (curSpd > 0) {
        if (curSpd > MAXSPEED && !ignoreMaxSpeed)
            curSpd = MAXSPEED;
        else if (curSpd <= MAXSPEED) ignoreMaxSpeed = false;
        int dp = scrollDown ? curSpd : -curSpd;
        container->move(container->x(), container->y() + dp);
    }
    else
        return;
    if (container->y() <= 0 && container->y() + container->height() >= this->height()) {
        curSpd -= damp;
        curSpd = curSpd < 0 ? 0 : curSpd;
    }
    else
        curSpd /= 2;
    if (curSpd == 0 && (container->y() > 0 || container->y() + container->height() < this->height()))
        bounceBack();
    else
        rfrshView->start(30);
}

void ScrollListWidget::updateSpd() {
    int spd = lastY - strtY;
    scrollDown = spd >= 0;
    strtY = lastY;
    curSpd = abs(spd);
}

void ScrollListWidget::addWidget(QWidget* newWidget, bool setAnimation) {
    newWidget->setParent(container);
    container->AddWidget(newWidget, setAnimation);
}

void ScrollListWidget::removeWidget(QWidget* w) {
    container->RemoveWidget(w);
}

void ScrollListWidget::scrollToTop() {
    curSpd = sqrt(8 * (-container->pos().y()) + 2) / 2;
    scrollDown = true;
    getCord->stop();
    rfrshView->stop();
    outOfEdge = false;
    moveStored = 0;
    nextMove = 1;
    ignoreMaxSpeed = true;
    rfrshView->start(30);
}

void ScrollListWidget::updateHeight() {
    container->updateHeight();
}

void ScrollListWidget::clear() {
    container->clear();
}

void ScrollListWidget::scrollIndicator(int dp) {
    int newY = container->y() - dp * container->height() / this->height();
    if (newY > 0)
        newY = 0;
    else if (newY < this->height() - container->height())
        newY = this->height() - container->height();
    container->move(container->x(), newY);
    update();
}

void ScrollListWidget::wheelEvent(QWheelEvent* event) {
    if (container->y() > 0 || container->y() + container->height() < this->height())
        return;
    curSpd += 5;
    bool newDirection = event->angleDelta().y() > 0;
    if (newDirection != scrollDown)
        curSpd = 5;
    if (curSpd > MAXSPEED)
        curSpd = MAXSPEED;
    scrollDown = newDirection;
    if (!rfrshView->isActive())
        rfrshView->start(30);
    update();
}

ScrollListContainer::ScrollListContainer(QWidget* parent) : QWidget(parent) {}

void ScrollListContainer::paintEvent(QPaintEvent* event) {
    for (int i = 0; i < widgets.size(); i++) {
        widgets[i]->resize(this->width(), widgets[i]->height());
    }
}

void ScrollListContainer::AddWidget(QWidget* widget, bool setAnimation) {
    //Add animation for all widgets current
    this->resize(this->width(), this->height() + widget->height() + spacing);
    widgets.push_back(widget);
    size++;
    ys.push_back(0);
    widget->resize(this->width(), widget->height());
    widget->show();

    if (setAnimation) {
        QGraphicsOpacityEffect* widgetOpac = new QGraphicsOpacityEffect(widget);
        widgetOpac->setOpacity(0);
        widget->setGraphicsEffect(widgetOpac);
        QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup;
        QSequentialAnimationGroup* newWidgetFadeIn = new QSequentialAnimationGroup;
        for (int i = 0; i < size - 1; i++) {
            ys[i] += widget->height() + spacing;
            QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos");
            move->setDuration(750);
            move->setStartValue(widgets[i]->pos());
            move->setEndValue(QPoint(widgets[i]->x(), ys[i]));
            move->setEasingCurve(QEasingCurve::InOutQuart);
            dpGroup->addAnimation(move);
        }
        newWidgetFadeIn->addPause(300);
        QPropertyAnimation* fade = new QPropertyAnimation(widgetOpac, "opacity", widget);
        fade->setDuration(300);
        fade->setStartValue(0);
        fade->setEndValue(0.99);
        newWidgetFadeIn->addAnimation(fade);
        dpGroup->addAnimation(newWidgetFadeIn);
        dpGroup->start();
        connect(dpGroup, &QPropertyAnimation::stateChanged, [=]() {
            if (dpGroup->state() == QAbstractAnimation::Stopped) {
                if (widgetOpac->opacity() != 0.99) {
                    fade->start(QAbstractAnimation::DeleteWhenStopped);
                    connect(fade, &QPropertyAnimation::finished, [=]() {widgetOpac->deleteLater(); });
                }
                else {
                    dpGroup->deleteLater();
                    widgetOpac->deleteLater();
                }
            }
            });
    }
    else {
        for (int i = 0; i < size - 1; i++) {
            ys[i] += widget->height() + spacing;
            widgets[i]->move(QPoint(widgets[i]->pos().x(), ys[i]));
        }
    }
}

void ScrollListContainer::RemoveWidget(QWidget* widget) {
    int index;
    if (widget == nullptr) {
        index = size - 1;
        if (index != -1)
            widget = widgets[index];
    }
    else
        index = widgets.indexOf(widget);
    if (index == -1 || widget == nullptr) {
        return;
    }
    this->resize(this->width(), this->height() - widget->height() - spacing);
    this->parentWidget()->update();
    widget->hide();
    widget->setParent(nullptr);
    QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup;
    for (int i = index - 1; i >= 0; i--) {
        ys[i] -= (widget->height() + spacing);
        QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos");
        move->setDuration(750);
        move->setStartValue(widgets[i]->pos());
        move->setEndValue(QPoint(widgets[i]->x(), ys[i]));
        move->setEasingCurve(QEasingCurve::InOutQuart);
        dpGroup->addAnimation(move);
    }
    dpGroup->start(QAbstractAnimation::DeleteWhenStopped);
    widgets.remove(index);
    size--;
    ys.remove(index);
}

void ScrollListContainer::updateHeight() {
    for (int i = size - 2; i >= 0; i--) {
        ys[i] = ys[i + 1] + widgets[i + 1]->height() + spacing;
        widgets[i]->move(widgets[i]->pos().x(), ys[i]);
    }
    this->resize(this->width(), ys[0] + widgets[0]->height() + 3);
}

void ScrollListContainer::clear() {
    int n = size;
    for (int i = 0; i < n; i++)
        RemoveWidget();
}

ScrollListIndicator::ScrollListIndicator(QWidget* parent) : QWidget(parent)
{
    this->resize(defaultWidth, 0);
    hovTimer = new QTimer(this);
    hovTimer->setSingleShot(true);
    aniPause = new QTimer(this);
    aniPause->setSingleShot(true);
    QObject::connect(hovTimer, SIGNAL(timeout()), this, SLOT(setHoverActive()));
    this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    this->curColor = defaultColor;

    this->setMouseTracking(true);
}

void ScrollListIndicator::paintEvent(QPaintEvent* event) {
    QPainter painter(this);
    painter.setPen(Qt::NoPen);
    painter.setBrush(curColor);
    painter.drawRect(this->rect());
}

void ScrollListIndicator::enterEvent(QEnterEvent* event) {
    if (!pressed) {
        hovTimer->start(100);
        curColor = hoverColor;
        update();
    }
}

void ScrollListIndicator::leaveEvent(QEvent* event) {
    hovTimer->stop();
    curColor = defaultColor;
    QPropertyAnimation* narrow = new QPropertyAnimation(this, "geometry");
    narrow->setDuration(300);
    narrow->setStartValue(QRect(this->x(), this->y(), this->width(), this->height()));
    narrow->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidth, this->y(), defaultWidth, this->height()));
    narrow->setEasingCurve(QEasingCurve::InOutQuad);
    narrow->start(QAbstractAnimation::DeleteWhenStopped);
    aniPause->start(300);
    update();
}

void ScrollListIndicator::mousePressEvent(QMouseEvent* event) {
    curColor = pressColor;
    pressed = true;
    //>note: globalPos -> globalPosition here due to deprecation
    //>      may cause issues
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
    lastY = event->globalPosition().y();
#else
    lastY = event->globalPos().y();
#endif
    update();
}

void ScrollListIndicator::mouseMoveEvent(QMouseEvent* event) {
    if (pressed && !aniPause->isActive()) {
        //>note: globalPos -> globalPosition here due to deprecation
        //>      may cause issues
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
        int dp = event->globalPosition().y() - lastY;
#else
        int dp = event->globalPos().y() - lastY;
#endif
        emit scrollPage(dp);
        //>note: globalPos -> globalPosition here due to deprecation
        //>      may cause issues
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
        lastY = event->globalPosition().y();
#else
        lastY = event->globalPos().y();
#endif
    }
}

void ScrollListIndicator::mouseReleaseEvent(QMouseEvent* event) {
    pressed = false;
    curColor = hoverColor;
    update();
}

void ScrollListIndicator::setHoverActive() {
    QPropertyAnimation* widen = new QPropertyAnimation(this, "geometry");
    widen->setDuration(300);
    widen->setStartValue(QRect(this->x(), this->y(), this->width(), this->height()));
    widen->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidthAtFocus, this->y(), defaultWidthAtFocus, this->height()));
    widen->setEasingCurve(QEasingCurve::InOutQuad);
    widen->start(QAbstractAnimation::DeleteWhenStopped);
    aniPause->start(300);
}
