#!/usr/bin/env python3

import subprocess
import sys
from datetime import datetime
from pathlib import Path

import gi

gi.require_version("Gtk", "3.0")
gi.require_version("GtkLayerShell", "0.1")

from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GtkLayerShell

sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")

import classroom_style as style  # noqa: E402
from classroom_config import (  # noqa: E402
    enabled,
    read_device_config,
    scaled,
    ui_scale,
)

# Optional on purpose. The dock is the one thing the classroom cannot
# afford to lose, so a missing DVD helper simply disables disc detection
# instead of taking the whole shell down.
try:
    import classroom_optical as optical
except ImportError:
    optical = None


ICON_DIR = Path("/etc/ekklesia-classroom/icons")

DVD_POLL_SECONDS = 2

# Every measurement in the shell, at UI_SCALE=1.0. The CSS template
# carries the same numbers; these are the ones GTK needs in Python.
SHELL_HEIGHT = 76
DOCK_SPACING = 10
CLOCK_EDGE_PADDING = 20

BROWSER_ICON_SIZE = 28
DVD_ICON_SIZE = 40
SANCTUARY_ICON_SIZE = 38


def launch(command):
    subprocess.Popen(command, start_new_session=True)


def notify(title, message):
    launch([
        "notify-send",
        "--app-name=Ekklesia Classroom",
        title,
        message,
    ])


def themed_icon(*names):
    theme = Gtk.IconTheme.get_default()

    for name in names:
        if theme.has_icon(name):
            return Gtk.Image.new_from_icon_name(
                name,
                Gtk.IconSize.DIALOG,
            )

    return Gtk.Image.new_from_icon_name(
        "application-x-executable",
        Gtk.IconSize.DIALOG,
    )


def custom_icon(filename, size):
    path = ICON_DIR / filename

    if not path.exists():
        return None

    pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
        str(path),
        size,
        size,
        True,
    )

    return Gtk.Image.new_from_pixbuf(pixbuf)


class ClassroomShell(Gtk.Window):
    def __init__(self):
        super().__init__()

        screen = self.get_screen()
        visual = screen.get_rgba_visual()

        if visual is not None:
            self.set_visual(visual)

        self.set_app_paintable(True)
        self.set_name("classroom-shell")
        self.set_decorated(False)
        self.set_resizable(False)

        self.config = read_device_config()
        self.scale = ui_scale(self.config)

        self.dvd_image = None
        self.disc_ready = None

        GtkLayerShell.init_for_window(self)
        GtkLayerShell.set_layer(
            self,
            GtkLayerShell.Layer.TOP,
        )

        GtkLayerShell.set_anchor(
            self,
            GtkLayerShell.Edge.BOTTOM,
            True,
        )
        GtkLayerShell.set_anchor(
            self,
            GtkLayerShell.Edge.LEFT,
            True,
        )
        GtkLayerShell.set_anchor(
            self,
            GtkLayerShell.Edge.RIGHT,
            True,
        )

        shell_height = scaled(SHELL_HEIGHT, self.scale)

        GtkLayerShell.set_exclusive_zone(self, shell_height)
        GtkLayerShell.set_keyboard_interactivity(
            self,
            False,
        )

        layout = Gtk.Overlay()
        layout.set_size_request(-1, shell_height)

        dock = self.build_dock()

        dock_align = Gtk.Alignment(
            xalign=0.5,
            yalign=0.5,
            xscale=0,
            yscale=0,
        )
        dock_align.add(dock)
        layout.add(dock_align)

        self.clock = Gtk.Label()
        self.clock.get_style_context().add_class("clock")

        clock_align = Gtk.Alignment(
            xalign=1.0,
            yalign=0.5,
            xscale=0,
            yscale=0,
        )
        clock_align.set_padding(
            0,
            0,
            0,
            scaled(CLOCK_EDGE_PADDING, self.scale),
        )
        clock_align.add(self.clock)

        layout.add_overlay(clock_align)
        layout.set_overlay_pass_through(clock_align, True)

        self.add(layout)

        self.update_time()
        GLib.timeout_add_seconds(
            15,
            self.update_time,
        )

    def build_dock(self):
        dock = Gtk.Box(
            orientation=Gtk.Orientation.HORIZONTAL,
            spacing=scaled(DOCK_SPACING, self.scale),
        )
        dock.get_style_context().add_class("dock")

        dock.pack_start(
            self.make_button(
                self.browser_icon(),
                lambda *_: launch(
                    ["/usr/local/bin/classroom-browser"]
                ),
            ),
            False,
            False,
            0,
        )

        if enabled(
            self.config,
            "DVD_ENABLED",
            False,
        ):
            dock.pack_start(
                self.make_button(
                    self.dvd_icon(),
                    lambda *_: launch(
                        ["/usr/local/bin/classroom-dvd", "toggle"]
                    ),
                ),
                False,
                False,
                0,
            )

            self.start_disc_watch()

        dock.pack_start(
            self.make_button(
                self.sanctuary_icon(),
                lambda *_: notify(
                    "Sanctuary",
                    "Sanctuary live view is being prepared.",
                ),
            ),
            False,
            False,
            0,
        )

        return dock

    def make_button(self, image, callback):
        button = Gtk.Button()
        button.set_relief(Gtk.ReliefStyle.NONE)
        button.get_style_context().add_class(
            "dock-button"
        )

        button.add(image)
        button.connect("clicked", callback)

        return button

    def browser_icon(self):
        size = scaled(BROWSER_ICON_SIZE, self.scale)

        image = custom_icon(
            "browser.svg",
            size,
        )

        if image is not None:
            return image

        image = themed_icon(
            "web-browser-symbolic",
            "internet-web-browser-symbolic",
        )
        image.set_pixel_size(size)

        return image

    def dvd_icon(self):
        size = scaled(DVD_ICON_SIZE, self.scale)

        image = custom_icon(
            "dvd.svg",
            size,
        )

        if image is not None:
            self.dvd_image = image
            return image

        image = themed_icon(
            "media-optical-symbolic",
            "media-optical",
        )
        image.set_pixel_size(size)

        return image

    def sanctuary_icon(self):
        is_live = enabled(
            self.config,
            "SANCTUARY_LIVE",
            False,
        )

        filename = (
            "live.svg"
            if is_live
            else "live-off.svg"
        )

        size = scaled(SANCTUARY_ICON_SIZE, self.scale)

        image = custom_icon(
            filename,
            size,
        )

        if image is not None:
            return image

        image = themed_icon(
            "video-display-symbolic",
            "video-display",
        )
        image.set_pixel_size(size)

        return image

    def start_disc_watch(self):
        if optical is None:
            return

        self.check_disc()

        GLib.timeout_add_seconds(
            DVD_POLL_SECONDS,
            self.check_disc,
        )

    def check_disc(self):
        ready = optical.drive_status() == optical.CDS_DISC_OK

        # The disc indicator: the DVD logo sits back when the drive is
        # empty or unplugged and comes up to full strength once a disc is
        # ready, the same gray/colour language the LIVE icon uses.
        if self.dvd_image is not None:
            self.dvd_image.set_opacity(1.0 if ready else 0.55)

        # The first poll only records what is already in the drive, so a
        # disc left in over the week does not announce itself at boot.
        if self.disc_ready is not None and ready and not self.disc_ready:
            notify(
                "DVD Ready",
                "A disc is ready to play.",
            )

        self.disc_ready = ready

        return True

    def update_time(self):
        self.clock.set_text(
            datetime.now().strftime("%-I:%M %p")
        )
        return True


def load_css(scale):
    # Styling is worth losing before the dock is. A stylesheet that will
    # not parse leaves an unstyled but working shell rather than no shell.
    try:
        provider = Gtk.CssProvider()
        provider.load_from_data(
            style.render(scale).encode("utf-8")
        )
    except (OSError, GLib.Error) as error:
        print(
            f"classroom-dock: stylesheet not applied: {error}",
            file=sys.stderr,
        )
        return

    screen = Gdk.Screen.get_default()

    Gtk.StyleContext.add_provider_for_screen(
        screen,
        provider,
        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
    )


def main():
    load_css(ui_scale(read_device_config()))

    shell = ClassroomShell()
    shell.connect(
        "destroy",
        Gtk.main_quit,
    )
    shell.show_all()

    Gtk.main()


if __name__ == "__main__":
    main()
