#!/usr/bin/python

from Gui import *
from math import *
from time import sleep
from traceback import *
from threading import *
from random import *
import string
import os
debug = 0

class World(Gui):

    def __init__(self):
        Gui.__init__(self)
        self.exists = True
        self.animals = []
        self.inter = Interpreter(self, top_globals, top_locals)
        
    def quit(self):
        self.exists = False
        Gui.quit(self)

    def register(self, animal):
        self.animals.append(animal)

    def unregister(self, animal):
        self.animals.remove(animal)

    def clear(self):
        for animal in self.animals:
            animal.undraw()
        self.animals = []
        self.delete_item('all')

    def step(self):
        for animal in self.animals:
            animal.step()
        
    def run(self):
        self.running = 1
        while self.exists and self.running:
            self.step()
            self.update()
            sleep(self.delay)

    def stop(self):
        self.running = 0

    def map_animals(self, callable):
        map(callable, self.animals)
        
    def run_text(self):
        source = self.te_code.get(1.0, END)
        self.inter.run_code(source, '<user-provided code>')

    def run_file(self):
        filename = self.en_file.get()
        fp = file(filename)
        source = fp.read()
        self.inter.run_code(source, filename)

    def trans(self, coords):
        for trans in self.transforms:
            coords = trans.trans_list(coords)
        return coords

    def invert(self, coords):
        for trans in self.transforms:
            coords = trans.invert_list(coords)
        return coords

class Agitator(Thread):

    def __init__(self, delay, callable, *args, **kwds):
        Thread.__init__(self, target=self.agitate)
        self.callable=callable
        self.args=args
        self.kwds=kwds
        self.delay=delay
        self.running=1
        self.start()

    def agitate(self):
        while self.running:
            self.callable(*self.args, **self.kwds)
            sleep(self.delay)
            


class RotateTransform(Transform):

    def __init__(self, theta):
        self.theta = theta

    def rotate(self, p, theta):
        s = sin(theta)
        c = cos(theta)
        x =   c * p[0] + s * p[1]
        y =  -s * p[0] + c * p[1]
        return [x, y]
    
    def trans(self, p):
        return self.rotate(p, self.theta)

    def invert(self, p):
        return self.rotate(p, -self.theta)


class SwirlTransform(RotateTransform):

    def trans(self, p):
        d = sqrt(p[0]*p[0] + p[1]*p[1])
        return self.rotate(p, self.theta*d)

    def invert(self, p):
        d = sqrt(p[0]*p[0] + p[1]*p[1])
        return self.rotate(p, -self.theta*d)




class TurtleWorld(World):

    def __init__(self):
        World.__init__(self)
        self.delay = 0.1           # time in seconds to sleep after an update
        self.setup()
#        self.transforms = [ SwirlTransform(pi/400),
#                            CanvasTransform(self.ca_width, self.ca_height) ]
        self.transforms = [ CanvasTransform(self.ca_width, self.ca_height) ]

    def setup(self):
        self.ca_width = 400
        self.ca_height = 400

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

        # right frame
        self.fr(LEFT, fill=BOTH, expand=1)

        self.fr()
        self.bu(LEFT, text='Make Turtle', command=self.make_turtle)
        self.bu(LEFT, text='Print canvas', command=self.print_canvas)
        self.bu(LEFT, text='Quit', command=self.quit)
        self.endfr()

        self.fr(fill=X)
        self.bu(LEFT, text='Run file', command=self.run_file)
        self.en_file = self.en(LEFT, text='turtle_code.py', width=5,
                               fill=X, expand=1)
        self.endfr()

        self.bu(BOTTOM, text='run this code', command=self.run_text)
        self.te_code = self.te(BOTTOM, height=10, width=40)
        self.te_code.insert(END, 'world.clear()\n')
        self.te_code.insert(END, 'bob = Turtle(world)\n')
        # leave the right frame open so that Turtles can add TurtleControls
        # self.endfr()

    def setup_run(self):
        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()

    def make_turtle(self):
        turtle = Turtle(self)
        control = TurtleControl(turtle)


class TurmiteWorld(World):

    def __init__(self):
        World.__init__(self)
        self.delay = 0.0           # time in seconds to sleep after an update
        self.ca_width = 500
        self.ca_height = 500
        self.csize = 5
        self.cells = {}
        self.transforms = [ SwirlTransform(pi/400),
                           CanvasTransform(self.ca_width, self.ca_height) ]
#        self.transforms = [ CanvasTransform(self.ca_width, self.ca_height) ]
        self.setup()
        self.mainloop()

    def setup(self):

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

        # right frame
        self.fr(LEFT, fill=BOTH, expand=1)

        self.fr()
        self.bu(LEFT, text='Make Turmite', command=self.make_turmite)
        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()

        low = [-20, -20]
        high = [20, 20]
        # self.make_cells([low, high], **self.unmarked_options)

    def clear(self):
        for animal in self.animals:
            animal.undraw()
        for cell in self.cells.values():
            cell.undraw()
        self.animals = []
        self.cells = {}

    def quit(self):
        self.running = 0
        self.root.quit()

    def get_bounds(self):
        return self.trans.invert_list([[0, 0],
                                       [self.ca_width, self.ca_height]])

    def cell_bounds(self, x, y):
        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):
        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):
        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):
        turmite = Turmite(self)

# assemble a set of keyword arguments into a dictionary
def makedict(**kwds):
    return kwds

class Cell:
    
    def __init__(self, world, bounds):
        self.world = world
        self.marked_options = makedict(fill='black', outline='gray80')
        self.unmarked_options = makedict(fill='yellow', outline='gray80')
        self.tag = world.create_polygon(bounds, **self.unmarked_options)
        self.marked = 0

    def __del__(self):
        self.undraw()

    def undraw(self):
        self.world.delete_item(self.tag)
        
    def config_cell(self, **options):
        self.world.itemconfig(self.tag, **options)

    def cget_cell(self, x, y, option):
        print self.world.itemconfig(self.tag, option)

    def mark(self):
        self.marked = 1
        self.config_cell(**self.marked_options)
        
    def unmark(self):
        self.marked = 0
        self.config_cell(**self.unmarked_options)
        
    def is_marked(self):
        return self.marked



class Interpreter:

    def __init__(self, world, globals=globals(), locals=locals()):
        self.world = world
        self.globals = globals
        self.locals = locals
        self.globals['world'] = world

    def run_code_thread(self, *args):
        Thread(target=self.run_code, args=args).start()
        
    def run_code(self, source, filename):
        code = compile(source, filename, 'exec')
        try:
            exec code in self.globals, self.locals
        except KeyboardInterrupt:
            self.world.quit




class TurtleControl:

    def __init__(self, turtle):
        self.turtle = turtle
        self.setup()

    def setup(self):
        w = self.turtle.world

        w.fr(bd=3, relief=SUNKEN)
        w.la(text='Turtle Control')

        # ROW 1
        w.fr(fill=X, expand=1)
        w.bu(LEFT, text=' fd ', command=self.fd)
        self.en_r = w.en(LEFT, X, 1, text='10', width=5)
        w.bu(LEFT, text='lt', command=self.turtle.lt)
        w.bu(LEFT, text='rt', command=self.turtle.rt)
        w.endfr()

        # ROW 2
        w.fr(fill=X, expand=1)
        w.bu(LEFT, text='pu', command=self.turtle.pu)
        w.bu(LEFT, text='pd', command=self.turtle.pd)

        colors = 'red', 'orange', 'yellow', 'green', 'blue', 'violet'
        self.mb = w.mb(text=colors[0], fill=X, relief=SUNKEN)
        self.mb.pack(pady=1)
        for color in colors:
            w.mi(self.mb, color, command=Callback(self.color, color))

        w.endfr()

        w.endfr()

    def color(self, color):
        self.mb.config(text=color)
        self.turtle.set_color(color)

    def fd(self):
        r = int(self.en_r.get())
        self.turtle.fd(r)

    def turn(self):
        theta = int(self.en_theta.get())
        self.turtle.turn(theta)


class Animal:

    def __init__(self, world):
        self.world = world
        self.x = 0
        self.y = 0
        self.delay = 0
        
    def __del__(self):
        self.undraw()

    def step(self):
        pass

    def draw(self):
        pass

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

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

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


class Turtle(Animal):

    def __init__(self, world):
        Animal.__init__(self, world)
        self.r = 5
        self.theta = 0
        self.pen = 1
        self.color = 'red'
        self.delay = 0.2
        self.draw()
        world.register(self)

    def step(self):
        self.fd()

    def polar(self, x, y, r, theta):
        rad = theta * pi/180
        s = sin(rad)
        c = cos(rad)
        return [ x + r * c, y + r * s ]         

    def draw_line(self, scale, dtheta, **options):
        r = scale * self.r
        theta = self.theta + dtheta
        head = self.polar(self.x, self.y, r, theta)
        tail = self.polar(self.x, self.y, -r, theta)
        self.world.create_line([tail, head], **options)

    def draw(self):
        self.tag = 'Turtle%d' % id(self)
        lw = self.r/2
        self.draw_line(2.5, 0, tags=self.tag, width=lw, arrow=LAST)
        self.draw_line(1.8, 40, tags=self.tag, width=lw)
        self.draw_line(1.8, -40, tags=self.tag, width=lw)
        self.world.create_circle(self.x, self.y, self.r, self.color,
                                 tags=self.tag)
        if self.delay > 0:
            self.update()

    def fd(self, r=1):
        x = self.x
        y = self.y
        p1 = [x, y]
        p2 = self.polar(x, y, r, self.theta)
        self.x = p2[0]
        self.y = p2[1]
        if (self.pen):
            self.world.create_line([p1, p2], fill='black')
        self.redraw()

    def bk(self, r=1):
        self.fd(-r)

    def rt(self, angle=90):
        self.theta = self.theta - angle
        self.redraw()

    def lt(self, angle=90):
        self.theta = self.theta + angle
        self.redraw()

    def pd(self):
        self.pen = 1

    def pu(self):
        self.pen = 0

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

    def set_color(self, color):
        self.color = color
        self.redraw()


class Turmite(Animal):

    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):
        bounds = world.cell_bounds(self.x, self.y)
        bounds = rotate(bounds, self.dir)
        mid = vmid(bounds[1], bounds[2])
        self.tag = self.world.create_polygon([bounds[0], mid, bounds[3]],
                               fill='red')

    def fd(self, r=1):
        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):
        self.fd(-r)

    def rt(self):
        self.dir = (self.dir-1) % 4

    def lt(self):
        self.dir = (self.dir+1) % 4

    def get_cell(self):
        return self.world.get_cell(self.x, self.y)
        
    def mark(self):
        cell = self.get_cell()
        cell.mark()

    def unmark(self):
        cell = self.get_cell()
        cell.unmark()

    def toggle(self):
        if self.is_on_mark():
            self.unmark()
        else:
            self.mark()

    def is_on_mark(self):
        cell = self.get_cell()
        return cell.is_marked()

    def step(self):
        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):
    return [x+y for x,y in zip(p1, p2)]

def vscale(p, s):
    return [x*s for x in p]

def vmid(p1, p2):
    return vscale(vadd(p1, p2), 0.5)

def rotate(v, n=1):
    n %= len(v)
    return v[n:] + v[:n]

# add the turtle functions to the global dictionary
# so they can be invoked as simple functions (not methods)
fd = Turtle.fd
bk = Turtle.bk
lt = Turtle.lt
rt = Turtle.rt
pu = Turtle.pu
pd = Turtle.pd
die = Turtle.die
set_color = Turtle.set_color

# 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 = TurtleWorld()
    world.mainloop()
