''' Base class for every drawable object
'''

from functools import wraps
from OpenGL import GL
import numpy as np
from .Tools import glm
from .Tools.visualizationEnums import VisualizationObject


fSize = np.dtype(np.float32).itemsize
iSize = np.dtype(np.int32).itemsize
uSize = np.dtype(np.uint32).itemsize


def propagateToChildren(func):
	""" Decorator for propagate the drawing function to the object's children.
	"""
	@wraps(func)
	def wrapper(*args, **kwargs):
		funcsName = func.__name__

		for child in args[0].children:
			getattr(child, funcsName)(*args[:-1], **kwargs)

		return func(*args, **kwargs)

	return wrapper

def drawable(func):
	""" Decorator for checking the drawable flag state from the object
	that calls the draw function from any VisualizationBaseObject inherited class.
	"""
	@wraps(func)
	def wrapper(*args, **kwargs):
		if args[0].drawable is True:
			return func(*args, **kwargs)
	return wrapper


def config(func):
	""" Decorator for draw function in any VisualizationBaseObject inherited class.
	Configures the using program and the desired vao
	and unlinks the vao when it's finished
	"""
	@wraps(func)
	def wrapper(*args, **kwargs):
		obj = args[0]
		obj.shader[obj.selectedShader].glUseProgram()
		obj.loadUniforms()
		if type(obj.vao) is not np.ndarray:
			GL.glBindVertexArray(obj.vao)
		else:
			GL.glBindVertexArray(obj.vao[obj.selectedShader])
		func(*args, **kwargs)	# the draw function
		GL.glBindVertexArray(0)
	return wrapper


class VisualizationBaseObject:
	def __init__(self, parent=None, shaderDic=None):

		# Parent and children
		self.parent = parent
		self.children = []
		self.identifier = VisualizationObject.NotDefined

		# OpenGL buffer references
		self.vbo = None
		self.ebo = None
		self.vao = None

		self.texture = None

		# OpenGL shader program handler
		if isinstance(shaderDic, dict):
			self.shader = shaderDic[type(self)]
		else:
			self.shader = shaderDic
		self.nShader = 1

		# Matrixs
		self.model = glm.identity()

		self.inverseModel = glm.identity()

		self.rotationMat = glm.identity()
		self.translateMat = glm.identity()
		self.scaleMat = glm.identity()

		# Flags
		self.drawable = False
		self.clean = False
		self.selectedShader = 0

		self.boundingbox = None


	def cleanOpenGL(self):
		print('Must clean befor leaving... ', type(self))


	def __del__(self):
		if not self.clean:
			self.cleanOpenGL()


	@propagateToChildren
	def clearRef(self):
		self.parent = None
		self.children.clear()


	def draw(self):
		pass


	def setCamera(self, newCamera):
		pass

	def get_center(self):
		raise NotImplementedError

	def get_size(self):
		raise NotImplementedError

	def loadUniforms(self):

		GL.glUniformMatrix4fv(self.shader[self.selectedShader].glGetUniformLocation(
			"M"), 1, GL.GL_TRUE, self.model.getA())


	def remove(self, item):
		self.children.remove(item)


	def rotate(self, center, angle, axis):
		axis = (np.append(axis, 1) * self.rotationMat).getA()[0, :3]
		rotate = glm.translateMatrix(center) * glm.rotateMatrix(angle, axis) * glm.translateMatrix(-center)

		self.rotationMat *= rotate

		self.model = self.translateMat*self.rotationMat*self.scaleMat
		self.inverseModel = np.linalg.inv(self.model)

	def spin(self, angle, axis):
		self.rotate(self.get_center(), angle, axis)


	def translate(self, vec):
		self.translateMat = glm.translateMatrix(vec)

		self.model = self.translateMat*self.rotationMat*self.scaleMat
		self.inverseModel = np.linalg.inv(self.model)


	def stackTranslate(self, vec):
		newTranslateMat = glm.translateMatrix(vec)

		self.translateMat = newTranslateMat*self.translateMat

		self.model = self.translateMat*self.rotationMat*self.scaleMat
		self.inverseModel = np.linalg.inv(self.model)


	def scale(self, vec, center):
		self.scaleMat = glm.translateMatrix(center)*glm.scaleMatrix(vec)*glm.translateMatrix(-center)

		self.model = self.translateMat*self.rotationMat*self.scaleMat
		self.inverseModel = np.linalg.inv(self.model)


	def stackScale(self, vec, center):
		newScaleMat = glm.translateMatrix(center)*glm.scaleMatrix(vec)*glm.translateMatrix(-center)

		self.scaleMat = newScaleMat*self.scaleMat

		self.model = self.translateMat*self.rotationMat*self.scaleMat
		self.inverseModel = np.linalg.inv(self.model)


	def resetModel(self):
		self.scaleMat = glm.identity()

		self.translateMat = glm.identity()

		self.rotationMat = glm.identity()

		self.model = glm.identity()
		self.inverseModel = glm.identity()


	def setSelectedShader(self, newShaderId):
		if newShaderId < self.nShader:
			self.selectedShader = newShaderId
		else:
			raise TypeError('Unsupported shaderID, maximun is ', self.nShader, ' got ', newShaderId, '.')

	def setDrawBB(self, boolean):
		if self.boundingbox is not None:
			self.boundingbox.drawable = boolean
