from functools import partial
import locale

from gettext import gettext as _
from gi.repository import Adw, Gio, GLib, Gdk, Gtk

from datetime import datetime, timedelta
from timetrack.activity import Activity
from timetrack.activity import ActivityWidget
from timetrack.activity import ActivitySummary
from timetrack.activity import ActivitySummaryWidget
from timetrack.edit import EditDialog
from timetrack.export import ExportDialog
from timetrack.headerbar import TimetrackHeaderBar # noqa: E261


@Gtk.Template(resource_path="/net/danigm/timetrack/main_window.ui")
class MainWindow(Adw.ApplicationWindow):
    __gtype_name__ = "MainWindow"

    application = NotImplemented
    current_activity = NotImplemented

    main_stack = Gtk.Template.Child()
    timer = Gtk.Template.Child()
    timespent = Gtk.Template.Child()
    activities_per_day_list = Gtk.Template.Child()
    load_more = Gtk.Template.Child()

    # Variables about displayed activities
    offset = 0
    load_each_time = 10
    displayed_days = 0

    # List of models per day
    groups_per_day = []
    activity_models_per_day = []

    report_stack = Gtk.Template.Child()
    report_date_button = Gtk.Template.Child()
    report_calendar = Gtk.Template.Child()
    report_total = Gtk.Template.Child()
    report_filter = Gtk.Template.Child()

    report_day_list = Gtk.Template.Child()
    report_week_list = Gtk.Template.Child()
    report_month_list = Gtk.Template.Child()
    report_day_model = NotImplemented
    report_week_model = NotImplemented
    report_month_model = NotImplemented

    headerbar = Gtk.Template.Child()

    comments = None
    start_date = None
    activity_id = None

    disable_calendar_change = False

    report_day_date = datetime.now()
    report_week_date = datetime.now()
    report_month_date = datetime.now()

    report_total_day = 0
    report_total_week = 0
    report_total_month = 0

    new_action = NotImplemented
    edit_action = NotImplemented
    continue_action = NotImplemented
    remove_action = NotImplemented
    restore_action = NotImplemented
    load_action = NotImplemented

    deleted_activity = None

    toast_overlay = Gtk.Template.Child()

    today_spent = 0

    def __init__(self, *args, **kwargs):
        self.localedir = kwargs.pop('localedir')
        if self.localedir:
            locale.bindtextdomain('timetrack', self.localedir)
            locale.textdomain('timetrack')

        super().__init__(*args, **kwargs)

        self.headerbar.window = self
        self.logging_manager = kwargs['application'].get_logger()

        self.assemble_window()
        self.set_size_request(-1, 500)

    def do_close_request(self):
        if self.start_date:
            self.stop_activity()

    def assemble_window(self):
        self.create_container()
        self.custom_css()

        if Gio.Application.get_default().development_mode is True:
            context = self.get_style_context()
            context.add_class("devel")

    def create_container(self):
        self.create_report()

    def create_report(self):
        self.report_day_model = Gio.ListStore()
        self.report_week_model = Gio.ListStore()
        self.report_month_model = Gio.ListStore()

        self.report_day_list.bind_model(self.report_day_model,
                                        self.create_activity_summary_widget)
        self.report_week_list.bind_model(self.report_week_model,
                                         self.create_activity_summary_widget)
        self.report_month_list.bind_model(self.report_month_model,
                                          self.create_activity_summary_widget)

    def custom_css(self):
        css_provider = Gtk.CssProvider()
        css_provider.load_from_resource("/net/danigm/timetrack/timetrack.css")

        display = Gdk.Display.get_default()
        Gtk.StyleContext.add_provider_for_display(
            display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)

    def add_actions(self):
        start_action = Gio.SimpleAction.new("activity.start", None)
        start_action.connect("activate", self.startstop_activity)
        start_action.set_enabled(False)
        self.start_action = start_action
        self.application.add_action(start_action)

        stop_action = Gio.SimpleAction.new("activity.stop", None)
        stop_action.connect("activate", self.startstop_activity)
        self.application.add_action(stop_action)

        action = Gio.SimpleAction.new("report", None)
        action.connect("activate", self.show_report)
        self.application.add_action(action)

        action = Gio.SimpleAction.new("list", None)
        action.connect("activate", self.show_list)
        self.application.add_action(action)

        # new
        action = Gio.SimpleAction.new("new", None)
        action.connect("activate", self.new_activity)
        self.new_action = action
        self.application.add_action(action)

        # edit
        action = Gio.SimpleAction.new("edit", GLib.VariantType.new("i"))
        action.connect("activate", self.edit_activity)
        self.edit_action = action
        self.application.add_action(action)

        # continue
        action = Gio.SimpleAction.new("continue", GLib.VariantType.new("i"))
        action.connect("activate", self.continue_activity)
        self.continue_action = action
        self.application.add_action(action)

        # remove
        action = Gio.SimpleAction.new("remove", GLib.VariantType.new("i"))
        action.connect("activate", self.remove_activity)
        self.remove_action = action
        self.application.add_action(action)

        # restore
        action = Gio.SimpleAction.new("restore", None)
        action.connect("activate", self.restore_activity)
        action.set_enabled(False)
        self.restore_action = action
        self.application.add_action(action)

        # load more
        load_action = Gio.SimpleAction.new("load-more", None)
        load_action.connect("activate", self.load_last)
        self.load_action = load_action
        self.application.add_action(load_action)

        # report actions
        action = Gio.SimpleAction.new("report-left", None)
        action.connect("activate", self.report_go_left)
        self.application.add_action(action)
        action = Gio.SimpleAction.new("report-right", None)
        action.connect("activate", self.report_go_right)
        self.application.add_action(action)
        action = Gio.SimpleAction.new("report-today", None)
        action.connect("activate", self.report_go_today)
        self.application.add_action(action)

        action = Gio.SimpleAction.new("report-export", None)
        action.connect("activate", self.export_report)
        self.application.add_action(action)

        action = Gio.SimpleAction.new("report-day", None)
        action.connect("activate", partial(self.report_show, "day"))
        self.application.add_action(action)

        action = Gio.SimpleAction.new("report-week", None)
        action.connect("activate", partial(self.report_show, "week"))
        self.application.add_action(action)

        action = Gio.SimpleAction.new("report-month", None)
        action.connect("activate", partial(self.report_show, "month"))
        self.application.add_action(action)

        action = Gio.SimpleAction.new("report-filter", None)
        action.connect("activate", self.filter)
        self.application.add_action(action)

        # Text copy action
        copy_text = Gio.SimpleAction.new("copy-text",
                                         GLib.VariantType.new("s"))
        copy_text.connect("activate", self.on_copy_text)
        copy_text.set_enabled(True)
        self.application.add_action(copy_text)

    def on_copy_text(self, action, text):
        display = Gdk.Display.get_default()
        clipboard = display.get_clipboard()
        clipboard.set(text.get_string())

    def filter(self, *args, **kwargs):
        self.show_report()
        self.report_filter.grab_focus()

    def report_show(self, name, *args, **kwargs):
        self.show_report()
        self.report_stack.set_visible_child_name(name)

    def show_report(self, *args, **kwargs):
        self.main_stack.set_visible_child_name("report")
        self.headerbar.show_report()
        self.fill_report()

    def fill_report(self):
        self.report_date_button.set_label(
            self.report_day_date.strftime("%d/%M/%Y"))
        self.fill_report_day()
        self.fill_report_week()
        self.fill_report_month()
        self.report_changed()

    def fill_report_day(self):
        now = self.report_day_date
        s = datetime(now.year, now.month, now.day)
        e = s + timedelta(days=1)
        r = self.fill_report_generic(s, e, self.report_day_model)
        self.report_total_day = r

    def fill_report_week(self):
        now = self.report_week_date
        s = datetime(now.year, now.month, now.day)
        s -= timedelta(days=s.isoweekday() - 1)
        e = s + timedelta(days=7)
        r = self.fill_report_generic(s, e, self.report_week_model)
        self.report_total_week = r

    def fill_report_month(self):
        now = self.report_month_date
        s = datetime(now.year, now.month, 1)
        y = now.year
        m = now.month + 1
        if m > 12:
            m = 1
            y += 1
        elif m < 1:
            m = 12
            y -= 1
        e = datetime(y, m, 1)
        r = self.fill_report_generic(s, e, self.report_month_model)
        self.report_total_month = r

    def fill_report_generic(self, start, end, model):
        total = 0
        model.remove_all()

        filter = self.report_filter.get_text()
        query = self.application.db.report(start=start,
                                           end=end, activity=filter)
        for (act, seconds) in query:
            total += seconds
            model.append(ActivitySummary(name=act, seconds=seconds))

        return total

    def export_report(self, *args, **kwargs):
        mode = self.report_stack.get_visible_child_name()
        dialog = ExportDialog(self.application.db, self)
        now = self.report_day_date

        if mode == 'day':
            start = now - timedelta(days=1)
            end = now + timedelta(days=1)
        elif mode == 'week':
            start = now - timedelta(days=now.isoweekday() - 1)
            end = ((start - timedelta(days=start.isoweekday() - 1))
                   + timedelta(days=6))
        elif mode == 'month':
            start = datetime(now.year, now.month, 1)
            end = start + timedelta(days=30)

        dialog.set_default_dates(start, end)
        dialog.open()

    def show_list(self, *args, **kwargs):
        self.main_stack.set_visible_child_name("main")
        self.headerbar.show_list()

    @Gtk.Template.Callback()
    def startstop_activity(self, *args, **kwargs):
        activity = self.headerbar.activity
        if len(activity) > 0:
            if self.start_date:
                self.stop_activity()
                return

            self.headerbar.set_working()

            self.start_date = datetime.now()
            self.comments = ""
            self.today_spent = self.application.db.summary(self.start_date)
            GLib.timeout_add(1000, self.update_timer, None)

            activity = Activity(id=None, name=activity,
                                start=self.start_date, stop=None)
            self.headerbar.update_current_popup(activity)
            self.activity_id = self.application.db.store(activity)

    @Gtk.Template.Callback()
    def check_enable_start(self, *args, **kwargs):
        self.start_action.set_enabled(self.headerbar.activity)

    def stop_activity(self, *args, **kwargs):
        self.headerbar.set_waiting()
        self.timer.set_text("00:00:00")
        self.timespent.set_text("")

        name = self.headerbar.current_name
        start = self.start_date
        stop = datetime.now()

        if (stop - start).seconds > 0:
            activity = Activity(id=self.activity_id, name=name, start=start,
                                stop=stop, comments=self.comments)

            self.application.db.set_stop(self.activity_id, stop,
                                         activity.seconds)

            self.insert_activity(activity, True)

        self.comments = ""
        self.start_date = None
        self.activity_id = None

    def update_timer(self, *args, **kwargs):
        if not self.start_date:
            return False

        now = datetime.now()
        diff = int((now - self.start_date).total_seconds())
        timepass = self.seconds_to_time(diff)
        self.timer.set_text(timepass)

        self.timespent.set_text(self.seconds_to_time(diff + (self.today_spent or 0)))

        return True

    def load_last(self, *args, **kwargs):
        count = 0

        for act in self.application.db.get_last(self.load_each_time, self.offset):
            self.insert_activity(act)
            count = count + 1

        self.offset = self.offset + count

        if count < self.load_each_time:
            self.load_more.props.sensitive = False

    def seconds_to_time(self, seconds):
        m = seconds // 60
        s = seconds % 60
        h = m // 60
        m = m % 60

        return "{:02d}:{:02d}:{:02d}".format(h, m, s)

    def edit_activity(self, action, activity_id):
        # Get int from Variant
        aid = GLib.Variant.get_int32(activity_id)
        # Find index and activity in the list by ID
        (list_index, index, act) = self.find_activity(aid)

        def on_save(act):
            self.activity_models_per_day[list_index].remove(index)
            self.offset = self.offset - 1
            self.hide_show_list(list_index)
            self.insert_activity(act, True)

        dialog = EditDialog(self.application.db, self, on_save)
        dialog.open(act.id)

    def continue_activity(self, action, activity_id):
        # Get int from Variant
        aid = GLib.Variant.get_int32(activity_id)
        # Find index and activity in the list by ID
        (list_index, index, act) = self.find_activity(aid)

        # Remove from the list
        self.activity_models_per_day[list_index].remove(index)
        self.offset = self.offset - 1

        # Hide or show list
        self.hide_show_list(list_index)
        self.total_hours_per_day(list_index)

        if self.start_date:
            self.stop_activity()

        self.headerbar.activity = act.name
        self.headerbar.set_working()
        self.start_date = act.start
        self.comments = act.comments
        self.today_spent = self.application.db.summary(self.start_date)
        GLib.timeout_add(1000, self.update_timer, None)

        self.headerbar.update_current_popup(act)
        self.activity_id = act.id

    def new_activity(self, *args, **kwargs):
        def on_save(act):
            self.insert_activity(act, True)

        EditDialog(self.application.db, self, on_save)

    def remove_activity(self, action, activity_id):
        # Get int from Variant
        aid = GLib.Variant.get_int32(activity_id)
        # Find index and activity in the list by ID
        (list_index, index, act) = self.find_activity(aid)

        # Remove from the database
        self.application.db.delete(aid)

        # Remove from the list
        self.activity_models_per_day[list_index].remove(index)
        self.offset = self.offset - 1

        # Hide or show list
        self.hide_show_list(list_index)
        self.total_hours_per_day(list_index)

        # Set restore options
        self.deleted_activity = (list_index, act)
        self.restore_action.set_enabled(True)

        # Pop the Undo message
        msg = _('"{name}" activity deleted')
        self.create_toast(msg.format(name=act.name), "app.restore", "Undo")

    def restore_activity(self, *args, **kwargs):
        (list_index, act) = self.deleted_activity
        self.application.db.restore(act.id)
        self.insert_activity_with_day_offset(list_index, act)

        msg = _('"{name}" activity restored')
        self.create_toast(msg.format(name=act.name))

        self.deleted_activity = None
        self.restore_action.set_enabled(False)

    def report_go_left(self, *args, **kwargs):
        selected = self.report_stack.get_visible_child_name()
        if selected == 'day':
            self.report_day_date -= timedelta(days=1)
        elif selected == 'week':
            self.report_week_date -= timedelta(days=7)
        elif selected == 'month':
            date = self.report_month_date
            y, m = date.year, date.month
            m = m - 1
            if m < 1:
                m = 12
                y -= 1
            self.report_month_date = datetime(y, m, 1)
        self.fill_report()

    def report_go_right(self, *args, **kwargs):
        selected = self.report_stack.get_visible_child_name()
        if selected == 'day':
            self.report_day_date += timedelta(days=1)
        elif selected == 'week':
            self.report_week_date += timedelta(days=7)
        elif selected == 'month':
            date = self.report_month_date
            y, m = date.year, date.month
            m = m + 1
            if m > 12:
                m = 1
                y += 1
            self.report_month_date = datetime(y, m, 1)
        self.fill_report()

    def report_go_today(self, *args, **kwargs):
        selected = self.report_stack.get_visible_child_name()
        if selected == 'day':
            self.report_day_date = datetime.now()
        elif selected == 'week':
            self.report_week_date = datetime.now()
        elif selected == 'month':
            n = datetime.now()
            self.report_month_date = datetime(n.year, n.month, 1)
        self.fill_report()

    def select_datetime(self, date):
        timetuple = date.timetuple()[0:6]
        dt = GLib.DateTime.new_local(*timetuple)
        self.report_calendar.select_day(dt)

    @Gtk.Template.Callback()
    def report_changed(self, *args, **kwargs):
        self.disable_calendar_change = True
        self.report_calendar.clear_marks()

        text = ""
        total = 0
        selected = self.report_stack.get_visible_child_name()
        if selected == 'day':
            text = self.report_day_date.strftime("%d/%m/%Y")
            self.select_datetime(self.report_day_date)
            total = self.report_total_day

        elif selected == 'week':
            now = self.report_week_date
            s = datetime(now.year, now.month, now.day)
            s -= timedelta(days=s.isoweekday() - 1)
            self.report_week_date = s
            e = s + timedelta(days=6)
            text = s.strftime("%d/%m") + "-" + e.strftime("%d/%m")
            self.select_datetime(s)
            while s <= e:
                self.report_calendar.mark_day(s.day)
                s += timedelta(days=1)
            total = self.report_total_week

        elif selected == 'month':
            now = self.report_month_date
            s = datetime(now.year, now.month, 1)
            self.report_month_date = now
            text = s.strftime("%m/%Y")
            self.select_datetime(s)
            m = s.month
            while s.month == m:
                self.report_calendar.mark_day(s.day)
                s += timedelta(days=1)
            total = self.report_total_month

        self.report_date_button.set_label(text)
        self.report_total.set_text(self.seconds_to_time(total))

        self.disable_calendar_change = False

    @Gtk.Template.Callback()
    def report_change_day(self, *args, **kwargs):
        if self.disable_calendar_change:
            return

        new_date = self.report_calendar.get_date()
        d = datetime(new_date.get_year(), new_date.get_month(),
                     new_date.get_day_of_month())

        selected = self.report_stack.get_visible_child_name()
        if selected == 'day':
            self.report_day_date = d
        elif selected == 'week':
            self.report_week_date = d
        elif selected == 'month':
            self.report_month_date = datetime(d.year, d.month, 1)

        self.fill_report()

    @Gtk.Template.Callback()
    def filter_report(self, *args, **kwargs):
        self.fill_report()

    def insert_activity(self, activity, update=False):
        # Compute the day day offset
        now = datetime.now()
        now = now.replace(hour=0, minute=0, second=0, microsecond=0)
        start_activity = activity.start.replace(hour=0, minute=0, second=0, microsecond=0)

        day_offset = (now - start_activity).days

        if day_offset >= 0 and day_offset < self.displayed_days:
            self.insert_activity_with_day_offset(day_offset, activity)
            if update:
                self.offset = self.offset + 1
        elif update:
            self.load_more.props.sensitive = True
        elif not update:
            self.add_new_days(day_offset - self.displayed_days + 1)
            self.displayed_days = day_offset + 1
            self.insert_activity_with_day_offset(day_offset, activity)

    def insert_activity_with_day_offset(self, day_offset, activity):
        self.activity_models_per_day[day_offset].insert_sorted(activity,
                                                               lambda a, b: a.start < b.start)

        self.hide_show_list(day_offset)
        self.total_hours_per_day(day_offset)

    def create_activity_widget(self, item):
        return ActivityWidget(item)

    def create_activity_summary_widget(self, item):
        return ActivitySummaryWidget(item)

    @Gtk.Template.Callback()
    def change_current(self, *args, **kwargs):
        if not self.activity_id or not self.start_date:
            return

        name = self.headerbar.current_name
        self.comments = self.headerbar.current_comments
        self.start_date = self.headerbar.current_date

        activity = Activity(id=self.activity_id,
                            name=name,
                            start=self.start_date,
                            stop=None,
                            comments=self.comments)
        self.application.db.update(activity)

    def create_toast(self, title, action=None, button_label=None):
        toast = Adw.Toast(title=title)

        if action and button_label:
            toast.set_action_name(action)
            toast.set_button_label(button_label)

        self.toast_overlay.add_toast(toast)

    def find_activity(self, activity_id):
        # Find the index and activity in the list of activities by ID
        for (list_index, activity_list) in enumerate(self.activity_models_per_day):
            for (index, act) in enumerate(activity_list):
                if act.id == activity_id:
                    return (list_index, index, act)

    def day_offset_to_string(self, day_offset):
        day = datetime.now() + timedelta(days=day_offset)
        offset = abs(day_offset)

        title = day.strftime("%x")
        description = None

        if day_offset == 0:
            title = _("Today")
        if day_offset == -1:
            title = _("Yesterday")
        if day_offset < -1 and day_offset > - 7:
            title = _(f"{offset} days ago") + day.strftime(_(" (%a - %b %d)"))
        if day_offset <= - 7:
            title = day.strftime(_("On %b %d"))
        if day_offset == 1:
            title = _("Tomorrow")
        if day_offset > 1:
            title = _(f"{offset} from today")

        return title, description

    def hide_show_list(self, index):
        if len(self.activity_models_per_day[index]):
            self.groups_per_day[index].show()
        else:
            self.groups_per_day[index].hide()

    def total_hours_per_day(self, index):
        total_seconds = 0
        for act in self.activity_models_per_day[index]:
            total_seconds = total_seconds + act.seconds

        m = total_seconds // 60
        h = m // 60
        m = m % 60

        duration_label = self.groups_per_day[index].get_header_suffix()
        duration_label.set_markup("<small><b>Total: {:d}:{:02d}</b></small>".format(h,m))

    def add_new_days(self, days=0):
        for index in range(self.displayed_days, self.displayed_days+days):
            self.activity_models_per_day.append(Gio.ListStore())

            # Create the box for tasks
            box_list = Gtk.ListBox()
            box_list.props.selection_mode = 0
            box_list.add_css_class("boxed-list")
            box_list.bind_model(self.activity_models_per_day[index],
                                      self.create_activity_widget)

            # Create the group for day
            title, description = self.day_offset_to_string(-index)
            group = Adw.PreferencesGroup(title=title, description=description)
            group.props.margin_top = 6
            group.props.margin_bottom = 6
            group.add(box_list)

            # Create the total duration label
            duration_label = Gtk.Label(margin_end = 12)
            duration_label.set_markup("<small><b>Total: </b></small>")
            context = duration_label.get_style_context()
            context.add_class("dim-label")
            group.set_header_suffix(duration_label)

            self.activities_per_day_list.append(group)
            self.groups_per_day.append(group)
            self.hide_show_list(index)

