/***************************************************************************
 *   Copyright (C) 2009 by Ilya Kotov                                      *
 *   forkotov02@hotmail.ru                                                 *
 *                                                                         *
 *   Copyright (C) 2003-2007 by Justin Karneges and Michail Pishchagin     *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QSettings>
#include <QX11Info>
#include <QEvent>
#include <QKeyEvent>
#include <QCoreApplication>
#include <QApplication>
#include <QDesktopWidget>
#define Visual XVisual
extern "C"
{
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
}
#undef CursorShape
#undef Status
#undef Bool
#undef None
#undef KeyPress
#undef Visual

#include <qmmp/qmmp.h>
#include <qmmp/soundcore.h>
#include <qmmpui/mediaplayer.h>
#include "hotkeymanager.h"

quint32 Hotkey::defaultKey()
{
    return defaultKey(action);
}

quint32 Hotkey::defaultKey(int act)
{
    //default key bindings
    QMap<int, quint32> keyMap;
    keyMap[PLAY] = XF86XK_AudioPlay;
    keyMap[STOP] = XF86XK_AudioStop;
    keyMap[PAUSE] = XF86XK_AudioPause;
    keyMap[PLAY_PAUSE] = 0;
    keyMap[NEXT] = XF86XK_AudioNext;
    keyMap[PREVIOUS] = XF86XK_AudioPrev;
    keyMap[SHOW_HIDE] = 0;
    keyMap[VOLUME_UP] = XF86XK_AudioRaiseVolume;
    keyMap[VOLUME_DOWN] = XF86XK_AudioLowerVolume;
    return keyMap[act];
}

HotkeyManager::HotkeyManager(QObject *parent) : General(parent)
{
    QCoreApplication::instance()->installEventFilter(this);
    WId rootWindow = QX11Info::appRootWindow();
    QSettings settings(Qmmp::configFile(), QSettings::IniFormat); //load settings
    settings.beginGroup("Hotkey");
    for (int i = Hotkey::PLAY, j = 0; i <= Hotkey::VOLUME_DOWN; ++i, ++j)
    {
        quint32 key = settings.value(QString("key_%1").arg(i), Hotkey::defaultKey(i)).toUInt();
        quint32 mod = settings.value(QString("modifiers_%1").arg(i), 0).toUInt();

        if (key)
        {
            foreach(long mask_mod, ignModifiersList())
            {
                Hotkey *hotkey = new Hotkey;
                hotkey->action = i;
                hotkey->key = key;
                hotkey->code = XKeysymToKeycode(QX11Info::display(), hotkey->key);
                if(!hotkey->code)
                    continue;
                XGrabKey(QX11Info::display(),  hotkey->code, mod | mask_mod, rootWindow, False,
                         GrabModeAsync, GrabModeAsync);
                hotkey->mod = mod | mask_mod;
                m_grabbedKeys << hotkey;
            }
        }
    }
    settings.endGroup();
    XSync(QX11Info::display(), False);
//         XSetErrorHandler();
}

HotkeyManager::~HotkeyManager()
{
    foreach(Hotkey *key, m_grabbedKeys)
    {
        if(key->code)
            XUngrabKey(QX11Info::display(), key->code, key->mod, QX11Info::appRootWindow());
    }
    while (!m_grabbedKeys.isEmpty())
        delete m_grabbedKeys.takeFirst ();
}

const QString HotkeyManager::getKeyString(quint32 key, quint32 modifiers)
{
    QString strModList[] = { "Control", "Shift", "Alt", "Mod2", "Mod3", "Super", "Mod5" };
    quint32 modList[] = { ControlMask, ShiftMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask };
    QString keyStr;
    for (int j = 0; j < 7; j++)
    {
        if (modifiers & modList[j])
            keyStr.append(strModList[j] + "+");
    }
    keyStr.append(XKeysymToString(key));
    return keyStr;
}

bool HotkeyManager::eventFilter(QObject* o, QEvent* e)
{
    //receive events from active and root windows only
    if (e->type() == QEvent::KeyPress && (o == qApp->desktop () || o == qApp->activeWindow ()))
    {
        QKeyEvent* k = static_cast<QKeyEvent*>(e);
        quint32 key = XKeycodeToKeysym(QX11Info::display(), k->nativeScanCode (), 0);
        quint32 mod = k->nativeModifiers ();
        foreach(Hotkey *hotkey, m_grabbedKeys)
        {
            if (hotkey->key != key || hotkey->mod != mod)
                continue;
            qDebug("HotkeyManager: [%s] pressed", qPrintable(getKeyString(key, mod)));

            switch (hotkey->action)
            {
            case Hotkey::PLAY:
                MediaPlayer::instance()->play();
                break;
            case Hotkey::STOP:
                MediaPlayer::instance()->stop();
                break;
            case Hotkey::PAUSE:
                SoundCore::instance()->pause();
                break;
            case Hotkey::PLAY_PAUSE:
                if (SoundCore::instance()->state() == Qmmp::Stopped)
                    MediaPlayer::instance()->play();
                else if (SoundCore::instance()->state() != Qmmp::FatalError)
                    SoundCore::instance()->pause();
                break;
            case Hotkey::NEXT:
                MediaPlayer::instance()->next();
                break;
            case Hotkey::PREVIOUS:
                MediaPlayer::instance()->previous();
                break;
            case Hotkey::SHOW_HIDE:
                toggleVisibility();
                break;
            case Hotkey::VOLUME_UP:
            case Hotkey::VOLUME_DOWN:
                 SoundCore *core = SoundCore::instance();
                 int volume = qMax(core->leftVolume(), core->rightVolume());
                 int balance = 0;
                 int left = core->leftVolume();
                 int right = core->rightVolume();
                 if (left || right)
                     balance = (right - left)*100/volume;
                 if(hotkey->action == Hotkey::VOLUME_UP)
                     volume = qMin (100, volume + 5);
                 else
                     volume = qMax (0, volume - 5);
                 core->setVolume(volume-qMax(balance,0)*volume/100,
                                 volume+qMin(balance,0)*volume/100);
                 break;
            }
            qApp->processEvents();
        }
    }
    return QObject::eventFilter(o, e);
}
long HotkeyManager::m_alt_mask = 0;
long HotkeyManager::m_meta_mask = 0;
long HotkeyManager::m_super_mask = 0;
long HotkeyManager::m_hyper_mask = 0;
long HotkeyManager::m_numlock_mask = 0;
bool HotkeyManager::m_haveMods = false;

//copied from globalshortcutmanager_x11.cpp by Justin Karneges and Michail Pishchagin (Psi project)
void HotkeyManager::ensureModifiers()
{
    if (m_haveMods)
        return;

    Display* appDpy = QX11Info::display();
    XModifierKeymap* map = XGetModifierMapping(appDpy);
    if (map)
    {
        // XKeycodeToKeysym helper code adapeted from xmodmap
        int min_keycode, max_keycode, keysyms_per_keycode = 1;
        XDisplayKeycodes (appDpy, &min_keycode, &max_keycode);
        XFree(XGetKeyboardMapping (appDpy, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode));

        int i, maskIndex = 0, mapIndex = 0;
        for (maskIndex = 0; maskIndex < 8; maskIndex++)
        {
            for (i = 0; i < map->max_keypermod; i++)
            {
                if (map->modifiermap[mapIndex])
                {
                    KeySym sym;
                    int symIndex = 0;
                    do
                    {
                        sym = XKeycodeToKeysym(appDpy, map->modifiermap[mapIndex], symIndex);
                        symIndex++;
                    }
                    while ( !sym && symIndex < keysyms_per_keycode);
                    if (!m_alt_mask && (sym == XK_Alt_L || sym == XK_Alt_R))
                    {
                        m_alt_mask = 1 << maskIndex;
                    }
                    if (!m_meta_mask && (sym == XK_Meta_L || sym == XK_Meta_R))
                    {
                        m_meta_mask = 1 << maskIndex;
                    }
                    if (!m_super_mask && (sym == XK_Super_L || sym == XK_Super_R))
                    {
                        m_super_mask = 1 << maskIndex;
                    }
                    if (!m_hyper_mask && (sym == XK_Hyper_L || sym == XK_Hyper_R))
                    {
                        m_hyper_mask = 1 << maskIndex;
                    }
                    if (!m_numlock_mask && (sym == XK_Num_Lock))
                    {
                        m_numlock_mask = 1 << maskIndex;
                    }
                }
                mapIndex++;
            }
        }

        XFreeModifiermap(map);

        // logic from qt source see gui/kernel/qkeymapper_x11.cpp
        if (!m_meta_mask || m_meta_mask == m_alt_mask)
        {
            // no meta keys... s,meta,super,
            m_meta_mask = m_super_mask;
            if (!m_meta_mask || m_meta_mask == m_alt_mask)
            {
                // no super keys either? guess we'll use hyper then
                m_meta_mask = m_hyper_mask;
            }
        }
    }
    else
    {
        // assume defaults
        m_alt_mask = Mod1Mask;
        m_meta_mask = Mod4Mask;
    }

    m_haveMods = true;
}

QList<long> HotkeyManager::ignModifiersList()
{
    ensureModifiers();
    QList<long> ret;
    if (m_numlock_mask)
    {
        ret << 0 << LockMask << m_numlock_mask << (LockMask | m_numlock_mask);
    }
    else
    {
        ret << 0 << LockMask;
    }
    return ret;
}

quint32 HotkeyManager::keycodeToKeysym(quint32 keycode)
{
    return XKeycodeToKeysym(QX11Info::display(), keycode, 0);
}
