"""Lists/tuples as data-format for storage

Note:
	This implementation is *far* less efficient than using Numpy
	to support lists/tuples, as the code here is all available in
	C-level code there.  This implementation is required to allow
	for usage without numpy installed.
"""
REGISTRY_NAME = 'ctypesarrays'
import ctypes, _ctypes

from OpenGL import constants, constant
from OpenGL.arrays import formathandler
#try:
#	from OpenGL.arrays import numpymodule
#except ImportError, err:
#	# we are required...
HANDLED_TYPES = (list,tuple)
#else:
#	HANDLED_TYPES = ()
import operator

class ListHandler( formathandler.FormatHandler ):
	"""Storage of array data in Python lists/arrays

	This mechanism, unlike multi-dimensional arrays, is not necessarily
	uniform in type or dimension, so we have to do a lot of extra checks
	to make sure that we get a correctly-structured array.  That, as
	well as the need to copy the arrays in Python code, makes this a far
	less efficient implementation than the numpy implementation, which
	does all the same things, but does them all in C code.

	Note: as an *output* format, this format handler produces ctypes
		arrays, not Python lists, this is done for convenience in coding
		the implementation, mostly.
	"""
	def from_param( self, instance, typeCode=None ):
		try:
			return ctypes.byref( instance )
		except TypeError, err:
			array = self.asArray( instance, typeCode )
			pp = ctypes.c_void_p( ctypes.addressof( array ) )
			pp._temporary_array_ = (array,)
			return pp
	dataPointer = staticmethod( ctypes.addressof )
	HANDLED_TYPES = HANDLED_TYPES 
	isOutput = True
	def voidDataPointer( cls, value ):
		"""Given value in a known data-pointer type, return void_p for pointer"""
		return ctypes.byref( value )
	def zeros( self, dims, typeCode ):
		"""Return array of zeros in given size"""
		type = GL_TYPE_TO_ARRAY_MAPPING[ typeCode ]
		for dim in dims:
			type *= dim 
		return type() # should expicitly set to 0s
	def dimsOf( cls, x ):
		"""Calculate total dimension-set of the elements in x
		
		This is *extremely* messy, as it has to track nested arrays
		where the arrays could be different sizes on all sorts of 
		levels...
		"""
		try:
			dimensions = [ len(x) ]
		except (TypeError,AttributeError,ValueError), err:
			return []
		else:
			childDimension = None
			for child in x:
				newDimension = cls.dimsOf( child )
				if childDimension is not None:
					if newDimension != childDimension:
						raise ValueError( 
							"""Non-uniform array encountered: %s versus %s"""%(
								newDimension, childDimension,
							), x
						)
	dimsOf = classmethod( dimsOf )

	def arrayToGLType( self, value ):
		"""Given a value, guess OpenGL type of the corresponding pointer"""

		result = ARRAY_TO_GL_TYPE_MAPPING.get( value._type_ )
		if result is not None:
			return result
		raise TypeError(
			"""Don't know GL type for array of type %r, known types: %s\nvalue:%s"""%(
				value._type_, ARRAY_TO_GL_TYPE_MAPPING.keys(), value,
			)
		)
	def arraySize( self, value, typeCode = None ):
		"""Given a data-value, calculate dimensions for the array"""
		dims = 1
		for base in self.types( value ):
			length = getattr( base, '_length_', None)
			if length is not None:
				dims *= length
		return dims 
	def types( self, value ):
		"""Produce iterable producing all composite types"""
		dimObject = value
		while dimObject is not None:
			yield dimObject
			dimObject = getattr( dimObject, '_type_', None )
			if isinstance( dimObject, (str,unicode)):
				dimObject = None 
	def dims( self, value ):
		"""Produce iterable of all dimensions"""
		for base in self.types( value ):
			length = getattr( base, '_length_', None)
			if length is not None:
				yield length
	def asArray( self, value, typeCode=None ):
		"""Convert given value to a ctypes array value of given typeCode
		
		This does a *lot* of work just to get the data into the correct
		format.  It's not going to be anywhere near as fast as a numpy
		or similar approach!
		"""
		if typeCode is None:
			raise NotImplementedError( """Haven't implemented type-inference for lists yet""" )
		arrayType = GL_TYPE_TO_ARRAY_MAPPING[ typeCode ]
		if isinstance( value, (list,tuple)):
			subItems = [
				self.asArray( item, typeCode )
				for item in value
			]
			if subItems:
				for dim in self.dimensions( subItems[0] )[::-1]:
					arrayType *= dim
				arrayType *= len( subItems )
				result = arrayType()
				result[:] = subItems
				return result
		else:
			return arrayType( value )
	def unitSize( self, value, typeCode=None ):
		"""Determine unit size of an array (if possible)"""
		return tuple(self.dims(value))[-1]
	def dimensions( self, value, typeCode=None ):
		"""Determine dimensions of the passed array value (if possible)"""
		return tuple( self.dims(value) )


ARRAY_TO_GL_TYPE_MAPPING = {
	constants.GLdouble: constants.GL_DOUBLE,
	constants.GLfloat: constants.GL_FLOAT,
	constants.GLint: constants.GL_INT,
	constants.GLuint: constants.GL_UNSIGNED_INT,
	constants.GLshort: constants.GL_SHORT,
	constants.GLushort: constants.GL_UNSIGNED_SHORT,
		
	constants.GLchar: constants.GL_CHAR,
	constants.GLbyte: constants.GL_BYTE,
	constants.GLubyte: constants.GL_UNSIGNED_BYTE,
}
GL_TYPE_TO_ARRAY_MAPPING = {
	constants.GL_DOUBLE: constants.GLdouble,
	constants.GL_FLOAT: constants.GLfloat,
	constants.GL_INT: constants.GLint,
	constants.GL_UNSIGNED_INT: constants.GLuint,
	constants.GL_SHORT: constants.GLshort,
	constants.GL_UNSIGNED_SHORT: constants.GLushort,
		
	constants.GL_CHAR: constants.GLchar,
	constants.GL_BYTE: constants.GLbyte,
	constants.GL_UNSIGNED_BYTE: constants.GLubyte,
}
