//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Base/CustomEventFilters.cpp
//! @brief     Defines classes releted to event filtering.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Base/CustomEventFilters.h"
#include "Base/Util/Assert.h"
#include <QApplication>
#include <QComboBox>
#include <QKeyEvent>
#include <QString>
#include <utility>

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

bool SpaceKeyEater::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() != QEvent::KeyPress)
        return QObject::eventFilter(obj, event);

    auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
    if (keyEvent->key() == Qt::Key_Space)
        return true; /* Always accept space bar */
    return QObject::eventFilter(obj, event);
    // standard event processing
}

// ----------------------------------------------------------------------------

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

void WheelEventEater::install(QObject* obj)
{
    auto* p = new WheelEventEater(obj);
    obj->installEventFilter(p);
}

bool WheelEventEater::eventFilter(QObject* obj, QEvent* event)
{
    if (auto* spinBox = qobject_cast<QAbstractSpinBox*>(obj)) {
        if (event->type() == QEvent::Wheel) {
            if (spinBox->focusPolicy() == Qt::WheelFocus) {
                event->accept();
                return false;
            }
            event->ignore();
            return true;
        }
        if (event->type() == QEvent::FocusIn)
            spinBox->setFocusPolicy(Qt::WheelFocus);
        else if (event->type() == QEvent::FocusOut)
            spinBox->setFocusPolicy(Qt::StrongFocus);
    } else if (qobject_cast<QComboBox*>(obj)) {
        if (event->type() == QEvent::Wheel) {
            event->ignore();
            return true;
        }
        event->accept();
        return false;
    }
    return QObject::eventFilter(obj, event);
}

// ----------------------------------------------------------------------------

bool DeleteEventFilter::eventFilter(QObject* dist, QEvent* event)
{
    Q_UNUSED(dist);
    if (event->type() == QEvent::KeyPress) {
        auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
        ASSERT(keyEvent);
        if (keyEvent->key() == Qt::Key_Delete)
            emit removeItem();
    }
    return QObject::eventFilter(dist, event);
}

// ----------------------------------------------------------------------------

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

bool LostFocusFilter::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() == QEvent::FocusOut)
        return true;

    return QObject::eventFilter(obj, event);
}

// ----------------------------------------------------------------------------

ShortcodeFilter::ShortcodeFilter(const QString& shortcode, QObject* parent)
    : QObject(parent)
    , m_shortcode(shortcode)
    , m_index(0)
{
}

bool ShortcodeFilter::eventFilter(QObject* obj, QEvent* event)
{
    Q_UNUSED(obj);
    if (event->type() == QEvent::KeyPress) {
        auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
        ASSERT(keyEvent);
        if (m_shortcode.at(m_index) == keyEvent->text()) {
            m_index++;
            if (m_index == m_shortcode.length()) {
                emit found();
                m_index = 0;
            }
        } else {
            int right = m_index;
            while (m_index > 0) {
                if (m_shortcode.at(m_index - 1) == keyEvent->text()
                    && m_shortcode.left(m_index - 1)
                           == m_shortcode.mid(right - m_index + 1, m_index - 1))
                    break;
                m_index--;
            }
        }
    }
    return false;
}

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

bool RightMouseButtonEater::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() != QEvent::MouseButtonPress)
        // standard event processing
        return QObject::eventFilter(obj, event);
    auto* mouseEvent = dynamic_cast<QMouseEvent*>(event);
    ASSERT(mouseEvent);
    if (mouseEvent->button() == Qt::RightButton) {
        event->ignore();
        return true;
    }
    event->accept();
    return false;
}

//! Passing focus-related events from child widget (e.g. QSpinBox) to parent (e.g. IntEditor)
//! to trigger QTreeView delegate's mechanism to switch editors on "tab" press key.
//! https://stackoverflow.com/questions/12145522/why-pressing-of-tab-key-emits-only-qeventshortcutoverride-event

TabFromFocusProxy::TabFromFocusProxy(QWidget* parent)
    : QObject(parent)
    , m_parent(parent)
{
    if (parent->focusProxy())
        parent->focusProxy()->installEventFilter(this);
}

bool TabFromFocusProxy::eventFilter(QObject* obj, QEvent* event)
{
    if (event->type() == QEvent::KeyPress) {
        auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
        ASSERT(keyEvent);
        if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
            // we are posting event as if m_parent had "tab" key
            QApplication::postEvent(
                m_parent, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));

            return false; // let QSpinBox that triggered the event process it
        }
    }

    else if (event->type() == QEvent::FocusOut) {
        auto* focusEvent = dynamic_cast<QFocusEvent*>(event);
        ASSERT(focusEvent);
        QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));

        return false; // Don't filter because focus can be changed internally in editor
    }

    return QObject::eventFilter(obj, event);
}
