'''TODO: Documentation
'''

from bge import logic
import time


def get_event_manager():
    scene = logic.getCurrentScene()
    if ULEventManager.update not in scene.post_draw:
        scene.post_draw.append(ULEventManager.update)
        # ULEventManager.initialized = True


class ULEventManager():
    '''Manager for `ULEvent` objects, not inteded for manual use.
    '''
    events = {}
    callbacks = []
    initialized = False
    done = []

    @classmethod
    def update(cls):
        for cb in cls.callbacks.copy():
            cb()

    @classmethod
    def log(cls):
        if cls.events:
            print('Events:')
            for evt in cls.events:
                print(f'\t{evt}:\t{cls.events[evt].content}')


    @classmethod
    def schedule(cls, cb):
        if not cls.initialized:
            get_event_manager()
        cls.callbacks.append(cb)

    @classmethod
    def deschedule(cls, cb):
        if not cls.initialized:
            get_event_manager()
        cls.callbacks.remove(cb)

    @classmethod
    def register(cls, event):
        if not cls.initialized:
            get_event_manager()
        cls.events[event.name] = event
        cls.schedule(event.remove)

    @classmethod
    def send(cls, name, content, messenger) -> None:
        if not cls.initialized:
            get_event_manager()
        ULEvent(name, content, messenger)


    @classmethod
    def receive(cls, name):
        if not cls.initialized:
            get_event_manager()
        return cls.events.get(name, None)

    @classmethod
    def consume(cls, name):
        if not cls.initialized:
            get_event_manager()
        return cls.events.pop(name, None)


class ULEvent():
    '''Event generated by `uplogic.events.send()`.
    
    **Not intended for manual use.**

    :param `name`: Name of the event; can be anything, not just `str`.
    :param `content`: This can be used to store data in an event.
    :param `messenger`: Can be used to store an object.
    '''

    def __init__(self, name, content=None, messenger=None):
        self.name = name
        self.content = content
        self.messenger = messenger
        ULEventManager.schedule(self.register)

    def register(self):
        ULEventManager.register(self)
        ULEventManager.deschedule(self.register)

    def remove(self):
        ULEventManager.events.pop(self.name, None)
        ULEventManager.deschedule(self.remove)


def send(name, content=None, messenger=None) -> None:
    '''Send an event that can be reacted to.

    :param `name`: Name of the event; can be anything, not just `str`.
    :param `content`: This can be used to store data in an event.
    :param `messenger`: Can be used to store an object.
    '''
    ULEventManager.send(name, content, messenger)


def receive(name) -> ULEvent:
    '''Check if an event has occured.

    :param `name`: Name of the event; can be anything, not just `str`.

    :returns: `ULEvent` with `name`, `content` and `messenger` as attributes.
    '''
    return ULEventManager.receive(name)


def consume(name: str):
    '''Check if an event has occured. This will remove the event.

    :param `name`: Name of the event; can be anything, not just `str`.

    :returns: `ULEvent` with `name`, `content` and `messenger` as attributes.
    '''
    return ULEventManager.consume(name, None)


def bind(name, callback):
    '''Send an event that can be reacted to.

    :param `name`: Name of the event; can be anything, not just `str`.
    :param `content`: This can be used to store data in an event.
    :param `messenger`: Can be used to store an object.
    '''
    def _check_evt(name, callback):
        evt = receive(name)
        if evt:
            callback(evt.name, evt.content, evt.messenger)
    logic.getCurrentScene().post_draw.append(_check_evt)


def schedule(name: str, content=None, messenger=None, delay=0.0):
    '''Send an event that can be reacted to with a delay.

    :param `name`: Name of the event; can be anything, not just `str`.
    :param `content`: This can be used to store data in an event.
    :param `messenger`: Can be used to store an object.
    :param `delay`: Delay with which to send the event in seconds.
    '''
    ScheduledEvent(delay, name, content, messenger)


class ScheduledEvent():
    '''Event generated by `uplogic.events.schedule()`.
    
    **Not intended for manual use.**

    :param `delay`: Delay with which to send the event in seconds.
    :param `name`: Name of the event; can be anything, not just `str`
    :param `content`: This can be used to store data in an event.
    :param `messenger`: Can be used to store an object.
    '''

    def __init__(self, delay, name, content, messenger):
        self.time = time.time()
        self.delay = self.time + delay
        self.name = name
        self.content = content
        self.messenger = messenger
        ULEventManager.schedule(self.send_scheduled)

    def send_scheduled(self):
        if time.time() >= self.delay:
            ULEventManager.deschedule(self.send_scheduled)
            ULEvent(self.name, self.content, self.messenger)


def schedule_callback(cb, delay=0.0, arg=None):
    '''Call a function with a delay. The function can have an argument when
    defined as a keyword.

    Callback cannot return anything.

    :param `cb`: Callback to be evaluated.
    :param `delay`: Delay with which to call the function in seconds.
    :param `arg`: If this is defined, callback will be called with this
    argument.
    '''
    ScheduledCallback(cb, delay, arg)


class ScheduledCallback():
    '''Event generated by `uplogic.events.schedule_callback()`.
    
    **Not intended for manual use.**

    :param `cb`: Callback to be evaluated.
    :param `delay`: Delay with which to call the function in seconds.
    :param `arg`: If this is defined, callback will be called with this
    argument.
    '''

    def __init__(self, cb, delay=0.0, arg=None):
        ULEventManager.schedule(self.call_scheduled)
        self.time = time.time()
        self.delay = self.time + delay
        self.callback = cb
        self.arg = arg

    def call_scheduled(self):
        if time.time() >= self.delay:
            if self.arg is not None:
                self.callback(self.arg)
            else:
                self.callback()
            ULEventManager.deschedule(self.call_scheduled)
