class FlockingMixIn:
	"""
	This class turns an entity into a Boid - gives it the ability to exhibit flocking behaviour.
	http://www.vergenet.net/~conrad/boids/pseudocode.html
	
	The entity must have GetPosition(), SetPosition(), GetVelocity() and SetVelocity()
	These should return vector types on which normal vector operations can be performed.
	E.g. +, -, /, * of other vectors of the same type, and scalars
	
	The three basic boid flocking rules are implmented, and other rules can be added as follows:
	thisThing.rules.append(self.MyExtraRuleMethod)
	
	Likewise, the existing flocking rules can be overridden in the rules array.
	"""
	def __init__(self):
		# these are the rules which will get called on this Boid
		self.rules = [self.TendTowardsCenter, self.Avoid, self.MatchVelocity]
		# these are my friend boids who's positions and velocities I care about
		self.friends = []
		# this is my cached new velocity which will get applied on Update()
		self.newVelocity = None
		
		# this is the weighting applied to the tendency to move towards center
		self.centerWeighting = 0.01
		
		# this is the factor applied to the avoidance factor
		self.avoidanceWeighting = 0.1
		
		# this is the factor applied to the velocity matching algorithm
		self.matchingFactor = 0.1
		
		# the maximum speed at which we can go
		self.maxSpeed = 5
	
	def SetFlockFriends(self, friends):
		"""
		Tell this Boid which other boids are proximous.
		friends is a list of other boids.
		"""
		self.friends = friends
	
	def Flock(self, friends=None):
		"""
		Execute the flocking rules to change our velocity vector.
		New vector will be stored but not actually set until UpdateFlockingVelocity() is called.
		This is so that the current state of all boids is preserved when calculating.
		Do this once each cycle.
		"""
		
		if friends:
			self.SetFriends(friends)
		
		# reset our velocity to be our old velocity to start with
		self.newVelocity = self.GetVelocity()
		
		# run the rules on each boid to incrementally determine our new velocity
		#for boid in self.friends:
		self.newVelocity = reduce(lambda x, y: x + y, [rule() for rule in self.rules], self.newVelocity)
		if abs(self.newVelocity) > self.maxSpeed:
			self.newVelocity.normalize() * self.maxSpeed
	
	def UpdateFlockingVelocity(self):
		"""
		Update our velocity to the preserved newly calculated velocity.
		Do this once each cycle.
		"""
		self.SetVelocity(self.newVelocity)
	
	#######################################################
	#
	# The following are our basic boid rules
	# Add your own rules for more complex behaviours
	#
	#######################################################
	
	def TendTowardsCenter(self):
		"""
		This rule gives a boid a tendency to move towards the center of it's friends.
		"""
		center = 0
		
		# loop through all our friends finding the center
		for boid in self.friends:
			# either initialise our initial center to be a boid
			# or add the next boid's center to our general center
			if center:
				center = center + boid.GetPosition()
			else:
				center = boid.GetPosition()
		
		# find the center by dividing all the added vectors by the scalar number of boids
		center = center / (len(self.friends) - 1)
		
		# return the vector pointing to that center multiplied by the weighting
		return (center - self.GetPosition()) * self.centerWeighting
	
	def Avoid(self):
		"""
		This rule gives a boid a tendency to avoid it's friends.
		"""
		avoid = None
		
		# loop through all our friends
		for boid in self.friends:
			# calculate the avoidance vector from one of my friends
			# this is the vector from him to me divided by our total distance squared (inverse square)
			# multiplied by some avoidance factor
			aVec = (self.GetPosition() - boid.GetPosition()) / pow(max(0.0001, abs(self.GetPosition() - boid.GetPosition())), 2) * self.avoidanceWeighting
			
			# initialise our avoidance vector to this
			# or if it's initialised then subtract the avoidance vector from our total avoidance
			if not avoid:
				avoid = aVec
			else:
				avoid = avoid - aVec
		
		return avoid
	
	def MatchVelocity(self):
		"""
		This rule gives a boid a tendency to match the velocity of it's friends.
		"""
		match = 0
		
		# sum the velocities of all the boids
		for boid in self.friends:
			if match:
				match = match + boid.GetVelocity()
			else:
				match = boid.GetVelocity()
		
		# average of all the vectors
		match = match / (len(self.friends) - 1)
		
		return (match - self.GetVelocity()) * self.matchingFactor
	

