# Source code for Level

    ''' Contains the Level object '''

    import os
    import numpy as np
    from PIL import Image

    from GramophoneTools.LinMaze import LinMaze, Frame, Event, Rule
    from GramophoneTools.LinMaze.Zone import Zone
    from GramophoneTools.LinMaze.Tools import Stopwatch, progressbar
    from GramophoneTools.LinMaze.Tools.filehandler import select_file

    class Level(object):
        '''
        An object that can be played for conditioning.
        You can add frames, events and rules to it to tune your conditioning.

        :param name: The name this Level will be refered to by (eg. in the default filename when saving)
        :type name:  str

        :param screen_res: The resolution of the simulation window. Set it to the resolution of the monitor
            if you are going to make the simulation full screen. eg.: (800,600)
        :type screen_res: int, int

        :param zone_offset: Zone offset in pixels, ie. what is the position of the mouse on the screen from the left.
        :type zone_offset: int

        :param transition_width: How long should the transition (gradual fade) shold be between zones in pixels.
        :type transition_width: int

        :param rgb: Red, Green and Blue levels of the frames as floats between 0 and 1.
        :type rgb: float, float, float
        '''

        def __init__(self, name, screen_res, zone_offset,
                     transition_width=100, rgb=(1, 1, 1)):
            print("\nCreating new VR level: ", name, "\n")
            self.name = name
            # self.screen_res = screen_res
            self.screen_width = screen_res[0]
            self.screen_height = screen_res[1]
            self.zone_offset = zone_offset
            self.transition_width = transition_width
            self.rgb = rgb

            self.rendered = False

            self.frames = []
            self.events = {}
            self.zones = []
            self.rules = []

        def __str__(self):
            level_string = self.name + '\nResolution: ' + str(self.screen_width)+'×'+str(self.screen_height) + \
                '\nZone offset: ' + str(self.zone_offset) +\
                '\nTransition width: ' + str(self.transition_width)+'\n'

            level_string += "\nBLOCKS\n"
            for frame, zone in zip(self.frames, self.zones):
                level_string += str(frame) + '\n' + str(zone) + '\n\n'

            level_string += "\nRULES\n"
            for rule in self.rules:
                level_string += 'If '+str(rule).lower() + \
                    ' then '+str(rule.event).lower()+'\n'

            return level_string

        @property
        def dummy_frame(self):
            ''' A frame that looks like the begining of the level. '''
            dummy_frame = Frame.Frame(self.screen_width, self.screen_height)
            dummy_frame.texture = self.combined_frame.texture[:, :self.screen_width].astype(
                np.uint8)
            dummy_frame.made = True
            return dummy_frame

        @property
        def combined_frame(self):
            ''' All the frames combined left to right. '''
            if not self.rendered:
                self.render()

            return Frame.combine(self.frames)

        @property
        def image(self):
            ''' An image of the Level's combined frames. '''
            return self.combined_frame.make_img()

        def add_block(self, frame_type, *args, **kwargs):
            ''' Adds a block of the specified type and parameters to the level. '''

            frame_type = frame_type.lower()

            width = kwargs.get('length', 0) + self.transition_width
            height = self.screen_height
            wavelength = kwargs.get('wavelength', None)
            angle = kwargs.get('angle', None)
            random_seed = kwargs.get('random_seed', None)
            side_length = kwargs.get('side_length', None)
            filename = kwargs.get('filename', None)

            frame_maker = {  # Frame type switch
                'sine': lambda: Frame.SineWave(width, height, wavelength, angle),
                'square': lambda: Frame.SquareWave(width, height, wavelength, angle),
                'greynoise': lambda: Frame.GreyNoise(width, height, random_seed),
                'binarynoise': lambda: Frame.BinaryNoise(width, height, random_seed),
                'cloud': lambda: Frame.Cloud(width, height, random_seed),
                'marble': lambda: Frame.Marble(width, height, random_seed),
                'wood': lambda: Frame.Wood(width, height, random_seed),
                'checkerboard': lambda: Frame.Checkerboard(width, height, side_length),
                'image': lambda: Frame.ImageFile(height, filename)
            }.get(frame_type, None)

            if frame_maker is None:
                raise ValueError(
                    'Error: \'{}\' is not a valid frame_type'.format(frame_type))
            frame = frame_maker()

            # New zone starts at the end of the last one
            if self.zones:
                begin = self.zones[-1].end
                end = self.zones[-1].end + frame.width - self.transition_width
            else:  # Fist zone starts at 0
                begin = 0
                end = frame.width - self.transition_width
            zone = Zone(begin, end, kwargs.get('zone_type', 'generic'))

            self.frames.append(frame)
            self.zones.append(zone)

            print("\nAdded block to " + self.name + ":")
            print(str(frame))
            print(str(zone))

        def add_event(self, name, event_type, *args):
            '''
            Adds an Event to the level which can be triggered by Rules.

            :param name: The name this Event can be referenced by
            :type name: string

            :param event_type: type of the event can be 'teleport', 'random_teleport',
                'start_burst', 'stop_burst', 'port_on', 'port_off', 'pause', 'unpause'
            :type event_type: string

            :param args: The arguments for the Event of the given type.
            '''

            if event_type == "teleport":
                self.events[name] = Event.Teleport(self, args[0])

            if event_type == "random_teleport":
                self.events[name] = Event.RandomTeleport(self, args[0])

            if event_type == "start_burst":
                self.events[name] = Event.StartBurst(
                    self, args[0], args[1], args[2])

            if event_type == "stop_burst":
                self.events[name] = Event.StopBurst(self, args[0])

            if event_type == "port_on":
                self.events[name] = Event.PortOn(self, args[0])

            if event_type == "port_off":
                self.events[name] = Event.PortOff(self, args[0])

            if event_type == "pause":
                self.events[name] = Event.Pause(self, args[0])

            if event_type == "unpause":
                self.events[name] = Event.UnPause(self, args[0])

            if event_type == "print":
                self.events[name] = Event.Print(self, args[0])

        def add_rule(self, rule_type, event_name, *args):
            '''
            Adds a rule that triggers events based on animal behaviour.

            :param rule_type: The type of this rule, can be 'zone', 'velocity' or 'speed'.
            :type rule_type: string

            :param event_name: The name of the Event this Rule can trigger.
            :type event_name: string

            :param args: The arguments for the Rule of the given type.
            '''
            if rule_type == "zone":
                self.rules.append(Rule.ZoneRule(
                    self, self.events[event_name], args[0], args[1]))
                # zone_type, delay

            if rule_type == "velocity":
                self.rules.append(Rule.VelocityRule(
                    self, self.events[event_name], args[0], args[1], args[2]))
                # vel_rule_type, threshold, delay

            if rule_type == "smooth_velocity":
                self.rules.append(Rule.SmoothVelocityRule(
                    self, self.events[event_name], args[0], args[1], args[2], args[3]))
                # bin_size, vel_rule_type, threshold, delay

            if rule_type == "speed":
                self.rules.append(Rule.SpeedRule(
                    self, self.events[event_name], args[0], args[1], args[2]))
                # speed_rule_type, threshold, bin_size

            if rule_type == "keypress":
                self.rules.append(Rule.KeyPressRule(
                    self, self.events[event_name], args[0]))
                # key

            if rule_type == "input":
                self.rules.append(Rule.InputRule(
                    self, self.events[event_name], args[0], args[1]))
                # input_id, trigger_type

        def render(self):
            ''' Renders all the frames of the level, making it ready to be played. '''
            if not self.rendered:
                print('\n'+self.name,  '>>\n')
                self.frames = Frame.multi_make(self.frames)
                self.frames = Frame.frame_transitions(self.frames, self.transition_width)

                loadtime = Stopwatch.Stopwatch()
                for count, frame in enumerate(self.frames):
                    frame.make_texture()
                    progressbar.printProgressBar(count, len(self.frames),
                                                 prefix=' Loading  ',
                                                 suffix='  (' + str(loadtime) + ')')
                progressbar.printProgressBar(len(self.frames), len(self.frames),
                                             prefix=' Loading  ',
                                             suffix='  (' + str(loadtime) + ')')
                print('\n')

                self.rendered = True

        def show_image(self):
            ''' Displays the level as a picture '''
            self.image.show()

        def save_image(self):
            ''' Saves the picture of the Level '''
            filename = select_file(defaultextension='.bmp',
                                   filetypes=[('VR Level image', '.bmp')],
                                   title='Save image of Level',
                                   initialdir=os.getcwd(),
                                   initialfile=self.name)
            self.image.convert('RGB').save(filename)

        def save_summary(self):
            ''' Saves a human readable summary of the Level '''
            filename = select_file(defaultextension='.txt',
                                   filetypes=[('Text summary', '.txt')],
                                   title='Save summary of Level',
                                   initialdir=os.getcwd(),
                                   initialfile=self.name)

            with open(filename, 'w') as summary_file:
                summary_file.write(str(self))

        def play(self, *args, **kwargs):
            '''
            Plays the Level by making a Session for it.

            :param args: Arguments of the Session created.
            :param kwargs: Keyword arguments of the Session created.

            '''
            try:
                LinMaze.Session(self, *args, **kwargs)
            except LinMaze.LinMazeError as err:
                print('\nERROR:', err)
                input()