//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Widget/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/View/Widget/WidgetMoverButton.h"
#include "Base/Util/Assert.h"
#include <QLayout>
#include <QMouseEvent>
#include <QScrollBar>

WidgetMoverButton::WidgetMoverButton(QWidget* parent, QWidget* widgetToMove, int ignoreOnTop)
    : QToolButton(parent)
    , m_widget_to_move(widgetToMove)
    , m_drop_above_this_widget(nullptr)
    , m_ignore_on_top(ignoreOnTop)
    , m_scroll_area(nullptr)
{
    setIcon(QIcon(":/images/move_up_down.svg"));
    m_drag_scroll_timer.setSingleShot(false);
}

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

    ASSERT(m_scroll_area);
    m_global_mouse_downY = event->globalY();
    m_hot_spot =
        event->globalPos()
        - m_widget_to_move->parentWidget()->mapToGlobal(m_widget_to_move->geometry().topLeft());
    m_pressed = true;
}

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

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

    m_drag_scroll_timer.stop();
    m_pressed = false;
    m_started = false;
    if (m_layout_to_deactivate != nullptr) {
        m_layout_to_deactivate->setEnabled(true);
        m_layout_to_deactivate->update();
    }
    emit finishedMoving(m_widget_to_move, m_drop_above_this_widget);
}

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

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

        m_layout_to_deactivate = m_widget_to_move->parentWidget()->layout();

        m_y_of_first_relevant_widget =
            m_layout_to_deactivate->itemAt(m_ignore_on_top)->geometry().top();
        m_layout_to_deactivate->setEnabled(false);
        m_original_widgetY = m_widget_to_move->y();
        m_widget_to_move->raise();
        emit startingToMove();
        m_widget_to_move->move(m_widget_to_move->x() + 5, m_widget_to_move->y());
        m_widget_to_move->resize(m_widget_to_move->width() - 10, m_widget_to_move->height());
    }

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

        // -- move other widgets to make room
        m_drop_above_this_widget = nullptr;
        int yTopOfLayoutItem = m_y_of_first_relevant_widget;

        for (int i = m_ignore_on_top; i < m_layout_to_deactivate->count(); ++i) {
            auto* layoutItem = m_layout_to_deactivate->itemAt(i);
            if (layoutItem->widget() == m_widget_to_move)
                continue;

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

            QRect r = layoutItem->geometry();
            if (movedWidgetIsAboveThisLayoutItem)
                r.moveTop(yTopOfLayoutItem + m_widget_to_move->height()
                          + m_layout_to_deactivate->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_layout_to_deactivate->spacing();
        }

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

void WidgetMoverButton::scrollParent(bool up)
{
    auto* scrollbar = m_scroll_area->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_widget_to_move->move(m_widget_to_move->x(),
                           m_widget_to_move->y() + scrollbar->value() - oldScrollValue);
}
