"""
Panorama weather: weather app for GNU/Linux desktops.
Copyright 2026, 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 itertools
import locale
import os
import sys
import json

import gi

from pathlib import Path

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

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


module_directory = Path(__file__).resolve().parent
locale.bindtextdomain("panorama-weather", str(module_directory / "locale"))
locale.textdomain("locale")
locale.setlocale(locale.LC_ALL)
_ = locale.gettext


custom_css = """
.panorama-weather-current-temperature {
    font-weight: 100;
    font-size: 5rem;
}

.panorama-weather-table-header {
    font-weight: 600;
}

.panorama-weather-table-temperature {
    font-size: 1.125em;
}
"""
css_provider = Gtk.CssProvider()
css_provider.load_from_data(custom_css)
Gtk.StyleContext.add_provider_for_display(
    Gdk.Display.get_default(),
    css_provider,
    100
)


def relative_timestamp(datetime: GLib.DateTime) -> str:
    now = GLib.DateTime.new_now_local()
    seconds = int(now.difference(datetime) / GLib.TIME_SPAN_SECOND)

    if seconds < 30:
        return _("just now")
    if seconds < 240:
        return _("{time} seconds ago").format(time=seconds)
    if seconds < 7200:
        return _("{time} minutes ago").format(time=seconds//60)
    if seconds < 345600:
        return _("{time} hours ago").format(time=seconds//3600)
    return _("{time} days ago").format(time=seconds//86400)


@Gtk.Template(filename=str(module_directory / "panorama-weather-report.ui"))
class WeatherReport(Gtk.Box):
    __gtype_name__ = "WeatherReport"

    current_details: Gtk.Label = Gtk.Template.Child()
    current_temperature: Gtk.Label = Gtk.Template.Child()
    current_weather_icon: Gtk.Image = Gtk.Template.Child()
    forecast_box: Gtk.ListBox = Gtk.Template.Child()
    attribution_label: Gtk.Label = Gtk.Template.Child()
    
    def __init__(self, data, **kwargs):
        super().__init__(**kwargs)

        self.data = data
        self.current_weather_icon.set_from_icon_name(data["icon_name"])
        self.current_temperature.set_text(_("{temp} °{temp_unit}").format(
                temp=locale.format_string("%.0f", data["temp_value"]),
                temp_unit=data["units"]["temp"]
        ))
        self.current_details.set_text(_("{prec} {prec_unit} in the last hour").format(
                prec=locale.format_string("%.2f", data["prec"]),
                prec_unit=data["units"]["prec"]
        ))
        self.update_time = GLib.DateTime.new_from_unix_utc(data["time"]).to_local()
        self.attribution_label.set_text(_("Retrieved {timestamp}. Source: {attribution}.").format(
                timestamp=relative_timestamp(self.update_time),
                attribution=data["attribution"]
        ))

        self.time_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
        self.icon_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
        self.temperature_size_group = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)

        self.header_box = Gtk.Box(spacing=4)
        self.time_header = Gtk.Label.new(_("Time"))
        self.time_header.add_css_class("panorama-weather-table-header")
        self.time_size_group.add_widget(self.time_header)
        self.condition_header = Gtk.Box()
        self.icon_size_group.add_widget(self.condition_header)
        self.temperature_header = Gtk.Label.new(_("Temperature"))
        self.temperature_header.add_css_class("panorama-weather-table-header")
        self.temperature_size_group.add_widget(self.temperature_header)
        self.header_box.append(self.time_header)
        self.header_box.append(Gtk.Box(hexpand=True))  # some empty space
        self.header_box.append(self.temperature_header)
        self.header_box.append(self.condition_header)
        self.table_header = Gtk.ListBoxRow(child=self.header_box, activatable=False)
        self.update_forecast_table()

    def update_forecast_table(self):
        self.forecast_box.remove_all()
        self.forecast_box.append(self.table_header)

        for point in self.data["forecast"]:
            box = Gtk.Box(spacing=4)

            forecasted_time = GLib.DateTime.new_from_unix_utc(point["time"]).to_local()
            time_label = Gtk.Label.new(forecasted_time.format("%H"))
            time_label.set_xalign(0)
            self.time_size_group.add_widget(time_label)
            box.append(time_label)

            box.append(Gtk.Box(hexpand=True))  # some empty space

            temperature_label = Gtk.Label.new(_("{temp} °{temp_unit}").format(
                    temp=locale.format_string("%.1f", point["temp_value"]),
                    temp_unit=self.data["units"]["temp"]
            ))
            temperature_label.set_xalign(1)
            temperature_label.add_css_class("panorama-weather-table-temperature")
            self.temperature_size_group.add_widget(temperature_label)
            box.append(temperature_label)

            icon = Gtk.Image.new_from_icon_name(point["icon_name"])
            icon.set_icon_size(Gtk.IconSize.LARGE)
            icon.set_halign(Gtk.Align.END)
            self.icon_size_group.add_widget(icon)
            box.append(icon)

            row = Gtk.ListBoxRow(child=box)
            row.day = GLib.DateTime.new_local(forecasted_time.get_year(), forecasted_time.get_month(), forecasted_time.get_day_of_month(), 0, 0, 0)
            self.forecast_box.append(row)

        self.forecast_box.set_header_func(self.make_day_headers)

    def make_day_headers(self, b: Gtk.ListBoxRow, a: Gtk.ListBoxRow):
        if a is not None and hasattr(b, "day") and ((hasattr(a, "day") and not a.day.equal(b.day)) or not hasattr(a, "day")):
            b.set_header(Gtk.Label.new(b.day.format("%A, %-d %B")))
        else:
            b.set_header(None)


@Gtk.Template(filename=str(module_directory / "panorama-weather.ui"))
class WeatherWindow(Gtk.Window):
    __gtype_name__ = "WeatherWindow"

    sidebar_revealer: Gtk.Revealer = Gtk.Template.Child()
    sidebar: Gtk.Box = Gtk.Template.Child()
    inner_sidebar: Gtk.Box = Gtk.Template.Child()
    drag_handle: Gtk.Separator = Gtk.Template.Child()
    weather_stack: Gtk.Stack = Gtk.Template.Child()

    def sidebar_revealer_change(self, action, state):
        action.set_state(state)
        self.sidebar_revealer.set_reveal_child(state.get_boolean())

    def begin_drag(self, gesture, start_x, start_y):
        x_win, y_win = self.drag_handle.translate_coordinates(self, start_x, start_y)
        gesture.widget_to_update.start_w = gesture.widget_to_update.get_width()
        gesture.widget_to_update.start_x = x_win
        gesture.start_x = start_x

    def update_drag(self, gesture, dx, dy):
        x_win, y_win = self.drag_handle.translate_coordinates(self, dx + gesture.start_x, 0)
        gesture.widget_to_update.set_size_request(gesture.widget_to_update.start_w + x_win - gesture.widget_to_update.start_x, -1)

    def set_window_title(self, stack, *args):
        self.set_title(stack.get_page(stack.get_visible_child()).get_title())

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.sidebar_revealer.set_hexpand(False)
        self.weather_stack.set_hexpand(True)
        self.weather_stack.set_halign(Gtk.Align.FILL)
        self.weather_stack.connect("notify::visible-child", self.set_window_title)

        self.action_group = Gio.SimpleActionGroup()
        self.sidebar_action = Gio.SimpleAction.new_stateful("reveal-sidebar", None, GLib.Variant.new_boolean(True))
        self.sidebar_action.connect("change-state", self.sidebar_revealer_change)
        self.action_group.add_action(self.sidebar_action)
        self.insert_action_group("win", self.action_group)

        drag_controller = Gtk.GestureDrag()
        drag_controller.widget_to_update = self.sidebar
        drag_controller.connect("drag-begin", self.begin_drag)
        drag_controller.connect("drag-update", self.update_drag)
        self.drag_handle.add_controller(drag_controller)
        self.drag_handle.set_cursor(Gdk.Cursor.new_from_name("ew-resize"))

    def read_all(self):
        application: PanoramaWeather = self.get_application()
        # Read the weather files
        for file in application.data_directory.iterdir():
            with open(file, "r") as f:
                data = json.load(f)
                self.weather_stack.add_titled(WeatherReport(data), None, data["location"]["name"])


class PanoramaWeather(Gtk.Application):
    def __init__(self):
        super().__init__(
                application_id="com.roundabout_host.roundabout.PanoramaWeather"
        )
        self.data_directory = Path(os.environ.get("XDG_DATA_HOME", "~/.local/share")).expanduser() / "weather" / "data" / "locations"

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

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

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

        window = WeatherWindow()
        self.add_window(window)
        window.present()
        window.read_all()


if __name__ == "__main__":
    app = PanoramaWeather()
    app.run(sys.argv)
