from pygame import draw, Rect
from PodSix.Gfx2d import gfx
import copy

class InvalidScaleException(Exception):
	pass

class Entity:
	"""
	This is a single entity in the world possibly made up of several animated frames.
	These should be exported from the puffer vector editor.
	"""
	def __init__(self, world):
		"""
		All we need is the world that we belong to.
		"""
		self.world = world
		
		self.frames = []
		self.animations = {"": [[]]}
		self.frame = 0.0
		self.animation = ""
		self.framerate = 1.0
		self.boundingbox = {}
		
		self.width = 0
		self.height = 0
		self.scale = 0
		self.worldscale = 1.0
		self.position = [0, 0]
		self.lastposition = [0, 0]
		self.scaledposition = [0, 0]
		self.velocity = [0, 0]
		
		self.screenwidth = gfx.screen.get_width()
		self.screenheight = gfx.screen.get_height()
	
	def TestCollisions(self, others):
		"""
		Checks whether the current bounding box collides with a list of other entities
		"""
		box = self.GetBoundingBox()
		[self.Collide(o) or o.Collide(self) for o in others if box.colliderect(o.GetBoundingBox())]
	
	def AddAnimation(self, name, frames, actions=[]):
		"""
		Add a puffer animation to this entity's available animations.
		"""
		if self.scale == 0:
			raise InvalidScaleException("Please set this object's scale before adding animations")
		# make a copy of the polygon data incase someone else wants the original
		new = copy.deepcopy(frames)
		# run the transforms we want on the polygon data
		[a(new) for a in actions]
		self.animations[name] = new
		self.boundingbox[name] = Rect([0, 0, self.width * self.screenwidth * self.scale, self.height * self.screenwidth * self.scale])
	
	def Animate(self, which):
		"""
		Run one of my animations (increasing framerate each time we're called)
		"""
		if which != self.animation:
			self.animation = which
			self.frame = 0
		else:
			self.IncFrame()
	
	def AnimateOnce(self, which):
		"""
		Play through an animation just once
		"""
		if which != self.animation:
			self.animation = which
			self.frame = 0
		else:
			self.IncFrameOnce()
	
	def SetFrame(self, frame, anim):
		"""
		Set to a specific frame (possibly of a specific animation)
		"""
		if anim:
			self.animation = anim
		self.frame = frame
	
	def GetBoundingBox(self):
		"""
		Returns the bounding box of the current animation with offsets computed for current position
		"""
		box = self.boundingbox[self.animation]
		box.left = (self.position[0] - self.world.position[0]) * self.screenwidth - box.width / 2
		box.top = self.position[1] * self.screenheight - box.height
		return box
	
	def IncFrame(self):
		"""
		Increase the frame by one.
		"""
		self.frame = (self.frame + self.framerate) % len(self.animations[self.animation])
	
	def IncFrameOnce(self):
		"""
		Increase the frame until we hit the end of the animation.
		"""
		if self.frame < self.animations[self.animation]:
			self.frame += self.framerate
	
	def Normalise(self, frames):
		"""
		Figure out the width and height of a set of frames and then normalise all points within them.
		Normalises with the values of the size of the puffer editor screen.
		"""
		low = [10000, 10000]
		high = [0, 0]
		for f in frames:
			for p in f:
				for points in p['points']:
					low[0] = min(low[0], points[0])
					low[1] = min(low[1], points[1])
					high[0] = max(high[0], points[0])
					high[1] = max(high[1], points[1])
		
		# normalise frames using the max and min values we found and the built in screensize of the puffer editor
		remove = []
		for f in frames:
			if len(f):
				for p in f:
					p['points'] = [[(x - (high[0] - low[0]) / 2.0 - low[0]) / 400.0, (y - (high[1] - low[1]) - low[1]) / 400.0] for x,y in p['points']]
				f.reverse()
			else:
				remove.append(f)
		
		self.width = (high[0] - low[0]) / 400.0
		self.height = (high[1] - low[1]) / 400.0
		
		# remove any frames that we found that were empty
		for f in remove:
			frames.remove(f)
		
		return frames
	
	def Resize(self, frames):
		"""
		Figure out the width and height of a set of frames and then normalise all points within them.
		Like normalise but it uses the size of the thing, not it's editor size.
		"""
		low = [60000, 60000]
		high = [-60000, -60000]
		for f in frames:
			for p in f:
				for points in p['points']:
					low[0] = min(low[0], points[0])
					low[1] = min(low[1], points[1])
					high[0] = max(high[0], points[0])
					high[1] = max(high[1], points[1])
		
		# normalise frames using the max and min values we found and the built in screensize of the puffer editor
		remove = []
		for f in frames:
			if len(f):
				for p in f:
					p['points'] = [[(x - (high[0] - low[0]) / 2.0 - low[0]) / (high[1] - low[1]), (y - (high[1] - low[1]) - low[1]) / (high[1] - low[1])] for x,y in p['points']]
				f.reverse()
			else:
				remove.append(f)
		
		self.width = (high[0] - low[0]) / (high[1] - low[1])
		self.height = (high[1] - low[1]) / (high[0] - low[0])
		
		# remove any frames that we found that were empty
		for f in remove:
			frames.remove(f)
		
		return frames
	
	def ReplaceColor(self, a, b):
		"""
		Replace all instances of a certain color within a puffer model with another color/
		"""
		for anm in self.animations:
			for f in self.animations[anm]:
				for p in f:
					if p['color'] == a:
						p['color'] = b
					if p['outline'] == a:
						p['outline'] = b
	
	def Darken(self, c, amount=20):
		"""
		Darken a color.
		"""
		return [max(0, p - amount) for p in c]
	
	def Reverse(self, frames):
		"""
		Flips frames horizontally.
		"""
		for f in frames:
			for p in f:
				p['points'] = [[-x, y] for x,y in p['points']]
		
		return frames
	
	def Update(self):
		"""
		Do whatever you have to every frame. (override me higher up, probably to pump events)
		"""
	
	def Collide(self, entity):
		"""
		We have collided with entity
		"""
	
	def Draw(self, srf=None):
		"""
		Render this entity onto a surface.
		"""
		if srf == None:
			srf = gfx.screen
		for p in self.animations[self.animation][int(self.frame)]:
			points = [[self.screenwidth * (self.scale * x + (self.position[0] - self.world.position[0])), self.scale * y * self.screenwidth + self.screenheight * self.position[1]] for x,y in p['points']]
			if len(points) > 1:
				if p['filled'] and len(points) > 2:
					draw.polygon(srf, p['color'], points, 0)
				draw.lines(srf, p['outline'], p['closed'], points, p['width'])
		

