"""
Implements the org.freedesktop.StatusNotifierWatcher and nothing else.
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public Licence as published by
the Free Software Foundation, either version 3 of the Licence, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public Licence for more details.

You should have received a copy of the GNU General Public Licence
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gio, GLib

class StatusNotifierWatcher:
    def __init__(self, connection: Gio.DBusConnection, interface_name: str):
        self.connection = connection
        self.interface_name = interface_name
        self.items = {}
        self.hosts = set()

        self.node_info = Gio.DBusNodeInfo.new_for_xml(f"""
            <node>
              <interface name="{interface_name}">
                <method name="RegisterStatusNotifierItem">
                  <arg type="s" name="service" direction="in"/>
                </method>
                <method name="RegisterStatusNotifierHost">
                  <arg type="s" name="service" direction="in"/>
                </method>
                <property name="RegisteredStatusNotifierItems" type="as" access="read"/>
                <property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
                <property name="ProtocolVersion" type="i" access="read"/>
                <signal name="StatusNotifierItemRegistered">
                  <arg type="s" name="service" direction="out"/>
                </signal>
                <signal name="StatusNotifierItemUnregistered">
                  <arg type="s" name="service" direction="out"/>
                </signal>
                <signal name="StatusNotifierHostRegistered">
                  <arg type="s" name="service" direction="out"/>
                </signal>
              </interface>
            </node>
            """
        )
        self.interface_info = self.node_info.interfaces[0]

        self.connection.register_object(
            "/StatusNotifierWatcher",
            self.interface_info,
            self.handle_method_call,
            self.get_property,
            None
        )

        self.connection.signal_subscribe(
            "org.freedesktop.DBus",
            "org.freedesktop.DBus",
            "NameOwnerChanged",
            "/org/freedesktop/DBus",
            None,
            Gio.DBusSignalFlags.NONE,
            self.on_name_owner_changed,
        )

    def get_property(self, connection, sender, object_path, interface_name, property_name):
        if property_name == "IsStatusNotifierHostRegistered":
            return GLib.Variant("b", bool(self.hosts))
        elif property_name == "RegisteredStatusNotifierItems":
            return GLib.Variant("as", list(self.items))
        elif property_name == "ProtocolVersion":
            return GLib.Variant("i", 0)
        else:
            raise Exception(f"Unknown property {property_name}")

    def on_name_owner_changed(self, connection, sender_name, object_path, interface_name, signal_name, params):
        name, old_owner, new_owner = params.unpack()
        if new_owner == "":
            for lookup in (name, old_owner):
                if lookup in self.items:
                    path = self.items[lookup]
                    del self.items[lookup]
                    print(f"Item {lookup} vanished")
                    self.emit_signal("StatusNotifierItemUnregistered", lookup + ":" + path)
                if lookup in self.hosts:
                    self.hosts.discard(lookup)
                    print(f"Host {lookup} vanished")

    def handle_method_call(self, conn, sender, obj_path, iface_name, method, params, invocation):
        if method == "RegisterStatusNotifierItem":
            (arg_path,) = params.unpack()
            self._register_item(sender, arg_path)
            invocation.return_value(None)
        elif method == "RegisterStatusNotifierHost":
            (service,) = params.unpack()
            self._register_host(sender, service)
            invocation.return_value(None)
        else:
            invocation.return_dbus_error("org.freedesktop.DBus.Error.UnknownMethod", "Unknown method")

    def _register_item(self, sender, path):
        if sender not in self.items:
            if not path.startswith("/"):
                path = "/StatusNotifierItem"
            self.items[sender] = path
            print(f"Registered item {sender}")
            self.emit_signal("StatusNotifierItemRegistered", sender + ":" + path)

    def _register_host(self, sender, service):
        if sender not in self.hosts:
            self.hosts.add(sender)
            print(f"Registered host {sender}")
            self.emit_signal("StatusNotifierHostRegistered", sender)

    def emit_signal(self, name, arg=None):
        args = GLib.Variant("(s)", (arg,)) if arg else None
        self.connection.emit_signal(
            None,
            "/StatusNotifierWatcher",
            self.interface_name,
            name,
            args
        )


class WatcherApplication(Gtk.Application):
    def __init__(self):
        super().__init__(
            application_id="com.roundabout_host.roundabout.PanoramaIndicatorService",
            flags=Gio.ApplicationFlags.FLAGS_NONE,
        )
        self.fd_watcher = None
        self.kde_watcher = None

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

    def do_activate(self):
        print("StatusNotifierWatcher starting...")

        Gio.bus_own_name(
            Gio.BusType.SESSION,
            "org.freedesktop.StatusNotifierWatcher",
            Gio.BusNameOwnerFlags.NONE,
            self.on_bus_acquired_fd,
            None,
            None,
        )
        Gio.bus_own_name(
            Gio.BusType.SESSION,
            "org.kde.StatusNotifierWatcher",
            Gio.BusNameOwnerFlags.NONE,
            self.on_bus_acquired_kde,
            None,
            None,
        )

        self.hold()

    def on_bus_acquired_fd(self, connection, name):
        self.fd_watcher = StatusNotifierWatcher(connection, "org.freedesktop.StatusNotifierWatcher")

    def on_bus_acquired_kde(self, connection, name):
        self.kde_watcher = StatusNotifierWatcher(connection, "org.kde.StatusNotifierWatcher")

    def do_shutdown(self):
        Gtk.Application.do_shutdown(self)


if __name__ == "__main__":
    app = WatcherApplication()
    app.run(None)
