#!/usr/bin/env python
# Animation Template.  User must define a mydraw(brush,gc)

# Canonical Version of Spring 2008.

import pygtk
import gtk
import gobject
import time
import threading
from random import *

width,height = 900,700
delaytime = 50   # millisecond delay between animation frames

window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("Simplified Drawing Example")
window.connect("destroy", lambda w: gtk.main_quit())
area = gtk.DrawingArea()
area.set_size_request(width,height)
window.resize(width,height)
window.add(area)

window.show_all()
window.move(0,0)

gc0 = area.window.new_gc()
cmap = gc0.get_colormap()

# common colors
green = cmap.alloc_color(red=0,green=65535,blue=0)
blue = cmap.alloc_color(red=0,green=0,blue=65535)
red = cmap.alloc_color(red=65535,green=0,blue=0)
white = cmap.alloc_color("white")
black = cmap.alloc_color("black")
gray = cmap.alloc_color("gray")
yellow = cmap.alloc_color("yellow")
purple = cmap.alloc_color("purple")

gc0.set_foreground(green)
gc0.set_background(blue)
gc0.line_width = 1

# double buffering setup
dbuf = gtk.gdk.Pixmap(area.window,width,height,-1)
gc1 = dbuf.new_gc()
gc1.set_colormap(cmap)

### Thread control
syn = threading.Event()   # throttles mainloop
syn2 = threading.Event()  # throttles user thread
syn.clear()
syn2.clear()
stopall = False           # for cleanup


class MyThread ( threading.Thread ):
   def __init__(self,brush,gc):
       self.win = brush
       self.dgc = gc
       threading.Thread.__init__(self)
   def run ( self ):
       global mydraw
       mydraw(self.win,self.dgc)   

eehandler = 0
def area_expose_cb(area, event):
    global mainloop,eehandler
    my = MyThread(dbuf,gc1)
    my.start()             # start student thread, which calls mydraw
    area.handler_block(eehandler)  # prevent restart when window moves.
    mainloop(area.window)  # start main animation refresh cycle
    return True

# get ready for expose event
eehandler = area.connect("expose_event", area_expose_cb)


# animation refresh control loop:
def mainloop(brush):
    if not stopall:
        syn.wait()  # wait for permission to refresh screen
        syn.clear() # reset for next round
        brush.draw_drawable(gc0,dbuf,0,0,0,0,width,height) # refresh
        syn2.set()   # informs user thread to goto next iteration
        eid = gobject.timeout_add(delaytime, mainloop, brush) # reschedule
        return False
# end mainloop

def updateDisplay():
    syn.set()         # release mainloop to refresh screen
    syn2.wait()       # synch with mainloop before looping again
    syn2.clear()      # reset for next round
# end updateDisplay

def cleanup():
    global stopall
    stopall = True
    syn.set()
    syn2.set()
# end cleanup

# easier to use functions for drawing circle/sphere
def drawcircle(brush,gc,x,y,radius,fill):
    brush.draw_arc(gc,fill,x-radius,y-radius,2*radius,2*radius,0,360*64)

######################## YOUR CODE GOES BELOW ############################

# function to wrap around (returns new x,y coordinate)
def wraparound(x,y,radius):
   if (x<radius): x = width-radius  # left to right
   if (x>(width-radius)): x = radius # right to left
   if (y<radius): y = height-radius # top to bottom
   if (y>(height-radius)): y = radius # bottom to top
   return (x,y)
#wraparound

# distance function
def dist(x1,y1,x2,y2):
   dx = x1-x2
   dy = y1-y2
   return (dx*dx + dy*dy)**0.5
#dist

# collide - skip floating point comps
def collide(x1,y1,x2,y2,r1,r2):
   dx = abs(x1-x2)
   dy = abs(y1-y2)
   if (dx<r1+r2) and (dy<r1+r2): return True
   else: return False
#collide


# A function called 'mydraw' must be defined:
def mydraw(brush,gc):
   # animate n spheres

   n = 20  # number of spheres to animate
   colors =[red,green,blue,white,purple,yellow]  # available colors
   X= []   # array of x-coordinates
   Y= []   # array of y-coordinates
   R= []   # array of radii
   DX=[]   # array of dx (change in x) values
   DY=[]   # array of dy values
   C =[]   # array of colors

   # The properties of each sphere i is defined by the cross-section
   # X[i],Y[i],  DX[i],DY[i], R[i] and C[i]

   # Initial loops to set up spheres:
   i = 0 # loop counter
   while i<n:
      X.append(randint(0,width-1))
      Y.append(randint(0,height-1))
      R.append(randint(5,40))
      DX.append(randint(-5,5))
      DY.append(randint(-5,5))
      C.append(colors[randint(0,len(colors)-1)])
      i +=1
   # initialization loop

   j = 0 # outer loop counter
   while j<10000:
      gc.set_foreground(black) # Erase previous image
      brush.draw_rectangle(gc,True,0,0,width,height)

      # draw spheres:
      i = 0 # inner i loop counter
      while i<n:
         gc.set_foreground(C[i])
         drawcircle(brush,gc,X[i],Y[i],R[i],True) # draw circle

         # detect collision with all other spheres
         k = 0  # innermost loop counter
         while k<n:
            if (i!=k) and dist(X[i],Y[i],X[k],Y[k])<=R[i]+R[k]: # collide
               # exchange movement vectors
               DX[i],DX[k] = DX[k],DX[i]
               DY[i],DY[k] = DY[k],DY[i]
            k +=1
         # while k : collision detection loop

         X[i] += DX[i]
         Y[i] += DY[i]
         X[i],Y[i] = wraparound(X[i],Y[i],R[i])
         i +=1
      # while i
      j += 1
      updateDisplay()  # call at end of anim loop
   # main (outer) animation loop
   cleanup()
#end mydraw


##########################################################################
# The following line must be the last line in the program
gtk.main()

