'''
Clock tests
===========
'''
import gc
import weakref
import pytest


class ClockCounter:

    counter = 0

    def __call__(self, *args, **kwargs):
        self.counter += 1


@pytest.fixture()
def clock_counter():
    yield ClockCounter()


def test_schedule_once(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_schedule_once_twice(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.tick()
    assert clock_counter.counter == 2


def test_schedule_once_draw_after(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter, 0)
    kivy_clock.tick_draw()
    assert clock_counter.counter == 0
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_schedule_once_draw_before(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter, -1)
    kivy_clock.tick_draw()
    assert clock_counter.counter == 1
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_unschedule(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.unschedule(clock_counter)
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_unschedule_event(kivy_clock, clock_counter):
    ev = kivy_clock.schedule_once(clock_counter)
    kivy_clock.unschedule(ev)
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_unschedule_after_tick(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter, 5.)
    kivy_clock.tick()
    kivy_clock.unschedule(clock_counter)
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_unschedule_draw(kivy_clock, clock_counter):
    kivy_clock.schedule_once(clock_counter, 0)
    kivy_clock.tick_draw()
    assert clock_counter.counter == 0
    kivy_clock.unschedule(clock_counter)
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_trigger_create(kivy_clock, clock_counter):
    trigger = kivy_clock.create_trigger(clock_counter, 0)
    trigger()
    assert clock_counter.counter == 0
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_trigger_cancel(kivy_clock, clock_counter):
    trigger = kivy_clock.create_trigger(clock_counter, 5.)
    trigger()
    trigger.cancel()
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_trigger_interval(kivy_clock, clock_counter):
    trigger = kivy_clock.create_trigger(clock_counter, 0, interval=True)
    trigger()
    kivy_clock.tick()
    assert clock_counter.counter == 1
    kivy_clock.tick()
    assert clock_counter.counter == 2


def test_trigger_decorator(kivy_clock, clock_counter):
    from kivy.clock import triggered

    @triggered()
    def triggered_callback():
        clock_counter(dt=0)

    triggered_callback()
    assert clock_counter.counter == 0
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_trigger_decorator_cancel(kivy_clock, clock_counter):
    from kivy.clock import triggered

    @triggered()
    def triggered_callback():
        clock_counter(dt=0)

    triggered_callback()
    triggered_callback.cancel()
    kivy_clock.tick()
    assert clock_counter.counter == 0


def test_trigger_decorator_no_call(kivy_clock, clock_counter):
    from kivy.clock import triggered

    @triggered
    def triggered_callback():
        clock_counter(dt=0)

    triggered_callback()
    assert clock_counter.counter == 0
    kivy_clock.tick()
    assert clock_counter.counter == 1


def test_exception_caught(kivy_clock, clock_counter):
    exception = None

    def handle_test_exception(e):
        nonlocal exception
        exception = str(e)

    # monkey patch to ignore exception
    kivy_clock.handle_exception = handle_test_exception

    def raise_exception(*args):
        raise ValueError('Stooooop')

    kivy_clock.schedule_once(raise_exception)
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.tick()

    assert clock_counter.counter == 1
    assert exception == 'Stooooop'


def test_exception_ignored(kivy_clock, clock_counter):
    def raise_exception(*args):
        raise ValueError('Stooooop')

    kivy_clock.schedule_once(raise_exception)
    kivy_clock.schedule_once(clock_counter)

    with pytest.raises(ValueError):
        kivy_clock.tick()

    assert clock_counter.counter == 0


def test_exception_caught_handler(
        kivy_clock, clock_counter, kivy_exception_manager):
    from kivy.base import ExceptionHandler
    exception = None

    class KivyHandler(ExceptionHandler):

        def handle_exception(self, e):
            nonlocal exception
            exception = str(e)
            return kivy_exception_manager.PASS
    kivy_exception_manager.add_handler(KivyHandler())

    def raise_exception(*args):
        raise ValueError('Stooooop')

    kivy_clock.schedule_once(raise_exception)
    kivy_clock.schedule_once(clock_counter)
    kivy_clock.tick()

    assert clock_counter.counter == 1
    assert exception == 'Stooooop'


def test_clock_ended_callback(kivy_clock, clock_counter):
    counter2 = ClockCounter()
    counter_schedule = ClockCounter()

    kivy_clock.schedule_once(counter_schedule)
    event = kivy_clock.create_lifecycle_aware_trigger(clock_counter, counter2)
    event()

    kivy_clock.stop_clock()
    assert counter_schedule.counter == 0
    assert clock_counter.counter == 0
    assert counter2.counter == 1


def test_clock_ended_del_safe(kivy_clock, clock_counter):
    counter2 = ClockCounter()
    kivy_clock.schedule_lifecycle_aware_del_safe(clock_counter, counter2)

    kivy_clock.stop_clock()
    assert clock_counter.counter == 0
    assert counter2.counter == 1


def test_clock_ended_raises(kivy_clock, clock_counter):
    from kivy.clock import ClockNotRunningError
    event = kivy_clock.create_lifecycle_aware_trigger(
        clock_counter, clock_counter)

    kivy_clock.stop_clock()
    with pytest.raises(ClockNotRunningError):
        event()
    assert clock_counter.counter == 0

    # we should be able to create the event
    event = kivy_clock.create_lifecycle_aware_trigger(
        clock_counter, clock_counter)
    with pytest.raises(ClockNotRunningError):
        event()
    assert clock_counter.counter == 0

    kivy_clock.schedule_once(clock_counter)
    assert clock_counter.counter == 0


def test_clock_ended_del_safe_raises(kivy_clock, clock_counter):
    from kivy.clock import ClockNotRunningError
    counter2 = ClockCounter()

    kivy_clock.stop_clock()
    with pytest.raises(ClockNotRunningError):
        kivy_clock.schedule_lifecycle_aware_del_safe(clock_counter, counter2)
    assert clock_counter.counter == 0


def test_clock_stop_twice(kivy_clock, clock_counter):
    counter2 = ClockCounter()
    event = kivy_clock.create_lifecycle_aware_trigger(
        clock_counter, counter2)
    event()

    kivy_clock.stop_clock()
    assert clock_counter.counter == 0
    assert counter2.counter == 1

    kivy_clock.stop_clock()
    assert clock_counter.counter == 0
    assert counter2.counter == 1


def test_clock_restart(kivy_clock):
    kivy_clock.stop_clock()
    # with pytest.raises(TypeError):
    #     kivy_clock.start_clock()
    # for now it doesn't yet raise a error
    kivy_clock.start_clock()


def test_clock_event_trigger_ref(kivy_clock):
    value = None

    class Counter:
        def call(self, *args, **kwargs):
            nonlocal value
            value = 42

    event = kivy_clock.create_trigger(Counter().call)
    gc.collect()
    event()
    kivy_clock.tick()
    assert value is None

    kivy_clock.schedule_once(Counter().call)
    event()
    kivy_clock.tick()
    assert value is None

    event = kivy_clock.create_trigger(Counter().call, release_ref=False)
    gc.collect()
    event()
    kivy_clock.tick()
    assert value == 42
