---------------------------------------------------------------------------
-- @author Julien Danjou &lt;julien@danjou.info&gt;
-- @copyright 2008 Julien Danjou
-- @release v3.4.6
---------------------------------------------------------------------------

-- Grab environment we need
local util = require("awful.util")
local tostring = tostring
local pairs = pairs
local ipairs = ipairs
local table = table
local setmetatable = setmetatable
local capi =
{
    tag = tag,
    screen = screen,
    mouse = mouse,
    client = client
}

--- Useful functions for tag manipulation.
module("awful.tag")

-- Private data
local data = {}
data.history = {}
data.tags = setmetatable({}, { __mode = 'k' })

-- History functions
history = {}
history.limit = 20

--- Move a tag to an absolute position in the screen[]:tags() table.
-- @param new_index Integer absolute position in the table to insert.
function move(new_index, target_tag)
    local target_tag = target_tag or selected()
    local scr = target_tag.screen
    local tmp_tags = capi.screen[scr]:tags()

    if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
        return
    end

    for i, t in ipairs(tmp_tags) do
        if t == target_tag then
            table.remove(tmp_tags, i)
            break
        end
    end

    table.insert(tmp_tags, new_index, target_tag)
    capi.screen[scr]:tags(tmp_tags)
end

--- Add a tag.
-- @param name The tag name, a string
-- @param props The tags properties, a table
-- @return The created tag or nil if not created.
-- If the name is a valid string then the tag will be created and each key,value
-- pair in 'props' table is iterated and applied to the tag. If the 'name'
-- argument is nil or its string length is zero then the function returns nil.
function add(name, props)
    -- abort if the name is not provided
    if not name or #(tostring(name)) == 0 then return end

    local properties = props or {}
    local newtag = capi.tag{name = name}
    newtag.screen = properties.screen or capi.mouse.screen

    for k, v in pairs(properties) do
        setproperty(newtag, k, v)
    end

    return newtag
end

--- Create a set of tags and attach it to a screen.
-- @param names The tag name, in a table
-- @param screen The tag screen, or 1 if not set.
-- @param layout The layout or layout table to set for this tags by default.
-- @return A table with all created tags.
function new(names, screen, layout)
    local screen = screen or 1
    local tags = {}
    for id, name in ipairs(names) do
        table.insert(tags, id, add(name, {screen = screen,
                                            layout = (layout and layout[id]) or
                                                        layout}))
        -- Select the first tag.
        if id == 1 then
            tags[id].selected = true
        end
    end

    return tags
end

--- Update the tag history.
-- @param obj Screen object.
function history.update(obj)
    local s = obj.index
    local curtags = capi.screen[s]:tags()
    -- create history table
    if not data.history[s] then
        data.history[s] = {}
    -- limit history
    elseif #data.history[s] >= history.limit then
        for i = history.limit, #data.history[s] do
            data.history[s][i] = nil
        end
    end
    -- store previously selected tags in the history table
    table.insert(data.history[s], 1, data.history[s].current)
    data.history[s].previous = data.history[s][1]
    -- store currently selected tags
    data.history[s].current = setmetatable(selectedlist(s), { __mode = 'v' })
end

--- Revert tag history.
-- @param screen The screen number.
-- @param idx Index in history. Defaults to "previous" which is a special index
-- toggling between last two selected sets of tags. Number (eg 1) will go back
-- to the given index in history.
function history.restore(screen, idx)
    local s = screen or capi.mouse.screen
    local i = idx or "previous"
    local sel = selectedlist(s)
    -- do nothing if history empty
    if not data.history[s] or not data.history[s][i] then return end
    -- if all tags been deleted, try next entry
    if #data.history[s][i] == 0 then
        if i == "previous" then i = 0 end
        history.restore(s, i + 1)
        return
    end
    -- deselect all tags
    viewnone(s)
    -- select tags from the history entry
    for _, t in ipairs(data.history[s][i]) do
        t.selected = true
    end
    -- update currently selected tags table
    data.history[s].current = data.history[s][i]
    -- store previously selected tags
    data.history[s].previous = setmetatable(sel, { __mode = 'v' })
    -- remove the reverted history entry
    if i ~= "previous" then table.remove(data.history[s], i) end
end

--- Return a table with all visible tags
-- @param s Screen number.
-- @return A table with all selected tags.
function selectedlist(s)
    local screen = s or capi.mouse.screen
    local tags = capi.screen[screen]:tags()
    local vtags = {}
    for i, t in pairs(tags) do
        if t.selected then
            vtags[#vtags + 1] = t
        end
    end
    return vtags
end

--- Return only the first visible tag.
-- @param s Screen number.
function selected(s)
    return selectedlist(s)[1]
end

--- Set master width factor.
-- @param mwfact Master width factor.
function setmwfact(mwfact, t)
    local t = t or selected()
    if mwfact >= 0 and mwfact <= 1 then
        setproperty(t, "mwfact", mwfact)
    end
end

--- Increase master width factor.
-- @param add Value to add to master width factor.
function incmwfact(add, t)
    setmwfact(getmwfact(t) + add)
end

--- Get master width factor.
-- @param t Optional tag.
function getmwfact(t)
    local t = t or selected()
    return getproperty(t, "mwfact") or 0.5
end

--- Set the number of master windows.
-- @param nmaster The number of master windows.
-- @param t Optional tag.
function setnmaster(nmaster, t)
    local t = t or selected()
    if nmaster >= 0 then
        setproperty(t, "nmaster", nmaster)
    end
end

--- Get the number of master windows.
-- @param t Optional tag.
function getnmaster(t)
    local t = t or selected()
    return getproperty(t, "nmaster") or 1
end

--- Increase the number of master windows.
-- @param add Value to add to number of master windows.
function incnmaster(add, t)
    setnmaster(getnmaster(t) + add)
end


--- Set the tag icon
-- @param icon the icon to set, either path or image object
-- @param tag the tag
function seticon(icon, tag)
    local tag = tag or selected()
    setproperty(tag, "icon", icon)
end

--- Get the tag icon
-- @param t the tag
function geticon(tag)
    local tag = tag or selected()
    return getproperty(tag, "icon")
end

--- Set number of column windows.
-- @param ncol The number of column.
function setncol(ncol, t)
    local t = t or selected()
    if ncol >= 1 then
        setproperty(t, "ncol", ncol)
    end
end

--- Get number of column windows.
-- @param t Optional tag.
function getncol(t)
    local t = t or selected()
    return getproperty(t, "ncol") or 1
end

--- Increase number of column windows.
-- @param add Value to add to number of column windows.
function incncol(add, t)
    setncol(getncol(t) + add)
end

--- View no tag.
-- @param Optional screen number.
function viewnone(screen)
    local tags = capi.screen[screen or capi.mouse.screen]:tags()
    for i, t in pairs(tags) do
        t.selected = false
    end
end

--- View a tag by its taglist index.
-- @param i The relative index to see.
-- @param screen Optional screen number.
function viewidx(i, screen)
    local screen = screen and screen.index or capi.mouse.screen
    local tags = capi.screen[screen]:tags()
    local showntags = {}
    for k, t in ipairs(tags) do
        if not getproperty(t, "hide") then
            table.insert(showntags, t)
        end
    end
    local sel = selected(screen)
    viewnone(screen)
    for k, t in ipairs(showntags) do
        if t == sel then
            showntags[util.cycle(#showntags, k + i)].selected = true
        end
    end
    capi.screen[screen]:emit_signal("tag::history::update")
end

--- Get a tag's index in the screen[]:tags() table.
-- @param query_tag The tag object to find. [selected()]
-- @return The index of the tag, nil if the tag is not found.
function getidx(query_tag)
    local query_tag = query_tag or selected()
    if query_tag == nil then return end

    for i, t in ipairs(capi.screen[query_tag.screen]:tags()) do
        if t == query_tag then
            return i
        end
    end
end

--- View next tag. This is the same as tag.viewidx(1).
-- @param screen The screen number.
function viewnext(screen)
    return viewidx(1, screen)
end

--- View previous tag. This is the same a tag.viewidx(-1).
-- @param screen The screen number.
function viewprev(screen)
    return viewidx(-1, screen)
end

--- View only a tag.
-- @param t The tag object.
function viewonly(t)
    local tags = capi.screen[t.screen]:tags()
    -- First, untag everyone except the viewed tag.
    for _, tag in pairs(tags) do
        if tag ~= t then
            tag.selected = false
        end
    end
    -- Then, set this one to selected.
    -- We need to do that in 2 operations so we avoid flickering and several tag
    -- selected at the same time.
    t.selected = true
    capi.screen[t.screen]:emit_signal("tag::history::update")
end

--- View only a set of tags.
-- @param tags A table with tags to view only.
-- @param screen Optional screen number of the tags.
function viewmore(tags, screen)
    local screen_tags = capi.screen[screen or capi.mouse.screen]:tags()
    for _, tag in ipairs(screen_tags) do
        if not util.table.hasitem(tags, tag) then
            tag.selected = false
        end
    end
    for _, tag in ipairs(tags) do
        tag.selected = true
    end
    capi.screen[screen]:emit_signal("tag::history::update")
end

--- Toggle selection of a tag
-- @param tag Tag to be toggled
function viewtoggle(t)
    t.selected = not t.selected
    capi.screen[t.screen]:emit_signal("tag::history::update")
end

--- Get tag data table.
-- @param tag The Tag.
-- @return The data table.
function getdata(tag)
    return data.tags[tag]
end

--- Get a tag property.
-- @param tag The tag.
-- @param prop The property name.
-- @return The property.
function getproperty(tag, prop)
    if data.tags[tag] then
        return data.tags[tag][prop]
    end
end

--- Set a tag property.
-- This properties are internal to awful. Some are used to draw taglist, or to
-- handle layout, etc.
-- @param tag The tag.
-- @param prop The property name.
-- @param value The value.
function setproperty(tag, prop, value)
    if not data.tags[tag] then
        data.tags[tag] = {}
    end
    data.tags[tag][prop] = value
    tag:emit_signal("property::" .. prop)
end

--- Tag a client with the set of current tags.
-- @param c The client to tag.
-- @param startup Optional: don't do anything if true.
function withcurrent(c, startup)
    if startup ~= true and c.sticky == false then
        if #c:tags() == 0 then
            c:tags(selectedlist(c.screen))
        end
    end
end

local function attached_add_signal_screen(screen, sig, func)
    capi.screen[screen]:add_signal("tag::attach", function (s, tag)
        tag:add_signal(sig, func)
    end)
    capi.screen[screen]:add_signal("tag::detach", function (s, tag)
        tag:remove_signal(sig, func)
    end)
    for _, tag in ipairs(capi.screen[screen]:tags()) do
        tag:add_signal(sig, func)
    end
end

--- Add a signal to all attached tag and all tag that will be attached in the
-- future. When a tag is detach from the screen, its signal is removed.
-- @param screen The screen concerned, or all if nil.
function attached_add_signal(screen, ...)
    if screen then
        attached_add_signal_screen(screen, ...)
    else
        for screen = 1, capi.screen.count() do
            attached_add_signal_screen(screen, ...)
        end
    end
end

-- Register standards signals
capi.client.add_signal("manage", function(c, startup)
    -- If we are not managing this application at startup,
    -- move it to the screen where the mouse is.
    -- We only do it for "normal" windows (i.e. no dock, etc).
    if not startup
        and c.type ~= "desktop"
        and c.type ~= "dock"
        and c.type ~= "splash" then
        if c.transient_for then
            c.screen = c.transient_for.screen
            if not c.sticky then
                c:tags(c.transient_for:tags())
            end
        else
            c.screen = capi.mouse.screen
        end
    end
    c:add_signal("property::screen", withcurrent)
end)

capi.client.add_signal("manage", withcurrent)

for s = 1, capi.screen.count() do
    capi.screen[s]:add_signal("tag::history::update", history.update)
end

setmetatable(_M, { __call = function (_, ...) return new(...) end })

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