"""
Clock applet for the Panorama panel.
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 os
from pathlib import Path
import panorama_panel

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

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


SECOND_PLACEHOLDERS = ("%c", "%s", "%S", "%T", "%X")


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


@Gtk.Template(filename=str(module_directory / "panorama-clock-options.ui"))
class ClockOptions(Gtk.Window):
    __gtype_name__ = "ClockOptions"
    format_entry: Gtk.Entry = Gtk.Template.Child()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.connect("close-request", lambda *args: self.destroy())


class ClockApplet(panorama_panel.Applet):
    name = "Clock"
    description = "Read the current time and date"

    def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None):
        super().__init__(orientation=orientation, config=config)
        if config is None:
            config = {}
        self.button = Gtk.MenuButton()
        self.button.set_has_frame(False)   # flat look
        self.label = Gtk.Label()
        self.button.set_child(self.label)

        # Create the monthly calendar
        self.popover = Gtk.Popover()
        panorama_panel.track_popover(self.popover)
        self.calendar = Gtk.Calendar()
        self.calendar.set_show_week_numbers(True)
        self.popover.set_child(self.calendar)
        self.button.set_popover(self.popover)

        self.append(self.button)

        self.formatting = config.get("formatting", "%c")
        # Some placeholders require second precision, but not all of them. If not required,
        # use minute precision
        self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS)
        self.next_update = None
        self.set_time()

        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 make_context_menu(self):
        menu = Gio.Menu()
        menu.append("Clock _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 update_formatting(self, entry):
        self.formatting = entry.get_text()
        # Some placeholders require second precision, but not all of them. If not required,
        # use minute precision
        self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS)
        if self.next_update is not None:
            GLib.source_remove(self.next_update)
            self.next_update = None
        self.set_time()
        self.emit("config-changed")

    def show_options(self, _0=None, _1=None):
        if self.options_window is None:
            self.options_window = ClockOptions()
            self.options_window.format_entry.set_text(self.formatting)
            self.options_window.format_entry.connect("changed", self.update_formatting)

            def reset_window(*args):
                self.options_window = None

            self.options_window.connect("close-request", reset_window)
        self.options_window.present()

    def set_time(self):
        datetime = GLib.DateTime.new_now_local()
        formatted_time = datetime.format(self.formatting)
        if formatted_time is not None:
            self.label.set_text(datetime.format(self.formatting))
        else:
            self.label.set_text("Invalid time formatting")
            return False

        if self.has_second_precision:
            current_ms = GLib.DateTime.new_now_local().get_microsecond() // 1000
            self.next_update = GLib.timeout_add(1000 - current_ms + 1, self.set_time)  # 1ms is added to ensure the clock is updated
        else:
            now = GLib.DateTime.new_now_local()
            current_ms = now.get_second() * 1000 + now.get_microsecond() // 1000
            self.next_update = GLib.timeout_add(60000 - current_ms + 1, self.set_time)
        return False   # Do not rerun the current timeout; a new one has been scheduled

    def get_config(self):
        return {"formatting": self.formatting}

    def set_panel_position(self, position):
        self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position])
        self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]])
