//
// KeyGrabber.cc for pekwm
// Copyright © 2003-2009 Claes Nästén <me@pekdon.net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H

#include "KeyGrabber.hh"

#include "Config.hh"
#include "PScreen.hh"
#include "CfgParser.hh"
#include "Util.hh"

#include <iostream>

extern "C" {
#include <X11/Xutil.h>
#include <X11/keysym.h>
}

using std::cerr;
using std::endl;
using std::string;
using std::list;
using std::vector;
using std::find;

KeyGrabber* KeyGrabber::_instance = 0;

//! @brief Constructor for Chain class
KeyGrabber::Chain::Chain(uint mod, uint key) :
        _mod(mod), _key(key)
{
}

//! @brief Destructor for Chain class
KeyGrabber::Chain::~Chain(void)
{
    unload();
}

//! @brief Unloads all chains and keys
void
KeyGrabber::Chain::unload(void)
{
    list<KeyGrabber::Chain*>::iterator it(_chains.begin());
    for (; it != _chains.end(); ++it) {
        delete *it;
    }
    _chains.clear();
    _keys.clear();
}

//! @brief Searches the _chains list for an action
KeyGrabber::Chain*
KeyGrabber::Chain::findChain(XKeyEvent *ev)
{
    list<KeyGrabber::Chain*>::iterator it(_chains.begin());

    for (; it != _chains.end(); ++it) {
        if ((((*it)->getMod() == MOD_ANY) || ((*it)->getMod() == ev->state)) &&
                (((*it)->getKey() == 0) || ((*it)->getKey() == ev->keycode))) {
            return *it;
        }
    }

    return 0;
}

//! @brief Searches the _keys list for an action
ActionEvent*
KeyGrabber::Chain::findAction(XKeyEvent *ev)
{
    list<ActionEvent>::iterator it(_keys.begin());

    for (; it != _keys.end(); ++it) {
        if (((it->mod == MOD_ANY) || (it->mod == ev->state)) &&
                ((it->sym == 0) || (it->sym == ev->keycode))) {
            return &*it;
        }
    }

    return 0;
}

//! @brief KeyGrabber constructor
KeyGrabber::KeyGrabber(PScreen *scr)
    : _scr(scr), _menu_chain(0, 0),
      _global_chain(0, 0), _moveresize_chain(0, 0),
      _input_dialog_chain(0, 0)
{
#ifdef DEBUG
    if (_instance) {
        cerr << __FILE__ << "@" << __LINE__ << ": "
             << "KeyGrabber(" << this << ")::KeyGrabber(" << scr << ")" << endl
             << " *** _instance already set: " << _instance << endl;
    }
#endif // DEBUG
    _instance = this;

    _num_lock = _scr->getNumLock();
    _scroll_lock = _scr->getScrollLock();
}

//! @brief Destructor for KeyGrabber class
KeyGrabber::~KeyGrabber(void)
{
    _instance = 0;
}

//! @brief Parses the "KeyFile" and inserts into _global_keys.
//! If _global_keys holds any keygrabs they will be flushed before
//! reloading the new keybindings.
bool
KeyGrabber::load(const std::string &file, bool force)
{
    if (! force && ! Util::requireReload(_cfg_state, file)) {
        return false;
    }

    CfgParser key_cfg;
    if (! key_cfg.parse(file, CfgParserSource::SOURCE_FILE)) {
        _cfg_state.clear();
        if (! key_cfg.parse(SYSCONFDIR "/keys", CfgParserSource::SOURCE_FILE, true)) {
            cerr << __FILE__ << "@" << __LINE__ << "Error: no keyfile at " << file
                 << " or " << SYSCONFDIR "/keys" << endl;
            return false;
        }
    }

    if (key_cfg.is_dynamic_content()) {
        _cfg_state.clear();
    } else {
        _cfg_state = key_cfg.get_file_list();
    }

    CfgParser::Entry *section;

    section = key_cfg.get_entry_root()->find_section("GLOBAL");
    if (section) {
        _global_chain.unload();
        parseGlobalChain(section, &_global_chain);
    }

    section = key_cfg.get_entry_root ()->find_section("MOVERESIZE");
    if (section) {
        _moveresize_chain.unload ();
        parseMoveResizeChain (section, &_moveresize_chain);
    }

    // Previously there was only a CmdDialog section, however the text
    // handling parts have been moved into InputDialog but to keep
    // compatibility this check exists.
    section = key_cfg.get_entry_root()->find_section("INPUTDIALOG");
    if (! section) {
        section = key_cfg.get_entry_root()->find_section("CMDDIALOG");
    }

    if (section) {
        _input_dialog_chain.unload ();
        parseInputDialogChain (section, &_input_dialog_chain);
    }

    section = key_cfg.get_entry_root ()->find_section("MENU");
    if (section) {
        _menu_chain.unload();
        parseMenuChain(section, &_menu_chain);
    }

    return true;
}

//! @brief Parses chain, getting actions as plain ActionEvents
void
KeyGrabber::parseGlobalChain(CfgParser::Entry *section, KeyGrabber::Chain *chain)
{
    ActionEvent ae;
    uint key, mod;

    CfgParser::iterator it(section->begin());
    for (; it != section->end(); ++it) {
        if ((*it)->get_section() && *(*it) == "CHAIN") {
            // Figure out mod and key, create a new chain.
            if (Config::instance()->parseKey((*it)->get_value(), mod, key)) {
                KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key);
                parseGlobalChain((*it)->get_section(), sub_chain);
                chain->addChain(sub_chain);
            }
        } else if (Config::instance()->parseActionEvent((*it), ae, KEYGRABBER_OK, false)) {
            chain->addAction(ae);
        }
    }
}

//! @brief Parses chain, getting actions as MoveResizeEvents
void
KeyGrabber::parseMoveResizeChain(CfgParser::Entry *section, KeyGrabber::Chain *chain)
{
    ActionEvent ae;
    uint key, mod;

    CfgParser::iterator it(section->begin());
    for (; it != section->end(); ++it) {
        if ((*it)->get_section() && *(*it) == "CHAIN") {
            // Figure out mod and key, create a new chain.
            if (Config::instance()->parseKey((*it)->get_value(), mod, key)) {
                KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key);
                parseMoveResizeChain((*it)->get_section(), sub_chain);
                chain->addChain(sub_chain);
            }
        } else if (Config::instance()->parseMoveResizeEvent((*it), ae)) {
            chain->addAction(ae);
        }
    }
}

//! @brief Parses chain, getting actions as InputDialog Events
void
KeyGrabber::parseInputDialogChain(CfgParser::Entry *section, KeyGrabber::Chain *chain)
{
    ActionEvent ae;
    uint key, mod;

    CfgParser::iterator it(section->begin());
    for (; it != section->end(); ++it) {
        if ((*it)->get_section() && *(*it) == "CHAIN") {
            // Figure out mod and key, create a new chain.
            if (Config::instance()->parseKey((*it)->get_value(), mod, key)) {
                KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain(mod, key);
                parseInputDialogChain((*it)->get_section(), sub_chain);
                chain->addChain(sub_chain);
            }
        } else if (Config::instance()->parseInputDialogEvent((*it), ae)) {
            chain->addAction(ae);
        }
    }
}

//! @brief Parses chain, getting actions as MenuEvents
void
KeyGrabber::parseMenuChain(CfgParser::Entry *section, KeyGrabber::Chain *chain)
{
    ActionEvent ae;
    uint key, mod;

    CfgParser::iterator it(section->begin());
    for (; it != section->end(); ++it) {
        if ((*it)->get_section() && *(*it) == "CHAIN") {
            // Figure out mod and key, create a new chain.
            if (Config::instance()->parseKey((*it)->get_value(), mod, key)) {
                KeyGrabber::Chain *sub_chain = new KeyGrabber::Chain (mod, key);
                parseGlobalChain((*it)->get_section(), sub_chain);
                chain->addChain(sub_chain);
            }
        } else if (Config::instance()->parseMenuEvent((*it), ae)) {
            chain->addAction(ae);
        }
    }
}

//! @brief Grabs all the keybindings in _keybindings on the Window win.
//! @param win Window to grab the keys on.
void
KeyGrabber::grabKeys(Window win)
{
    const list<KeyGrabber::Chain*> chains = _global_chain.getChainList();
    list<KeyGrabber::Chain*>::const_iterator c_it = chains.begin();
    for (; c_it != chains.end(); ++c_it)
        grabKey(win, (*c_it)->getMod(), (*c_it)->getKey());

    const list<ActionEvent> keys = _global_chain.getKeyList();
    list<ActionEvent>::const_iterator k_it = keys.begin();
    for (; k_it != keys.end(); ++k_it)
        grabKey(win, k_it->mod, k_it->sym);
}

//! @brief Grabs a key with state on Window win with "all possible" modifiers.
//! @param win Window to grab the key on.
//! @param mod Modifier state to grab.
//! @param key Key state to grab.
void
KeyGrabber::grabKey(Window win, uint mod, uint key)
{
    Display *dpy = _scr->getDpy(); // convenience

    XGrabKey(dpy, key, mod, win, true, GrabModeAsync, GrabModeAsync);
    XGrabKey(dpy, key, mod|LockMask, win, true, GrabModeAsync, GrabModeAsync);

    if (_num_lock) {
        XGrabKey(dpy, key, mod|_num_lock,
                 win, true, GrabModeAsync, GrabModeAsync);
        XGrabKey(dpy, key, mod|_num_lock|LockMask,
                 win, true, GrabModeAsync, GrabModeAsync);
    }
    if (_scroll_lock) {
        XGrabKey(dpy, key, mod|_scroll_lock,
                 win, true, GrabModeAsync, GrabModeAsync);
        XGrabKey(dpy, key, mod|_scroll_lock|LockMask,
                 win, true, GrabModeAsync, GrabModeAsync);
    }
    if (_num_lock && _scroll_lock) {
        XGrabKey(dpy, key, mod|_num_lock|_scroll_lock,
                 win, true, GrabModeAsync, GrabModeAsync);
        XGrabKey(dpy, key, mod|_num_lock|_scroll_lock|LockMask,
                 win, true, GrabModeAsync, GrabModeAsync);
    }
}

//! @brief Ungrabs all the keybindings on the Window win.
//! @param win Window to ungrab keys on.
void
KeyGrabber::ungrabKeys(Window win)
{
    XUngrabKey(_scr->getDpy(), AnyKey, AnyModifier, win);
}

//! @brief Tries to match the XKeyEvent to an usefull action and return it
//! @param ev XKeyEvent to match.
ActionEvent*
KeyGrabber::findAction(XKeyEvent *ev, KeyGrabber::Chain *chain)
{
    if (! ev) {
        return 0;
    }

    PScreen::stripStateModifiers(&ev->state);

    ActionEvent *action = 0;
    KeyGrabber::Chain *sub_chain = _global_chain.findChain(ev);
    if (sub_chain && _scr->grabKeyboard(_scr->getRoot())) {
        XEvent c_ev;
        KeyGrabber::Chain *last_chain;
        bool exit = false;

        while (! exit) {
            XMaskEvent(_scr->getDpy(), KeyPressMask, &c_ev);
            PScreen::stripStateModifiers(&c_ev.xkey.state);

            if (IsModifierKey(XKeycodeToKeysym(_scr->getDpy(),
                                               c_ev.xkey.keycode, 0))) {
                // do nothing
            } else  if ((last_chain = sub_chain->findChain(&c_ev.xkey))) {
                sub_chain = last_chain;
            } else {
                action = sub_chain->findAction(&c_ev.xkey);
                exit = true;
            }
        }

        _scr->ungrabKeyboard();
    } else {
        action = chain->findAction(ev);
    }

    return action;
}

//! @brief Finds action matching ev, continues chain if needed
ActionEvent*
KeyGrabber::findAction(XKeyEvent *ev, PWinObj::Type type)
{
    ActionEvent *ae = 0;

    if (type == PWinObj::WO_MENU) {
        ae = findAction(ev, &_menu_chain);
    }
    if (type == PWinObj::WO_CMD_DIALOG || type == PWinObj::WO_SEARCH_DIALOG) {
        ae = findAction(ev, &_input_dialog_chain);
    }

    // no action the menu list, try the global list
    if (! ae) {
        ae = findAction(ev, &_global_chain);
    }

    return ae;
}

//! @brief Searches the _moveresize_chain for actions.
ActionEvent*
KeyGrabber::findMoveResizeAction(XKeyEvent *ev)
{
    return findAction(ev, &_moveresize_chain);
}
