# -*- coding: utf-8 -*-
#
#       talk_time.py
#
#       Copyright 2017 Cimbali <me@cimba.li>
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, 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 License for more details.
#
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
"""
:mod:`pympress.talk_time` -- Manages the clock of elapsed talk time
-------------------------------------------------------------------
"""

import logging
logger = logging.getLogger(__name__)

import time

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib


class TimeLabelColorer(object):
    """ Manage the colors of a label with a set of colors between which to fade, based on how much time remains.

    Times are given in seconds (<0 has run out of time). In between timestamps the color will interpolated linearly,
    outside of the intervals the closest color will be used.

    Args:
        label_time (:class:`Gtk.Label`): the label where the talk time is displayed
    """

    #: The :class:`Gtk.Label` whose colors need updating
    label_time = None

    #: :class:`~Gdk.RGBA` The default color of the info labels
    label_color_default = None

    #: :class:`~Gtk.CssProvider` affecting the style context of the labels
    color_override = None

    #: `list` of tuples (`int`, :class:`~Gdk.RGBA`), which are the desired colors at the corresponding timestamps.
    #: Sorted on the timestamps.
    color_map = []

    def __init__(self, label_time):
        self.label_time = label_time

        style_context = self.label_time.get_style_context()
        self.color_override = Gtk.CssProvider()
        style_context.add_provider(self.color_override, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1)

        self.label_color_default = self.load_color_from_css(style_context)
        label_color_ett_reached = self.load_color_from_css(style_context, "ett-reached")
        label_color_ett_info = self.load_color_from_css(style_context, "ett-info")
        label_color_ett_warn = self.load_color_from_css(style_context, "ett-warn")

        self.color_map = [
            ( 300, self.label_color_default),
            (   0, label_color_ett_reached),
            (-150, label_color_ett_info),
            (-300, label_color_ett_warn)
        ]


    def load_color_from_css(self, style_context, class_name = None):
        """ Add class class_name to the time label and return its color.

        Args:
            label_time (:class:`Gtk.Label`): the label where the talk time is displayed
            style_context (:class:`~Gtk.StyleContext`): the CSS context managing the color of the label
            class_name (`str` or `None`): The name of the class, if any

        Returns:
            :class:`~Gdk.RGBA`: The color of the label with class "class_name"
        """
        if class_name:
            style_context.add_class(class_name)

        self.label_time.show()
        color = style_context.get_color(Gtk.StateType.NORMAL)

        if class_name:
            style_context.remove_class(class_name)

        return color


    def default_color(self):
        """ Forces to reset the default colors on the label.
        """
        self.color_override.load_from_data(''.encode('ascii'))


    def update_time_color(self, remaining):
        """ Update the color of the time label based on how much time is remaining.

        Args:
            remaining (`int`): Remaining time until estimated talk time is reached, in seconds.
        """
        if (remaining <= 0 and remaining > -5) or (remaining <= -300 and remaining > -310):
            self.label_time.get_style_context().add_class("time-warn")
        else:
            self.label_time.get_style_context().remove_class("time-warn")

        prev_time, prev_color = None, None

        for timestamp, color in self.color_map:
            if remaining >= timestamp:
                break
            prev_time, prev_color = (timestamp, color)

        else:
            # if remaining < all timestamps, use only last color
            prev_color = None

        if prev_color:
            position = (remaining - prev_time) / (timestamp - prev_time)
            color_spec = '* {{color: mix({}, {}, {})}}'.format(prev_color.to_string(), color.to_string(), position)
        else:
            color_spec = '* {{color: {}}}'.format(color.to_string())

        self.color_override.load_from_data(color_spec.encode('ascii'))


class TimeCounter(object):
    """ A double counter, that displays the time elapsed in the talk and a clock.

    Args:
        builder (builder.Builder): The builder from which to load widgets.
        ett (`int`): the estimated time for the talk, in seconds.
        timing_tracker: (:class:`~pympress.extras.TimingReport`): to inform when the slides change
        autoplay: (:class:`~pympress.dialog.AutoPlay`): to adjust the timer display if we’re auto-playing/looping slides
    """
    #: Elapsed time :class:`~Gtk.Label`
    label_time = None
    #: Clock :class:`~Gtk.Label`
    label_clock = None

    #: Time at which the counter was started, `int` in seconds as returned by :func:`~time.time()`
    restart_time = 0
    #: Time elapsed since the beginning of the presentation, `int` in seconds
    elapsed_time = 0
    #: Timer paused status, `bool`
    paused = True

    #: :class:`~TimeLabelColorer` that handles setting the colors of :attr:`label_time`
    label_colorer = None

    #: :class:`~pympress.editable_label.EstimatedTalkTime` that handles changing the ett
    ett = None

    #: The pause-timer :class:`~Gio.Action`
    pause_action = None

    #: The :class:`~pympress.extras.TimingReport`, needs to know when the slides change
    timing_tracker = None
    #: The :class:`~pympress.dialog.AutoPlay`, to adjust the timer display if we’re auto-playing/looping slides
    autoplay = None

    def __init__(self, builder, ett, timing_tracker, autoplay):
        super(TimeCounter, self).__init__()

        self.label_colorer = TimeLabelColorer(builder.get_object('label_time'))
        self.ett = ett
        self.timing_tracker = timing_tracker
        self.autoplay = autoplay

        builder.load_widgets(self)

        builder.setup_actions({
            'pause-timer':    dict(activate=self.switch_pause, state=self.paused),
            'reset-timer':    dict(activate=self.reset_timer),
        })
        self.pause_action = builder.get_application().lookup_action('pause-timer')

        # Setup timer for clocks
        GLib.timeout_add(250, self.update_time)


    def switch_pause(self, gaction, param=None):
        """ Switch the timer between paused mode and running (normal) mode.

        Returns:
            `bool`: whether the clock's pause was toggled.
        """
        if self.paused:
            self.unpause()
        else:
            self.pause()
        return None


    def pause(self):
        """ Pause the timer if it is not paused, otherwise do nothing.

        Returns:
            `bool`: whether the clock's pause was toggled.
        """
        if self.paused:
            return False

        self.paused = True
        self.pause_action.change_state(GLib.Variant.new_boolean(self.paused))

        self.elapsed_time += time.time() - self.restart_time
        self.timing_tracker.end_time = self.elapsed_time

        if self.autoplay.is_looping():
            self.autoplay.pause()

        self.update_time()
        return True


    def unpause(self):
        """ Unpause the timer if it is paused, otherwise do nothing.

        Returns:
            `bool`: whether the clock's pause was toggled.
        """
        if not self.paused:
            return False

        self.restart_time = time.time()

        self.paused = False
        self.pause_action.change_state(GLib.Variant.new_boolean(self.paused))

        if self.autoplay.is_looping():
            self.autoplay.unpause()

        self.update_time()
        return True


    def reset_timer(self, *args):
        """ Reset the timer.
        """
        self.timing_tracker.reset(self.current_time())

        self.restart_time = time.time()
        self.elapsed_time = 0
        if self.autoplay.is_looping():
            self.autoplay.start_looping()
        self.update_time()


    def current_time(self):
        """ Returns the time elapsed in the presentation.

        Returns:
            `int`: the time since the presentation started in seconds.
        """
        # Time elapsed since the beginning of the presentation
        if self.paused:
            return self.elapsed_time
        else:
            return self.elapsed_time + (time.time() - self.restart_time)


    def update_time(self):
        """ Update the timer and clock labels.

        Returns:
            `bool`: `True` (to prevent the timer from stopping)
        """
        # Current time
        clock = time.strftime('%X')  # '%H:%M:%S'
        elapsed = self.current_time()

        # Time elapsed since the beginning of the presentation
        if self.autoplay.is_looping():
            first, stop, loop, delay = self.autoplay.get_page_range()
            display_time = '{} {}-{} / {:.1f}s'.format(_('Loop') if loop else _('Auto'), first + 1, stop, delay / 1000)
        else:
            display_time = '{:02}:{:02}'.format(*divmod(int(elapsed), 60))

        if self.paused:
            display_time += ' ' + _('(paused)')

        self.label_time.set_text(display_time)
        self.label_clock.set_text(clock)
        if not self.paused:
            self.timing_tracker.end_time = elapsed

        if self.ett.est_time:
            self.label_colorer.update_time_color(self.ett.est_time - elapsed)
        else:
            self.label_colorer.default_color()

        return True
