//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Support/Util/WidgetMoverButton.cpp
//! @brief     Implements class WidgetMoverButton
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Support/Util/WidgetMoverButton.h"
#include "Base/Util/Assert.h"
#include <QLayout>
#include <QMouseEvent>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QScrollBar>

WidgetMoverButton::WidgetMoverButton(QWidget* parent, QWidget* widgetToMove, int ignoreOnTop)
    : QToolButton(parent)
    , m_widgetToMove(widgetToMove)
    , m_dropAboveThisWidget(nullptr)
    , m_ignoreOnTop(ignoreOnTop)
    , m_scrollArea(nullptr)
{
    setIcon(QIcon(":/images/move_up_down.svg"));
    m_dragScrollTimer.setSingleShot(false);
}

void WidgetMoverButton::mousePressEvent(QMouseEvent* event)
{
    if (m_scrollArea == nullptr) {
        QWidget* p = parentWidget();
        do {
            m_scrollArea = dynamic_cast<QScrollArea*>(p);
            p = p->parentWidget();
        } while (p != nullptr && m_scrollArea == nullptr);
    }

    ASSERT(m_scrollArea);
    m_globalMouseDownY = event->globalY();
    m_hotSpot = event->globalPos()
                - m_widgetToMove->parentWidget()->mapToGlobal(m_widgetToMove->geometry().topLeft());
    m_pressed = true;
}

void WidgetMoverButton::mouseReleaseEvent(QMouseEvent*)
{
    if (!m_pressed)
        return;

    qDeleteAll(m_animations.values());
    m_animations.clear();

    m_dragScrollTimer.stop();
    m_pressed = false;
    m_started = false;
    if (m_layoutToDeactivate != nullptr) {
        m_layoutToDeactivate->setEnabled(true);
        m_layoutToDeactivate->update();
    }
    emit finishedMoving(m_widgetToMove, m_dropAboveThisWidget);
}

void WidgetMoverButton::mouseMoveEvent(QMouseEvent* event)
{
    if (!m_pressed)
        return;

    int dy = event->globalY() - m_globalMouseDownY;
    if (abs(dy) > 5 && !m_started) {
        m_started = true;

        m_layoutToDeactivate = m_widgetToMove->parentWidget()->layout();

        m_yOfFirstRelevantWidget = m_layoutToDeactivate->itemAt(m_ignoreOnTop)->geometry().top();
        m_layoutToDeactivate->setEnabled(false);
        m_originalWidgetY = m_widgetToMove->y();
        m_widgetToMove->raise();
        emit startingToMove();
        m_widgetToMove->move(m_widgetToMove->x() + 5, m_widgetToMove->y());
        m_widgetToMove->resize(m_widgetToMove->width() - 10, m_widgetToMove->height());
    }

    if (m_started) {
        // -- move the dragged widget to the new position
        QPoint newPos =
            m_widgetToMove->parentWidget()->mapFromGlobal(event->globalPos()) - m_hotSpot;
        m_widgetToMove->move(m_widgetToMove->x(), newPos.y());

        // -- move other widgets to make room
        m_dropAboveThisWidget = nullptr;
        int yTopOfLayoutItem = m_yOfFirstRelevantWidget;

        for (int i = m_ignoreOnTop; i < m_layoutToDeactivate->count(); ++i) {
            auto* layoutItem = m_layoutToDeactivate->itemAt(i);
            if (layoutItem->widget() == m_widgetToMove)
                continue;

            const bool movedWidgetIsAboveThisLayoutItem =
                m_widgetToMove->y() < yTopOfLayoutItem + layoutItem->geometry().height() / 2;
            if (movedWidgetIsAboveThisLayoutItem && layoutItem->widget() != nullptr
                && m_dropAboveThisWidget == nullptr)
                m_dropAboveThisWidget = layoutItem->widget();

            QRect r = layoutItem->geometry();
            if (movedWidgetIsAboveThisLayoutItem)
                r.moveTop(yTopOfLayoutItem + m_widgetToMove->height()
                          + m_layoutToDeactivate->spacing());
            else
                r.moveTop(yTopOfLayoutItem);

            if (r != layoutItem->geometry()) {
                QWidget* w = layoutItem->widget();
                if (w == nullptr)
                    layoutItem->setGeometry(r);
                else if (!m_animations.contains(w)) {
                    auto* animation = new QPropertyAnimation(w, "geometry");
                    animation->setDuration(100);
                    animation->setEasingCurve(QEasingCurve::OutQuad);
                    animation->setStartValue(w->geometry());
                    animation->setEndValue(r);
                    animation->start();
                    m_animations[w] = animation;
                } else {
                    auto* animation = m_animations[w];
                    if (animation->endValue() != r) {
                        animation->stop();
                        animation->setStartValue(w->geometry());
                        animation->setEndValue(r);
                        animation->start();
                    }
                }
            }

            yTopOfLayoutItem += r.height() + m_layoutToDeactivate->spacing();
        }

        // -- check whether scrolling is necessary
        QPoint parPos = m_scrollArea->mapFromGlobal(mapToGlobal(event->pos()));
        if (parPos.y() < 20) {
            m_dragScrollTimer.setInterval(10);
            m_dragScrollTimer.disconnect();
            connect(&m_dragScrollTimer, &QTimer::timeout, [this]() { scrollParent(true); });
            m_dragScrollTimer.start();
        } else if (parPos.y() > m_scrollArea->height() - 20) {
            m_dragScrollTimer.setInterval(10);
            m_dragScrollTimer.disconnect();
            connect(&m_dragScrollTimer, &QTimer::timeout, [this]() { scrollParent(false); });
            m_dragScrollTimer.start();
        } else
            m_dragScrollTimer.stop();
    }
}

void WidgetMoverButton::scrollParent(bool up)
{
    auto* scrollbar = m_scrollArea->verticalScrollBar();
    if (!scrollbar->isVisible())
        return;

    const int oldScrollValue = scrollbar->value();
    scrollbar->setValue(oldScrollValue + (up ? -5 : 5));

    // move the dragged widget to the new position to avoid jitter effects
    // use the real change of value, not +/-5: scrolling may have been unsuccessful
    m_widgetToMove->move(m_widgetToMove->x(),
                         m_widgetToMove->y() + scrollbar->value() - oldScrollValue);
}
