#!/usr/bin/env python
"""
 OneDCA_tk.py: One-dimensional elementary cellular automaton simulator
 	Using TkInter and Image graphics

 Options:
 -t #  Number of time steps
 -n #  Size of lattice
 -c #  Size of cell
 -r #  Random initial configuration, rather than a single ON site
 -R #  Use rule # for the simulation
"""

from Tkinter import *
import Image, ImageTk     # Python Imaging Library
import getopt,sys
from random import randint

	# Initialize CA lattice, load in initial configuration
def CAInit(nSites,dorandom):
	if dorandom:   # Random IC
		first_row = [randint(0,1) for i in xrange(nSites)]
	else:          # Single ON site
		first_row = [0]*nSites
		first_row[nSites/2] = 1
	return [first_row]

	# Convert rule number to map (look-up table)
	#   from neighborhoods to next values
def RuleNumberToLUT(rulenum):
	lut = [(rulenum/pow(2,i)) % 2 for i in xrange(8)]
	return lut

	# Iterate the CA lattice, producing a spacetime diagram:
	#   New value of site is a function of previous values of
	#      itself and two neighbors.
	#   Form integer of the previous values, use as index into look-up table.
	#   Impose spatially periodic boundary conditions.
def CAIterate(state,nSteps,nSites,rule):
	for i in xrange(nSteps-1):
		data = state[-1]
		new = [rule[4*data[(j-1)%nSites]+2*data[j]+data[(j+1)%nSites]]
			   for j in xrange(nSites)]
		state.append(new)
	return state

def pil_render(data,nSteps,nSites,img,size):
	import ImageDraw
	draw = ImageDraw.Draw(img)
	box = (0,0,nSites*size,nSteps*size)
	draw.rectangle(box,(255,255,255))  # Clear the previous image
	for y in xrange(nSteps):
		for x in xrange(nSites):
			box = (x*size,y*size,(x+1)*size,(y+1)*size)
			if data[y][x]: draw.rectangle(box,(0,0,0))

class PaintCanvas(Canvas):
	def __init__(self, master, image, tkim):
		Canvas.__init__(self, master, width=image.size[0], height=image.size[1])
		# fill the canvas
		self.tkim = tkim
		self.create_image(0, 0, image=self.tkim, anchor=NW,tag="Image")
		self.image = image
		self.bind("<B1-Motion>", self.paint)
		self.bind("<Button-1>", self.iterate)

	def paint(self, event):
		self.tkim.paste(self.image)
		self.update_idletasks()

	def iterate(self, event):
		global state,nSteps,nSites,LUT,CellSize
		im = self.image
		# process the image in some fashion
		t = [state[-1]] # Last state configuration (at time = nSteps)
		state = t     # Put this at beginning, and throw away all other iterates
		CAIterate(state,nSteps,nSites,LUT)
		pil_render(state,nSteps,nSites,im,CellSize)
		self.tkim.paste(im)
		self.update_idletasks()

# Set defaults
CellSize = 4
nSteps   = 125
nSites   = 107
dorandom = 1
rule     = 18

# Get command line arguments, if any
opts,args = getopt.getopt(sys.argv[1:],'t:n:r:R:')
for key,val in opts:
	if key == '-t': nSteps   = int(val)
	if key == '-n': nSites   = int(val)
	if key == '-c': CellSize = int(val)
	if key == '-r': dorandom = 1
	if key == '-R': rule     = int(val)

	# Set initial configuration
state = CAInit(nSites,dorandom)
	# Translate rule number to look-up table
LUT = RuleNumberToLUT(rule)
	# Make spacetime diagram by iterating CA
CAIterate(state,nSteps,nSites,LUT)

	# Render spacetime diagram into image for display
im = Image.new("RGB",(nSites*CellSize,nSteps*CellSize),(255,255,255))
pil_render(state,nSteps,nSites,im,CellSize)

	# Display
root = Tk()
tkim = ImageTk.PhotoImage(im)
PaintCanvas(root, im,tkim).pack()

root.mainloop()
