# encoding: utf-8

import timeit
import datetime
import time
import logging_helper
logging = logging_helper.setup_logging()

__author__ = u'Hywel Thomas'
__copyright__ = u'Copyright (C) 2016 Hywel Thomas'

SECOND = 1
TEN_SECONDS = 10 * SECOND
MINUTE = 60
TEN_MINUTES = 10 * MINUTE
ONE_HOUR = 60 * MINUTE

UTC = datetime.datetime.utcnow()
NOW = datetime.datetime.now()

tz_adjust = (NOW - UTC).seconds



class Timeout(object):
    def __init__(self,
                 seconds=None,
                 start_time_offset=0,
                 start_in_an_expired_state=False,
                 ):
        """
        Re-usable timeouts with reporting.
        
        :param seconds:
        :param start_time_offset:
        :param start_in_an_expired_state:
        """
        self.__timeout_length = seconds
        self.__start_time_date = datetime.datetime.now()
        self.__start_time = timeit.default_timer() + start_time_offset

        self.__end_time = self.__start_time
        if not start_in_an_expired_state:
            self.__end_time += self.__timeout_length

        self.__expired = None

    @property
    def expired(self):
        if not self.__expired:
            self.__expired = timeit.default_timer() >= self.__end_time
        return self.__expired

    def expire(self):
        if not self.__expired:
            self.__end_time = self.__start_time
            self.__expired = True

    def restart(self):
        self.__start_time_date = datetime.datetime.now()
        self.__start_time = timeit.default_timer()
        self.__end_time = self.__start_time + self.__timeout_length
        self.__expired = False

    @property
    def elapsed_time(self):
        return timeit.default_timer() - self.__start_time

    @property
    def seconds_remaining(self):
        if self.__expired or self.__timeout_length <= 0:
            logging.debug(u'seconds remaining:{sr}'.format(sr=0))
            return 0

        logging.debug(u'end_time:{et}; timeit.default_timer():{tc}'
                      .format(et=self.__end_time,
                              tc=timeit.default_timer()))

        time_remaining = self.__end_time - timeit.default_timer()

        if time_remaining > self.__timeout_length or time_remaining < 0:
            self.__expired = True
            logging.debug(u'seconds remaining:{sr}'.format(sr=0))
            return 0

        logging.debug(u'seconds remaining:{sr}'.format(sr=time_remaining))
        return time_remaining

    @property
    def time_remaining(self):
        return datetime.timedelta(seconds=self.seconds_remaining)

    def wait(self,
             message=None):

        if message is None:
            while not self.expired:
                wait_time = self.seconds_remaining
                logging.debug(u'wait_time:{wait_time}'.format(wait_time=wait_time))
                time.sleep(wait_time)
        else:
            while not self.expired:
                remaining = self.time_remaining.seconds

                # print rather than log as this gives a live
                # progress to the user for very long intervals
                # but is not relevant in logs.
                print(u'{message} in {minutes}m {seconds}s.'
                      .format(message=message,
                              minutes=remaining / 60,
                              seconds=remaining % 60))

                if remaining > ONE_HOUR:
                    if remaining > ONE_HOUR + TEN_MINUTES:
                        Timeout(ONE_HOUR).wait()
                    else:
                        Timeout(ONE_HOUR - TEN_MINUTES).wait()

                elif remaining > TEN_MINUTES:
                    Timeout(TEN_MINUTES).wait()

                elif remaining > MINUTE:
                    Timeout(MINUTE).wait()

                else:
                    Timeout(TEN_SECONDS
                            if remaining >= TEN_SECONDS
                            else SECOND).wait()

            print (u'Wait finished.')


class Throttle(object):
    def __init__(self,
                 rate=None,
                 interval=None):
        """
        Perpetually restarting timer
        Example use is to limit the number of requests per second to a service

        :param rate: allowed events per second
        :param period: time in seconds before next event is permitted
        """
        if rate:
            try:
                self.interval = 1.0 / rate
            except ZeroDivisionError:
                self.interval = 0
        else:
            self.interval = interval
        # logging.debug(u'Throttle interval:{interval}'
        #               .format(interval=interval))

        self.timeout = Timeout(seconds=self.interval)

    def wait(self,
             message=None):
        self.timeout.wait(message=message)
        self.timeout.restart()


class Stopwatch(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.__stop_time = None
        self.__laps = []
        self.__start_time = timeit.default_timer()

    def stop(self):
        if self.__stop_time is None:
            self.__stop_time = timeit.default_timer()
            return self.glance
        else:
            return self.__stop_time

    def lap(self):
        lap_end_time = timeit.default_timer()
        lap_start_time = (self.__start_time
                          if not self.__laps
                          else self.__laps[-1][u'lap_end_time'])
        self.__laps.append({u'lap_start_time': lap_start_time,
                            u'lap_end_time'  : lap_end_time,
                            u'lap_time'      : lap_end_time - lap_start_time,
                            })
        return self.__laps[-1][u'lap_time']

    @property
    def lap_times(self):
        return self.__laps

    @property
    def glance(self):
        if self.__stop_time:
            return self.__stop_time - self.__start_time
        else:
            return time.clodefault_timerck() - self.__start_time


if __name__ == u"__main__":

    timeout = Timeout(-1)
    assert timeout.seconds_remaining <= 0, timeout.seconds_remaining
    assert timeout.time_remaining == datetime.timedelta(seconds=0), \
           timeout.time_remaining
    assert timeout.expired

    timeout = Timeout(0)
    assert timeout.seconds_remaining == 0, timeout.seconds_remaining
    assert timeout.time_remaining == datetime.timedelta(seconds=0), \
           timeout.time_remaining
    assert timeout.expired

    t = Throttle(2)
    assert t.interval == 0.5
    for _ in range(5):
        print datetime.datetime.now()
        t.wait()

    start_time = datetime.datetime.now()
    Timeout(5).wait()
    assert datetime.datetime.now() >= datetime.timedelta(seconds=5) + start_time

    start_time = datetime.datetime.now()
    Timeout(0.5).wait()
    assert (datetime.datetime.now()
            >= datetime.timedelta(milliseconds=500) + start_time)
    assert (datetime.datetime.now()
            < datetime.timedelta(milliseconds=600) + start_time)

    start_time = datetime.datetime.now()
    timeout = Timeout(10)
    while not timeout.expired:
        time.sleep(1)
        print timeout.elapsed_time, timeout.time_remaining
        print timeout.seconds_remaining

    assert (datetime.datetime.now()
            >= datetime.timedelta(seconds=10) + start_time)

    stopwatch = Stopwatch()
    for _ in xrange(3):
        time.sleep(1)
        print stopwatch.lap()
    print stopwatch.stop()
