import mobius, cairo, math, numpy, cmath, time

IMAGE_WIDTH = 1920
IMAGE_HEIGHT = 1080
SCALE = 225.0 # pixels_per_unit

FPS = 60
DURATION = 29
NUM_FRAMES = FPS*DURATION

def draw_frame(path, circles, depth):
    # cairo stuff
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, IMAGE_WIDTH, IMAGE_HEIGHT)
    cr = cairo.Context(surface)

    cr.set_line_width(1.0)

    # define our transforms
    generators = []
    for circle in circles:
        generators.append(mobius.circle_involution(circle[0], circle[1]))

    # drawing routine
    cr.set_source_rgb(0.0, 1.0, 0.0)

    draw_word_action(cr, circles, generators, [], depth)

    surface.write_to_png(path)

def draw_word_action(cr, circles, generators, word, depth):
    "Apply the given word to circles and draw, then compose a generator and recur."
    # apply current word to points and draw 
    circle_images = [x for x in circles]
    for index in word:
        circle_images = [generators[index].of_circle(x[0], x[1]) for x in circle_images]
        circle_images = [x for x in circle_images if x[1] != 0]
    for circle in circle_images:
        cr.new_path()
        center, radius = circle
        x = IMAGE_WIDTH/2 + center.real*SCALE
        y = IMAGE_HEIGHT/2 - center.imag*SCALE
        cr.arc(x, y, radius*SCALE, 0.0, 2.0*math.pi)
        cr.stroke()

    if depth > 0:
        # recur at smaller depth
        for i in range(len(generators)):
            gen = generators[i]
            set_color_from_palette(cr, i)
            if word == [] or word[-1] != i:
	        draw_word_action(cr, circles, generators, word + [i], depth - 1)

def set_color_from_palette(cr, i):
    "Picks the current color based on a pre-set palette."
    palette = [(1.0, 0.0, 0.0),
               (0.0, 1.0, 0.0),
               (0.0, 0.0, 1.0),
               (1.0, 1.0, 0.0),
               (1.0, 0.0, 1.0),
               (0.0, 1.0, 1.0)]
    i = i % len(palette)
    (r, g, b) = palette[i]
    cr.set_source_rgb(r, g, b)


def circles_converge(num_circles, radius, depth):
    start_time = time.clock()
    radius = 1.0
    for frame in range(NUM_FRAMES):
        # pick circle centers, radii
        distance_from_origin = radius*1.5*(NUM_FRAMES - 2.0*frame)/float(NUM_FRAMES)
        circles = []
        for i in range(num_circles):
            center = cmath.exp(float(i)/num_circles*2j*math.pi)*distance_from_origin
            circles.append((center, radius))
        report_progress(frame, start_time)
        draw_frame(get_filename(frame), circles, depth)

def get_filename(t):
	"Takes frame and names it appropriately."
	full_digits = len(str(NUM_FRAMES))
	digits = len(str(t))
	leading_zeroes = ""
	for i in range(full_digits - digits):
		leading_zeroes = leading_zeroes + "0"
	return "frame" + leading_zeroes + str(t) + ".png"

def report_progress(frame_index, start_time):
        print "Rendering frame " + str(frame_index) + " of " + str(NUM_FRAMES)
        elapsed = time.clock() - start_time
        print "Time elapsed:  " + str(elapsed)
        if frame_index > 0:
            seconds_remaining = int(float(NUM_FRAMES - frame_index)*elapsed/frame_index)
            seconds = seconds_remaining%60
            minutes = (seconds_remaining/60)%60
            hours = seconds_remaining/3600
            remaining = str(hours) + "h" + str(minutes) + "m" + str(seconds) + "s"
        else:
            remaining = "(unknown)"
        print "Estimated time remaining:  " + str(remaining)

circles_converge(3, 1.0, 9)