/*
 * globalshortcutmanager_win.cpp - Windows implementation of global shortcuts
 * Copyright (C) 2003-2006  Justin Karneges, Maciej Niedzielski
 *
 * 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, see <https://www.gnu.org/licenses/>.
 *
 */

#include "globalshortcutmanager.h"
#include "globalshortcuttrigger.h"

#include <QAbstractNativeEventFilter>
#include <QApplication>
#include <QWidget>
#include <windows.h>

class GlobalShortcutManager::KeyTrigger::Impl : public QWidget {
    class WinEventFilter : public QAbstractNativeEventFilter {
        GlobalShortcutManager::KeyTrigger::Impl *impl;

    public:
        WinEventFilter(GlobalShortcutManager::KeyTrigger::Impl *parent) : impl(parent)
        {
            qApp->installNativeEventFilter(this);
        }

        virtual bool nativeEventFilter(const QByteArray &eventType, void *m, long *result) Q_DECL_OVERRIDE
        {
            if (eventType == "windows_generic_MSG") {
                return impl->nativeEvent(eventType, static_cast<MSG *>(m), result);
            }
            return false;
        }
    };

public:
    /**
     * Constructor registers the hotkey.
     */
    Impl(GlobalShortcutManager::KeyTrigger *t, const QKeySequence &ks) :
        filter(new WinEventFilter(this)), trigger_(t), id_(0)
    {
        UINT mod = 0, key = 0;
        if (convertKeySequence(ks, &mod, &key))
            if (RegisterHotKey(HWND(winId()), nextId, mod, key))
                id_ = nextId++;
    }

    /**
     * Destructor unregisters the hotkey.
     */
    ~Impl()
    {
        delete filter;
        if (id_)
            UnregisterHotKey(HWND(winId()), id_);
    }

    /**
     * Triggers triggered() signal when the hotkey is activated.
     */
    bool nativeEvent(const QByteArray &eventType, MSG *m, long *result)
    {
        Q_UNUSED(eventType);
        if (m->message == WM_HOTKEY && m->wParam == id_) {
            emit trigger_->triggered();
            return true;
        }
        Q_UNUSED(result);
        return false;
    }

private:
    WinEventFilter *filter;
    KeyTrigger *    trigger_;
    WPARAM          id_;
    static WPARAM   nextId;

private:
    struct Qt_VK_Keymap {
        int  key;
        UINT vk;
    };
    static Qt_VK_Keymap qt_vk_table[];

    static bool convertKeySequence(const QKeySequence &ks, UINT *mod_, UINT *key_)
    {
        int code = ks[0];

        UINT mod = 0;
        if (code & Qt::META)
            mod |= MOD_WIN;
        if (code & Qt::SHIFT)
            mod |= MOD_SHIFT;
        if (code & Qt::CTRL)
            mod |= MOD_CONTROL;
        if (code & Qt::ALT)
            mod |= MOD_ALT;

        UINT key = 0;
        code &= ~Qt::KeyboardModifierMask;
        if (code >= 0x20 && code <= 0x7f)
            key = code;
        else {
            for (int n = 0; qt_vk_table[n].key != Qt::Key_unknown; ++n) {
                if (qt_vk_table[n].key == code) {
                    key = qt_vk_table[n].vk;
                    break;
                }
            }
            if (!key)
                return false;
        }

        if (mod)
            *mod_ = mod;
        if (key)
            *key_ = key;

        return true;
    }
};

GlobalShortcutManager::KeyTrigger::Impl::Qt_VK_Keymap GlobalShortcutManager::KeyTrigger::Impl::qt_vk_table[] = {
    { Qt::Key_Escape, VK_ESCAPE },
    { Qt::Key_Tab, VK_TAB },
    { Qt::Key_Backtab, 0 },
    { Qt::Key_Backspace, VK_BACK },
    { Qt::Key_Return, VK_RETURN },
    { Qt::Key_Enter, VK_RETURN },
    { Qt::Key_Insert, VK_INSERT },
    { Qt::Key_Delete, VK_DELETE },
    { Qt::Key_Pause, VK_PAUSE },
    { Qt::Key_Print, VK_PRINT },
    { Qt::Key_SysReq, 0 },
    { Qt::Key_Clear, VK_CLEAR },
    { Qt::Key_Home, VK_HOME },
    { Qt::Key_End, VK_END },
    { Qt::Key_Left, VK_LEFT },
    { Qt::Key_Up, VK_UP },
    { Qt::Key_Right, VK_RIGHT },
    { Qt::Key_Down, VK_DOWN },
    { Qt::Key_PageUp, VK_PRIOR },
    { Qt::Key_PageDown, VK_NEXT },
    { Qt::Key_Shift, VK_SHIFT },
    { Qt::Key_Control, VK_CONTROL },
    { Qt::Key_Meta, VK_LWIN },
    { Qt::Key_Alt, VK_MENU },
    { Qt::Key_CapsLock, VK_CAPITAL },
    { Qt::Key_NumLock, VK_NUMLOCK },
    { Qt::Key_ScrollLock, VK_SCROLL },
    { Qt::Key_F1, VK_F1 },
    { Qt::Key_F2, VK_F2 },
    { Qt::Key_F3, VK_F3 },
    { Qt::Key_F4, VK_F4 },
    { Qt::Key_F5, VK_F5 },
    { Qt::Key_F6, VK_F6 },
    { Qt::Key_F7, VK_F7 },
    { Qt::Key_F8, VK_F8 },
    { Qt::Key_F9, VK_F9 },
    { Qt::Key_F10, VK_F10 },
    { Qt::Key_F11, VK_F11 },
    { Qt::Key_F12, VK_F12 },
    { Qt::Key_F13, VK_F13 },
    { Qt::Key_F14, VK_F14 },
    { Qt::Key_F15, VK_F15 },
    { Qt::Key_F16, VK_F16 },
    { Qt::Key_F17, VK_F17 },
    { Qt::Key_F18, VK_F18 },
    { Qt::Key_F19, VK_F19 },
    { Qt::Key_F20, VK_F20 },
    { Qt::Key_F21, VK_F21 },
    { Qt::Key_F22, VK_F22 },
    { Qt::Key_F23, VK_F23 },
    { Qt::Key_F24, VK_F24 },
    { Qt::Key_F25, 0 },
    { Qt::Key_F26, 0 },
    { Qt::Key_F27, 0 },
    { Qt::Key_F28, 0 },
    { Qt::Key_F29, 0 },
    { Qt::Key_F30, 0 },
    { Qt::Key_F31, 0 },
    { Qt::Key_F32, 0 },
    { Qt::Key_F33, 0 },
    { Qt::Key_F34, 0 },
    { Qt::Key_F35, 0 },
    { Qt::Key_Super_L, 0 },
    { Qt::Key_Super_R, 0 },
    { Qt::Key_Menu, 0 },
    { Qt::Key_Hyper_L, 0 },
    { Qt::Key_Hyper_R, 0 },
    { Qt::Key_Help, 0 },
    { Qt::Key_Direction_L, 0 },
    { Qt::Key_Direction_R, 0 },

    { Qt::Key_unknown, 0 },
};

WPARAM GlobalShortcutManager::KeyTrigger::Impl::nextId = 1;

GlobalShortcutManager::KeyTrigger::KeyTrigger(const QKeySequence &key) { d = new Impl(this, key); }

GlobalShortcutManager::KeyTrigger::~KeyTrigger()
{
    delete d;
    d = nullptr;
}
