"""
Infinidoge's Qtile Configuration

Sets up
- Keybindings
- Widgets
- Screens
"""
import os

from typing import List, Any  # noqa: F401

from libqtile import bar, layout, widget
from libqtile.config import (
    Click,
    Drag,
    DropDown,
    Group,
    Key,
    KeyChord,
    Match,
    ScratchPad,
    Screen,
)
from libqtile.lazy import lazy
from libqtile.utils import guess_terminal


LAPTOP = bool(os.getenv("LAPTOP", False))

colors = [
    # panel background
    ["#282c34", "#282c34"],  # 0
    # background for current screen tab
    ["#3d3f4b", "#434758"],  # 1
    # font color for group names
    ["#ffffff", "#ffffff"],  # 2
    # border line color for current tab
    ["#ff5555", "#ff5555"],  # 3
    # border line color for 'other tabs' and color for 'odd widgets'
    ["#74438f", "#74438f"],  # 4
    # color for the 'even widgets'
    ["#4f76c7", "#4f76c7"],  # 5
    # window name
    ["#e1acff", "#e1acff"],  # 6
    # background for inactive screens
    ["#ecbbfb", "#ecbbfb"],  # 7
]


class Apps:
    """
    Commonly referenced apps, such as the terminal or editor
    """

    TERMINAL = guess_terminal(preference="kitty")  # Set preference if necessary
    SHELL = "zsh"
    EDITOR = "nvim"

    @classmethod
    def terminal_command(cls, program, args=tuple(), *, terminal=None):
        """
        Returns a string of a command to open a program in the terminal, with the given arguments.
        """
        return f"{terminal or cls.TERMINAL} {' '.join(args)} -- {program}"

    @classmethod
    def shell_command(cls, command, args=tuple(), *, shell=None):
        """
        Returns a string of a command to run a command in the shell, with the given arguments.
        """
        return f'{shell or cls.SHELL} {" ".join(args)} -- "{command}"'

    @classmethod
    def open_in_terminal(cls, program, args=tuple(), *, terminal=None):
        """
        Opens a program in the terminal, with the given arguments.
        """
        return lazy.spawn(cls.terminal_command(program, args=args, terminal=terminal))

    @classmethod
    def shell_in_terminal(cls, command, sargs=tuple(), targs=tuple(), *, shell=None, terminal=None):
        """
        Opens a program in the terminal using the shell, with the given shell and terminal arguments.
        """
        return cls.open_in_terminal(cls.shell_command(command, sargs, shell=shell), targs, terminal=terminal)

    @classmethod
    def open_in_editor(cls, file_, args=tuple()):
        """
        Opens a file in the editor, with the given arguments
        """
        return cls.open_in_terminal(f"{cls.EDITOR} {' '.join(args)} {file_}")


class Keys:
    """
    Common modifier keys used in key configuration.
    """

    SUPER = "mod4"  # Windows key/super key
    CONTROL = "control"
    SHIFT = "shift"
    ALT = "mod1"
    ALT_GR = "mod5"
    META = None  # "mod3" # Not assigned to key yet
    CAPS_LOCK = "lock"
    NUM_LOCK = "mod2"


def run_command(*args, **kwargs):
    return os.popen(*args, **kwargs).read()


def optional_list(condition, lst):
    return lst if condition else []


# fmt: off
keys = [
    # Switch between windows
    Key(
        [Keys.SUPER], "Left",
        lazy.layout.left(),
        desc="Move focus to left",
    ),
    Key(
        [Keys.SUPER], "Right",
        lazy.layout.right(),
        desc="Move focus to right",
    ),
    Key(
        [Keys.SUPER], "Down",
        lazy.layout.down(),
        desc="Move focus down",
    ),
    Key(
        [Keys.SUPER], "Up",
        lazy.layout.up(),
        desc="Move focus up",
    ),
    Key(
        [Keys.SUPER], "space",
        lazy.layout.next(),
        desc="Move window focus to other window",
    ),

    # Switch focus of monitors
    Key(
        [Keys.SUPER, Keys.CONTROL], "1",
        lazy.to_screen(1),
        desc="Keyboard focus to left monitor"
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "2",
        lazy.to_screen(0),
        desc="Keyboard focus to middle monitor"
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "3",
        lazy.to_screen(2),
        desc="Keyboard focus to right monitor"
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "comma",
        lazy.next_screen(),
        desc="Move focus to next monitor"
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "period",
        lazy.prev_screen(),
        desc="Move focus to prev monitor"
    ),

    # Move windows between left/right columns or move up/down in current stack.
    # Moving out of range in Columns layout will create new column.
    Key(
        [Keys.SUPER, Keys.SHIFT], "Left",
        lazy.layout.shuffle_left(),
        desc="Move window to the left",
    ),
    Key(
        [Keys.SUPER, Keys.SHIFT], "Right",
        lazy.layout.shuffle_right(),
        desc="Move window to the right",
    ),
    Key(
        [Keys.SUPER, Keys.SHIFT], "Down",
        lazy.layout.shuffle_down(),
        desc="Move window down",
    ),
    Key(
        [Keys.SUPER, Keys.SHIFT], "Up",
        lazy.layout.shuffle_up(),
        desc="Move window up",
    ),

    # Toggle between split and unsplit sides of stack.
    # Split = all windows displayed
    # Unsplit = 1 window displayed, like Max layout, but still with
    # multiple stack panes
    Key(
        [Keys.SUPER, Keys.SHIFT], "Return",
        lazy.layout.toggle_split(),
        desc="Toggle between split and unsplit sides of stack",
    ),

    # Grow windows. If current window is on the edge of screen and direction
    # will be to screen edge - window would shrink.
    Key(
        [Keys.SUPER, Keys.CONTROL], "Left",
        lazy.layout.grow_left(),
        desc="Grow window to the left",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "Right",
        lazy.layout.grow_right(),
        desc="Grow window to the right",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "Down",
        lazy.layout.grow_down(),
        desc="Grow window down",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "Up",
        lazy.layout.grow_up(),
        desc="Grow window up",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "n",
        lazy.layout.normalize(),
        desc="Reset all window sizes",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "m",
        lazy.layout.maximize(),
        desc="Maximize the selected window",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "m",
        lazy.layout.move_to_slice(),
        desc="Move window to slice",
    ),

    # Toggle between different layouts as defined below
    Key(
        [Keys.SUPER], "Tab",
        lazy.next_layout(),
        desc="Move forward through layouts",
    ),
    Key(
        [Keys.SUPER, Keys.SHIFT], "Tab",
        lazy.prev_layout(),
        desc="Move backward through layouts",
    ),

    # Qtile management keys (close, restart, shutdown, lock, spawn)
    Key(
        [Keys.SUPER], "w",
        lazy.window.kill(),
        desc="Kill focused window",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "f",
        lazy.window.toggle_floating(),
        desc="Toggle window's floating mode"
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "r",
        lazy.reload_config(),
        desc="Reload Qtile Configuration",
    ),
    # Key(
    #     [Keys.SUPER, Keys.CONTROL], "r",
    #     lazy.restart(),
    #     desc="Restart Qtile",
    # ),
    # Key(
    #     [Keys.SUPER, Keys.CONTROL], "q",
    #     lazy.shutdown(),
    #     desc="Shutdown Qtile",
    # ),
    Key(
        [Keys.SUPER, Keys.CONTROL], "l",
        lazy.spawn("@locker@"),
        desc="Lock Screen",
    ),

    # Keys for spawning commands or applications
    Key(
        [Keys.SUPER], "r",
        lazy.spawncmd(command=Apps.shell_command("%s", args=("-ic",))),
        desc="Spawn a command using a prompt widget",
    ),
    Key(
        [Keys.SUPER, Keys.SHIFT], "r",
        lazy.spawncmd(prompt="shell", command=Apps.terminal_command(Apps.shell_command("%s; echo '\n---- Press enter to exit ----'; read", args=("-ic",))), shell=False),
        desc="Spawn a command in a shell using a prompt widget",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "r",
        lazy.spawn("rofi -show drun"),
        desc="Spawn a rofi menu to select a desktop application to open",
    ),
    Key(
        [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "r",
        lazy.spawn("rofi -show run"),
        desc="Spawn something in the path",
    ),

    # Power management
    KeyChord(
        [Keys.SUPER], "p",
        [
            Key(
                [], "h",
                lazy.spawn("systemctl hibernate"),
                desc="Hibernates the system",
            ),
            Key(
                [], "s",
                lazy.spawn("systemctl suspend"),
                desc="Suspends the system",
            ),
            Key(
                [], "b",
                lazy.spawn("xset dpms force off"),
                desc="Turns off the screen",
            ),
            Key(
                [], "r",
                lazy.spawn("reboot"),
                desc="Reboots the system",
            ),
            Key(
                [], "p",
                lazy.spawn("shutdown now"),
                desc="Shuts down the system",
            ),
            Key(
                [], "l",
                lazy.spawn("kill -9 -1"),
                desc="Logs out of the system",
            ),
        ],
        name="Power",
    ),

    # Volume
    Key(
        [], "XF86AudioRaiseVolume",
        lazy.spawn("wpctl set-volume @DEFAULT_SINK@ 2%+"),
        desc="Raise volume",
    ),
    Key(
        [], "XF86AudioLowerVolume",
        lazy.spawn("wpctl set-volume @DEFAULT_SINK@ 2%-"),
        desc="Lower volume",
    ),
    Key(
        [Keys.SHIFT], "XF86AudioRaiseVolume",
        lazy.spawn("wpctl set-volume @DEFAULT_SINK@ 1%+"),
        desc="Raise volume",
    ),
    Key(
        [Keys.SHIFT], "XF86AudioLowerVolume",
        lazy.spawn("wpctl set-volume @DEFAULT_SINK@ 1%-"),
        desc="Lower volume",
    ),
    Key(
        [], "XF86AudioMute",
        lazy.spawn("wpctl set-mute @DEFAULT_SINK@ toggle"),
        desc="Toggle mute",
    ),

    # Playpack
    Key(
        [], "XF86AudioPlay",
        lazy.spawn("playerctl play-pause"),
        desc="Play/Pause current media",
    ),
    Key(
        [], "XF86AudioPrev",
        lazy.spawn("playerctl previous"),
        desc="Skip to previous track",
    ),
    Key(
        [], "XF86AudioNext",
        lazy.spawn("playerctl next"),
        desc="Skip to next track",
    ),

    # Brightness
    *optional_list(
        LAPTOP,
        [
            Key(
                [], "XF86MonBrightnessUp",
                lazy.spawn("brightnessctl set +5%"),
                desc="Increase brightness",
            ),
            Key(
                [], "XF86MonBrightnessDown",
                lazy.spawn("brightnessctl set 5%-"),
                desc="Decrease brightness",
            )
        ]
    ),

    # Application shortcuts
    Key(
        [Keys.SUPER], "Return",
        lazy.spawn(Apps.TERMINAL),
        desc="Launch terminal",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "f",
        lazy.spawn("@firefox@ --new-window"),
        desc="Start Firefox (New Window)",
    ),
    Key(
        [Keys.SUPER, Keys.ALT, Keys.CONTROL], "f",
        lazy.spawn("@firefox@ --private-window"),
        desc="Start Firefox (New Private Window)",
    ),
    Key(
        [Keys.SUPER, Keys.ALT, Keys.SHIFT], "f",
        lazy.spawncmd(prompt="search", command="@firefox@ --search '%s'", complete=None, shell=False),
        desc="Search for a term in Firefox",
    ),
    Key(
        [Keys.SUPER, Keys.ALT, Keys.CONTROL, Keys.SHIFT], "f",
        lazy.spawncmd(prompt="private search", command="@firefox@  --private-window 'https://google.com/search?q=%s'", complete=None, shell=False),
        desc="Search for a term in Firefox",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "e",
        Apps.open_in_terminal(Apps.EDITOR),
        desc="Launch editor",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "d",
        lazy.spawn("discordcanary"),
        desc="Launch Discord",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "g",
        lazy.spawn("schildichat-desktop"),
        desc="Launch Schildichat",
    ),
    # Key(
    #     [Keys.SUPER, Keys.ALT], "c",
    #     lazy.spawn("speedcrunch"),
    #     desc="Launch Speedcrunch",
    # ),
    Key(
        [Keys.SUPER, Keys.ALT], "s",
        lazy.spawn("flameshot gui"),
        desc="Launch screenshot tool",
    ),
    # Key(
    #     [Keys.SUPER, Keys.ALT], "m",
    #     lazy.spawn("arandr"),
    #     desc="Launch arandr",
    # ),
    Key(
        [Keys.SUPER, Keys.ALT], "t",
        lazy.spawn("thunderbird"),
        desc="Launch thunderbird",
    ),
    Key(
        [Keys.SUPER, Keys.ALT, Keys.CONTROL], "m",
        lazy.spawn("autorandr -c"),
        desc="Run autorandr",
    ),
    Key(
        [Keys.SUPER, Keys.ALT], "XF86AudioPlay",
        lazy.spawn("feishin"),
        desc="Laucn Feishin",
    ),
]
# fmt: on

layouts = [
    layout.Columns(border_focus_stack="#d75f5f"),
    layout.Max(),
    layout.TreeTab(),
]

groups = [
    *[Group(i) for i in "12345678"],
    Group(
        "9",
        layouts=[
            *layouts,
            layout.Slice(name="left slice", width=1000, fallback=layouts[0], side="left"),
            layout.Slice(name="top slice", width=800, fallback=layouts[0], side="top"),
        ],
    ),
    Group("0", layouts=[layout.TreeTab()]),
]

for i in groups:
    keys.extend(
        [
            Key(
                [Keys.SUPER],
                i.name,
                lazy.group[i.name].toscreen(toggle=True),
                desc=f"Switch to group {i.name}",
            ),
            Key(
                [Keys.SUPER, Keys.SHIFT],
                i.name,
                lazy.window.togroup(i.name, switch_group=True),
                desc=f"Switch to & move focused window to group {i.name}",
            ),
            Key(
                [Keys.SUPER, Keys.CONTROL, Keys.SHIFT],
                i.name,
                lazy.window.togroup(i.name),
                desc=f"Move focused window to group {i.name}",
            ),
        ]
    )

groups.append(
    ScratchPad(
        "scratchpad",
        [
            DropDown("calculator", "speedcrunch", x=0.2, y=0.2, width=0.6, height=0.6),
            DropDown("arandr", "arandr", x=0.2, y=0.1, width=0.6, height=0.4),
            DropDown("bluetooth", "blueman-manager", x=0.2, y=0.2, width=0.6, height=0.6),
            DropDown("wifi", "wpa_gui", x=0.3, y=0.3, width=0.4, height=0.4),
        ],
    )
)

# fmt: off
keys.extend(
    [
        Key(
            [Keys.SUPER, Keys.ALT], "c",
            lazy.group["scratchpad"].dropdown_toggle("calculator"),
        ),
        Key(
            [Keys.SUPER, Keys.ALT], "m",
            lazy.group["scratchpad"].dropdown_toggle("arandr"),
        ),
        Key(
            [Keys.SUPER, Keys.ALT], "b",
            lazy.group["scratchpad"].dropdown_toggle("bluetooth"),
        ),
        Key(
            [Keys.SUPER, Keys.ALT], "n",
            lazy.group["scratchpad"].dropdown_toggle("wifi"),
        ),
    ]
)
# fmt: on

widget_defaults = dict(font="sans", fontsize=12, padding=2, background=colors[0])
extension_defaults = widget_defaults.copy()


def create_powerline(
    widgets: List[List[Any]],
    *,
    base,
    foreground,
    backgrounds,
    sep="",
    padding=0,
    fontsize=37,
):
    """
    Takes in a list of widgets and produces a powerline-style bar as the result.
    Cycles through the provided colors and uses the provided character as a separator.

    Takes input as a list of widgets, then base, foreground, and backgrounds as keyword arguments.
    Setting sep changes the separator
    """
    output = []

    for i, widgets_list in enumerate(widgets):
        bg_index = (len(widgets) - i) % len(backgrounds)
        output.append(
            widget.TextBox(
                text=sep,
                foreground=backgrounds[bg_index],
                background=base if i == 0 else backgrounds[bg_index - (len(backgrounds) - 1)],
                fontsize=fontsize,
                padding=padding,
            )
        )

        for elem in widgets_list:
            elem.foreground = foreground
            elem.background = backgrounds[bg_index]
            output.append(elem)

    return output


def init_widget_list(main=True, laptop=False):
    """
    Returns a list of widgets suitable for a qtile bar
    """

    interfaces = run_command(
        # fmt:off
        "ifconfig -s"
        " | awk {'print $1'}"
        " | grep -Ev -e Iface -e lo -e vir.+ -e docker.+ -e tailscale.+"
        " | tac"
        # fmt:on
    ).splitlines()

    wireless_interfaces = list(filter(lambda x: x.startswith("w"), interfaces))

    network_widgets = sum(
        [
            [
                *optional_list(
                    i != 0,
                    [
                        widget.Sep(linewidth=2, padding=3),
                    ],
                ),
                widget.TextBox(text=f"{interface}:", padding=2),
                widget.Net(
                    interface=interface,
                    format="{down} ↓↑ {up}",
                    padding=5,
                ),
            ]
            for i, interface in enumerate(interfaces)
        ],
        [],
    )

    main_widgets = [
        *(
            [[widget.Wlan(format=" {essid} {percent:2.0%}", interface=wireless_interfaces[0], padding=1)]]
            if len(wireless_interfaces)
            else []
        ),
        *([network_widgets] if len(network_widgets) else []),
        [
            widget.TextBox(text="󰍛", padding=1, fontsize=18),
            widget.Memory(padding=5, measure_mem="G", format="{MemUsed:.1f}{mm}/{MemTotal:.1f}{mm}"),
        ],
        [
            widget.TextBox(text=" Vol:", padding=0),
            widget.PulseVolume(padding=5),
        ],
    ]

    laptop_widgets = [
        *(
            [
                [
                    widget.TextBox(text=" 🔆", padding=0, fontsize=14),
                    widget.Backlight(
                        backlight_name=(backlight.splitlines()[0].split(",")[0]),
                        change_command="brightnessctl set {0}%",
                        step=5,
                        padding=5,
                    ),
                ]
            ]
            if (backlight := run_command("brightnessctl -lm --class=backlight"))
            else []
        ),
        [  # TODO: Create battery icon widget using NerdFont icons
            widget.Battery(
                format="{char} {percent:2.1%} {hour:d}h:{min:02d}m",
                update_interval=10,
                padding=5,
                charge_char="",
                discharge_char="",
            ),
        ],
    ]

    powerline_widgets = [
        # Widgets only found on the main screen's powerline
        *optional_list(main, main_widgets + optional_list(laptop, laptop_widgets)),
        # Widgets found on the powerline of all screens
        [
            widget.CurrentLayoutIcon(padding=0, scale=0.7),
            widget.CurrentLayout(padding=5),
        ],
        [
            widget.Clock(
                format="%A, %B %d, %Y - %H:%M:%S ",
                timezone=None,
            ),
        ],
    ]

    widget_list = [
        widget.Sep(linewidth=0, padding=6, foreground=colors[2], background=colors[0]),
        widget.GroupBox(
            font="Ubuntu Mono",
            fontsize=14,
            margin_y=3,
            margin_x=0,
            padding_y=5,
            padding_x=3,
            borderwidth=3,
            active=colors[2],
            inactive=colors[7],
            rounded=False,
            highlight_color=colors[1],
            highlight_method="line",
            this_current_screen_border=colors[5],
            this_screen_border=colors[6],
            other_current_screen_border=colors[5],
            other_screen_border=colors[4],
            foreground=colors[2],
            background=colors[0],
            use_mouse_wheel=False,
            disable_drag=True,
        ),
        widget.Chord(
            font="Ubuntu Mono",
            padding=10,
            foreground=colors[3],
            background=colors[1],
            max_chars=10,
        ),
        widget.Sep(linewidth=0, padding=6, foreground=colors[2], background=colors[0]),
        widget.Prompt(
            prompt="{prompt}: ",
            font="Ubuntu Mono",
            padding=10,
            foreground=colors[3],
            background=colors[1],
        ),
        widget.Sep(linewidth=0, padding=40, foreground=colors[2], background=colors[0]),
        widget.WindowName(foreground=colors[6], background=colors[0], padding=0),
        *optional_list(
            main,
            [
                widget.Systray(background=colors[0], padding=5),
            ],
        ),
        widget.Sep(linewidth=0, padding=6, foreground=colors[0], background=colors[0]),
        *create_powerline(
            powerline_widgets,
            base=colors[0],
            foreground=colors[2],
            backgrounds=[
                colors[4],
                colors[5],
            ],
        ),
    ]

    return widget_list


screens = [
    Screen(
        bottom=bar.Bar(
            init_widget_list(main=(i == 0), laptop=LAPTOP),
            size=20,
            opacity=1.0,
        ),
        wallpaper="@wallpaper@",
        wallpaper_mode="@wallpaper_mode@",
    )
    for i in range(4)
]

# Drag floating layouts.
mouse = [
    Drag(
        [Keys.SUPER],
        "Button1",
        lazy.window.set_position_floating(),
        start=lazy.window.get_position(),
    ),
    Drag(
        [Keys.SUPER],
        "Button3",
        lazy.window.set_size_floating(),
        start=lazy.window.get_size(),
    ),
    Click(
        [Keys.SUPER],
        "Button2",
        lazy.window.bring_to_front(),
    ),
]

floating_layout = layout.Floating(
    float_rules=[
        # Run the utility of `xprop` to see the wm class and name of an X client.
        # Section taken from Default Float Rules
        Match(wm_type="utility"),
        Match(wm_type="notification"),
        Match(wm_type="toolbar"),
        Match(wm_type="splash"),
        Match(wm_type="dialog"),
        Match(wm_class="file_progress"),
        Match(wm_class="confirm"),
        Match(wm_class="dialog"),
        Match(wm_class="download"),
        Match(wm_class="error"),
        Match(wm_class="notification"),
        Match(wm_class="splash"),
        Match(wm_class="toolbar"),
        Match(func=lambda c: c.has_fixed_size()),
        Match(func=lambda c: c.has_fixed_ratio()),
        # End section
        Match(wm_class="confirmreset"),  # gitk
        Match(wm_class="makebranch"),  # gitk
        Match(wm_class="maketag"),  # gitk
        Match(wm_class="ssh-askpass"),  # ssh-askpass
        Match(title="branchdialog"),  # gitk
        Match(title="pinentry"),  # GPG key password entry
        Match(wm_class="pinentry"),
    ]
)


dgroups_key_binder = None
dgroups_app_rules = []  # type: List

follow_mouse_focus = True
bring_front_click = False
cursor_warp = True
auto_fullscreen = True
focus_on_window_activation = "never"
reconfigure_screens = True

# If things like steam games want to auto-minimize themselves when losing
# focus, should we respect this or not?
auto_minimize = True

# XXX: Gasp! We're lying here. In fact, nobody really uses or cares about this
# string besides java UI toolkits; you can see several discussions on the
# mailing lists, GitHub issues, and other WM documentation that suggest setting
# this string if your java app doesn't work correctly. We may as well just lie
# and say that we're a working one by default.
#
# We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in
# java that happens to be on java's whitelist.
wmname = "LG3D"
