''' Events define something that can happen while the level is being played. Triggered by rules. '''
from abc import ABC, abstractproperty


class Event(ABC):
    ''' 
    Generic Event object all specific Events inherit from.

    :param level: The Level this Event will be used on.
    :type level: Level
    '''

    def __init__(self, level):
        self.level = level
        self.session = None
        self.trigger_count = 0

    def trigger(self):
        ''' Triggers the Event. Prints Event information. '''
        self.trigger_count += 1
        print(self, "["+str(self.trigger_count)+"]", '\n')


    @abstractproperty
    def triggerable(self):
        ''' Tell whether it makes sense to trigger this event at the moment. '''
        pass

    def set_session(self, session):
        ''' 
        Sets the Session the event will be triggered in. 

        :param session: The session this Event will be triggered in.
        :type session: Session
        '''
        self.session = session


class Teleport(Event):
    '''
    Teleports to a set location 

    :param level: The Level this Event will be used on.
    :type level: Level

    :param target_position: The target of the teleportation.
    :type target_position: int
    '''

    def __init__(self, level, target_position):
        super().__init__(level)
        self.target_position = target_position

    def trigger(self):
        self.session.teleport(self.target_position)
        super().trigger()

    @property
    def triggerable(self):
        return not self.session.paused

    def __str__(self):
        return "Teleport to position " + str(self.target_position)


class RandomTeleport(Event):
    ''' 
    Teleports to a type of zone that is on the given list randomly. 

    :param level: The Level this Event will be used on.
    :type level: Level

    :param list_of_target_zones: The possible Zones this teleport can land is.
    :type list_of_target_zones: [Zone]
    '''

    def __init__(self, level, list_of_target_zones):
        super().__init__(level)
        self.list_of_target_zones = list_of_target_zones

    def trigger(self):
        self.session.random_teleport(self.list_of_target_zones)
        super().trigger()

    @property
    def triggerable(self):
        return not self.session.paused

    def __str__(self):
        return "Teleport to a random zone with type: " + str(self.list_of_target_zones)


class PortOn(Event):
    '''
    Turns on a port on the Level's Gramophone.

    :param level: The Level this Event will be used on.
    :type level: Level

    :param port: Which output to use on the device.
    :type port: int
    '''

    def __init__(self, level, port):
        super().__init__(level)
        self.port = port

    def trigger(self):
        self.session.gramophone.write_output(self.port, 1)
        self.session.gramophone.read_outputs()
        super().trigger()

    def __str__(self):
        return "Turn ON port " + str(self.port)

    @property
    def triggerable(self):
        if self.port == 1:
            return not bool(self.session.gramophone.last_out_1)
        if self.port == 2:
            return not bool(self.session.gramophone.last_out_2)
        if self.port == 3:
            return not bool(self.session.gramophone.last_out_3)
        if self.port == 4:
            return not bool(self.session.gramophone.last_out_4)


class PortOff(Event):
    '''
    Turns off a port on the Level's Gramophone.

    :param level: The Level this Event will be used on.
    :type level: Level

    :param port: Which output should be turned off.
    :type port: str -- 'A', 'B' or 'C'
    '''

    def __init__(self, level, port):
        super().__init__(level)
        self.port = port

    def trigger(self):
        self.session.gramophone.write_output(self.port, 0)
        self.session.gramophone.read_outputs()
        super().trigger()

    @property
    def triggerable(self):
        if self.port == 1:
            return bool(self.session.gramophone.last_out_1)
        if self.port == 2:
            return bool(self.session.gramophone.last_out_2)
        if self.port == 3:
            return bool(self.session.gramophone.last_out_3)
        if self.port == 4:
            return bool(self.session.gramophone.last_out_4)


    def __str__(self):
        return "Turn OFF port " + str(self.port)


class StartBurst(Event):
    '''
    Starts bursting a port on the Level's gramophone.
    
    
    :param level: The Level this Event will be used on.
    :type level: Level
    
    :param port: Which output to use on the device.
    :type port: int

    :param on_time: How long should the port by set to high before a pause.
    :type on_time: float

    :param pause_time: How long should the pauses be.
    :type pause_time: float
    '''

    def __init__(self, level, port, on_time, pause_time):
        super().__init__(level)
        self.port = port
        self.on_time = on_time
        self.pause_time = pause_time

    def trigger(self):
        self.session.gramophone.start_burst(
            self.port, self.on_time, self.pause_time)
        super().trigger()

    @property
    def triggerable(self):
        return not self.session.gramophone.bursting[self.port]

    def __str__(self):
        return "Start " + str(self.on_time) + " sec bursts with " \
            + str(self.pause_time) + " sec pauses on port " + str(self.port)


class StopBurst(Event):
    '''
    Stops bursting a port on the Level's gramophone.
    
    :param level: The Level this Event will be used on.
    :type level: Level

    :param port: Which output should stop bursting.
    :type port: int
    '''

    def __init__(self, level, port):
        super().__init__(level)
        self.port = port

    def trigger(self):
        self.session.gramophone.stop_burst(self.port)
        super().trigger()

            # print("Bursting is already off on port", self.port, '\n')
    @property
    def triggerable(self):
        return self.session.gramophone.bursting[self.port]

    def __str__(self):
        return "Stop the bursts on port " + str(self.port)


class Pause(Event):
    '''
    Pauses the Level where it is or at a given position.

    :param level: The Level this Event will be used on.
    :type level: Level
    
    :param pause_position: Set to None to pause at current position. None by default.
    :type pause_position: int or None
    '''

    def __init__(self, level, pause_position=None):
        super().__init__(level)
        self.pause_position = pause_position

    def trigger(self):
        self.session.pause(self.pause_position)
        super().trigger()

    @property
    def triggerable(self):
        return not self.session.paused

    def __str__(self):
        pos_name = 'current' if self.pause_position is None else str(self.pause_position)
        return "Pause at " + pos_name + " position"


class UnPause(Event):
    '''
    Unpauses the Level where it is or at a given position.
    
    :param level: The Level this Event will be used on.
    :type level: Level
    
    :param unpause_position: Set to None to pause at current position. None by default.
    :type unpause_position: int or None
    '''

    def __init__(self, level, unpause_position=None):
        super().__init__(level)
        self.unpause_position = unpause_position

    def trigger(self):
        self.session.unpause(self.unpause_position)
        super().trigger()

    @property
    def triggerable(self):
        return self.session.paused

    def __str__(self):
        pos_name = 'current' if self.unpause_position is None else str(self.unpause_position)
        return "Unpause at " + pos_name + " position"


class Print(Event):
    """
    Prints the given message to the console window.

    :param message: The message that will be printed when triggered
    :type message: str
    """
    def __init__(self, level, message):
        super().__init__(level)
        self.message = message

    def __str__(self):
        return self.message

    @property
    def triggerable(self):
        return True