---------------------------------------------------------------------------
--- Keep track of the focused clients.
--
-- @author Julien Danjou <julien@danjou.info>
-- @copyright 2008 Julien Danjou
-- @submodule client
---------------------------------------------------------------------------
local grect = require("gears.geometry").rectangle

local capi =
{
    screen = screen,
    client = client,
}

-- We use a metatable to prevent circular dependency loops.
local screen
do
    screen = setmetatable({}, {
        __index = function(_, k)
            screen = require("awful.screen")
            return screen[k]
        end,
        __newindex = error -- Just to be sure in case anything ever does this
    })
end

local client
do
    client = setmetatable({}, {
        __index = function(_, k)
            client = require("awful.client")
            return client[k]
        end,
        __newindex = error -- Just to be sure in case anything ever does this
    })
end

local focus = {history = {list = {}}}

local function get_screen(s)
    return s and capi.screen[s]
end

--- Remove a client from the focus history
--
-- @client c The client that must be removed.
-- @function awful.client.focus.history.delete
function focus.history.delete(c)
    for k, v in ipairs(focus.history.list) do
        if v == c then
            table.remove(focus.history.list, k)
            break
        end
    end
end

--- Focus a client by its relative index.
--
-- @function awful.client.focus.byidx
-- @param i The index.
-- @client[opt] c The client.
function focus.byidx(i, c)
    local target = client.next(i, c)
    if target then
        target:emit_signal("request::activate", "client.focus.byidx",
                           {raise=true})
    end
end

--- Filter out window that we do not want handled by focus.
-- This usually means that desktop, dock and splash windows are
-- not registered and cannot get focus.
--
-- @client c A client.
-- @return The same client if it's ok, nil otherwise.
-- @function awful.client.focus.filter
function focus.filter(c)
    if c.type == "desktop"
        or c.type == "dock"
        or c.type == "splash"
        or not c.focusable then
        return nil
    end
    return c
end

--- Update client focus history.
--
-- @client c The client that has been focused.
-- @function awful.client.focus.history.add
function focus.history.add(c)
    -- Remove the client if its in stack
    focus.history.delete(c)
    -- Record the client has latest focused
    table.insert(focus.history.list, 1, c)
end

--- Get the latest focused client for a screen in history.
--
-- @tparam int|screen s The screen to look for.
-- @tparam int idx The index: 0 will return first candidate,
--   1 will return second, etc.
-- @tparam function filter An optional filter.  If no client is found in the
--   first iteration, `awful.client.focus.filter` is used by default to get any
--   client.
-- @treturn client.object A client.
-- @function awful.client.focus.history.get
function focus.history.get(s, idx, filter)
    s = get_screen(s)
    -- When this counter is equal to idx, we return the client
    local counter = 0
    local vc = client.visible(s, true)
    for _, c in ipairs(focus.history.list) do
        if get_screen(c.screen) == s then
            if not filter or filter(c) then
                for _, vcc in ipairs(vc) do
                    if vcc == c then
                        if counter == idx then
                            return c
                        end
                        -- We found one, increment the counter only.
                        counter = counter + 1
                        break
                    end
                end
            end
        end
    end
    -- Argh nobody found in history, give the first one visible if there is one
    -- that passes the filter.
    filter = filter or focus.filter
    if counter == 0 then
        for _, v in ipairs(vc) do
            if filter(v) then
                return v
            end
        end
    end
end

--- Focus the previous client in history.
-- @function awful.client.focus.history.previous
function focus.history.previous()
    local sel = capi.client.focus
    local s = sel and sel.screen or screen.focused()
    local c = focus.history.get(s, 1)
    if c then
        c:emit_signal("request::activate", "client.focus.history.previous",
                      {raise=false})
    end
end

--- Focus a client by the given direction.
--
-- @tparam string dir The direction, can be either
--   `"up"`, `"down"`, `"left"` or `"right"`.
-- @client[opt] c The client.
-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
-- @function awful.client.focus.bydirection
function focus.bydirection(dir, c, stacked)
    local sel = c or capi.client.focus
    if sel then
        local cltbl = client.visible(sel.screen, stacked)
        local geomtbl = {}
        for i,cl in ipairs(cltbl) do
            geomtbl[i] = cl:geometry()
        end

        local target = grect.get_in_direction(dir, geomtbl, sel:geometry())

        -- If we found a client to focus, then do it.
        if target then
            cltbl[target]:emit_signal("request::activate",
                                      "client.focus.bydirection", {raise=false})
        end
    end
end

--- Focus a client by the given direction. Moves across screens.
--
-- @param dir The direction, can be either "up", "down", "left" or "right".
-- @client[opt] c The client.
-- @tparam[opt=false] boolean stacked Use stacking order? (top to bottom)
-- @function awful.client.focus.global_bydirection
function focus.global_bydirection(dir, c, stacked)
    local sel = c or capi.client.focus
    local scr = get_screen(sel and sel.screen or screen.focused())

    -- change focus inside the screen
    focus.bydirection(dir, sel)

    -- if focus not changed, we must change screen
    if sel == capi.client.focus then
        screen.focus_bydirection(dir, scr)
        if scr ~= get_screen(screen.focused()) then
            local cltbl = client.visible(screen.focused(), stacked)
            local geomtbl = {}
            for i,cl in ipairs(cltbl) do
                geomtbl[i] = cl:geometry()
            end
            local target = grect.get_in_direction(dir, geomtbl, scr.geometry)

            if target then
                cltbl[target]:emit_signal("request::activate",
                                          "client.focus.global_bydirection",
                                          {raise=false})
            end
        end
    end
end

return focus

-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80