local awful = require("awful")
local gears = require("gears")
local naughty = require("naughty")
local beautiful = require("beautiful")
local volume_widget = require("components.widgets.volume-widget.volume")
local dpi = beautiful.xresources.apply_dpi

local modkey = "Mod4"

-- define module table
local keys = {}

-- AwesomeWM Vim Tmux Navigator

local function get_first_nonempty_tag()
    local screen = awful.screen.focused()
    local tags = screen.tags
    for _, t in ipairs(tags) do
        if #t:clients() == 0 then
            return t
        end
    end
end

local focus_bydirection = function(direction)
    awful.client.focus.global_bydirection(direction)
    if client.focus then
        -- focus on the client
        client.focus:raise()
    end

    -- BUG: focus across screens is wonky when there are no clients on the destination screen
    -- https://github.com/awesomeWM/awesome/issues/3638
    -- Workaround: manually unfocus client after moving focus to an empty screen
    local is_empty_destination = #awful.screen.focused().clients < 1

    if is_empty_destination then
        -- manually unfocus the current focused client
        client.focus = nil
    end
end

require("awesomewm-vim-tmux-navigator") {
    up = {"Up", "k"},
    down = {"Down", "j"},
    left = {"Left", "h"},
    right = {"Right", "l"},
    mod = "Mod4",
    mod_keysym = "Super_L",
    experimental = true,
    focus = focus_bydirection
}


-- ===================================================================
-- Movement Functions (Called by some keybinds)
-- ===================================================================
local function get_screen(s)
    return s and screen[s]
end

-- Move given client to given direction
local function move_client(sel, dir)
    sel = sel or awful.client.focus
    if sel then
        -- move focus
        awful.client.focus.global_bydirection(dir, sel)
        local c = client.focus

        -- swapping inside a screen
        if get_screen(sel.screen) == get_screen(c.screen) and sel ~= c then
            c:swap(sel)
            client.focus = sel
            sel:raise()

            -- swapping to an empty screen
        elseif sel == c then
            sel:move_to_screen(awful.screen.focused())

            -- swapping to a nonempty screen
        elseif get_screen(sel.screen) ~= get_screen(c.screen) and sel ~= c then
            sel:move_to_screen(c.screen)
            client.focus = sel
            sel:raise()
        end
    end
end


-- Resize client in given direction
local floating_resize_amount = dpi(20)
local tiling_resize_factor = 0.01

local function resize_client(c, direction)
    if awful.layout.get(mouse.screen) == awful.layout.suit.floating or (c and c.floating) then
        if direction == "up" then
            c:relative_move(0, 0, 0, -floating_resize_amount)
        elseif direction == "down" then
            c:relative_move(0, 0, 0, floating_resize_amount)
        elseif direction == "left" then
            c:relative_move(0, 0, -floating_resize_amount, 0)
        elseif direction == "right" then
            c:relative_move(0, 0, floating_resize_amount, 0)
        end
    else
        if direction == "up" then
            awful.client.incwfact(-tiling_resize_factor)
        elseif direction == "down" then
            awful.client.incwfact(tiling_resize_factor)
        elseif direction == "left" then
            awful.tag.incmwfact(-tiling_resize_factor)
        elseif direction == "right" then
            awful.tag.incmwfact(tiling_resize_factor)
        end
    end
end


-- ===================================================================
-- Mouse bindings
-- ===================================================================


-- Mouse buttons on the desktop
keys.desktopbuttons = gears.table.join(
    -- left click on desktop to hide notification
    awful.button({}, 1,
        function ()
            naughty.destroy_all_notifications()
        end
    ),
    awful.button({}, 7, function() volume_widget:inc(5) end),
    awful.button({}, 6, function() volume_widget:dec(5) end)
)

-- Mouse buttons on the client
keys.clientbuttons = gears.table.join(
    -- Raise client
    awful.button({}, 1,
        function(c)
            client.focus = c
            c:raise()
        end
    ),

    -- Move and Resize Client
    awful.button({modkey}, 1, awful.mouse.client.move),
    awful.button({modkey}, 3, awful.mouse.client.resize),
    awful.button({}, 7, function() volume_widget:inc(5) end),
    awful.button({}, 6, function() volume_widget:dec(5) end)
)


-- ===================================================================
-- Desktop Key bindings
-- ===================================================================


keys.globalkeys = gears.table.join(
    -- =========================================
    -- SPAWN APPLICATION KEY BINDINGS
    -- =========================================

    -- Spawn terminal
    awful.key({modkey}, "Return",
        function()
            local c = client.focus
            if c and c.class == "Alacritty" then
                awful.spawn(string.format(os.getenv("XDG_CONFIG_HOME") .. "/scripts/launch_alacritty.sh %d", c.pid))
            else
                awful.spawn('bash -c "WINIT_X11_SCALE_FACTOR=1 alacritty"')
            end
        end,
        {description = "open a terminal", group = "launcher"}
    ),
    -- Spawn terminal
    awful.key({modkey, "Shift"}, "Return",
        function()
            awful.spawn('bash -c "WINIT_X11_SCALE_FACTOR=1 alacritty"')
        end,
        {description = "open a terminal", group = "launcher"}
    ),
    -- Spawn browser
    awful.key({modkey}, "b",
        function()
            awful.spawn("firefox")
        end,
        {description = "Open Firefox", group = "launcher"}
    ),
    -- Lock the screen
    awful.key({"Control", "Mod1"}, "l",
        function()
            awful.spawn('bash -c "i3lockr --blur 25"')
        end,
        {description = "Open lock", group = "launcher"}
    ),
    -- Generate TOTP
    awful.key({modkey}, "y",
        function()
            awful.spawn.with_shell(os.getenv("XDG_CONFIG_HOME") .. "/rofi/rofi-ykman.sh")
        end,
        {description = "Generate TOTP", group = "launcher"}
    ),
    -- Spawn file manager
    awful.key({modkey}, "a",
        function()
            awful.spawn("nautilus")
        end,
        {description = "Open Nautilus", group = "launcher"}
    ),
    -- launch rofi
    awful.key({modkey}, "XF86Launch5",
        function()
            awful.spawn(os.getenv("XDG_CONFIG_HOME") .. "/scripts/toggle_rofi.sh")
        end,
        {description = "application launcher", group = "launcher"}
    ),
    -- launch rofi
    awful.key({modkey}, "d",
        function()
            awful.spawn(os.getenv("XDG_CONFIG_HOME") .. "/scripts/toggle_rofi.sh")
        end,
        {description = "application launcher", group = "launcher"}
    ),
    -- launch clipman
    awful.key({modkey}, "c",
        function()
            awful.spawn.with_shell('CM_LAUNCHER=rofi-script rofi -modi "clipmenu:/usr/bin/clipmenu" -show clipmenu')
        end,
        {description = "Clipboard manager", group = "launcher"}
    ),
    -- Go to sleep
    awful.key({"Control", "Mod2", "Mod4"}, "q",
        function()
            awful.spawn.with_shell('systemctl suspend')
        end,
        {description = "Clipboard manager", group = "launcher"}
    ),

    -- Quake
    awful.key({ }, "F9", function () awful.screen.focused().quake:toggle() end),

    -- =========================================
    -- FUNCTION KEYS
    -- =========================================

    -- Brightness
    awful.key({}, "XF86MonBrightnessUp",
        function()
            awful.spawn("xbacklight -inc 10", false)
        end,
        {description = "+10%", group = "hotkeys"}
    ),
    awful.key({}, "XF86MonBrightnessDown",
        function()
            awful.spawn("xbacklight -dec 10", false)
        end,
        {description = "-10%", group = "hotkeys"}
    ),

    -- ALSA volume control
    awful.key({}, "XF86AudioRaiseVolume",
        function()
            volume_widget:inc(5)
        end,
        {description = "volume up", group = "hotkeys"}
    ),
    awful.key({}, "XF86AudioLowerVolume",
        function()
            volume_widget:dec(5)
        end,
        {description = "volume down", group = "hotkeys"}
    ),
    awful.key({}, "XF86AudioMute",
        function()
            volume_widget:toggle()
        end,
        {description = "toggle mute", group = "hotkeys"}
    ),
    awful.key({}, "XF86AudioNext",
        function()
            awful.spawn("playerctl next", false)
        end,
        {description = "next music", group = "hotkeys"}
    ),
    awful.key({}, "XF86AudioPrev",
        function()
            awful.spawn("playerctl previous", false)
        end,
        {description = "previous music", group = "hotkeys"}
    ),
    awful.key({}, "XF86AudioPlay",
        function()
            awful.spawn("playerctl play-pause", false)
        end,
        {description = "play/pause music", group = "hotkeys"}
    ),
    awful.key({modkey}, "XF86AudioMute",
        function()
            awful.spawn("playerctl play-pause", false)
        end,
        {description = "play/pause music", group = "hotkeys"}
    ),
    awful.key({modkey}, "XF86AudioRaiseVolume",
        function()
            awful.spawn("playerctl next", false)
        end,
        {description = "volume up", group = "hotkeys"}
    ),
    awful.key({modkey}, "XF86AudioLowerVolume",
        function()
            awful.spawn("playerctl previous", false)
        end,
        {description = "volume down", group = "hotkeys"}
    ),

    -- Screenshot on prtscn using scrot
    awful.key({}, "Print",
        function()
            awful.spawn("scrot -e 'mv $f ~/Pictures/Screenshots/ 2>/dev/null'", false)
        end
    ),

    -- =========================================
    -- RELOAD / QUIT AWESOME
    -- =========================================

    -- Reload Awesome
    awful.key({modkey, "Shift"}, "r",
        awesome.restart,
        {description = "reload awesome", group = "awesome"}
    ),

    -- Quit Awesome
    awful.key({modkey}, "Escape",
        function()
            -- emit signal to show the exit screen
            awful.spawn("rofi -show power-menu -modi power-menu:" .. os.getenv("XDG_CONFIG_HOME") .. "/scripts/rofi-power-menu")
        end,
        {description = "toggle exit screen", group = "hotkeys"}
    ),

    awful.key({}, "XF86PowerOff",
        function()
            -- emit signal to show the exit screen
            awful.spawn("rofi -show power-menu -modi power-menu:~/.config/scripts/rofi-power-menu")
        end,
        {description = "toggle exit screen", group = "hotkeys"}
    ),

    awful.key({}, "XF86Calculator",
        function()
            awful.spawn("rofi -show calc -modi calc -no-show-match -no-sort -calc-command \"echo -n '{result}' | xsel --clipboard\"")
        end,
        {description = "Launch the calculator", group = "hotkeys"}
    ),

    -- Focus client by index (cycle through clients)
    awful.key({modkey}, "Tab",
        function()
            local screen = awful.screen.focused()
            local tags = screen.tags
            local current_tag = screen.selected_tag
            local used_tags = {}
            for _, t in ipairs(tags) do
                if t == current_tag or #t:clients() > 0 then
                    table.insert(used_tags, t)
                end
            end
            local found = false
            for _, t in ipairs(used_tags) do
                if found then
                    t:view_only()
                    return
                elseif t == current_tag then
                    found = true
                end
            end
            used_tags[1]:view_only()
        end,
        {description = "Switch to next tag", group = "client"}
    ),
    awful.key({modkey, "Shift"}, "Tab",
        function()
            local tags = root.tags()
            local current_tag = awful.screen.focused().selected_tag
            local used_tags = {}
            for _, t in ipairs(tags) do
                if t == current_tag or #t:clients() > 0 then
                    table.insert(used_tags, t)
                end
            end
            local found = false
            for _, t in ipairs(used_tags) do
                if found then
                    awful.screen.focus(t.screen.index)
                    t:view_only()
                    return
                elseif t == current_tag then
                    found = true
                end
            end
            used_tags[1]:view_only()
        end,
        {description = "Switch to next tag", group = "client"}
    ),
    awful.key({"Mod1"}, "Tab",  -- Alt-Tab Cycle through clients
        function()
            local clients = {}
            local found = false
            local function focus_client(c)
                awful.screen.focus(c.screen.index)
                c.first_tag:view_only()
                client.focus = c
                c:raise()
            end
            for _, t in ipairs(root.tags()) do
                for i, c in ipairs(t:clients()) do
                    if found then
                        focus_client(c)
                        return
                    elseif c == client.focus then
                        found = true
                    end
                    table.insert(clients, c)
                end
            end
            focus_client(clients[1])
        end,
        {description = "Switch to next tag", group = "client"}
    ),
    awful.key({modkey}, "t",
        function()
            local first_empty = get_first_nonempty_tag()
            if first_empty ~= nil then
                first_empty:view_only()
            end
        end,
        {description = "Switch to next tag", group = "client"}
    ),

    awful.key({modkey, "Shift"}, "t",
        function()
            local first_empty = get_first_nonempty_tag()
            if first_empty ~= nil then
                client.focus:move_to_tag(first_empty)
                first_empty:view_only()
            end
        end,
        {description = "Switch to next tag", group = "client"}
    ),

    -- =========================================
    -- SCREEN FOCUSING
    -- =========================================

    -- Focus screen by index (cycle through screens)
    awful.key({modkey}, "s",
        function()
            awful.screen.focus_relative(-1)
        end
    ),

    -- =========================================
    -- CLIENT RESIZING
    -- =========================================

    awful.key({modkey, "Control"}, "Down",
        function(_)
            resize_client(client.focus, "down")
        end
    ),
    awful.key({modkey, "Control"}, "Up",
        function(_)
            resize_client(client.focus, "up")
        end
    ),
    awful.key({modkey, "Control"}, "Left",
        function(_)
            resize_client(client.focus, "left")
        end
    ),
    awful.key({modkey, "Control"}, "Right",
        function(_)
            resize_client(client.focus, "right")
        end
    ),
    awful.key({modkey, "Control"}, "j",
        function(_)
            resize_client(client.focus, "down")
        end
    ),
    awful.key({ modkey, "Control" }, "k",
        function(_)
            resize_client(client.focus, "up")
        end
    ),
    awful.key({modkey, "Control"}, "h",
        function(_)
            resize_client(client.focus, "left")
        end
    ),
    awful.key({modkey, "Control"}, "l",
        function(_)
            resize_client(client.focus, "right")
        end
    ),

    -- =========================================
    -- GAP CONTROL
    -- =========================================

    -- Gap control
    -- awful.key({modkey, :Shift"}, "minus",
    --     function()
    --         awful.tag.incgap(5, nil)
    --     end,
    --     {description = "increment gaps size for the current tag", group = "gaps"}
    -- ),
    -- awful.key({modkey}, "minus",
    --     function()
    --         awful.tag.incgap(-5, nil)
    --     end,
    --     {description = "decrement gap size for the current tag", group = "gaps"}
    -- ),

    awful.key({ modkey }, "=",
        function ()
            awful.tag.incncol( 1, nil, true)
        end,
        {description = "increase the number of columns", group = "layout"}
    ),

    awful.key({ modkey }, "minus",
        function ()
            awful.tag.incncol(-1, nil, true)
        end,
        {description = "decrease the number of columns", group = "layout"}
    ),

    -- =========================================
    -- LAYOUT SELECTION
    -- =========================================

    -- select next layout
    awful.key({modkey}, "space",
        function()
            awful.layout.inc(1)
        end,
        {description = "select next", group = "layout"}
    ),
    -- select previous layout
    awful.key({modkey, "Shift"}, "space",
        function()
            awful.layout.inc(-1)
        end,
        {description = "select previous", group = "layout"}
    ),

    -- =========================================
    -- CLIENT MINIMIZATION
    -- =========================================

    -- restore minimized client
    awful.key({modkey, "Shift"}, "n",
        function()
            local c = awful.client.restore()
            -- Focus restored client
            if c then
                client.focus = c
                c:raise()
            end
        end,
        {description = "restore minimized", group = "client"}
    )
)


-- ===================================================================
-- Client Key bindings
-- ===================================================================


keys.clientkeys = gears.table.join(
    -- Move to edge or swap by direction
    awful.key({modkey, "Shift"}, "Down",
        function(c)
            move_client(c, "down")
        end
    ),
    awful.key({modkey, "Shift"}, "Up",
        function(c)
            move_client(c, "up")
        end
    ),
    awful.key({modkey, "Shift"}, "Left",
        function(c)
            move_client(c, "left")
        end
    ),
    awful.key({modkey, "Shift"}, "Right",
        function(c)
            move_client(c, "right")
        end
    ),
    awful.key({modkey, "Shift"}, "j",
        function(c)
            move_client(c, "down")
        end
    ),
    awful.key({modkey, "Shift"}, "k",
        function(c)
            move_client(c, "up")
        end
    ),
    awful.key({modkey, "Shift"}, "h",
        function(c)
            move_client(c, "left")
        end
    ),
    awful.key({modkey, "Shift"}, "l",
        function(c)
            move_client(c, "right")
        end
    ),

    -- close client
    awful.key({modkey}, "q",
        function(c)
            c:kill()
            for _, i in ipairs(client.get()) do
                i.minimized = false
            end
        end,
        {description = "close", group = "client"}
    ),

    -- float client
    awful.key({modkey}, "f",
        function(c)
            awful.client.floating.toggle(c)
        end,
        {description = "float", group = "client"}
    ),

    -- Minimize
    awful.key({modkey}, "n",
        function(c)
            c.minimized = true
        end,
        {description = "minimize", group = "client"}
    ),

    -- Maximize
    awful.key({modkey}, "m",
        function(c)
            c.maximized = not c.maximized
            c:raise()
        end,
        {description = "(un)maximize", group = "client"}
    ),

    -- Full Screen
    awful.key({modkey}, "F11",
        function(c)
            c.fullscreen = not c.fullscreen
            c:raise()
        end,
        {description = "(un)fullscreen", group = "client"}
    )
)

local function register_tag(tag)
    keys.globalkeys = gears.table.join(keys.globalkeys,
        -- Switch to tag
        awful.key({modkey}, "#" .. tag + 9,
            function()
                local screen = awful.screen.focused()
                local t = screen.tags[tag]
                if t then
                    t:view_only()
                end
            end,
            {description = "view tag #"..tag, group = "tag"}
        ),
        -- Move client to tag
        awful.key({modkey, "Shift"}, "#" .. tag + 9,
            function()
                if client.focus then
                    local t= client.focus.screen.tags[tag]
                    if t then
                        client.focus:move_to_tag(t)
                        t:view_only()
                    end
                end
            end,
            {description = "move focused client to tag #"..tag, group = "tag"}
        )
    )
end

-- Bind all key numbers to tags
for i = 1, 10 do
    register_tag(i)
end

return keys
