"""
------------------------------------------------------------------------------
--                                                                          --
--                                 LAB 04                                   --
--                                                                          --
--                             L A B 0 4 . P Y                              --
--                                                                          --
------------------------------------------------------------------------------
-- <Your Name Here>                                                         --
--                                                                          --
-- <Class Name Here>                                                        --
--                                                                          --
------------------------------------------------------------------------------
-- This file contains a simple Zelda demo.                                  --
--                                                                          --
------------------------------------------------------------------------------
"""

from Tkinter import *
import Frames

class AnimatedSprite:
    """
    Represents a 2d animated sprite.  The Sprite can be set into motion in
    a particular direction by setting that direction to True.  Conversly,
    Setting the direction to False will stop motion in that direction.
    """
    directions = {
        'North' : 1,
        'South' : 2, 
        'East' : 4,
        'West' : 8 
    }

    def reset_animation(self):
        """
        Resets the current frame of self to the first image in its current
        animation set.
        """
        self.current = 0
        self.image = self.frames[self.current_set][0]

    def __init__(self, frames, x=0, y=0):
        self.frames = frames
        self.current_set = 0
        self.reset_animation()
        self._moving = 0
        self.x = x
        self.y = y

    def moving(self):
        """
        Returns true if and only if self is currently in motion
        """
        return self._moving > 0

    def __getitem__(self, key):
        """
        Defines a [] operator for Sprites which takes a direction string as an
        index.
        """
        if (type(key) != str and type(key) != unicode) or key not in AnimatedSprite.directions:
            raise IndexError, 'Index for Sprite must be a valid direction'

        return bool(self._moving & AnimatedSprite.directions[key])

    def __setitem__(self, key, value):
        """
        Defines a [] operator for Sprites which takes a direction string key as 
        an index, and a boolean value to assign to the direction.
        """
        if (type(key) != str and type(key) != unicode) or key not in AnimatedSprite.directions:
            raise IndexError, 'Index for Sprite must be a valid direction'

        if type(value) != bool:
            raise TypeError, 'Value for a direction can only be True or False'

        if value:
            self._moving = self._moving | AnimatedSprite.directions[key]
        else:
            self._moving = self._moving & ~AnimatedSprite.directions[key]

    def update_frame(self):
        """
        Sets the current frame of self to the next frame in the appropriate
        animation
        """
        curr_set = 1

        if self['South']:
            curr_set = 0
        elif self['North']:
            curr_set = 2
        elif self['East']:
            curr_set = 3

        if curr_set != self.current_set:
            self.current = 0
            self.current_set = curr_set
        else:
            self.current = self.current + 1

        if self.current == len(self.frames[self.current_set]):
            self.current = 0

        self.image = self.frames[self.current_set][self.current]

    def set_frame(self, num):
        """
        Forces the current frame of self to be the num'th frame in its current
        animation
        """
        self.current = num
        self.image = self.frames[self.current_set][self.current]


class World(Frame):
    """
    Represents a simple 'toroidal' world (toroidal just means 'shaped like a
    donut'.  What this means in our case is that walking off the left end of
    the screen will take you over to the right end and vice-versa.  Likewise
    for the top and bottom of the screen.  This particular world also contains
    Link from The Legend of Zelda.
    """
    def __init__(self, master=None):
        Frame.__init__(self, master)
        Pack.config(self)

        self.world_map = Canvas(self, width=640, height=480)
        self.world_map.pack()

        frame_sets = []
        for binaries in Frames.link_sets:
            frames = []
            for binary in binaries:
                frames += [PhotoImage(data = binary)]
            frame_sets += [frames]

        self.link = AnimatedSprite(frame_sets, 250, 250)
        self.stop_counter = 0
        self.sprite = self.world_map.create_image(self.link.x, self.link.y, image = self.link.image)

        self.bind_all('<KeyPress>', self.onKeyPress)
        self.bind_all('<KeyRelease>', self.onKeyRelease)
        self.after(10, World.update, self)

    def update(self):
        """
        Updates world self by one 'tick'.  If Link is moving, he'll be moved
        one step in the correct direction(s).  His sprite will also be updated
        by one frame.
        """
        self.world_map.delete(self.sprite)
        if self.link.moving():
            self.link.update_frame()

            if self.link['North']:
                self.link.y -= 3
                if self.link.y < -64:
                    self.link.y = int(self.world_map.cget('height')) + 20

            if self.link['South']:
                self.link.y += 3
                if self.link.y > int(self.world_map.cget('height')) + 32:
                    self.link.y = -84

            if self.link['East']:
                self.link.x += 3
                if self.link.x > int(self.world_map.cget('width')) + 24:
                    self.link.x = -68

            if self.link['West']:
                self.link.x -= 3
                if self.link.x < -48:
                    self.link.x = int(self.world_map.cget('width')) + 20

            self.stop_counter = 0
        elif self.stop_counter > 3:
            self.stop_counter = 0
            self.link.reset_animation()
        else:
            self.stop_counter = self.stop_counter + 1

        self.sprite = self.world_map.create_image(self.link.x, self.link.y, image = self.link.image)

        self.after(42, World.update, self)

    def onKeyPress(self, event):
        """
        Whenever a key is pressed on the keyboard, this function is called.
        event.char gives the key which was pressed as a string.
        """
        if event.char == 'z':
            print 'Zelda!'
        else:
            print event.char

    def onKeyRelease(self, event):
        """
        Whenever a key is let go on the keyboard, this function is called.
        event.char gives the key which was let go as a string.
        """
        pass

if __name__ == '__main__':
    world = World()

    world.mainloop()
