/*************************************************************************
 * Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr>    *
 *                                                                       *
 * This program is free software; you can redistribute it and/or modify  *
 * it under the terms of the GNU General Public License as published by  *
 * the Free Software Foundation; either version 2 of the License, or     *
 * (at your option) any later version.                                   *
 *                                                                       *
 * This program is distributed in the hope that it will be useful,       *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 * GNU General Public License for more details.                          *
 *                                                                       *
 * You should have received a copy of the GNU General Public License     *
 * along with this program; if not, write to the                         *
 * Free Software Foundation, Inc.,                                       *
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 *************************************************************************/

#ifndef ADWAITA_WINDOW_MANAGER_H
#define ADWAITA_WINDOW_MANAGER_H

#include "adwaita.h"
#include "adwaitaqt_export.h"
#include "config-adwaita.h"

#include <QEvent>

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

namespace Adwaita
{
class ADWAITAQT_EXPORT WindowManager : public QObject
{
    Q_OBJECT
public:
    //* constructor
    explicit WindowManager(QObject *);

    //* destructor
    virtual ~WindowManager()
    {
    }

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

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

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

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

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

    //* 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;
    }

    //* returns true if window manager is used for moving
    bool useWMMoveResize() const
    {
        return supportWMMoveResize() && _useWMMoveResize;
    }

    //* use window manager for moving, when available
    void setUseWMMoveResize(bool value)
    {
        _useWMMoveResize = 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 dragable
    bool isDragable(QWidget *);

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

    //* returns true if widget is dragable
    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(QWidget *, const QPoint &);

    //* X11 specific implementation for startDrag
    void startDragX11(QWidget *, const QPoint &);

    //* returns true if window manager is used for moving
    /** right now this is true only for X11 */
    bool supportWMMoveResize() const;

    //* 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 0L if none
    template<typename T> T findParent(const QWidget *) const;

private:
    //* enability
    bool _enabled;

    //* use WM moveResize
    bool _useWMMoveResize;

    //* drag mode
    int _dragMode;

    //* drag distance
    /** this is copied from kwin::geometry */
    int _dragDistance;

    //* drag delay
    /** this is copied from kwin::geometry */
    int _dragDelay;

    //* wrapper for exception id
    class ExceptionId : public QPair<QString, QString>
    {
    public:
        //* constructor
        explicit ExceptionId(const QString &value)
        {
            const QStringList args(value.split(QChar::fromLatin1('@')));
            if (args.isEmpty()) {
                return;
            }

            second = args[0].trimmed();
            if (args.size() > 1) {
                first = args[1].trimmed();
            }
        }

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

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

    //* 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;

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

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

    //* true if drag is locked
    bool _locked;

    //* cursor override
    /** used to keep track of application cursor being overridden when dragging in non-WM mode */
    bool _cursorOverride;

    //* application event filter
    QObject *_appEventFilter;

    //* 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 0L;
    }

    for (QWidget *parent = widget->parentWidget(); parent; parent = parent->parentWidget()) {
        if (T cast = qobject_cast<T>(parent)) {
            return cast;
        }
    }

    return 0L;
}

} // namespace Adwaita

#endif // ADWAITA_WINDOW_MANAGER_H
