#!/usr/bin/env python
import cairo
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, GObject
import threading
import time
import _thread
from random import random,randint
from math import pi

width,height = 1024,700
win = Gtk.Window()
drawingarea = Gtk.DrawingArea()
win.add(drawingarea)
brush = 1
da = 1
delaytime = 500
eehandler = 0

### Thread control
syn = threading.Event()   # throttles mainloop
syn2 = threading.Event()  # throttles user thread
syn.clear()  # .set allows to go through, .clear blocks
syn2.clear()  # block
stopall = False           # for cleanup

# old code, Thread permits a while loop inside mydraw
class MyThread ( threading.Thread ):
    def __init__(self,d,b):
        global brush,da
        threading.Thread.__init__(self)
        self.da = d
        self.br = b
        brush = b
        da = d
    def run ( self ):
        global mydraw
        mydraw()
#class MyThread

# animation refresh control loop:
def mainloop(da1, brush1):
    global da,brush
#    da = da1
    brush = brush1
#    print("mainloop")	
    if not stopall:
        syn.wait()  # wait for permission to refresh screen
        syn.clear() # reset for next round
        brush.stroke() 
        syn2.set()   # informs user thread to goto next iteration
        win.queue_draw_area(0,0,width-1,height-1)
        return False
# end mainloop

def area_expose_cb(daa, bbrush):	
    global brush, da, eehandler,mainloop
    brush = bbrush
    my = MyThread(daa,bbrush)
    my.start()
    drawingarea.handler_block(eehandler)
    eeh = drawingarea.connect('draw', mainloop)  # reconnect
    #win.queue_draw()
    mainloop(daa,bbrush)  # sxxtart main animation refresh cycle
    return True

win.connect('destroy', lambda w: Gtk.main_quit())
win.set_default_size(width,height)
eehandler = drawingarea.connect('draw', area_expose_cb)
win.show_all()


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

### some common colors as RGB triples:
black = (0,0,0)
white=(1,1,1)
red = (1,0,0)
green = (0,1,0)
blue=(0,0,1)
yellow = (1,1,0)
aqua=(0,1,1)
magenta=(1,0,1)
gray=(.5,.5,.5)
purple=(.5,0,0.5)

COLORS = [black,white,red,green,blue,yellow,aqua,magenta,gray,purple]

##########################################################################
############################# STUDENT CODE ###############################
##########################################################################



## To affect animation, entire image must be redrawn with each updateDisplay
## Animate one moving circle, defined by x,y,r,dx,dy,c for color

# calculate Eclidean distance between x1,y1 and x2,y2
def distance(x1,y1,x2,y2):
    dx = x1-x2
    dy = y1-y2
    return (dx*dx + dy*dy)**0.5  # **0.5 takes square root
#distance

# determine collision between two circles with center x,y and radius r:
def collide(x1,y1,r1, x2,y2,r2):
    return (distance(x1,y1,x2,y2) <= (r1+r2)) # returns True if collided
#collide


## main animation routine MUST BE CALLED mydraw():
def mydraw():
    # sets attributes of circle

    n = 10  # number of circles
    
#    r = randint(8,64)  # radius of circle between 8 and 64 inclusive
    R = []  # radii array
    i = 0
    while i<n:
        R.append( randint(8,64) );
        i +=1
    # while i - constructs R
    X = [width//2] * n
    Y = [height//2] *n
    #x,y = width//2, height//2   # start at center
    DX = [0] *n  # preallocates array
    DY = [0] *n  # preallocates array
    CLS = [0] * n
    i = 0
    while i<n:
        X[i] = randint(R[i],width-R[i])
        Y[i] = randint(R[i],height-R[i])
        DX[i] = randint(-8,8)
        DY[i] = randint(-6,6)
#        CLS[i] = COLORS[ randint(1,len(COLORS)-1) ]
        CLS[i] = COLORS[ i % len(COLORS) ]
        i+=1
    #whilei
    #dx,dy = randint(-8,8), randint(-6,6)  # movement vector
    #color = COLORS[ randint(1,len(COLORS)-1) ]
    while True:
        brush.set_source_rgb(*black)  # * needed to unravel tuple
        brush.paint()  # paints over background to draw new frame

        # detect collision between circles
        i = 0
        while i<=n-2:

            k = i+1
            while k<=n-1:
                if collide(X[i],Y[i],R[i],X[k],Y[k],R[k]):
                    # swap movement vectors
                    DX[i],DX[k] = DX[k],DX[i]
                    DY[i],DY[k] = DY[k],DY[i]
                k += 1
            #while k inner loop
            
            i += 1
        #while i outer loop
        
        # draw circles
        i = 0
        while i<n:
            brush.set_source_rgb(*CLS[i])
            brush.new_sub_path()    # avoids extra line in arc
            brush.arc(X[i],Y[i],R[i],0,2*pi)   # circle
            brush.fill()  # solid circle

            X[i] = X[i] + DX[i]  # change position by movement vector
            Y[i] = Y[i] + DY[i]
            if (X[i]<=R[i] or X[i]>=width-R[i]): DX[i] = -1 * DX[i]
            if (Y[i]<=R[i] or Y[i]>=height-R[i]): DY[i] *= -1 #
            i += 1
        #while i - inner while

        updateDisplay()   # draw frame
        time.sleep(0.1)  # animation delay
    return False
#mydraw




#### The following must be the last line of the program: don't move
Gtk.main()
