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

#pragma once

#include "breeze.h"
#include "breezestyleconfigdata.h"
#include "config-breeze.h"

#include <QEvent>

#include <QApplication>
#include <QBasicTimer>
#include <QObject>
#include <QSet>
#include <QString>
#include <QWidget>

#if BREEZE_HAVE_QTQUICK
#include <QQuickItem>
#endif

namespace Breeze
{
class AppEventFilter;

class WindowManager : public QObject
{
    Q_OBJECT

public:
    //* constructor
    explicit WindowManager();

    //* initialize
    /** read relevant options from config */
    void initialize();

    //* register widget
    void registerWidget(QWidget *);

#if BREEZE_HAVE_QTQUICK
    //* register quick item
    void registerQuickItem(QQuickItem *);
#endif

    //* unregister widget
    void unregisterWidget(QWidget *);

    //* event filter [reimplemented]
    bool eventFilter(QObject *, QEvent *) override;

protected:
    //* timer event,
    /** used to start drag if button is pressed for a long enough time */
    void timerEvent(QTimerEvent *) override;

    //* mouse press event
    bool mousePressEvent(QObject *, QEvent *);

    //* mouse move event
    bool mouseMoveEvent(QObject *, QEvent *);

    //* mouse release event
    bool mouseReleaseEvent(QObject *, QEvent *);

    //*@name configuration
    //@{

    //* enable state
    bool enabled() const
    {
        return _enabled;
    }

    //* enable state
    void setEnabled(bool value)
    {
        _enabled = value;
    }

    //* drag mode
    int dragMode() const
    {
        return _dragMode;
    }

    //* drag mode
    void setDragMode(int value)
    {
        _dragMode = value;
    }

    //* drag distance (pixels)
    void setDragDistance(int value)
    {
        _dragDistance = value;
    }

    //* drag delay (msec)
    void setDragDelay(int value)
    {
        _dragDelay = value;
    }

    //* set list of whiteListed widgets
    /**
    white list is read from options and is used to adjust
    per-app window dragging issues
    */
    void initializeWhiteList();

    //* set list of blackListed widgets
    /**
    black list is read from options and is used to adjust
    per-app window dragging issues
    */
    void initializeBlackList();

    //@}

    //* returns true if widget is draggable
    bool isDraggable(QWidget *);

    //* returns true if widget is draggable
    bool isBlackListed(QWidget *);

    //* returns true if widget is draggable
    bool isWhiteListed(QWidget *) const;

    //* returns true if drag can be started from current widget
    bool canDrag(QWidget *);

    //* returns true if drag can be started from current widget and position
    /** child at given position is passed as second argument */
    bool canDrag(QWidget *, QWidget *, const QPoint &);

    //* reset drag
    void resetDrag();

    //* start drag
    void startDrag(QWindow *);

    //* utility function
    bool isDockWidgetTitle(const QWidget *) const;

    //*@name lock
    //@{

    void setLocked(bool value)
    {
        _locked = value;
    }

    //* lock
    bool isLocked() const
    {
        return _locked;
    }

    //@}

    //* returns first widget matching given class, or nullptr if none
    template<typename T>
    T findParent(const QWidget *) const;

private:
    //* enability
    bool _enabled = true;

    //* drag mode
    int _dragMode = StyleConfigData::WD_FULL;

    //* drag distance
    /** this is copied from kwin::geometry */
    int _dragDistance = QApplication::startDragDistance();

    //* drag delay
    /** this is copied from kwin::geometry */
    int _dragDelay = QApplication::startDragTime();

    //* wrapper for exception id
    class ExceptionId
    {
    public:
        //* constructor
        explicit ExceptionId(const QString &value)
        {
            const QStringList args(value.split(QChar::fromLatin1('@')));
            if (args.isEmpty()) {
                return;
            }
            _exception.second = args[0].trimmed();
            if (args.size() > 1) {
                _exception.first = args[1].trimmed();
            }
        }

        const QString &appName() const
        {
            return _exception.first;
        }

        const QString &className() const
        {
            return _exception.second;
        }

    private:
        QPair<QString, QString> _exception;

        friend size_t qHash(const ExceptionId &value, size_t seed = 0)
        {
            return qHash(value._exception, seed);
        }

        friend bool operator==(const ExceptionId &lhs, const ExceptionId &rhs)
        {
            return lhs._exception == rhs._exception;
        }
    };

    //* exception set
    using ExceptionSet = QSet<ExceptionId>;

    //* list of white listed special widgets
    /**
    it is read from options and is used to adjust
    per-app window dragging issues
    */
    ExceptionSet _whiteList;

    //* list of black listed special widgets
    /**
    it is read from options and is used to adjust
    per-app window dragging issues
    */
    ExceptionSet _blackList;

    //* drag point
    QPoint _dragPoint;
    QPoint _globalDragPoint;

    //* drag timer
    QBasicTimer _dragTimer;

    //* target being dragged
    /** Weak pointer is used in case the target gets deleted while drag is in progress */
    WeakPointer<QWidget> _target;

#if BREEZE_HAVE_QTQUICK
    WeakPointer<QQuickItem> _quickTarget;
#endif

    //* true if drag is about to start
    bool _dragAboutToStart = false;

    //* true if drag is in progress
    bool _dragInProgress = false;

    //* true if drag is locked
    bool _locked = false;

    //* true if the event we are intercepting passed through a QQuickWidget.
    /**In this case we shouldn't start a drag, because if it was to start, we would have done it in the event filter of a qquickwidget.
     * Event handlers don't accept input events, but they do block QQuickItems to receive the event, so the event may have been
     * managed by an handler and not blocked by the root qml item.
     **/
    bool _eventInQQuickWidget = false;

    // lifetime: parented to this
    //* application event filter
    AppEventFilter *_appEventFilter = nullptr;

    //* allow access of all private members to the app event filter
    friend class AppEventFilter;
};

//____________________________________________________________________
template<typename T>
T WindowManager::findParent(const QWidget *widget) const
{
    if (!widget) {
        return nullptr;
    }
    for (QWidget *parent = widget->parentWidget(); parent; parent = parent->parentWidget()) {
        if (T cast = qobject_cast<T>(parent)) {
            return cast;
        }
    }

    return nullptr;
}

}
