//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Item/ItemViewOverlayButtons.cpp
//! @brief     Implements class ItemViewOverlayButtons
//!
//! @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/Item/ItemViewOverlayButtons.h"
#include <QAbstractItemView>
#include <QAction>
#include <QApplication>
#include <QBoxLayout>
#include <QKeyEvent>
#include <QMenu>
#include <QStyledItemDelegate>
#include <QToolButton>

namespace {

class ItemViewOverlayWidget : public QWidget {
public:
    ItemViewOverlayWidget(QAbstractItemView* view, const QModelIndex& index);

    static int heightForDelegate();

    void setHover(bool b);
    void create();
    void hover(bool h);
    void setHorizontalAlignment(Qt::Alignment a);

protected:
    void enterEvent(QEnterEvent* event) override;
    void leaveEvent(QEvent* event) override;
    void mouseDoubleClickEvent(QMouseEvent* event) override;

private:
    bool m_hover;
    QAbstractItemView* m_view;
    QModelIndex m_index;
    Qt::Alignment m_horizontalAlignment;
};

class ItemViewOverlayDelegate : public QStyledItemDelegate {
public:
    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
    {
        QSize s = QStyledItemDelegate::sizeHint(option, index);
        if (index.parent().isValid()) {
            const int h = ItemViewOverlayWidget::heightForDelegate();
            s.setHeight(std::max(s.height(), h));
        }

        return s;
    }
};

ItemViewOverlayWidget::ItemViewOverlayWidget(QAbstractItemView* view, const QModelIndex& index)
    : QWidget(view)
    , m_hover(false)
    , m_view(view)
    , m_index(index)
    , m_horizontalAlignment(Qt::AlignRight)
{
    setMouseTracking(true);
    setFocusPolicy(Qt::NoFocus); // do not receive keypress!
}

void ItemViewOverlayWidget::setHover(bool b)
{
    m_hover = b;
}

int ItemViewOverlayWidget::heightForDelegate()
{
    QToolButton btn;
    const int size = QApplication::style()->pixelMetric(QStyle::PM_ToolBarIconSize);
    btn.setIconSize(QSize(size, size));
    return btn.sizeHint().height() + 6; // 3px on top and bottom
}

void ItemViewOverlayWidget::enterEvent(QEnterEvent*)
{
    hover(true);
}

void ItemViewOverlayWidget::leaveEvent(QEvent*)
{
    hover(false);
}

void ItemViewOverlayWidget::mouseDoubleClickEvent(QMouseEvent* ev)
{
    if (m_view->editTriggers().testFlag(QAbstractItemView::DoubleClicked)
        && m_index.flags().testFlag(Qt::ItemIsEditable)) {
        m_view->setIndexWidget(m_index, nullptr);
        m_view->edit(m_index);
        ev->accept();
        return;
    }

    ev->ignore();
}

void ItemViewOverlayWidget::create()
{
    auto* h1 = new QHBoxLayout;

    h1->setContentsMargins(0, 3, 5, 3);
    h1->setAlignment(m_horizontalAlignment | Qt::AlignTop);
    setLayout(h1);

    for (auto* a : actions()) {
        auto* btn = new QToolButton(this);
        btn->setDefaultAction(a);
        Qt::ToolButtonStyle btnStyle = (Qt::ToolButtonStyle)a->data().toInt();
        btn->setToolButtonStyle(btnStyle);

        const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
        btn->setIconSize(QSize(size, size));

        if (a->menu() != nullptr)
            btn->setPopupMode(QToolButton::InstantPopup);

        h1->addWidget(btn);

        if (m_hover)
            btn->hide();
    }
}

void ItemViewOverlayWidget::hover(bool h)
{
    if (!m_hover)
        return;

    if (h) {
        if (actions().size() != findChildren<QToolButton*>().size())
            create();
        for (auto* w : findChildren<QToolButton*>())
            w->show();
    } else {
        for (auto* w : findChildren<QToolButton*>())
            w->hide();
    }
}

void ItemViewOverlayWidget::setHorizontalAlignment(Qt::Alignment a)
{
    m_horizontalAlignment = a;
}

} // namespace

void ItemViewOverlayButtons::install(QAbstractItemView* view, FnGetActions fnGetActions)
{
    auto* h = new ItemViewOverlayButtons(view);
    h->m_getActions = fnGetActions;
    h->m_view = view;
    auto* d = new ItemViewOverlayDelegate;
    view->setItemDelegate(d);
    view->installEventFilter(h);
    h->update();

    connect(d, &QAbstractItemDelegate::closeEditor, h, &ItemViewOverlayButtons::update);

    connect(view->model(), &QAbstractItemModel::modelReset, h, &ItemViewOverlayButtons::update,
            Qt::QueuedConnection);
    connect(view->model(), &QAbstractItemModel::rowsInserted, h, &ItemViewOverlayButtons::update,
            Qt::QueuedConnection); // Queued: important!
    connect(view->model(), &QAbstractItemModel::rowsRemoved, h, &ItemViewOverlayButtons::update,
            Qt::QueuedConnection); // Queued: important!
}

bool ItemViewOverlayButtons::eventFilter(QObject* obj, QEvent* event)
{
    // F2 would start the editing. Since OverlayWidgets are an editor already (indexWidget uses the
    // editor internals), we remove the overlay at this place, so the view will properly open the
    // editor
    if (event->type() == QEvent::KeyPress) {
        auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
        if (keyEvent->key() == Qt::Key_F2) {
            QModelIndex ci = m_view->currentIndex();
            if (ci.isValid() && ci.flags().testFlag(Qt::ItemIsEditable))
                m_view->setIndexWidget(ci, nullptr);
        }
    }
    return QObject::eventFilter(obj, event);
}

ItemViewOverlayButtons::ItemViewOverlayButtons(QObject* parent)
    : QObject(parent)
{
}

void ItemViewOverlayButtons::updateRecursive(const QModelIndex& index)
{
    const auto hoverIfNecessary = [&](QModelIndex index) {
        QPoint viewPortMousePos = m_view->mapFromGlobal(QCursor::pos());
        if (m_view->indexAt(viewPortMousePos) == index)
            if (auto* w = dynamic_cast<ItemViewOverlayWidget*>(m_view->indexWidget(index)))
                w->hover(true);
    };

    if (m_view->indexWidget(index) == nullptr)
        installOverlay(index);
    hoverIfNecessary(index);

    auto* m = m_view->model();
    for (int childRow = 0; childRow < m->rowCount(index); childRow++)
        updateRecursive(m->index(childRow, 0, index));
}


void ItemViewOverlayButtons::update()
{
    if (m_view->model() == nullptr)
        return;
    auto* m = m_view->model();
    for (int row = 0; row < m->rowCount(); row++)
        updateRecursive(m->index(row, 0));
}

void ItemViewOverlayButtons::installOverlay(const QModelIndex& index)
{
    const auto permanentActions = m_getActions(index, false);
    const auto hoverActions = m_getActions(index, true);

    if (permanentActions.isEmpty() && hoverActions.isEmpty())
        return;

    auto* w = new ItemViewOverlayWidget(m_view, index);

    const auto setAlignment = [&](const QList<QAction*> actions) {
        w->setHorizontalAlignment(Qt::AlignRight);
        if (actions.first() == nullptr && actions.last() == nullptr)
            w->setHorizontalAlignment(Qt::AlignCenter);
        else if (actions.first() != nullptr && actions.last() == nullptr)
            w->setHorizontalAlignment(Qt::AlignLeft);
    };

    if (!permanentActions.isEmpty()) {
        setAlignment(permanentActions);
        w->addActions(permanentActions);
        w->setHover(false);
    } else if (!hoverActions.isEmpty()) {
        setAlignment(hoverActions);
        w->addActions(hoverActions);
        w->setHover(true);
    }

    w->create();
    m_view->setIndexWidget(index, w);
}
