import dataclasses
import os
from pathlib import Path
from pywayland.client import Display
from pywayland.protocol.wayland import WlRegistry
from pywayland.protocol.wlr_foreign_toplevel_management_unstable_v1 import (
    ZwlrForeignToplevelManagerV1,
    ZwlrForeignToplevelHandleV1
)
import panorama_panel

import gi

gi.require_version("Gtk", "4.0")

from gi.repository import Gtk, GLib, Gio, Gdk


module_directory = Path(__file__).resolve().parent


def split_bytes_into_ints(array: bytes, size: int = 4) -> list[int]:
    if len(array) % size:
        raise ValueError(f"The byte string's length must be a multiple of {size}")

    values: list[int] = []
    for i in range(0, len(array), size):
        values.append(int.from_bytes(array[i : i+size], byteorder="little"))

    return values


@dataclasses.dataclass
class WindowState:
    minimised: bool
    maximised: bool
    fullscreen: bool
    focused: bool

    @classmethod
    def from_state_array(cls, array: bytes):
        values = split_bytes_into_ints(array)
        instance = cls(False, False, False, False)
        for value in values:
            match value:
                case 0:
                    instance.maximised = True
                case 1:
                    instance.minimised = True
                case 2:
                    instance.focused = True
                case 3:
                    instance.fullscreen = True

        return instance


class WindowButton(Gtk.ToggleButton):
    def __init__(self, window_id, window_title, **kwargs):
        super().__init__(**kwargs)

        self.window_id = window_id
        self.set_has_frame(False)
        self.label = Gtk.Label()
        self.icon = Gtk.Image.new_from_icon_name("application-x-executable")
        box = Gtk.Box()
        box.append(self.icon)
        box.append(self.label)
        self.set_child(box)

        self.window_title = window_title

        self.last_state = False

    @property
    def window_title(self):
        return self.label.get_text()

    @window_title.setter
    def window_title(self, value):
        self.label.set_text(value)

    def set_icon_from_app_id(self, app_id):
        # Try getting an icon from the correct theme
        app_ids = app_id.split()
        icon_theme = Gtk.IconTheme.get_for_display(self.get_display())

        for app_id in app_ids:
            if icon_theme.has_icon(app_id):
                self.icon.set_from_icon_name(app_id)
                return

        # If that doesn't work, try getting one from .desktop files
        for app_id in app_ids:
            try:
                desktop_file = Gio.DesktopAppInfo.new(app_id + ".desktop")
                if desktop_file:
                    self.icon.set_from_gicon(desktop_file.get_icon())
                    return
            except TypeError:
                # Due to a bug, the constructor may sometimes return C NULL
                pass


class WFWindowList(panorama_panel.Applet):
    name = "Wayfire window list"
    description = "Traditional window list (for Wayfire)"

    def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None):
        super().__init__(orientation=orientation, config=config)
        if config is None:
            config = {}

        self.toplevel_buttons: dict[ZwlrForeignToplevelHandleV1, WindowButton] = {}
        # This button doesn't belong to any window but is used for the button group and to be
        # selected when no window is focused
        self.initial_button = Gtk.ToggleButton()

        self.display = Display()
        self.display.connect()
        self.registry = self.display.get_registry()
        self.registry.dispatcher["global"] = self.on_global
        self.display.roundtrip()
        fd = self.display.get_fd()
        GLib.io_add_watch(fd, GLib.IO_IN, self.on_display_event)

        self.context_menu = self.make_context_menu()
        panorama_panel.track_popover(self.context_menu)

        right_click_controller = Gtk.GestureClick()
        right_click_controller.set_button(3)
        right_click_controller.connect("pressed", self.show_context_menu)

        self.add_controller(right_click_controller)

        action_group = Gio.SimpleActionGroup()
        options_action = Gio.SimpleAction.new("options", None)
        options_action.connect("activate", self.show_options)
        action_group.add_action(options_action)
        self.insert_action_group("applet", action_group)

        self.options_window = None

    def on_display_event(self, source, condition):
        if condition == GLib.IO_IN:
            self.display.dispatch(block=True)
        return True

    def on_global(self, registry, name, interface, version):
        if interface == "zwlr_foreign_toplevel_manager_v1":
            self.print_log("Interface registered")
            self.manager = registry.bind(name, ZwlrForeignToplevelManagerV1, version)
            self.manager.dispatcher["toplevel"] = self.on_new_toplevel
            self.manager.dispatcher["finished"] = lambda *a: print("Toplevel manager finished")
            self.display.roundtrip()
            self.display.flush()

    def on_new_toplevel(self, manager: ZwlrForeignToplevelManagerV1,
                         handle: ZwlrForeignToplevelHandleV1):
        handle.dispatcher["title"] = lambda h, title: self.on_title_changed(h, title)
        handle.dispatcher["app_id"] = lambda h, app_id: self.on_app_id_changed(h, app_id)
        handle.dispatcher["state"] = lambda h, states: self.on_state_changed(h, states)
        handle.dispatcher["closed"] = lambda h: self.on_closed(h)

    def on_title_changed(self, handle, title):
        if handle not in self.toplevel_buttons:
            button = WindowButton(handle, title)
            button.set_group(self.initial_button)
            button.connect("clicked", lambda *a: self.on_button_click(handle))
            self.toplevel_buttons[handle] = button
            self.append(button)
        else:
            button = self.toplevel_buttons[handle]
            button.window_title = title

    def on_state_changed(self, handle, states):
        if handle in self.toplevel_buttons:
            state_info = WindowState.from_state_array(states)
            button = self.toplevel_buttons[handle]
            if state_info.focused:
                button.set_active(True)
            else:
                self.initial_button.set_active(True)

    def on_app_id_changed(self, handle, app_id):
        if handle in self.toplevel_buttons:
            button = self.toplevel_buttons[handle]
            button.set_icon_from_app_id(app_id)

    def on_closed(self, handle):
        if handle in self.toplevel_buttons:
            self.remove(self.toplevel_buttons[handle])
            self.toplevel_buttons.pop(handle)

    def make_context_menu(self):
        menu = Gio.Menu()
        menu.append("Window list _options", "applet.options")
        context_menu = Gtk.PopoverMenu.new_from_model(menu)
        context_menu.set_has_arrow(False)
        context_menu.set_parent(self)
        context_menu.set_halign(Gtk.Align.START)
        context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
        return context_menu

    def show_context_menu(self, gesture, n_presses, x, y):
        rect = Gdk.Rectangle()
        rect.x = int(x)
        rect.y = int(y)
        rect.width = 1
        rect.height = 1

        self.context_menu.set_pointing_to(rect)
        self.context_menu.popup()

    def show_options(self, _0=None, _1=None):
        pass

    def get_config(self):
        return {}
