#!/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 ############################

# 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

# 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


#### Class for a sphere
class sphere:
   def __init__(self,x0,y0,r0,c0):
      self.X = x0  # center coordinates
      self.Y = y0
      self.R = r0  # radius
      self.C = c0  # color
      self.DX = 0  # initial zero movement vector
      self.DY = 0
   # init

   def setvector(self,cx,cy):  # set movement vector
      self.DX = cx
      self.DY = cy
   # setvector

   def move(self):    # move according to movement vector
         self.X += self.DX
         self.Y += self.DY
         self.X,self.Y = wraparound(self.X,self.Y,self.R)
   #move

   def draw(self,brush,gc):  # draw with graphical context to buffer
      gc.set_foreground(self.C) # set color
      drawcircle(brush,gc,self.X,self.Y,self.R,True)
   # draw

   def collide(self,B): # detect and effect collision with sphere B
      ds = dist(self.X,self.Y,B.X,B.Y) # distance
      if ds <= self.R+B.R: # collision detected
         self.DX,B.DX = B.DX,self.DX  # swap movement vectors
         self.DY,B.DY = B.DY,self.DY
   # collide
      
# class sphere


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

   n = 8  # number of spheres to animate
   colors =[red,green,blue,white,purple,yellow]  # available colors
   S=[]   # array of spheres
   # Initial loops to set up spheres:
   i = 0 # loop counter
   while i<n:
      X = randint(0,width-1)
      Y = randint(0,height-1)
      R = randint(5,40)
      DX = randint(-5,5)
      DY = randint(-5,5)
      C  = colors[randint(0,len(colors)-1)]
      S.append( sphere(X,Y,R,C) )
      S[i].setvector(DX,DY)  # ok, now that S[i] exists
      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:
         S[i].draw(brush,gc)

         # detect collision with all other spheres
         k = i+1  # innermost loop counter
         while k<n:
            S[i].collide(S[k])
            k +=1
         # while k : collision detection loop

         S[i].move()
         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()

