import glm
from OpenGL import GL
import numpy as np

from .GLInfo import GLInfo
from .EBO import EBO
from .SameTypeList import SameTypeList
from .utils import checktype
from .helper import sizeof
from .Increment import Increment

class Indices(SameTypeList):
	@checktype
	def __init__(self, _list:(list,np.ndarray)=None, draw_type:GLInfo.draw_types=GL.GL_STATIC_DRAW, dtype=None):
		SameTypeList.__init__(self, _list, glm.uvec3)
		
		self.stride = sizeof(glm.uvec3)
		self._ebo = EBO()
		self._draw_type = draw_type

		self._temp_buffer = None
		self._temp_buffer_changed = False
		self._temp_ebo = EBO()

	def _check_type(self, triangle):
		if not isinstance(triangle, glm.uvec3):
			raise TypeError("indices should be in type uvec3")

	@property
	def ebo(self):
		if self._temp_buffer is None:
			return self._ebo
		else:
			return self._temp_ebo

	@property
	def draw_type(self):
		return self._draw_type

	@draw_type.setter
	def draw_type(self, value):
		self._draw_type = value

	@property
	def temp_buffer(self):
		return self._temp_buffer
	
	@temp_buffer.setter
	def temp_buffer(self, temp_buffer:bytes):
		if self._temp_buffer == temp_buffer:
			return
		
		self._temp_buffer = temp_buffer
		self._temp_buffer_changed = True

	def _temp_apply(self):
		self._temp_ebo.bufferData(self._temp_buffer)
		self._temp_buffer_changed = False

	def _first_apply(self):
		if self._increment is not None or not self:
			return False

		self._increment = Increment(self)

		if self._ebo.nbytes == 0:
			self._ebo.bufferData(self.buffer, draw_type=self._draw_type)

		return True

	def _apply_increment(self):
		if not self._increment.is_changed:
			return
		
		patch = self._increment.patch()
		
		if patch["old_capacity"] == patch["new_capacity"]:
			for move in patch["move"]:
				self._ebo.memmove(move["old_start"]*self.stride, move["size"]*self.stride, move["new_start"]*self.stride)
		else:
			temp_ebo = EBO()
			temp_ebo.malloc(patch["new_capacity"]*self.stride, self._draw_type)
			self._ebo.copy_to(0, min(patch["old_size"], patch["new_size"])*self.stride, temp_ebo, 0)
			for move in patch["move"]:
				self._ebo.copy_to(move["old_start"]*self.stride, move["size"]*self.stride, temp_ebo, move["new_start"]*self.stride)

			self._ebo.delete()
			self._ebo = temp_ebo

		new_data = patch["new_data"]
		patch_update = patch["update"]
		len_patch_update = len(patch_update)

		if new_data and patch_update:
			temp_buffer = np.array(new_data).tobytes()
			if len_patch_update > 1:
				temp_ebo = EBO()
				temp_ebo.bufferData(temp_buffer, self._draw_type)

				for update in patch_update:
					dest_start = update["dest_start"]
					size = update["size"]
					src_start = update["src_start"]
					temp_ebo.copy_to(src_start*self.stride, size*self.stride, self._ebo, dest_start*self.stride)
			else:
				update = patch_update[0]
				dest_start = update["dest_start"]
				size = update["size"]
				self._ebo.bufferSubData(dest_start*self.stride, size*self.stride, temp_buffer)

	def _apply(self):
		if self._temp_buffer is not None:
			if self._temp_buffer_changed:
				self._temp_apply()
			return
		else:
			if self._temp_buffer_changed:
				self._temp_ebo.delete()
		
		self._check_in_items()
		
		success = False
		if self._increment is None:
			success = self._first_apply()
		else:
			self._apply_increment()
			success = True

		if success:
			self._ebo.bind()

	@property
	def buffer(self):
		return bytearray(self.ndarray.tobytes()) + bytearray((self.capacity - len(self))*self.stride)