#  AUTOMATA CLASS
#  Purpose/Functionality: This class provides methods for seeding and iterating a Wrapped2DArray based on some elsewhere specified flow.  Imposes mass limit.
from numpy import *
from Wrapped2DArray import *
import getopt,sys
import time
from random import *

class Automata():
#VARIABLES, with default values

	def __init__(self):
		self.cur = Wrapped2DArray()
		self.add = Wrapped2DArray()
		self.curSeed = Wrapped2DArray()
		self.neighborhood = []
		self.step_num = 0
		self.maxMass = 1.0000000000
		self.c = .05
		self.k = 0.1
		self.w = 0.1
		self.numN = 4

#	Binary CA, radius 1.
#   von Neumann neighborhood
#            a
#           b d
#            e
#	ordering in list:  abde


########  METHODS (AKA FUNCTIONS)  ########
	def seed(self,dorandom,sz):
		if dorandom == 1:
			seed()	# seed the random number generator
                else:		#set seed, so we get same results each time
			seed(dorandom)

		self.resize_Automata(sz)
		self.curSeed.clear('all')# clears array

		in_Array = True

		if dorandom:   # Random IC
			while in_Array:
			# Generates a random float in interval (0.0, 1.0)
				self.curSeed.set(0,random())
				in_Array = self.curSeed.next(0)

		else:		# not a random IC
                                #load seed array from file
			fname = raw_input("file name of seed array: ")
			temp = self.curSeed.load(fname)
			self.resize_Automata(temp.shape[0])
			while in_Array:
				self.curSeed.ThisArray = temp
				in_Array = self.curSeed.next(0)

		#Initialize cur array
		in_Array = True
		while in_Array:
			self.cur.pointer[0] = self.curSeed.pointer[0]
			self.cur.set( 0, self.curSeed.fetch(0) )
			in_Array = self.curSeed.next(0)
		return

#Return pointer indices of von Neumann neighborhood
	def find_neighbors(self, pointer_index):
		m = self.cur
		p = m.add_pointer	# adds a new pointer for each neighbor
		self.neighborhood = []
		self.neighborhood.append( p(m.look_up(pointer_index)) )	#a
		self.neighborhood.append( p(m.look_left(pointer_index)) )	#b
		self.neighborhood.append( p(m.look_right(pointer_index)) )	#d
		self.neighborhood.append( p(m.look_down(pointer_index)) )	#e
		return	# now self.neighborhood = [a,b,d,e]

#remove all memory of pointers, to keep pointer list tidy
# ALWAYS start popping at end of array so indices aren't messed up
	def forget_neighbors(self):
		for i in range(self.numN-1,-1,-1): #3,2,1,0
			self.cur.remove_pointer(self.neighborhood[i])
			self.neighborhood.pop()
		return # now self.neighborhood = []

#determine how to move mass around among neighbors
	def divvy(self, pointer_index):

		f = self.cur.fetch		# shorthand notation
		toAdd = []			# list of how much mass to take from each neighbor [abcde]

		                    
		self.find_neighbors(pointer_index)#create pointers to neighbors
		
		                    #Check to see if cell already has max allowed mass...
			              #otherwise calculate how much mass flows from each neighbor to current cell
		for i in range(0,self.numN):
			flow = self.Flow_Rule(pointer_index, self.neighborhood[i], self.c) # finds flow suggestion 
			to_amount = self.cur.fetch(pointer_index)
			from_amount = self.cur.fetch(self.neighborhood[i])
			zero_flow = False
			if (from_amount - flow) >= self.maxMass:   #keeps flow from breaking maxMass at from
				if (from_amount >= self.maxMass) and (from_amount - flow >= from_amount):
					flow = 0
					zero_flow = True
				if from_amount < self.maxMass:
					flow = from_amount - self.maxMass
			elif (from_amount - flow) <= 0: #keeps flow from breaking min at from
				if (from_amount <= 0) and (from_amount -flow <= from_amount):
					flow = 0
					zero_flow = True
				if from_amount > 0:
					flow = from_amount
		#This set MUST be second, elsewise the zero_flow conditions will need to be changed
			if ((flow + to_amount >= self.maxMass) and (zero_flow == False)):  #keeps flow from breaking max at to
				if (to_amount >= self.maxMass) and (flow + to_amount >= to_amount):
					flow = 0
				if to_amount < self.maxMass:
					flow = self.maxMass - to_amount
			elif ((flow + to_amount <= 0) and (zero_flow == False)): # keeps flow from breaking min at to
				if (to_amount <= 0) and (flow + to_amount <= to_amount):
					flow = 0
				if to_amount > 0:
					flow = -1*to_amount
			toAdd.append(flow)


		#clean up all the extra pointers we made
		self.forget_neighbors()

		# return list of amount to add to current cell
		# from each neighbor
		return toAdd

	#takes affinity information and puts into 'add' array
	def adder(self, toAdd):
		#populate the intermediary 'add' array
		self.find_neighbors(0)

		for i in range(0, self.numN):
			self.add.pointer[0] = self.cur.pointer[self.neighborhood[i]]

			newVal = self.add.fetch(0) - toAdd[i]
			self.add.set( 0, newVal )
		self.add.pointer[0] = self.cur.pointer[0]
		newVal = self.add.fetch(0) + add.reduce(toAdd)
		self.add.set(0, newVal )
		self.forget_neighbors()
		return


	#iterate one step
	def step(self):
		self.cur.clear('pointers')	#get rid of any info left over from last iteration
		self.add.clear('all')		#clear the intermediary 'add' array

		in_Array=True
		while in_Array:	    		# starts at (0,0) 
			self.add.pointer[0] = self.cur.pointer[0]
			toAdd = self.divvy(0)	# Determine how much mass to pass to/from neighbors
			self.adder(toAdd)	    # Write this info for cell into 'add' array
			in_Array = self.cur.next(0)# loops through whole cur array until [arraysize-1,arraysize-1]


		#now check to see if we have gone over max mass allowed
		self.cur.clear('pointers')
		self.add.clear('pointers')
		in_Array = True
		
		#add the original cur to the intermediary 'add' array to get updated cur array
		self.cur.clear('pointers')
		in_Array = True
		while in_Array:
			self.add.pointer[0] = self.cur.pointer[0]
			self.cur.set( 0, self.cur.fetch(0) + self.add.fetch(0) )
			in_Array = self.cur.next(0)
		self.step_num += 1
		return

	#resize ALL arrays
	def resize_Automata(self, newSize):
		self.cur.resize_array(newSize)
		self.add.resize_array(newSize)
		self.curSeed.resize_array(newSize)
		return
