# cellular automaton

__version__ = "0.1"
__author__ = "Jason Michael Adams (jmadams@cs.cmu.edu, http://mendicantbug.com)"
__license__ = "http://creativecommons.org/licenses/BSD/"	# please cite my website if you use or post this code (http://mendicantbug.com)

import math
import sys


class CellularAutomaton(object):
	"""Usage: CellularAutomaton(boardSize, ruleSize, automatonNumber)

	"""

	def __init__(self, size, rule_size, effects, initial_board=None,alpha=None):
		assert size % 2 == 1, "Size of board must be odd."
		self.size = size
		self.time = 0
		self.rule_size = rule_size
		rules = list()
		for x in xrange(2 ** rule_size):
			rules.append(make_binary_rep(x, rule_size))
		rules.reverse()
		ex = make_binary_rep(effects, 2 ** rule_size)
		self.rules = RuleSet(rules, ex)
		self.board = dict()
		if alpha is None:
			self.alpha = [0,1]
		else:
			self.alpha = alpha

		self.board[0] = self._init_board(initial_board)
		if initial_board is None:
			self.board[0][self.size / 2] = self.alpha[1]

	
	def increment(self):
		self.time += 1
		self.board[self.time] = self._init_board()
		
		for x in xrange(self.size):
			context = list()
			rn = self.rule_size / 2
			for y in xrange(self.rule_size):
				context.append(self.board[self.time - 1][(x + y - rn) % self.size])

			self.board[self.time][x] = self.rules * context
		
		return self.board[self.time]



	def run(self, cycles):
		for x in xrange(cycles):
			tmp = self.increment()

		print self
		


	def _init_board(self, initial_board=None):
		if initial_board is None:
			board = list()
			for x in xrange(self.size):
				board.append(self.alpha[0])
			board[self.size / 2] = self.alpha[1]
		else:
			assert len(initial_board) == size, "Size of initial board setting must be equal to size of board."
			board = initial_board

		return board


	def __repr__(self):
		keys = self.board.keys()
		keys.sort()
		tmps = list()
		for key in keys:
			tmps.append(self.__repb__(key))
		return '\n'.join(tmps)



	def __repb__(self, b):
		board = self.board[b]
		tmp = ""
		for x in xrange(len(self.board[b])):
			tmp += str(self.board[b][x])
		return tmp
		
		

	

class Rule(object):
	"""Usage:  Rule(rule, effect)

	The rule parameter is a odd-length list indicating the
	prior pattern that will trigger the single value in
	effect.  The effect parameter should be anything that
	has a string representation.
	"""

	def __init__(self, rule, effect):
		assert len(rule) % 2 == 1, "Size of rule must be odd."
		self.rule = rule
		self.effect = effect
		self.size = len(rule)


	def __mul__(self, context):
		assert len(context) == self.size, "Size of context must be equal to size of the rule (%d)." % (self.size)

		accept = True
		
		for x in xrange(len(context)):
			if self.rule[x] != context[x]:
				accept = False
				break

		if accept is True:
			return self.effect
		else:
			return None


	def __repr__(self):
		return "%s => %s" % (str(self.rule), self.effect)



class RuleSet(object):
	"""A set of Rule objects that can be applied to
	a given context.
	"""
	
	def __init__(self, rules, effects):
		self.rules = list()
		for rx in xrange(len(rules)):
			r = Rule(rules[rx], effects[rx])
			self.rules.append(r)


	def __mul__(self, context):
		middle = context[len(context) / 2]
		for rule in self.rules:
			outp = rule * context
			if outp is not None:
				return outp
		return middle


	def __repr__(self):
		return str(self.rules)



def make_binary_rep(num, size=0):
	outp = list()
	if size <= 0:
		size = int(round(math.log(num) / math.log(2)))
	for x in xrange(size - 1, -1, -1):
		if (num & (2 ** x)) > 0:
			outp.append(1)
		else:
			outp.append(0)
	return outp



if __name__ == '__main__':
	if len(sys.argv) < 4:
		print "Usage: %s <neighborhood size> <automaton #> <# cycles>" % (sys.argv[0])
		sys.exit(0)
	nhsize  = int(sys.argv[1])
	autonum = int(sys.argv[2])
	cycles  = int(sys.argv[3])
	ca = CellularAutomaton(81, nhsize, autonum)
	ca.run(cycles)

