#!/usr/bin/python3

"""Tests for EveryNSeconds class."""

from __future__ import print_function

import sys
import time
import functools

import every_n_seconds


def timed_function(seconds=3600):
    """Blip every "seconds" seconds."""
    time.sleep(seconds)
    # print('Blip. {}'.format(time.ctime()))


def timed_test():
    """Run a timed test."""
    # I've intentionally used something that doesn't divide evenly
    callback = functools.partial(timed_function, seconds=1.0 / 3.0)
    seconds = 2.0
    max_reps = 3
    tolerance = 0.1
    runner = every_n_seconds.EveryNSeconds(
        seconds=seconds,
        resolution=0.01,
        max_reps=max_reps,
    )
    time0 = time.time()
    while not runner.is_done_running(callback=callback):
        pass
    time1 = time.time()
    difference = time1 - time0
    expected_duration = seconds * max_reps
    if \
            expected_duration - tolerance < \
            abs(difference) < \
            expected_duration + tolerance:
        return True

    string = '{}: timed_test: duration bad, not near {}: {}\n'
    tuple_ = (sys.argv[0], expected_duration, difference)
    sys.stderr.write(string.format(*tuple_))
    return False


COUNTED_REPS = 0


def counted_function(seconds=3600):
    """Blip every "seconds" seconds."""
    # pylint: disable=global-statement
    global COUNTED_REPS
    COUNTED_REPS += 1
    time.sleep(seconds)
    # print('Blip. {}'.format(time.ctime()))


def counted_test():
    """Run a counted test."""
    callback = functools.partial(counted_function, seconds=2)
    runner = every_n_seconds.EveryNSeconds(
        seconds=0.5,
        resolution=0.01,
        max_reps=2,
    )
    while not runner.is_done_running(callback=callback):
        pass

    if COUNTED_REPS == 2:
        return True

    string = '{}: counted_test: rep count bad, not 2: {}\n'
    sys.stderr.write(string.format(sys.argv[0], COUNTED_REPS))
    return False


class RetryExc(Exception):
    """We raise this to test if a retry exception will incur an extra delay or not."""


RETRY_EXC_REPS = 0


def retry_exc_function(seconds=3600):
    # pylint: disable=global-statement
    """Blip every "seconds" seconds."""
    global RETRY_EXC_REPS
    RETRY_EXC_REPS += 1
    if RETRY_EXC_REPS == 2:
        raise RetryExc
    time.sleep(seconds)
    # print('Blip. {} {}'.format(RETRY_EXC_REPS, time.ctime()))


def retry_exc_test():
    """Run a test with exception retrying."""
    # I've intentionally used something that doesn't divide evenly
    callback = functools.partial(retry_exc_function, seconds=1.0 / 3.0)
    seconds = 2.0
    max_reps = 3
    tolerance = 0.1
    runner = every_n_seconds.EveryNSeconds(
        seconds=seconds,
        resolution=0.01,
        max_reps=max_reps,
        retry_exceptions=(RetryExc, ),
    )
    time0 = time.time()
    while not runner.is_done_running(callback=callback):
        pass
    time1 = time.time()
    difference = time1 - time0
    expected_duration = seconds * max_reps
    if \
            expected_duration - tolerance < \
            abs(difference) < \
            expected_duration + tolerance:
        return True

    string = '{}: timed_test: duration bad, not near {}: {}\n'
    tuple_ = (sys.argv[0], expected_duration, difference)
    sys.stderr.write(string.format(*tuple_))
    return False


class NoRetryExc(Exception):
    """We raise this to test if a no-retry exception will terminate iteration."""


NO_RETRY_EXC_REPS = 0


def no_retry_exc_function(seconds=3600):
    # pylint: disable=global-statement
    """Blip every "seconds" seconds."""
    global NO_RETRY_EXC_REPS
    NO_RETRY_EXC_REPS += 1
    if NO_RETRY_EXC_REPS == 2:
        raise NoRetryExc
    time.sleep(seconds)
    # print('Blip. {} {}'.format(RETRY_EXC_REPS, time.ctime()))


def no_retry_exc_test():
    """Run a test with an uncaught exception."""
    # I've intentionally used something that doesn't divide evenly
    callback = functools.partial(no_retry_exc_function, seconds=1.0 / 3.0)
    seconds = 2.0
    max_reps = 3
    tolerance = 0.1
    runner = every_n_seconds.EveryNSeconds(
        seconds=seconds,
        resolution=0.01,
        max_reps=max_reps,
        retry_exceptions=(),
    )
    time0 = time.time()
    try:
        while not runner.is_done_running(callback=callback):
            pass
    except NoRetryExc:
        pass
    else:
        sys.stderr.write('{}: Did not get NoRetryExc\n'.format(sys.argv[1]))
        return False
    time1 = time.time()
    difference = time1 - time0
    expected_duration = seconds * 1
    if \
            expected_duration - tolerance < \
            abs(difference) < \
            expected_duration + tolerance:
        return True

    string = '{}: timed_test: duration bad, not near {}: {}\n'
    tuple_ = (sys.argv[0], expected_duration, difference)
    sys.stderr.write(string.format(*tuple_))
    return False


def main():
    """Run all tests."""
    # We could use py.test or nose or something, but I like the control
    # afforded by doing it manually
    all_good = True

    all_good &= timed_test()
    all_good &= counted_test()
    all_good &= retry_exc_test()
    all_good &= no_retry_exc_test()

    if all_good:
        print('All tests passed.')
        sys.exit(0)
    else:
        sys.stderr.write('{}: One or more tests failed\n'.format(sys.argv[0]))
        sys.exit(1)


main()