#!/usr/bin/env python3
import os.path

from gi.repository import GLib

import subprocess
from datetime import datetime

from nwg_panel.tools import (check_key, eprint, local_dir, load_json, save_json, update_image, update_gtk_entry,
                             create_background_task, cmd_through_compositor)

import gi

gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')

from gi.repository import Gtk, Gdk, GtkLayerShell


class Clock(Gtk.EventBox):
    def __init__(self, settings, icons_path=""):
        self.reminder_img_updated = False
        self.path = ""
        self.cal = None
        self.note_box = None
        self.popup = None
        self.note_entry = None
        self.icons_path = icons_path

        self.settings = settings
        Gtk.EventBox.__init__(self)
        self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)

        self.add(self.box)
        self.label = Gtk.Label.new("")

        defaults = {"root-css-name": "root-clock",
                    "css-name": "clock",
                    "tooltip-text": "",
                    "tooltip-date-format": False,
                    "on-right-click": "",
                    "on-middle-click": "",
                    "on-scroll-up": "",
                    "on-scroll-down": "",
                    "interval": 1,
                    "angle": 0.0,
                    "calendar-path": "",
                    "calendar-css-name": "calendar-window",
                    "calendar-placement": "top",
                    "calendar-margin-horizontal": 0,
                    "calendar-margin-vertical": 0,
                    "calendar-icon-size": 24,
                    "calendar-interval": 60,
                    "calendar-on": True}

        for key in defaults:
            check_key(settings, key, defaults[key])

        self.reminder_img = Gtk.Image()

        self.calendar = {}

        self.set_property("name", settings["root-css-name"])
        self.label.set_property("name", settings["css-name"])

        self.label.set_angle(settings["angle"])

        if settings["tooltip-text"]:
            self.set_tooltip_text(settings["tooltip-text"])

        if "format" not in settings:
            self.settings["format"] = "%a, %d. %b  %H:%M:%S"

        self.connect('button-release-event', self.on_button_release)
        if self.settings["calendar-on"] or self.settings["on-middle-click"] or self.settings["on-right-click"] or \
                self.settings["on-scroll-up"] or self.settings["on-scroll-down"]:
            self.connect('enter-notify-event', self.on_enter_notify_event)
        self.connect('leave-notify-event', self.on_leave_notify_event)
        if settings["on-scroll-up"] or settings["on-scroll-down"]:
            self.add_events(Gdk.EventMask.SCROLL_MASK)
            self.connect('scroll-event', self.on_scroll)

        self.load_calendar()
        self.build_box()
        self.refresh()

    def update_widget(self, output, tooltip=""):
        self.label.set_text(output)
        if self.settings["tooltip-date-format"] and tooltip:
            self.set_tooltip_text(tooltip)

        return False

    def get_output(self):
        now = datetime.now()
        try:
            time = now.strftime(self.settings["format"])
            tooltip = now.strftime(self.settings["tooltip-text"]) if self.settings["tooltip-date-format"] else ""
            GLib.idle_add(self.update_widget, time, tooltip)
        except Exception as e:
            print(e)

        ymd = now.strftime("%Y#%m#%d").split("#")
        y = ymd[0]
        try:
            month = int(ymd[1]) - 1
            m = str(month)
        except:
            m = None
        d = ymd[2]
        if self.has_note(y, m, d):
            if not self.reminder_img_updated:
                update_image(self.reminder_img, "gtk-apply", self.settings["calendar-icon-size"], self.icons_path)
                self.reminder_img_updated = True
            self.reminder_img.set_visible(True)
        else:
            self.reminder_img.set_visible(False)

    def refresh(self):
        thread = create_background_task(self.get_output, self.settings["interval"])
        thread.start()

        thread = create_background_task(self.reload_calendar, self.settings["calendar-interval"])
        thread.start()

    def build_box(self):
        if self.settings["calendar-on"]:
            self.box.pack_start(self.reminder_img, False, False, 0)
        self.box.pack_start(self.label, False, False, 6)

    def on_enter_notify_event(self, widget, event):
        widget.set_state_flags(Gtk.StateFlags.DROP_ACTIVE, clear=False)
        widget.set_state_flags(Gtk.StateFlags.SELECTED, clear=False)

    def on_leave_notify_event(self, widget, event):
        widget.unset_state_flags(Gtk.StateFlags.DROP_ACTIVE)
        widget.unset_state_flags(Gtk.StateFlags.SELECTED)

    def on_button_release(self, widget, event):
        if event.button == 1:
            if self.settings["calendar-on"]:
                self.display_calendar_window()
        elif event.button == 2 and self.settings["on-middle-click"]:
            self.launch(self.settings["on-middle-click"])
        elif event.button == 3 and self.settings["on-right-click"]:
            self.launch(self.settings["on-right-click"])

    def on_scroll(self, widget, event):
        if event.direction == Gdk.ScrollDirection.UP and self.settings["on-scroll-up"]:
            self.launch(self.settings["on-scroll-up"])
        elif event.direction == Gdk.ScrollDirection.DOWN and self.settings["on-scroll-up"]:
            self.launch(self.settings["on-scroll-down"])
        else:
            print("No command assigned")

    def launch(self, cmd):
        cmd = cmd_through_compositor(cmd)

        print(f"Executing: {cmd}")
        subprocess.Popen('{}'.format(cmd), shell=True)

    def display_calendar_window(self):
        if self.popup:
            if self.popup.is_visible():
                self.popup.destroy()
                return

            self.popup.destroy()

        self.load_calendar()

        self.popup = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
        self.popup.set_property("name", self.settings["calendar-css-name"])
        self.popup.connect("key-release-event", self.handle_keyboard)
        GtkLayerShell.init_for_window(self.popup)
        GtkLayerShell.set_layer(self.popup, GtkLayerShell.Layer.TOP)
        GtkLayerShell.set_keyboard_mode(self.popup, GtkLayerShell.KeyboardMode.ON_DEMAND)

        if self.settings["calendar-placement"] in ["top-left", "top", "top-right"]:
            GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.TOP, 1)
        elif self.settings["calendar-placement"] in ["bottom-left", "bottom", "bottom-right"]:
            GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.BOTTOM, 1)

        if self.settings["calendar-placement"] in ["top-left", "bottom-left"]:
            GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.LEFT, 1)
        elif self.settings["calendar-placement"] in ["top-right", "bottom-right"]:
            GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.RIGHT, 1)

        GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.TOP, self.settings["calendar-margin-vertical"])
        GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.BOTTOM, self.settings["calendar-margin-vertical"])
        GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.RIGHT, self.settings["calendar-margin-horizontal"])
        GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.LEFT, self.settings["calendar-margin-horizontal"])

        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
        vbox.set_property("margin", 6)
        self.popup.add(vbox)
        self.cal = Gtk.Calendar.new()
        self.cal.connect("day-selected", self.on_day_selected)
        self.cal.connect("next-month", self.mark_days)
        self.cal.connect("prev-month", self.mark_days)
        self.cal.connect("next-year", self.mark_days)
        self.cal.connect("prev-year", self.mark_days)
        vbox.pack_start(self.cal, False, False, 0)

        self.popup.show_all()
        self.mark_days()

        self.note_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
        self.note_entry = Gtk.Entry()
        self.note_entry.set_property("margin-top", 6)
        self.note_entry.connect("changed", self.on_note_changed)
        self.note_entry.connect("icon-release", self.on_note_icon_click)
        update_gtk_entry(self.note_entry, Gtk.EntryIconPosition.SECONDARY,
                         "edit-clear", self.settings["calendar-icon-size"],
                         self.icons_path)

        vbox.pack_start(self.note_box, False, False, 0)
        self.note_box.pack_start(self.note_entry, True, True, 0)
        btn = Gtk.Button()
        btn.set_property("margin-top", 6)
        img = Gtk.Image()
        btn.set_image(img)
        update_image(img, "gtk-close", self.settings["calendar-icon-size"], self.icons_path)
        btn.set_tooltip_text("Cancel & close")
        btn.connect("clicked", self.cancel_close_popup)
        btn.set_always_show_image(True)
        self.note_box.pack_start(btn, False, False, 0)

        btn = Gtk.Button()
        btn.set_property("margin-top", 6)
        img = Gtk.Image()
        btn.set_image(img)
        update_image(img, "object-select", self.settings["calendar-icon-size"], self.icons_path)
        btn.set_tooltip_text("Save & close")
        btn.connect("clicked", self.apply_close_popup)
        btn.set_always_show_image(True)
        self.note_box.pack_start(btn, False, False, 0)

        self.popup.set_size_request(self.popup.get_allocated_width() * 2, 0)

        y, m, d = self.cal.get_date()
        if self.has_note(y, m, d):
            self.cal.select_day(d)

    def mark_days(self, *args):
        for i in range(1, 32):
            self.cal.unmark_day(i)
        y, m, d = self.cal.get_date()
        y, m, d = str(y), str(m), str(d)
        if y in self.calendar and m in self.calendar[y]:
            for i in range(1, 32):
                if str(i) in self.calendar[y][m]:
                    self.cal.mark_day(i)

    def has_note(self, year, month, day):
        y = str(year)
        m = str(month)
        d = str(day)
        if y in self.calendar and m in self.calendar[y] and d in self.calendar[y][m] and self.calendar[y][m][d]:
            return True

        return False

    def cancel_close_popup(self, *args):
        self.popup.destroy()

    def apply_close_popup(self, *args):
        c = {}
        if len(self.calendar) > 0:
            for key_year in self.calendar:
                if len(self.calendar[key_year]) > 0:
                    for key_month in self.calendar[key_year]:
                        if len(self.calendar[key_year][key_month]) > 0:
                            for key_day in self.calendar[key_year][key_month]:
                                if self.calendar[key_year][key_month][key_day]:
                                    if key_year not in c:
                                        c[key_year] = {}
                                    if key_month not in c[key_year]:
                                        c[key_year][key_month] = {}
                                    note = self.calendar[key_year][key_month][key_day]
                                    if note:
                                        c[key_year][key_month][key_day] = note
        save_json(c, self.path)
        self.popup.destroy()

    def load_calendar(self):
        if self.settings["calendar-path"]:
            self.path = self.settings["calendar-path"]
            c = load_json(self.path)
            if c is not None:
                self.calendar = c
                return True
            else:
                result = save_json(self.calendar, self.path)
                if result == "ok":
                    print("Created new calendar file at '{}'".format(self.path))
                    return True
                else:
                    eprint("Couldn't create '{}': {}. Using default path.".format(self.path, result))

        self.path = os.path.join(local_dir(), "calendar.json")
        if not os.path.isfile(self.path):
            result = save_json(self.calendar, self.path)
            if result == "ok":
                print("Created new calendar file at '{}'".format(self.path))
            else:
                eprint("Couldn't create '{}': {}. No more idea...".format(self.path, result))
        c = load_json(self.path)
        self.calendar = c

    def reload_calendar(self):
        if not self.popup or not self.popup.is_visible():
            self.load_calendar()

    def handle_keyboard(self, win, event):
        if event.type == Gdk.EventType.KEY_RELEASE and event.keyval == Gdk.KEY_Escape:
            self.popup.destroy()

    def on_day_selected(self, cal):
        y, m, d = cal.get_date()
        y, m, d = str(y), str(m), str(d)
        if y in self.calendar and m in self.calendar[y] and d in self.calendar[y][m]:
            self.note_entry.set_text(self.calendar[y][m][d])
        else:
            self.note_entry.set_text("")

        self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, self.note_entry.get_text())
        self.note_box.show_all()

    def on_note_changed(self, eb):
        y, m, d = self.cal.get_date()
        y, m, d = str(y), str(m), str(d)
        if y not in self.calendar:
            self.calendar[y] = {}
        if m not in self.calendar[y]:
            self.calendar[y][m] = {}
        note = eb.get_text()
        if note:
            self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, True)
            self.calendar[y][m][d] = eb.get_text()
        elif d in self.calendar[y][m]:
            self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, False)
            self.calendar[y][m].pop(d, None)

    def on_note_icon_click(self, entry, icon, event):
        entry.set_text("")
