---------------------------------------------------------------------------
--- Utilities related to the keyboard and keybindings.
--
-- @author Emmanuel Lepage Vallee &lt;elv1313@gmail.com&gt;
-- @copyright 2018-2019 Emmanuel Lepage Vallee
-- @inputmodule awful.keyboard
---------------------------------------------------------------------------

local capi   = {root = root, awesome = awesome, client = client}
local module = {}

--- Convert the modifiers into pc105 key names
local conversion = nil

local function generate_conversion_map()
    if conversion then return conversion end

    local mods = capi.awesome._modifiers
    assert(mods)

    conversion = {}

    for mod, keysyms in pairs(mods) do
        for _, keysym in ipairs(keysyms) do
            assert(keysym.keysym)
            conversion[mod] = conversion[mod] or keysym.keysym
            conversion[keysym.keysym] = mod
        end
    end

    return conversion
end

capi.awesome.connect_signal("xkb::map_changed", function() conversion = nil end)

--- Execute a key combination.
--
-- If an awesome keybinding is assigned to the combination, it should be
-- executed.
--
-- To limit the chances of accidentally leaving a modifier key locked when
-- calling this function from a keybinding, make sure is attached to the
-- release event and not the press event.
--
-- @see root.fake_input
-- @tparam table modifiers A modified table. Valid modifiers are: `Any`, `Mod1`,
--   `Mod2`, `Mod3`, `Mod4`, `Mod5`, `Shift`, `Lock` and `Control`.
-- @tparam string key The key.
-- @noreturn
-- @staticfct awful.keyboard.emulate_key_combination
function module.emulate_key_combination(modifiers, key)
    local modmap = generate_conversion_map()
    local active = capi.awesome._active_modifiers

    -- Release all modifiers
    for _, m in ipairs(active) do
        assert(modmap[m])
        capi.root.fake_input("key_release", modmap[m])
    end

    for _, v in ipairs(modifiers) do
        local m = modmap[v]
        if m then
            capi.root.fake_input("key_press", m)
        end
    end

    capi.root.fake_input("key_press"  , key)
    capi.root.fake_input("key_release", key)

    for _, v in ipairs(modifiers) do
        local m = modmap[v]
        if m then
            capi.root.fake_input("key_release", m)
        end
    end

    -- Restore the previous modifiers all modifiers. Please note that yes,
    -- there is a race condition if the user was fast enough to release the
    -- key during this operation.
    for _, m in ipairs(active) do
        capi.root.fake_input("key_press", modmap[m])
    end
end

--- Add an `awful.key` based keybinding to the global set.
--
-- A **global** keybinding is one which is always present, even when there is
-- no focused client. If your intent is too add a keybinding which acts on
-- the focused client do **not** use this.
--
-- @staticfct awful.keyboard.append_global_keybinding
-- @tparam awful.key key The key object.
-- @noreturn
-- @see awful.key
-- @see awful.keyboard.append_global_keybindings
-- @see awful.keyboard.remove_global_keybinding

function module.append_global_keybinding(key)
    capi.root._append_key(key)
end

local function append_keybindings(keys, cb)
    local g = keys.group
    keys.group = nil

    -- Avoid the boilerplate. If the user is adding multiple keys at once, then
    -- they are probably related.
    if g then
        for _, k in ipairs(keys) do
            k.group = g
        end
    end

    cb(keys)

    keys.group = g
end

--- Add multiple `awful.key` based keybindings to the global set.
--
-- A **global** keybinding is one which is always present, even when there is
-- no focused client. If your intent is too add a keybinding which acts on
-- the focused client do **not** use this
--
-- @tparam table keys A table of `awful.key` objects.
-- @tparam[opt] string keys.group If set, the `group` property will be set on all `awful.keys`objects.
-- @noreturn
-- @see awful.key
-- @see awful.keyboard.append_global_keybinding
-- @see awful.keyboard.remove_global_keybinding

function module.append_global_keybindings(keys)
    append_keybindings(keys, function(ks)
        capi.root._append_keys(ks)
    end)
end

--- Remove a keybinding from the global set.
--
-- @staticfct awful.keyboard.remove_global_keybinding
-- @tparam awful.key key The key object.
-- @noreturn
-- @see awful.key
-- @see awful.keyboard.append_global_keybinding

function module.remove_global_keybinding(key)
    capi.root._remove_key(key)
end


local default_keys = {}

--- Add an `awful.key` to the default client keys.
--
-- @staticfct awful.keyboard.append_client_keybinding
-- @tparam awful.key key The key.
-- @noreturn
-- @emits client_keybinding::added
-- @emitstparam client_keybinding::added awful.key key The key.
-- @see awful.key
-- @see awful.keyboard.append_client_keybindings

function module.append_client_keybinding(key)
    table.insert(default_keys, key)

    for _, c in ipairs(capi.client.get(nil, false)) do
        c:append_keybinding(key)
    end

    capi.client.emit_signal("client_keybinding::added", key)
end

--- Add a `awful.key`s to the default client keys.
--
-- @staticfct awful.keyboard.append_client_keybindings
-- @tparam table keys A table of `awful.key` objects.
-- @tparam[opt] string keys.group If set, the `group` property will be set on all `awful.keys`objects.
-- @noreturn
-- @emits client_keybinding::added
-- @emitstparam client_keybinding::added awful.key key The key.
-- @see awful.key
-- @see awful.keyboard.append_client_keybinding
function module.append_client_keybindings(keys)
    append_keybindings(keys, function(ks)
        for _, key in ipairs(ks) do
            module.append_client_keybinding(key)
        end
    end)
end

--- Remove a key from the default client keys.
--
-- @staticfct awful.keyboard.remove_client_keybinding
-- @tparam awful.key key The key.
-- @treturn boolean True if the key was removed and false if it wasn't found.
-- @see awful.keyboard.append_client_keybinding

function module.remove_client_keybinding(key)
    for k, v in ipairs(default_keys) do
        if key == v then
            table.remove(default_keys, k)

            for _, c in ipairs(capi.client.get(nil, false)) do
                c:remove_keybinding(key)
            end

            return true
        end
    end

    return false
end

--- Get X11 keysym and a one-character representation from an Awesome keycode.
--
-- A "one-character representation" is a single UTF-8 representing the typical
-- output from that keysym in a text editor (e.g. " " for space, "ñ" for
-- n_tilde, "Ā" for A_macron). It usually matches the main engraving of the key
-- for level-0 symbols (but lowercase).
--
-- Keycodes may be given in a string in any valid format for `awful.key`:
-- "#" + keycode, the symkey name and the UTF-8 representation will all work.
--
-- If no suitable keysym is found, or a malformed keycode is given as an
-- argument, this function will return (nil, nil)
--
-- @treturn[1] string keysym The keysym name
-- @treturn[1] nil keysym If no valid keysym is found
-- @treturn[2] string printsymbol The xkb_keysym_to_utf8 result
-- @treturn[2] nil printsymbol If the keysym has no printable representation.
-- @staticfct awful.keyboard.get_key_name
function module.get_key_name(key)
    return capi.awesome._get_key_name(key)
end


capi.client.connect_signal("scanning", function()
    capi.client.emit_signal("request::default_keybindings", "context")
end)

-- Private function to be used by `ruled.client`.
function module._get_client_keybindings()
    return default_keys
end

return module
