#!/usr/bin/env python
import sys
import os


# POLYWINS.py

# SETTINGS


name_style = None  # options: upper, lower, None
separator = " "  # What to separate workspaces with
windowlist_prefix = " "  # prefix before listing windows, default is " "
show = "window_classname"
forbidden_classes = "Polybar Conky Gmrun Pavucontrol".casefold().split(" ")
hide_unpopulated_desktops = False
iconize = True
hide_name = True  # Controls whether to hide window names when an icon is present

char_limit = 10
max_windows = 10
add_spaces = "true"
resize_increment = 16
resize_offset = resize_increment / 2
use_pywal = False

override_names = [
    " %{F#b4befe} %{F-}",
    " %{F#b4befe} %{F-}",
    " %{F#b4befe} %{F-}"
]  # Either a list containing the focused, populated and unfocused versions of workspace name, or False

underline = True
highlight_active_wps = True

if len(sys.argv) <= 2:
    try:
        if use_pywal is not True:
            raise TypeError
        with open(os.path.expanduser("~/.cache/wal/colors")) as colors:
            colors = tuple(map(lambda x: x[:-1], colors.readlines()))
        active_text_color = colors[1]
        active_underline = colors[1] if underline is not False else None
        inactive_text_color = colors[7]
        inactive_underline = colors[7] if underline is not False else None

    except (OSError, IndexError, TypeError):
        active_text_color = "#cdd6f4"
        active_underline = "#EB6572" if underline is not False else None
        inactive_text_color = "#9399b2"
        inactive_underline = "#C0CAF5" if underline is not False else None

    # WINDOW LIST SETUP

    active_left = "%{F" + active_text_color + "}"
    active_right = "%{F-} "
    inactive_left = "%{F" + inactive_text_color + "}"
    inactive_right = "%{F-}"
    # separator = "%{F" + inactive_text_color + "}" + separator + "%{F-}"

    wps_active_left = (
        "%{F" + (inactive_text_color if highlight_active_wps is False else active_text_color) + "}"
    )
    wps_active_right = "%{F-}"
    wps_inactive_left = "%{F" + inactive_text_color + "}"
    wps_inactive_right = "%{F-}"
    if active_underline is not None:
        active_left = active_left + "%{+u}%{u" + active_underline + "}"
        active_right = "%{-u}" + active_right
        wps_active_left = "%{F" + inactive_text_color + "}%{+u}%{u" + inactive_underline + "}"
        wps_active_right = "%{-u}%{u}%{F-}"

    if inactive_underline is not None:
        inactive_left += "%{+u}%{u" + inactive_underline + "}"
        inactive_right = "%{-u}" + inactive_right

    on_click = " ".join(sys.argv[:2])
    monitor = sys.argv[1]

    printf = sys.stdout.write

    superscript = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")

    class_icons = {
        "alacritty": "",
        "atom": "",
        "vscode": "",
        "code": "",
        "code-oss": "",
        "neovim": "",
        "nvim": "",
        "nvim-qt": "",
        "neovide": "",
        "banshee": "",
        "blender": "",
        "chromium": "",
        "google-chrome": "",
        "cura": "",
        "darktable": "",
        "discord": "ﭮ",
        "eclipse": "",
        "emacs": "",
        "eog": "",
        "evince": "",
        "evolution": "",
        "feh": "",
        "file-roller": "ﭕ",
        "filezilla": "",
        "firefox": "",
        "firefox-esr": "",
        "firefoxdev": "",
        "navigator": "",
        "gimp": "",
        "gimp-2.8": "",
        "gnome-control-center": "",
        "gnome-terminal-server": "",
        "prusa-slicer": "",
        "gpick": "",
        "imv": "",
        "insomnia": "",
        "java": "",
        "jetbrains-idea": "",
        "jetbrains-studio": "",
        "keepassxc": "",
        "keybase": "",
        "kicad": "",
        "kitty": "",
        "st-256color": "",
        "st": "",
        "libreoffice": "",
        "lua5.1": "",
        "mpv": "",
        "mupdf": "",
        "mysql-workbench-bin": "",
        "nautilus": "",
        "nemo": "",
        "openscad": "",
        "pavucontrol": "",
        "postman": "",
        "rhythmbox": "蓼",
        "robo3t": "",
        "signal": "",
        "slack": "",
        "slic3r.pl": "",
        "spotify": "",
        "steam": "",
        "subl": "",
        "subl3": "",
        "sublime_text": "",
        "thunar": "",
        "thunderbird": "",
        "totem": "",
        "urxvt": "",
        "xfce4-terminal": "",
        "xournal": "",
        "yelp": "",
        "zenity": "",
        "zoom": "",
        "telegram": "",
        "kotatogram": "",
        "lunarclient": "",
        "viber": "",
    }


def get_active_wid():
    return os.popen("bspc query -N -n .focused").read()[:-1]


def ensure_len(ID, length=10):
    while len(ID) < length - 1:
        ID = "0x0" + ID[2:]
    return ID


if hide_name:

    def to_icon(name):
        try:
            return class_icons[name.lower()] + " "
        except KeyError:
            if not name.casefold().startswith("lunar client"):
                return name[:char_limit]
            else:
                return " "

else:

    def to_icon(name):
        try:
            return class_icons[name.lower()] + " " + name[:char_limit]
        except KeyError:
            if not name.casefold().startswith("lunar client"):
                return name[:char_limit]
            else:
                return ""


def wid_to_name(wid, cache={}):
    if not isinstance(wid, list):
        if show == "window_class":
            out = os.popen(f"xprop -id {wid} WM_CLASS 2> /dev/null").read().split('"')
        if show == "window_classname":
            out = os.popen(f"xprop -id {wid} WM_CLASS 2> /dev/null").read().split('"')[:-1][-1]
        if show == "window_title":
            out = os.popen(f"xprop -id {wid} _NET_WM_NAME 2> /dev/null").read().split('"')[1]
        if name_style == "upper":
            out = out.upper()
        elif name_style == "lower":
            out = out.lower()
        return out
    else:
        out = {}
        cached = cache.keys()
        for id in wid:
            if id not in cached:
                if show == "window_class":
                    name = os.popen(f"xprop -id {id} WM_CLASS 2> /dev/null").read().split('"')[1]
                if show == "window_classname":
                    name = os.popen(f"xprop -id {id} WM_CLASS 2> /dev/null").read().split('"')[-2]
                if show == "window_title":
                    name = (
                        os.popen(f"xprop -id {id} _NET_WM_NAME 2> /dev/null").read().split('"')[1]
                    )
                if name.lower() not in forbidden_classes:
                    if iconize:
                        name = to_icon(name)
                    try:
                        out[name].append(id)
                    except KeyError:
                        out[name] = [id]
                    cache[id] = name
            else:
                try:
                    out[cache[id]].append(id)
                except KeyError:
                    out[cache[id]] = [id]
        return out, cache


def generate(workspaces, focused_desk, order):
    global classcache
    focused = os.popen(f"bspc query -N -m {mon_id} -n .focused").read()[
        :-1
    ]  # ID of the currently focused window
    for workspace_id in order:
        if (
            len(workspaces[workspace_id][0]) < hide_unpopulated_desktops
            and workspace_id != focused_desk
        ):
            continue
        printf(
            "%{A1:"
            + on_click
            + " switch_workspace "
            + workspace_id
            + ":}"
            + "%{A2:"
            + on_click
            + " swap_workspace "
            + workspace_id
            + ":}"
            + wps_inactive_left
            
            + (workspaces[workspace_id][1] if override_names is False else (override_names[1] if len(workspaces[workspace_id][0]) else override_names[0]))
            if workspace_id != focused_desk
            else wps_active_left
            
            + (workspaces[workspace_id][1] if override_names is False else override_names[2])
        )
        if len(workspaces[workspace_id][0]) == 0:
            #printf(separator + wps_active_right + "%{A}%{A}")
            printf( wps_active_right + "%{A}%{A}")
        else:
            printf(windowlist_prefix + "%{A}%{A}")
            windows, classcache = wid_to_name(workspaces[workspace_id][0], classcache)
            win_length = len(windows.keys())
            for i, win_class in enumerate(windows.keys()):
                if i == max_windows:
                    break
                wid = " ".join(windows[win_class])
                printf(
                    "%{A1:"
                    + on_click
                    + " focus "
                    + wid
                    + ":}%{A2:"
                    + on_click
                    + " close "
                    + wid
                    + ":}%{A3:"
                    + on_click
                    + " slop_resize "
                    + wid
                    + ":}%{A4:"
                    + on_click
                    + " increment_size "
                    + wid
                    + ":}%{A5:"
                    + on_click
                    + " decrement_size "
                    + wid
                    + ":}"
                )
                printf(active_left if focused in windows[win_class] else inactive_left)
#                printf(separator if i == 0 else "")
                printf(
                    win_class.upper()
                    if name_style == "upper"
                    else (win_class.lower() if name_style == "lower" else win_class)
                )
                printf(
                    #(separator if i < (win_length - 1) else "")
                    ""
                    if len(windows[win_class]) <= 1
                    else str(len(windows[win_class])).translate(superscript)
                )
                printf(active_right + "%{A}%{A}%{A}%{A}%{A}")
            printf(wps_active_right)
            if len(windows.keys()) > max_windows:
                printf(f"+{len(windows.keys())-max_windows}")


def main():
    if len(sys.argv) <= 2:
        focused = ""
        command = os.popen(
            "bspc subscribe desktop_focus desktop_add desktop_rename desktop_remove desktop_swap node_add node_remove node_swap node_transfer node_focus"
        )
        global mon_id
        mon_id = os.popen(f"bspc query -M -m '{monitor}'").read()[:-1]
        workspace_order = []
        workspaces = {}  # workspace ID and name pairs
        for workspace in [
            workspace[:-1] for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
        ]:
            workspace_order.append(workspace)
            workspaces[workspace] = (
                [
                    window[:-1]
                    for window in os.popen(f"bspc query -N -d {workspace} -n .window").readlines()
                ],
                os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
            )

        focused_workspace = os.popen(f"bspc query -D -m {mon_id} -d .focused").read()[
            :-1
        ]  # ID of the currently focused workspace
        global classcache
        classcache = {}
        try:
            generate(
                workspaces,
                focused_workspace,
                workspace_order,
            )
            printf("\n")
            sys.stdout.flush()
        except:
            workspace_order = []
            workspaces = {}  # workspace ID and name pairs
            for workspace in [
                workspace[:-1] for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
            ]:
                workspace_order.append(workspace)
                workspaces[workspace] = (
                    [
                        window[:-1]
                        for window in os.popen(
                            f"bspc query -N -d {workspace} -n .window"
                        ).readlines()
                    ],
                    os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
                )
                focused_workspace = os.popen(f"bspc query -D -m {mon_id} -d .focused").read()[
                    :-1
                ]  # ID of the currently focused workspace

        while True:
            update = command.readline()[:-1]
            if (
                mon_id in update
                or update.startswith("node_remove")
                or update.startswith("node_focus")
                or update.startswith("desktop_focus")
            ):
                if update.startswith("node"):
                    update = update[5:].split(" ")
                    if update[0] == "focus":
                        # global focused
                        # focused = update[3]
                        pass
                    elif update[0] == "add":
                        try:
                            workspaces[update[2]][0].append(update[4])
                        except KeyError:
                            pass
                    elif update[0] == "remove":
                        classcache.pop(update[3], None)
                        try:
                            workspaces[update[2]][0].remove(update[3])
                        except KeyError:
                            continue
                    elif update[0] == "swap":
                        if update[1] == mon_id:
                            workspaces[update[2]][0].remove(update[3])
                            workspaces[update[2]][0].append(update[6])
                        if update[4] == mon_id:
                            workspaces[update[5]][0].remove(update[6])
                            workspaces[update[5]][0].append(update[3])
                    else:
                        if update[1] == mon_id:
                            workspaces[update[2]][0].remove(update[3])
                        if update[4] == mon_id:
                            workspaces[update[5]][0].append(update[3])
                else:
                    update = update[8:].split(" ")
                    if update[0] == "focus":
                        focused_workspace = update[-1]
                    elif update[0] == "add":
                        workspaces[update[2]] = ([], update[-1])
                    elif update[0] == "rename":
                        workspaces[update[2]] = (
                            [
                                window[:-1]
                                for window in os.popen(f"bspc query -N -d {update[2]}").readlines()
                            ],
                            update[-1],
                        )
                    elif update[0] == "remove":
                        workspaces.pop(update[-1], None)
                    else:
                        if update[1] == mon_id and update[3] == mon_id:
                            index = workspace_order.index(update[4])
                            workspace_order[workspace_order.index(update[2])] = update[4]
                            workspace_order[index] = update[2]
                        else:
                            workspace_order = []
                            workspaces = {}  # workspace ID and name pairs
                            for workspace in [
                                workspace[:-1]
                                for workspace in os.popen(
                                    f"bspc query -D -m '{mon_id}'"
                                ).readlines()
                            ]:
                                workspace_order.append(workspace)
                                workspaces[workspace] = (
                                    [
                                        window[:-1]
                                        for window in os.popen(
                                            f"bspc query -N -d {workspace} -n .window"
                                        ).readlines()
                                    ],
                                    os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
                                )
                                focused_workspace = os.popen(
                                    f"bspc query -D -m {mon_id} -d .focused"
                                ).read()[
                                    :-1
                                ]  # ID of the currently focused workspace

                try:
                    generate(
                        workspaces,
                        focused_workspace,
                        workspace_order,
                    )
                    printf("\n")
                    sys.stdout.flush()
                except:
                    workspace_order = []
                    workspaces = {}  # workspace ID and name pairs
                    for workspace in [
                        workspace[:-1]
                        for workspace in os.popen(f"bspc query -D -m '{mon_id}'").readlines()
                    ]:
                        workspace_order.append(workspace)
                        workspaces[workspace] = (
                            [
                                window[:-1]
                                for window in os.popen(
                                    f"bspc query -N -d {workspace} -n .window"
                                ).readlines()
                            ],
                            os.popen(f"bspc query -D -d {workspace} --names").read()[:-1],
                        )
                        focused_workspace = os.popen(
                            f"bspc query -D -m {mon_id} -d .focused"
                        ).read()[
                            :-1
                        ]  # ID of the currently focused workspace
                        command = os.popen(
                            "bspc subscribe desktop_focus desktop_add desktop_rename desktop_remove desktop_swap node_add node_remove node_swap node_transfer node_focus"
                        )
    else:
        exec(sys.argv[2] + "(" + "'" + " ".join(sys.argv[3:]) + "')")


def slop_resize(window):
    window = sorted(window.split(" "))
    try:
        window = window[window.index(get_active_wid())]
    except ValueError:
        window = window[0]
    os.system(
        f"""bash -c 'bspc node "{window}" -g hidden=off &
bspc node "{window}" -g hidden=off &
xdo hide "{window}" &
pos="$(slop -b 2 -c 0.75,0.8,0.96.1 -f 0,%x,%y,%w,%h)"
xdo show "{window}"
bspc node "{window}" -t floating
bspc node "{window}" -d focused
wmctrl -ir "{window}" -e "$pos"
xdo activate "{window}"'"""
    )


def close(window):
    window = sorted(window.split(" "))
    try:
        window = window[window.index(get_active_wid())]
    except ValueError:
        window = window[0]
    os.system("xdo close " + window)


def focus(window):
    window = sorted(window.split(" "))
    try:
        window = window[(window.index(get_active_wid()) + 1) % len(window)]
        os.system("bspc node " + window + " -g hidden=off")
        # os.system("bspc node -s " + window)
        os.system("bspc node -f " + window)
    except ValueError:
        window = window[0]
        os.system("bspc node " + window + " -g hidden=off")
        os.system("bspc node -f " + window)
        os.system("wmctrl -ia " + window)


def increment_size(window):
    window = sorted(window.split(" "))
    try:
        window = window[window.index(get_active_wid())]
    except ValueError:
        window = window[0]
    os.system(f"xdo move -x -{resize_offset} -y -{resize_offset} {window}")
    os.system(f"xdo resize -w +{resize_increment} -h +{resize_increment} {window}")


def decrement_size(window):
    window = sorted(window.split(" "))
    try:
        window = window[window.index(get_active_wid())]
    except ValueError:
        window = window[0]
    os.system(f"bspc node -t floating {window}")
    os.system(f"xdo move -x +{resize_offset} -y +{resize_offset} {window}")
    os.system(f"xdo resize -w -{resize_increment} -h -{resize_increment} {window}")


def switch_workspace(workspace):
    os.system(f"bspc desktop -f {workspace}")


def swap_workspace(workspace):
    os.system(f"bspc desktop -s {workspace}")


if __name__ == "__main__":
    main()