#!/usr/bin/python
# https://github.com/sentientwaffle/unity-ip-indicator
# This version has been updated to work with Gtk3 via GI

import os
import socket
import re

import gi
import netifaces
import ipaddress
import qrcode
import io
from PIL import Image

gi.require_version("Gtk", "3.0")
gi.require_version("AppIndicator3", "0.1")
gi.require_version("GdkPixbuf", "2.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, GLib, GdkPixbuf, Gdk
from gi.repository import AppIndicator3 as appindicator


class IPIndicator:
    REFRESH_PERIOD = 5000
    ICON_NAME = "network-workgroup-symbolic"
    HIDE_SUBNETS = (ipaddress.ip_network("10.200.0.0/16"),)
    HIDE_INTERFACES = (
        "lo",
        r"docker\d+",
    )

    def __init__(self):
        self.data = None
        self.startup_window = None
        self.startup_content = None
        self.ind = appindicator.Indicator.new(
            "ip-indicator",
            "indicator-messages",
            appindicator.IndicatorCategory.APPLICATION_STATUS,
        )
        self.ind.set_status(appindicator.IndicatorStatus.ACTIVE)
        self.ind.set_icon_full(self.ICON_NAME, "indicatorip")

        self.update()
        self.ind.set_menu(self.setup_menu())
        GLib.idle_add(self.show_startup_window)
        GLib.timeout_add(self.REFRESH_PERIOD, self.update)

    def get_active_ips(self):
        addresses = []

        for iface in netifaces.interfaces():
            iface_details = netifaces.ifaddresses(iface)
            if iface != "lo" and netifaces.AF_INET in iface_details:
                for entry in iface_details[netifaces.AF_INET]:
                    addr = ipaddress.ip_address(entry["addr"])
                    if not any([addr in sub for sub in self.HIDE_SUBNETS]):
                        addresses += [
                            f"{entry['addr']}/{ipaddress.IPv4Network('0.0.0.0/' + entry['netmask']).prefixlen}"
                        ]

        return addresses

    def get_interface_macs(self):
        """Get MAC addresses for non-local network interfaces, sorted by name"""
        macs = []

        for iface in sorted(netifaces.interfaces()):
            if any([re.match(pattern, iface) for pattern in self.HIDE_INTERFACES]):
                continue

            iface_details = netifaces.ifaddresses(iface)
            if netifaces.AF_LINK in iface_details:
                for entry in iface_details[netifaces.AF_LINK]:
                    if "addr" in entry:
                        mac = entry["addr"]
                        if mac and mac != "00:00:00:00:00:00":
                            macs.append((iface, mac))

        return macs

    def create_qr_pixbuf(self, data, size=100):
        """Create a QR code and return as GdkPixbuf"""
        qr = qrcode.QRCode(
            version=1,
            error_correction=qrcode.constants.ERROR_CORRECT_L,
            box_size=6,
            border=2,
        )
        qr.add_data(data)
        qr.make(fit=True)

        img = qr.make_image(fill_color="black", back_color="white")
        img = img.resize((size, size), Image.LANCZOS)

        # Convert PIL image to GdkPixbuf
        buf = io.BytesIO()
        img.save(buf, format="PNG")
        buf.seek(0)

        loader = GdkPixbuf.PixbufLoader.new_with_type("png")
        loader.write(buf.read())
        loader.close()

        return loader.get_pixbuf()

    def show_qr_window(self, widget, iface, mac):
        """Show a window with a large QR code for the MAC address"""
        window = Gtk.Window(title=f"QR Code for {iface}")
        window.set_default_size(400, 450)
        window.set_resizable(False)
        window.set_position(Gtk.WindowPosition.CENTER)

        # Create main container
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        vbox.set_margin_top(20)
        vbox.set_margin_bottom(20)
        vbox.set_margin_start(20)
        vbox.set_margin_end(20)

        # Interface name label
        title_label = Gtk.Label(label=f"Interface: {iface}")
        title_label.set_markup(f"<b><big>Interface: {iface}</big></b>")
        vbox.pack_start(title_label, False, False, 0)

        # MAC address label
        mac_label = Gtk.Label(label=f"MAC: {mac}")
        mac_label.set_markup(f"<tt>{mac}</tt>")
        mac_label.set_selectable(True)
        vbox.pack_start(mac_label, False, False, 0)

        # Create large QR code
        try:
            qr_pixbuf = self.create_qr_pixbuf(mac, 300)
            qr_image = Gtk.Image.new_from_pixbuf(qr_pixbuf)
            vbox.pack_start(qr_image, True, True, 0)
        except Exception as e:
            error_label = Gtk.Label(label=f"Error creating QR code: {e}")
            vbox.pack_start(error_label, True, True, 0)

        # Close button
        close_button = Gtk.Button(label="Close")
        close_button.connect("clicked", lambda w: window.destroy())
        vbox.pack_start(close_button, False, False, 0)

        window.add(vbox)
        window.show_all()

    def create_info_content(self, for_popup=False):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)

        hostname = socket.getfqdn()
        ips_list = self.get_active_ips()
        ip_text = ", ".join(ips_list) if ips_list else "N/A"

        if for_popup:
            box.set_margin_top(16)
            box.set_margin_bottom(16)
            box.set_margin_start(16)
            box.set_margin_end(16)

            title_label = Gtk.Label()
            title_label.set_markup(f"<b>{hostname}</b>")
            title_label.set_xalign(0)
            box.pack_start(title_label, False, False, 0)

            ip_label = Gtk.Label(label=f"IP: {ip_text}")
            ip_label.set_xalign(0)
            ip_label.set_selectable(True)
            box.pack_start(ip_label, False, False, 0)

            separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
            box.pack_start(separator, False, False, 6)

            section_label = Gtk.Label()
            section_label.set_markup("<b>Network Interfaces</b>")
            section_label.set_xalign(0)
            box.pack_start(section_label, False, False, 0)
        else:
            ip_header = Gtk.MenuItem(label=f"🖥️  {hostname}")
            ip_header.set_sensitive(False)
            ip_header.show()
            box.pack_start(ip_header, False, False, 0)

            ip_item = Gtk.MenuItem(label=f"📍 IP: {ip_text}")
            ip_item.set_sensitive(False)
            ip_item.show()
            box.pack_start(ip_item, False, False, 0)

        macs = self.get_interface_macs()

        if not macs:
            if for_popup:
                no_macs = Gtk.Label(label="No network interfaces found")
                no_macs.set_xalign(0)
                box.pack_start(no_macs, False, False, 0)
            else:
                no_macs = Gtk.MenuItem(label="No network interfaces found")
                no_macs.set_sensitive(False)
                no_macs.show()
                box.pack_start(no_macs, False, False, 0)
            return box

        for iface, mac in macs:
            if for_popup:
                button = Gtk.Button(label=f"{iface}: {mac}")
                button.set_hexpand(True)
                button.set_halign(Gtk.Align.FILL)
                button_child = button.get_child()
                if isinstance(button_child, Gtk.Label):
                    button_child.set_xalign(0)
                button.connect("clicked", self.show_qr_window, iface, mac)
                box.pack_start(button, False, False, 0)
            else:
                item = Gtk.MenuItem(label=f"   {iface}: {mac}")
                item.connect("activate", self.show_qr_window, iface, mac)
                item.show()
                box.pack_start(item, False, False, 0)

        return box

    def hide_startup_window(self, *args):
        if self.startup_window is not None:
            self.startup_window.hide()
        return False

    def on_startup_focus_out(self, widget, event):
        self.hide_startup_window()
        return False

    def position_startup_window(self):
        if self.startup_window is None:
            return

        display = self.startup_window.get_display()
        if display is None:
            return

        seat = display.get_default_seat() if display is not None else None
        pointer = seat.get_pointer() if seat is not None else None

        if pointer is not None:
            _, x_pos, y_pos = pointer.get_position()
            monitor = display.get_monitor_at_point(x_pos, y_pos)
        else:
            monitor = display.get_primary_monitor()

        if monitor is None:
            monitor = display.get_monitor(0)
        if monitor is None:
            return

        geometry = monitor.get_geometry()
        self.startup_window.get_size()
        width, height = self.startup_window.get_size()
        margin = 24

        x_pos = geometry.x + geometry.width - width - margin
        y_pos = geometry.y + geometry.height - height - margin
        self.startup_window.move(x_pos, y_pos)

    def show_startup_window(self):
        if self.startup_window is None:
            window = Gtk.Window(title="indicator-ip")
            window.set_type_hint(Gdk.WindowTypeHint.DIALOG)
            window.set_default_size(360, -1)
            window.set_resizable(False)
            window.set_decorated(False)
            window.set_skip_taskbar_hint(True)
            window.set_skip_pager_hint(True)
            window.set_keep_above(True)
            window.connect("focus-out-event", self.on_startup_focus_out)
            window.connect("delete-event", self.hide_startup_window)
            self.startup_window = window

        if self.startup_content is not None:
            self.startup_window.remove(self.startup_content)

        self.startup_content = self.create_info_content(for_popup=True)
        self.startup_window.add(self.startup_content)
        self.startup_window.show_all()
        self.position_startup_window()
        self.startup_window.present()
        return False

    def setup_menu(self):
        menu = Gtk.Menu()
        # Add current IP information at the top of the menu
        ips_list = self.get_active_ips()
        hostname = socket.getfqdn()
        ips_str = ", ".join(ips_list) if ips_list else "N/A"

        ip_header = Gtk.MenuItem(label=f"🖥️  {hostname}")
        ip_header.set_sensitive(False)
        ip_header.show()
        menu.append(ip_header)

        ip_item = Gtk.MenuItem(label=f"📍 IP: {ips_str}")
        ip_item.set_sensitive(False)
        ip_item.show()
        menu.append(ip_item)

        separator = Gtk.SeparatorMenuItem()
        separator.show()
        menu.append(separator)

        macs = self.get_interface_macs()

        if not macs:
            no_macs = Gtk.MenuItem(label="No network interfaces found")
            no_macs.set_sensitive(False)
            no_macs.show()
            menu.append(no_macs)
        else:
            mac_header = Gtk.MenuItem(label="🔗 Network Interfaces (click for QR):")
            mac_header.set_sensitive(False)
            mac_header.show()
            menu.append(mac_header)

            for iface, mac in macs:
                item = Gtk.MenuItem(label=f"   {iface}: {mac}")
                item.connect("activate", self.show_qr_window, iface, mac)
                item.show()
                menu.append(item)

        return menu

    def update(self):
        ips_list = self.get_active_ips()
        if ips_list:
            ips_str = ", ".join(ips_list)
        else:
            ips_str = "N/A"

        hostname = socket.getfqdn()
        macs = self.get_interface_macs()
        data = (ips_str, hostname, macs)

        if data != self.data:
            self.data = data
            # Set both label and icon tooltip for better visibility
            self.ind.set_label(f"{hostname} ({ips_str})", "")
            self.ind.set_icon_full(self.ICON_NAME, f"IP: {ips_str}")
            # Rebuild menu when interfaces change
            self.ind.set_menu(self.setup_menu())
            if self.startup_window is not None and self.startup_window.get_visible():
                if self.startup_content is not None:
                    self.startup_window.remove(self.startup_content)
                self.startup_content = self.create_info_content(for_popup=True)
                self.startup_window.add(self.startup_content)
                self.startup_window.show_all()

        return True


if __name__ == "__main__":
    i = IPIndicator()
    Gtk.main()
