
from Automata import *
from numpy import *


#this function rotates a given vector [,] by the angle from the positive x axis (in the usual sense) to the axis [,], and returns it as [,]
def Rotate(axis, vector):
        if axis[0] != 0:
                ratio = float(axis[1])/float(axis[0])
                angle = arctan(ratio) - pi/2 # angle of rotation, offset by 90 deg because our vectors are in a coordinate system 90 deg off from normal
        elif (axis[0] == 0) and (axis[1] >= 0): #required because the computer can't divide by zero
                angle = 0
        elif (axis[0] == 0) and (axis[1] < 0):
                angle = -1*pi
        Rotated_vector = [0,0]
        Rotated_vector[0] = int(round(vector[0]*cos(angle)  - vector[1]*sin(angle))) #usual rotation matrix.  Round needed since int(2.0) = 1 
        Rotated_vector[1] = int(round(vector[1]*cos(angle)  + vector[0]*sin(angle)))
        return Rotated_vector


class Adjacent_Flow(Automata):
	name = "Adjacent Flow Automata"
	description = "This compares the value at two pointers, and returns a fraction of the value at one pointer.  That fraction is the difference/max_difference.  If negative, returns zero.  The parameter reduces the flow by that fraction"

	def Flow_Rule(self, to_pointer, from_pointer, parameter):
		to_amount = self.cur.fetch(to_pointer)
		from_amount = self.cur.fetch(from_pointer)
		if to_amount >= from_amount:
			flow_amount = parameter*from_amount*(to_amount-from_amount)/self.maxMass
		else:
			flow_amount = 0
		return flow_amount

class Rev_Flow(Automata):
	name = "Reverse Flow Automata"
	description = "This compares the value at two pointers, and returns a fraction of the value at one pointer.  That fraction is the negative difference/max_difference.  If positive, returns zero.  The parameter reduces the flow by that fraction"


	def Flow_Rule(self, to_pointer, from_pointer, parameter):
		to_amount = self.cur.fetch(to_pointer)
		from_amount = self.cur.fetch(from_pointer)
		if to_amount >= from_amount:
			flow_amount = -1*parameter*from_amount*(to_amount-from_amount)/self.maxMass
		else:
			flow_amount = 0
		return flow_amount


class General_Flow(Automata):
	name = "Nearby Flow Automata"
	description =  "Like Adjacent Flow, but uses the the weighted average of several nearby cells instead of just one."



	def Flow_Rule(self, to_pointer, from_pointer, c):
		to_amount = self.cur.fetch(to_pointer)
		from_amount = self.cur.fetch(from_pointer)
		#axis is a vector that I want to align the neighbors with.
		axis = [0,0]
		for k in range(0,2):  # so get k = 0,1
			correction = 0
			if (self.cur.pointer[to_pointer][k] - self.cur.pointer[from_pointer][k]) >1:
				correction = -1*self.cur.arraysize
			if (self.cur.pointer[to_pointer][k] - self.cur.pointer[from_pointer][k]) <-1:
				correction = self.cur.arraysize
			axis[k] = self.cur.pointer[to_pointer][k] - self.cur.pointer[from_pointer][k] + correction
#		print axis
		weighted_average = [0,0] # to, from
		for j in xrange(0,2):
			original_neighbors = []
			if j == 1: 
				for i in xrange(0, len(self.from_base_neighbors)):
					original_neighbors.append(self.from_base_neighbors[i])
			else: 
				for i in xrange(0, len(self.to_base_neighbors)):
					original_neighbors.append(self.to_base_neighbors[i])
			sum_dist_sqr = 0
			new_neighbors = []
			for i in xrange(0,len(original_neighbors)): 
				# rotates each cell, finds the distance, and builds the weighted average normalization factor
				new_neighbors.append(Rotate(axis, original_neighbors[i]))
				dist = sqrt((abs(new_neighbors[i][0]) + abs(axis[0]))**2 + (abs(new_neighbors[i][1])+abs(axis[1]))**2)
				sum_dist_sqr += 1./dist**2

#			print new_neighbors
		
       			for i in xrange(0,len(original_neighbors)): 
				#builds weighted average by fetching values at each location
				if j == 1:	
					new_loc = [new_neighbors[i][0] + self.cur.pointer[from_pointer][0], new_neighbors[i][1] + self.cur.pointer[from_pointer][1]]
				else:
					new_loc = [new_neighbors[i][0] + self.cur.pointer[to_pointer][0], new_neighbors[i][1] + self.cur.pointer[to_pointer][1]]

				temp = self.cur.add_pointer(new_loc)
				dist = sqrt((abs(new_neighbors[i][0]) + abs(axis[0]))**2 + (abs(new_neighbors[i][1])+abs(axis[1]))**2)
#				print "fetch: " + str(self.cur.fetch(temp))
#				print "sum_dis_sqr: " + str(sum_dist_sqr)
#				print "dist: " + str(dist)
				weighted_average[j] += self.cur.fetch(temp)/(sum_dist_sqr*(dist**2))
				self.cur.remove_pointer(temp)
		parameter = self.Calc_Parameter(c, to_pointer)		
#		print [weighted_average[0], weighted_average[1]]
	       	if weighted_average[0] < weighted_average[1]: #uses weighted average to calculate flow amount
			flow_amount = parameter*from_amount*(weighted_average[0]-weighted_average[1])/self.maxMass
	       	else:
			flow_amount = 0


		return flow_amount


class C8_Flow_Half(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1],[2,0],[-2,0],[0,2]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction. [0,0] is at the FROM pointer
	to_base_neighbors = [[0,0]] #[0,0] is AT the TO pointer

	def Calc_Parameter(self,c, pointer):
		return c

class C5_Flow_Half(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0]] #relative to the 
	def Calc_Parameter(self,c, pointer):
		return c

class C8_Flow_Full(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1],[2,0],[-2,0],[0,2]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0],[0,-1],[1,0],[-1,0],[1,-1],[-1,-1],[2,0],[-2,0],[0,-2]]

	def Calc_Parameter(self,c, pointer):
		return c

class C5_Flow_Full(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0],[0,-1],[1,0],[-1,0],[1,-1],[-1,-1]]
	def Calc_Parameter(self,c, pointer):
		return c

class Sine_C8_Flow_Full(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1],[2,0],[-2,0],[0,2]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0],[0,-1],[1,0],[-1,0],[1,-1],[-1,-1],[2,0],[-2,0],[0,-2]]


	def Calc_Parameter(self,c, pointer):
		return c*sin(2*pi*self.k*self.cur.pointer[pointer][0]-2*pi*self.w*self.step_num)

class Sine_C5_Flow_Full(General_Flow):
	from_base_neighbors = [[0,0],[0,1],[1,0],[-1,0],[1,1],[-1,1]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0],[0,-1],[1,0],[-1,0],[1,-1],[-1,-1]]


	def Calc_Parameter(self,c, pointer):
		return c*sin(2*pi*self.k*self.cur.pointer[pointer][0]-2*pi*self.w*self.step_num)

class Sine_Simple_Flow_Full(General_Flow):
	from_base_neighbors = [[0,0]] #the relative positions of the cells around from pointer to be compared. Aligned to the RIGHTWARD direction.
	to_base_neighbors = [[0,0]]

	def Calc_Parameter(self,c, pointer):
		return c*sin(2*pi*self.k*self.cur.pointer[pointer][0]-2*pi*self.w*self.step_num)
