/*
 * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "breezesplitterproxy.h"

#include "breezestyleconfigdata.h"

#include <QCoreApplication>
#include <QDebug>
#include <QPainter>

// Q_FALLTHROUGH() for Qt < 5.8
#ifndef Q_FALLTHROUGH
#if defined(__has_cpp_attribute)
#if __has_cpp_attribute(fallthrough)
#define Q_FALLTHROUGH() [[fallthrough]]
#elif __has_cpp_attribute(clang::fallthrough)
#define Q_FALLTHROUGH() [[clang::fallthrough]]
#elif __has_cpp_attribute(gnu::fallthrough)
#define Q_FALLTHROUGH() [[gnu::fallthrough]]
#endif
#endif
#ifndef Q_FALLTHROUGH
#if __GNUC__ >= 7
#define Q_FALLTHROUGH() __attribute__((fallthrough))
#elif (__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 5)
#define Q_FALLTHROUGH() [[clang::fallthrough]]
#else
#define Q_FALLTHROUGH()
#endif
#endif
#endif

namespace Breeze
{
SplitterFactory::SplitterFactory()
    : QObject()
    , _enabled(false)
{
}

//____________________________________________________________________
void SplitterFactory::setEnabled(bool value)
{
    if (_enabled != value) {
        // store
        _enabled = value;

        // assign to existing splitters
        for (WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter) {
            if (iter.value()) {
                iter.value().data()->setEnabled(value);
            }
        }
    }
}

//____________________________________________________________________
bool SplitterFactory::registerWidget(QWidget *widget)
{
    // check widget type
    if (qobject_cast<QMainWindow *>(widget)) {
        WidgetMap::iterator iter(_widgets.find(widget));
        if (iter == _widgets.end() || !iter.value()) {
            widget->installEventFilter(&_addEventFilter);
            SplitterProxy *proxy(new SplitterProxy(widget, _enabled));
            widget->removeEventFilter(&_addEventFilter);

            widget->installEventFilter(proxy);
            _widgets.insert(widget, proxy);

        } else {
            widget->removeEventFilter(iter.value().data());
            widget->installEventFilter(iter.value().data());
        }

        return true;

    } else if (qobject_cast<QSplitterHandle *>(widget)) {
        QWidget *window(widget->window());
        WidgetMap::iterator iter(_widgets.find(window));
        if (iter == _widgets.end() || !iter.value()) {
            window->installEventFilter(&_addEventFilter);
            SplitterProxy *proxy(new SplitterProxy(window, _enabled));
            window->removeEventFilter(&_addEventFilter);

            widget->installEventFilter(proxy);
            _widgets.insert(window, proxy);

        } else {
            widget->removeEventFilter(iter.value().data());
            widget->installEventFilter(iter.value().data());
        }

        return true;

    } else {
        return false;
    }
}

//____________________________________________________________________
void SplitterFactory::unregisterWidget(QWidget *widget)
{
    WidgetMap::iterator iter(_widgets.find(widget));
    if (iter != _widgets.end()) {
        if (iter.value()) {
            iter.value().data()->deleteLater();
        }
        _widgets.erase(iter);
    }
}

//____________________________________________________________________
SplitterProxy::SplitterProxy(QWidget *parent, bool enabled)
    : QWidget(parent)
    , _enabled(enabled)
    , _timerId(0)
{
    setAttribute(Qt::WA_TranslucentBackground, true);
    setAttribute(Qt::WA_OpaquePaintEvent, false);
    hide();
}

//____________________________________________________________________
void SplitterProxy::setEnabled(bool value)
{
    // make sure status has changed
    if (_enabled != value) {
        _enabled = value;
        if (!_enabled) {
            clearSplitter();
        }
    }
}

//____________________________________________________________________
bool SplitterProxy::eventFilter(QObject *object, QEvent *event)
{
    // do nothing if disabled
    if (!_enabled) {
        return false;
    }

    // do nothing in case of mouse grab
    if (mouseGrabber()) {
        return false;
    }

    switch (event->type()) {
    case QEvent::HoverEnter:
        if (!isVisible()) {
            // cast to splitter handle
            if (QSplitterHandle *handle = qobject_cast<QSplitterHandle *>(object)) {
                setSplitter(handle);
            }
        }

        return false;

    case QEvent::HoverMove:
    case QEvent::HoverLeave:
        return isVisible() && object == _splitter.data();

    case QEvent::MouseMove:
    case QEvent::Timer:
    case QEvent::Move:
        return false;

    case QEvent::CursorChange:
        if (QWidget *window = qobject_cast<QMainWindow *>(object)) {
            if (window->cursor().shape() == Qt::SplitHCursor || window->cursor().shape() == Qt::SplitVCursor) {
                setSplitter(window);
            }
        }
        return false;

    case QEvent::WindowDeactivate:
    case QEvent::MouseButtonRelease:
        clearSplitter();
        return false;

    default:
        return false;
    }
}

//____________________________________________________________________
bool SplitterProxy::event(QEvent *event)
{
    switch (event->type()) {
#if 0
    case QEvent::Paint:
    {
        QPainter painter( this );
        painter.setClipRegion( static_cast<QPaintEvent*>( event )->region() );
        painter.setRenderHints( QPainter::Antialiasing );
        painter.setPen( Qt::red );
        painter.drawRect( QRectF( rect() ).adjusted( 0.5, 0.5, -0.5, -0.5 ) );
        return true;
    }
#endif

    case QEvent::MouseMove:
    case QEvent::MouseButtonPress:
    case QEvent::MouseButtonRelease: {
        // check splitter
        if (!_splitter) {
            return false;
        }

        event->accept();

        // grab on mouse press
        if (event->type() == QEvent::MouseButtonPress) {
            grabMouse();
            resize(1, 1);
        }

        // cast to mouse event
        QMouseEvent *mouseEvent(static_cast<QMouseEvent *>(event));

        // get relevant position to post mouse drag event to application
        if (event->type() == QEvent::MouseButtonPress) {
            // use hook, to make sure splitter is properly dragged
            QMouseEvent copy(mouseEvent->type(),
                             _hook,
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
                             mouseEvent->globalPosition().toPoint(),
#endif
                             mouseEvent->button(),
                             mouseEvent->buttons(),
                             mouseEvent->modifiers());

            QCoreApplication::sendEvent(_splitter.data(), &copy);

        } else {
            // map event position to current splitter and post.
            QMouseEvent copy(mouseEvent->type(),
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                             _splitter.data()->mapFromGlobal(mouseEvent->globalPosition().toPoint()),
#else
                             _splitter.data()->mapFromGlobal(mouseEvent->globalPos()),
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
                             mouseEvent->globalPosition().toPoint(),
#endif
                             mouseEvent->button(),
                             mouseEvent->buttons(),
                             mouseEvent->modifiers());

            QCoreApplication::sendEvent(_splitter.data(), &copy);
        }

        // release grab on mouse-Release
        if (event->type() == QEvent::MouseButtonRelease && mouseGrabber() == this) {
            releaseMouse();
        }

        return true;
    }

    case QEvent::Timer:
        if (static_cast<QTimerEvent *>(event)->timerId() != _timerId) {
            return QWidget::event(event);
        }

        /*
        Fall through is intended.
        We somehow lost a QEvent::Leave before timeout. We fix it from here
        */

        Q_FALLTHROUGH();

    case QEvent::HoverLeave:
    case QEvent::Leave: {
        if (mouseGrabber() == this) {
            return true;
        }

        // reset splitter
        if (isVisible() && !rect().contains(mapFromGlobal(QCursor::pos()))) {
            clearSplitter();
        }
        return true;
    }

    default:
        return QWidget::event(event);
    }
}

//____________________________________________________________________
void SplitterProxy::setSplitter(QWidget *widget)
{
    // check if changed
    if (_splitter.data() == widget) {
        return;
    }

    // get cursor position
    const QPoint position(QCursor::pos());

    // store splitter and hook
    _splitter = widget;
    _hook = _splitter.data()->mapFromGlobal(position);

    // adjust rect
    QRect rect(0, 0, 2 * StyleConfigData::splitterProxyWidth(), 2 * StyleConfigData::splitterProxyWidth());
    rect.moveCenter(parentWidget()->mapFromGlobal(position));
    setGeometry(rect);
    setCursor(_splitter.data()->cursor().shape());

    // show
    raise();
    show();

    // timer used to automatically hide proxy in case leave events are lost
    if (!_timerId) {
        _timerId = startTimer(150);
    }
}

//____________________________________________________________________
void SplitterProxy::clearSplitter()
{
    // check if changed
    if (!_splitter) {
        return;
    }

    // release mouse
    if (mouseGrabber() == this) {
        releaseMouse();
    }

    // send hover event
    if (_splitter) {
        // SplitterProxy intercepts HoverLeave/HoverMove events to _splitter,
        // but this is meant to reach it directly. Unset _splitter to stop interception.
        auto splitter = _splitter;
        _splitter.clear();
        QHoverEvent hoverEvent(qobject_cast<QSplitterHandle *>(splitter.data()) ? QEvent::HoverLeave : QEvent::HoverMove,
                               splitter.data()->mapFromGlobal(QCursor::pos()),
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
                               splitter.data()->mapFromGlobal(QCursor::pos()),
#endif
                               _hook);
        QCoreApplication::sendEvent(splitter.data(), &hoverEvent);
    }

    // kill timer if any
    if (_timerId) {
        killTimer(_timerId);
        _timerId = 0;
    }

    // hide
    parentWidget()->setUpdatesEnabled(false);
    // Note: This sends a synthetic mouse event to the widget below (to get focus), which might be
    // another SplitterHandle, therefore enabling this SplitterProxy again!
    hide();
    parentWidget()->setUpdatesEnabled(true);
}

}
