--[[

This module currently works by adding a new property to each client that is tabbed.
That new property is called bling_tabbed. 
So each client in a tabbed state has the property "bling_tabbed" which is a table.
Each client that is not tabbed doesn't have that property.
In the function themselves, the same object is refered to as "tabobj" which is why
you will often see something like: "local tabobj = some_client.bling_tabbed" at the beginning
of a function.

--]]

local awful = require("awful")
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
local helpers = require(tostring(...):match(".*bling") .. ".helpers")

local bar_style = beautiful.tabbar_style or "default"
local bar = require(
    tostring(...):match(".*bling") .. ".widget.tabbar." .. bar_style
)

tabbed = {}

-- helper function to connect to the (un)focus signals
local function update_tabbar_from(c)
    if not c or not c.bling_tabbed then
        return
    end
    tabbed.update_tabbar(c.bling_tabbed)
end

-- used to change focused tab relative to the currently focused one
tabbed.iter = function(idx)
    if not idx then
        idx = 1
    end
    if not client.focus or not client.focus.bling_tabbed then
        return
    end
    local tabobj = client.focus.bling_tabbed
    local new_idx = (tabobj.focused_idx + idx) % #tabobj.clients
    if new_idx == 0 then
        new_idx = #tabobj.clients
    end
    tabbed.switch_to(tabobj, new_idx)
end

-- removes a given client from its tab object
tabbed.remove = function(c)
    if not c or not c.bling_tabbed then
        return
    end
    local tabobj = c.bling_tabbed
    table.remove(tabobj.clients, tabobj.focused_idx)
    if not beautiful.tabbar_disable then
        awful.titlebar.hide(c, bar.position)
    end
    c.bling_tabbed = nil
    c:disconnect_signal("focus", update_tabbar_from)
    c:disconnect_signal("unfocus", update_tabbar_from)
    awesome.emit_signal("bling::tabbed::client_removed", tabobj, c)
    tabbed.switch_to(tabobj, 1)
end

-- removes the currently focused client from the tab object
tabbed.pop = function()
    if not client.focus or not client.focus.bling_tabbed then
        return
    end
    tabbed.remove(client.focus)
end

-- adds a client to a given tabobj
tabbed.add = function(c, tabobj)
    if c.bling_tabbed then
        tabbed.remove(c)
    end
    c:connect_signal("focus", update_tabbar_from)
    c:connect_signal("unfocus", update_tabbar_from)
    helpers.client.sync(c, tabobj.clients[tabobj.focused_idx])
    tabobj.clients[#tabobj.clients + 1] = c
    tabobj.focused_idx = #tabobj.clients
    -- calls update even though switch_to calls update again
    -- but the new client needs to have the tabobj property
    -- before a clean switch can happen
    tabbed.update(tabobj)
    awesome.emit_signal("bling::tabbed::client_added", tabobj, c)
    tabbed.switch_to(tabobj, #tabobj.clients)
end

-- use xwininfo to select one client and make it tab in the currently focused tab
tabbed.pick = function()
    if not client.focus then
        return
    end
    -- this function uses xwininfo to grab a client window id which is then
    -- compared to all other clients window ids

    local xwininfo_cmd =
        [[ xwininfo | grep 'xwininfo: Window id:' | cut -d " " -f 4 ]]
    awful.spawn.easy_async_with_shell(xwininfo_cmd, function(output)
        for _, c in ipairs(client.get()) do
            if tonumber(c.window) == tonumber(output) then
                if not client.focus.bling_tabbed and not c.bling_tabbed then
                    tabbed.init(client.focus)
                    tabbed.add(c, client.focus.bling_tabbed)
                end
                if not client.focus.bling_tabbed and c.bling_tabbed then
                    tabbed.add(client.focus, c.bling_tabbed)
                end
                if client.focus.bling_tabbed and not c.bling_tabbed then
                    tabbed.add(c, client.focus.bling_tabbed)
                end
                -- TODO: Should also merge tabs when focus and picked
                -- both are tab groups
            end
        end
    end)
end

-- select a client by direction and make it tab in the currently focused tab
tabbed.pick_by_direction = function(direction)
    local sel = client.focus
    if not sel then
        return
    end
    if not sel.bling_tabbed then
        tabbed.init(sel)
    end
    local c = helpers.client.get_by_direction(direction)
    if not c then
        return
    end
    tabbed.add(c, sel.bling_tabbed)
end

-- use dmenu to select a client and make it tab in the currently focused tab
tabbed.pick_with_dmenu = function(dmenu_command)
    if not client.focus then
        return
    end

    if not dmenu_command then
        dmenu_command = "rofi -dmenu -i"
    end

    -- get all clients from the current tag
    -- ignores the case where multiple tags are selected
    local t = awful.screen.focused().selected_tag
    local list_clients = {}
    local list_clients_string = ""
    for idx, c in ipairs(t:clients()) do
        if c.window ~= client.focus.window then
            list_clients[#list_clients + 1] = c
            if #list_clients ~= 1 then
                list_clients_string = list_clients_string .. "\\n"
            end
            list_clients_string = list_clients_string
                .. tostring(c.window)
                .. " "
                .. c.name
        end
    end

    if #list_clients == 0 then
        return
    end
    -- calls the actual dmenu
    local xprop_cmd = [[ echo -e "]]
        .. list_clients_string
        .. [[" | ]]
        .. dmenu_command
        .. [[ | awk '{ print $1 }' ]]
    awful.spawn.easy_async_with_shell(xprop_cmd, function(output)
        for _, c in ipairs(list_clients) do
            if tonumber(c.window) == tonumber(output) then
                if not client.focus.bling_tabbed then
                    tabbed.init(client.focus)
                end
                local tabobj = client.focus.bling_tabbed
                tabbed.add(c, tabobj)
            end
        end
    end)
end

-- update everything about one tab object
tabbed.update = function(tabobj)
    local currently_focused_c = tabobj.clients[tabobj.focused_idx]
    -- update tabobj of each client and other things
    for idx, c in ipairs(tabobj.clients) do
        if c.valid then
            c.bling_tabbed = tabobj
            helpers.client.sync(c, currently_focused_c)
            -- the following handles killing a client while the client is tabbed
            c:connect_signal("unmanage", function(c)
                tabbed.remove(c)
            end)
        end
    end

    -- Maybe remove if I'm the only one using it?
    awesome.emit_signal("bling::tabbed::update", tabobj)
    if not beautiful.tabbar_disable then
        tabbed.update_tabbar(tabobj)
    end
end

-- change focused tab by absolute index
tabbed.switch_to = function(tabobj, new_idx)
    local old_focused_c = tabobj.clients[tabobj.focused_idx]
    tabobj.focused_idx = new_idx
    for idx, c in ipairs(tabobj.clients) do
        if idx ~= new_idx then
            helpers.client.turn_off(c)
        else
            helpers.client.turn_on(c)
            c:raise()
            if old_focused_c and old_focused_c.valid then
                c:swap(old_focused_c)
            end
            helpers.client.sync(c, old_focused_c)
        end
    end
    awesome.emit_signal("bling::tabbed::changed_focus", tabobj)
    tabbed.update(tabobj)
end

tabbed.update_tabbar = function(tabobj)
    local flexlist = bar.layout()
    local tabobj_focused_client = tabobj.clients[tabobj.focused_idx]
    local tabobj_is_focused = (client.focus == tabobj_focused_client)
    -- itearte over all tabbed clients to create the widget tabbed list
    for idx, c in ipairs(tabobj.clients) do
        local buttons = gears.table.join(awful.button({}, 1, function()
            tabbed.switch_to(tabobj, idx)
        end))
        local wid_temp = bar.create(c, (idx == tabobj.focused_idx), buttons,
            not tabobj_is_focused)
        flexlist:add(wid_temp)
    end
    -- add tabbar to each tabbed client (clients will be hided anyway)
    if not beautiful.tabbar_disable then
        for _, c in ipairs(tabobj.clients) do
            local titlebar = awful.titlebar(c, {
                bg = bar.bg_normal,
                size = bar.size,
                position = bar.position,
            })
            titlebar:setup({ layout = wibox.layout.flex.horizontal, flexlist })
        end
    end
end

tabbed.init = function(c)
    local tabobj = {}
    tabobj.clients = { c }
    c:connect_signal("focus", update_tabbar_from)
    c:connect_signal("unfocus", update_tabbar_from)
    tabobj.focused_idx = 1
    tabbed.update(tabobj)
end

if beautiful.tabbed_spawn_in_tab then
    client.connect_signal("manage", function(c)
        local s = awful.screen.focused()
        local previous_client = awful.client.focus.history.get(s, 1)
        if previous_client and previous_client.bling_tabbed then
            tabbed.add(c, previous_client.bling_tabbed)
        end
    end)
end

return tabbed
