import os
import faulthandler
import ruamel.yaml as yaml
from pywayland.client import Display, EventQueue
from pywayland.protocol.wayland import WlOutput
from pathlib import Path

faulthandler.enable()

os.environ["GI_TYPELIB_PATH"] = "/usr/local/lib/x86_64-linux-gnu/girepository-1.0"

from ctypes import CDLL
CDLL("libgtk4-layer-shell.so")

import gi

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

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

import ctypes
from cffi import FFI
ffi = FFI()
ffi.cdef("""
void * gdk_wayland_display_get_wl_display (void * display);
void * gdk_wayland_surface_get_wl_surface (void * surface);
void * gdk_wayland_monitor_get_wl_output (void * monitor);
""")
gtk = ffi.dlopen("libgtk-4.so.1")


def get_config_file():
    config_home = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))

    return config_home / "panorama-bg" / "config.yaml"


FITTING_MODES = {
    "cover": Gtk.ContentFit.COVER,
    "contain": Gtk.ContentFit.CONTAIN,
    "fill": Gtk.ContentFit.FILL,
    "scale_down": Gtk.ContentFit.SCALE_DOWN
}


class PanoramaBG(Gtk.Application):
    def __init__(self):
        super().__init__(
                application_id="com.roundabout_host.roundabout.PanoramaBG"
        )
        self.wl_output_ids: dict[int, WlOutput] = {}
        self.wl_display = None
        self.display = None

    def make_backgrounds(self):
        for window in self.get_windows():
            window.destroy()

        with open(get_config_file(), "r") as config_file:
            yaml_loader = yaml.YAML(typ="rt")
            yaml_file = yaml_loader.load(config_file)

            monitors = self.display.get_monitors()

            for monitor in monitors:
                if monitor.get_connector() in yaml_file["monitors"]:
                    background_window = BackgroundWindow(self, monitor)
                    background_window.picture.set_filename(yaml_file["monitors"][monitor.get_connector()]["sources"][0])
                    background_window.picture.set_content_fit(FITTING_MODES[yaml_file["monitors"][monitor.get_connector()]["fitting_mode"]])
                    background_window.present()

    def do_startup(self):
        Gtk.Application.do_startup(self)
        self.hold()

        ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
        ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (ctypes.py_object,)

        self.display = Gdk.Display.get_default()

        self.wl_display = Display()
        wl_display_ptr = gtk.gdk_wayland_display_get_wl_display(
                ffi.cast("void *", ctypes.pythonapi.PyCapsule_GetPointer(self.display.__gpointer__, None)))
        self.wl_display._ptr = wl_display_ptr
        self.registry = self.wl_display.get_registry()
        self.registry.dispatcher["global"] = self.on_global
        self.registry.dispatcher["global_remove"] = self.on_global_remove
        self.wl_display.roundtrip()

    def receive_output_name(self, output: WlOutput, name: str):
        output.name = name

    def on_global(self, registry, name, interface, version):
        if interface == "wl_output":
            output = registry.bind(name, WlOutput, version)
            output.id = name
            output.name = None
            output.dispatcher["name"] = self.receive_output_name
            while not output.name:
                self.wl_display.dispatch(block=True)
            if not output.name.startswith("live-preview"):
                self.make_backgrounds()
                self.wl_output_ids[name] = output

    def on_global_remove(self, registry, name):
        if name in self.wl_output_ids:
            self.wl_output_ids[name].destroy()
            del self.wl_output_ids[name]


class BackgroundWindow(Gtk.Window):
    def __init__(self, application: Gtk.Application, monitor: Gdk.Monitor):
        super().__init__(application=application)
        self.set_decorated(False)

        Gtk4LayerShell.init_for_window(self)
        Gtk4LayerShell.set_namespace(self, "com.roundabout_host.panorama.background")
        Gtk4LayerShell.set_monitor(self, monitor)
        Gtk4LayerShell.set_layer(self, Gtk4LayerShell.Layer.BACKGROUND)

        Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.TOP, True)
        Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.LEFT, True)
        Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.BOTTOM, True)
        Gtk4LayerShell.set_anchor(self, Gtk4LayerShell.Edge.RIGHT, True)

        self.picture = Gtk.Picture()
        self.set_child(self.picture)


if __name__ == "__main__":
    app = PanoramaBG()
    app.run()

