#!/usr/bin/python

import random

from math import *
from Gui import *

class World(Gui):
    """A World is a Gui that has animals and an interpreter.

    the World class provides method for interacting with
    animals that live in the world"""
    def __init__(self):
        Gui.__init__(self)
        self.exists = True
        self.animals = []
        self.inter = Interpreter(self, top_globals, top_locals)

    def register(self, animal):
        "add a new animal to the list"
        self.animals.append(animal)

    def unregister(self, animal):
        "remove an animal from the list"
        self.animals.remove(animal)

    def clear(self):
        "remove all animals from the list and clear the canvas"
        for animal in self.animals:
            animal.undraw()
        self.animals = []
        self.canvas.delete('all')

    def step(self):
        "move all the animals one step"
        for animal in self.animals:
            animal.step()
        
    def run(self):
        "keep moving the animals until someone presses Stop or Quit"
        self.running = 1
        while self.exists and self.running:
            self.step()
            self.update()
            time.sleep(self.delay)

    def stop(self):
        "pressing Stop sets running to False"
        self.running = False

    def quit(self):
        "pressing Qut sets exists to False and exits"
        self.running = False
        self.exists = False
        Gui.quit(self)
        sys.exit()
        
    def map_animals(self, callable):
        "apply the given callable to all the animals"
        map(callable, self.animals)
        
    def run_text(self):
        """get the contents of the text entry and execute them in
        the interpreter"""
        source = self.te_code.get(1.0, END)
        self.inter.run_code(source, '<user-provided code>')

    def run_file(self):
        """get a filename from the entry, read the contents,
        and execute it in the interpreter"""
        filename = self.en_file.get()
        fp = file(filename)
        source = fp.read()
        self.inter.run_code(source, filename)


class Interpreter:
    """an Interpreter maintains local and global state and
    executes code at run time"""
    def __init__(self, world, globals=globals(), locals=locals()):
        self.world = world
        self.globals = globals
        self.locals = locals
        self.globals['world'] = world

    def run_code(self, source, filename):
        "run the given code in the environment of this interpreter"
        code = compile(source, filename, 'exec')
        try:
            exec code in self.globals, self.locals
        except KeyboardInterrupt:
            self.world.quit()

    def run_code_thread(self, *args):
        """run the given code in a new thread
        note: the Thread class I am using here is defined in Gui.py"""
        Thread(self.run_code, *args)
        

class TurmiteWorld(World):
    """a TermiteWorld has a canvas with cells """
    def __init__(self):
        World.__init__(self)
        self.delay = 0.0        # time in seconds to sleep after an update
        self.ca_width = 600     # canvas width and height
        self.ca_height = 600
        self.csize = 5          # cell size in pixels
        self.cells = {}         # a dictionary that maps coordinates to cells
        self.setup()
        self.mainloop()

    def setup(self):

        # the left frame contains the canvas
        self.fr(LEFT)
        self.canvas = self.ca(width=self.ca_width, height=self.ca_height,
                              bg='white')

        # uncomment the following line to apply a swirly transform
        # to the space.
        # self.canvas.add_transform(SwirlTransform(pi/400), 0)
        self.endfr()

        # the right frame contains various buttons
        self.fr(LEFT, fill=BOTH, expand=1)

        self.fr()
        self.bu(LEFT, text='Make Turmite', command=self.make_turmite)
        self.bu(LEFT, text='Print canvas', command=self.canvas.dump)
        self.bu(LEFT, text='Quit', command=self.quit)
        self.endfr()

        self.fr()
        self.bu(LEFT, text='Run', command=self.run)
        self.bu(LEFT, text='Stop', command=self.stop)
        self.bu(LEFT, text='Step', command=self.step)
        self.bu(LEFT, text='Clear', command=self.clear)
        self.endfr()

        self.bu(BOTTOM, text='run this code', command=self.run_text)
        self.te_code = self.te(BOTTOM, height=20, width=40)
        self.te_code.insert(END, 't1 = Turmite(world)\n')
        self.te_code.insert(END, 't2 = Turmite(world)\n')
        self.te_code.insert(END, 't3 = Turmite(world)\n')
        self.te_code.insert(END, 't2.lt()\n')
        self.te_code.insert(END, 't3.rt()\n')
        self.te_code.insert(END, 'world.run()\n')

        self.endfr()

        # you can pre-make a grid of cells, or create them on demand
        # low = [-20, -20]
        # high = [20, 20]
        # self.make_cells([low, high], **self.unmarked_options)

    def clear(self):
        """delete the cells and then call World.clear to handle
        the rest"""
        for cell in self.cells.values():
            cell.undraw()
        World.clear(self)
        
    def get_bounds(self):
        return self.trans.invert_list([[0, 0],
                                       [self.ca_width, self.ca_height]])

    def cell_bounds(self, x, y):
        """compute the coordinates of the four corners of the cell
        at index (x, y) and return them as a list of tuples"""
        p1 = [x, y]
        p2 = [x+1, y]
        p3 = [x+1, y+1]
        p4 = [x, y+1]
        bounds = [(p[0]*self.csize, p[1]*self.csize) for p in [p1, p2, p3, p4]]
        return bounds

    def make_cells(self, limits):
        """make a grid of cells with the specified limits.
        limits is a list of pairs, [[lowx, lowy], [highx, highy]]"""
        low, high = limits
        for x in range(low[0], high[0]):
            col = []
            for y in range(low[1], high[1]):
                bounds = self.cell_bounds(x, y)
                self.cells[x,y] = Cell(self, bounds)

    def get_cell(self, x, y):
        """find and return the cell at (x, y), creating one if necessary"""
        x, y = int(round(x)), int(round(y))
        cell = self.cells.get((x,y), None)
        if cell==None:
            bounds = self.cell_bounds(x, y)
            cell = Cell(self, bounds)
            self.cells[x,y] = cell
        return cell

    def make_turmite(self):
        "add a new turmite to this world"
        turmite = Turmite(self)


class Cell:
    """a Cell object represents a cell in a CellWorld.
    
    marked_options specifies how the cell is drawn when it is marked;
    unmarked_options specifies how it is drawn otherwise.  If Cells
    have more than two states, this part of the code will have to
    be redesigned.
    
    tag is the Tk tag the refers to the item on the canvas."""

    def __init__(self, world, bounds):
        self.world = world
        self.marked_options = dict(fill='black', outline='gray80')
        self.unmarked_options = dict(fill='yellow', outline='gray80')
        self.tag = world.canvas.polygon(bounds, **self.unmarked_options)
        self.marked = 0

    def __del__(self):
        "when this object is deleted, delete it's canvas item"
        self.undraw()

    def undraw(self):
        "delete this cell's canvas item"
        self.world.canvas.delete(self.tag)
        
    def config_cell(self, **options):
        "change the configuration of this cell's canvas item"
        self.world.canvas.itemconfig(self.tag, **options)

    def cget_cell(self, x, y, option):
        "get the current configuration of this cell's canvas item"
        return self.world.canvas.itemconfig(self.tag, option)

    def mark(self):
        "mark this cell and reconfigure it accordingly"
        self.marked = 1
        self.config_cell(**self.marked_options)
        
    def unmark(self):
        "unmark this cell and reconfigure it accordingly"
        self.marked = 0
        self.config_cell(**self.unmarked_options)
        
    def is_marked(self):
        return self.marked



class Animal:
    """Animal is the parent class for Turmites and other animals
    Most animals override step, draw and undraw.
    """
    def __init__(self, world):
        self.world = world
        self.x = 0
        self.y = 0
        self.delay = 0
        
    def __del__(self):
        "when this object is deleted, delete it's canvas item"
        self.undraw()

    def step(self):
        pass

    def draw(self):
        pass

    def undraw(self):
        try:
            self.world.canvas.delete(self.tag)
        except AttributeError:
            pass

    def die(self):
        self.world.unregister(self)
        self.undraw()

    def redraw(self):
        self.undraw()
        self.draw()

    def update(self):
        self.world.update()
        time.sleep(self.delay)


class Turmite(Animal):
    """a turmite is an animal that has a direction (4-state)
    This particular turmite is Langton's ant, described at
    http://mathworld.wolfram.com/LangtonsAnt.html
    """

    def __init__(self, world):
        Animal.__init__(self, world)
        self.dir = 0
        # have to draw yourself before registering!
        self.draw()
        world.register(self)

    def draw(self):
        """a turmite appears as a small red triangle that points
        in the direction the turmite is facing"""
        bounds = world.cell_bounds(self.x, self.y)
        bounds = rotate(bounds, self.dir)
        mid = vmid(bounds[1], bounds[2])
        self.tag = self.world.canvas.polygon([bounds[0], mid,
                                                     bounds[3]], fill='red')

    def fd(self, r=1):
        "move r steps in the direction the turmite is facing"
        if self.dir==0:
            self.x += r
        elif self.dir==1:
            self.y += r
        elif self.dir==2:
            self.x -= r
        else:
            self.y -=r
        self.redraw()

    def bk(self, r=1):
        "move backward r steps"
        self.fd(-r)

    def rt(self):
        "turn right 90 degrees"
        self.dir = (self.dir-1) % 4

    def lt(self):
        "turn left 90 degrees"
        self.dir = (self.dir+1) % 4

    def get_cell(self):
        "find the cell this turmite is on (creating it if necessary)"
        return self.world.get_cell(self.x, self.y)
        
    def mark(self):
        "mark the cell this turmite is on"
        cell = self.get_cell()
        cell.mark()

    def unmark(self):
        "unmark the cell this turmite is on"
        cell = self.get_cell()
        cell.unmark()

    def toggle(self):
        "toggle the cell this turmite is on"
        if self.is_on_mark():
            self.unmark()
        else:
            self.mark()

    def is_on_mark(self):
        "check whether this turmite is on a marked cell"
        cell = self.get_cell()
        return cell.is_marked()

    def step(self):
        "take one step, following the rules of Langton's ant"
        if self.is_on_mark():
            self.lt()
        else:
            self.rt()
        self.toggle()
        self.fd()


# the following are some random linear-algebra utilities
# written as functions (not methods)

def vadd(p1, p2):
    "add vectors p1 and p2 (returns a new vector)"
    return [x+y for x,y in zip(p1, p2)]

def vscale(p, s):
    "multiply p by a scalar (returns a new vector)"
    return [x*s for x in p]

def vmid(p1, p2):
    "return a new vector that is the pointwise average of p1 and p2"
    return vscale(vadd(p1, p2), 0.5)

def rotate(v, n=1):
    "rotate the list v by n places (returns a new list)"
    n %= len(v)
    return v[n:] + v[:n]


# top_globals and top_locals are used to create the
# Interpreter that executes user-provided code.
# It is necessary
# to provide a top-level environment so that user-
# defined functions are top-level functions

top_globals = globals()
top_locals = locals()

if __name__ == '__main__':
    world = TurmiteWorld()
    world.mainloop()
