"""Image-handling routines

### Unresolved:

	Following methods are not yet resolved due to my not being sure how the 
	function should be wrapped:
	
		glCompressedTexImage3D
		glCompressedTexImage2D
		glCompressedTexImage1D
		glCompressedTexSubImage3D
		glCompressedTexSubImage2D
		glCompressedTexSubImage1D
"""
from OpenGL.raw import GL as simple
from OpenGL import images, arrays, wrapper, platform
import ctypes

def asInt( value ):
	if isinstance( value, float ):
		return int(round(value,0))
	return value

## update the image tables with standard image types...
#images.FORMAT_BITS.update( {
#	simple.GL_BITMAP : 1, # must be GL_UNSIGNED_BYTE
#	
#	simple.GL_RED : 8,
#	simple.GL_GREEN : 8,
#	simple.GL_BLUE : 8,
#	simple.GL_ALPHA : 8,
#	simple.GL_LUMINANCE : 8,
#	simple.GL_LUMINANCE_ALPHA : 8,
#	simple.GL_COLOR_INDEX : 8,
#	simple.GL_STENCIL_INDEX : 8,
#	simple.GL_DEPTH_COMPONENT : 8,
#	simple.GL_RGB : 24,
#	simple.GL_BGR : 24,
#
#	simple.GL_RGBA : 32,
#	simple.GL_BGRA : 32,
#	simple.GL_ABGR_EXT : 32,
#	simple.GL_CMYK_EXT : 32,
#
#	simple.GL_CMYKA_EXT : 40,
#	
#	simple.GL_YCRCB_422_SGIX : 8, # must be GL_UNSIGNED_BYTE
#	simple.GL_YCRCB_444_SGIX : 8, # must be GL_UNSIGNED_SHORT
#
#	simple.GL_FORMAT_SUBSAMPLE_24_24_OML : 32, # must be GL_UNSIGNED_INT_10_10_10_2
#	simple.GL_FORMAT_SUBSAMPLE_244_244_OML : 32, # must be GL_UNSIGNED_INT_10_10_10_2
#} )
images.COMPONENT_COUNTS.update( {
	simple.GL_BITMAP : 1, # must be GL_UNSIGNED_BYTE
	
	simple.GL_RED : 1,
	simple.GL_GREEN : 1,
	simple.GL_BLUE : 1,
	simple.GL_ALPHA : 1,
	simple.GL_LUMINANCE : 1,
	simple.GL_LUMINANCE_ALPHA : 2,
	simple.GL_COLOR_INDEX : 1,
	simple.GL_STENCIL_INDEX : 1,
	simple.GL_DEPTH_COMPONENT : 1,
	simple.GL_RGB : 3,
	simple.GL_BGR : 3,

	simple.GL_RGBA : 4,
	simple.GL_BGRA : 4,
	simple.GL_ABGR_EXT : 4,
	simple.GL_CMYK_EXT : 4,

	simple.GL_CMYKA_EXT : 5,
	
	simple.GL_YCRCB_422_SGIX : 1, # must be GL_UNSIGNED_BYTE
	simple.GL_YCRCB_444_SGIX : 1, # must be GL_UNSIGNED_SHORT

	simple.GL_FORMAT_SUBSAMPLE_24_24_OML : 1, # must be GL_UNSIGNED_INT_10_10_10_2
	simple.GL_FORMAT_SUBSAMPLE_244_244_OML : 1, # must be GL_UNSIGNED_INT_10_10_10_2
} )

#images.TYPE_TO_BITS.update( {
#	simple.GL_UNSIGNED_BYTE_3_3_2 : 8,
#	simple.GL_UNSIGNED_BYTE_2_3_3_REV : 8,
#	simple.GL_UNSIGNED_SHORT_4_4_4_4 : 16,
#	simple.GL_UNSIGNED_SHORT_4_4_4_4_REV : 16,
#	simple.GL_UNSIGNED_SHORT_5_5_5_1 : 16,
#	simple.GL_UNSIGNED_SHORT_1_5_5_5_REV : 16,
#	simple.GL_UNSIGNED_SHORT_5_6_5 : 16,
#	simple.GL_UNSIGNED_SHORT_5_6_5_REV : 16,
#	simple.GL_UNSIGNED_INT_8_8_8_8 : 32,
#	simple.GL_UNSIGNED_INT_8_8_8_8_REV : 32,
#	simple.GL_UNSIGNED_INT_10_10_10_2 : 32,
#	simple.GL_UNSIGNED_INT_2_10_10_10_REV : 32,
#	simple.GL_UNSIGNED_BYTE : ctypes.sizeof(simple.GLubyte) * 8,
#	simple.GL_BYTE: ctypes.sizeof(simple.GLbyte) * 8,
#	simple.GL_UNSIGNED_SHORT :  ctypes.sizeof(simple.GLushort) * 8,
#	simple.GL_SHORT :  ctypes.sizeof(simple.GLshort) * 8,
#	simple.GL_UNSIGNED_INT : ctypes.sizeof(simple.GLuint) * 8,
#	simple.GL_INT : ctypes.sizeof(simple.GLint) * 8,
#	simple.GL_FLOAT : ctypes.sizeof(simple.GLfloat) * 8,
#	simple.GL_DOUBLE : ctypes.sizeof(simple.GLdouble) * 8,
#} )
images.TYPE_TO_ARRAYTYPE.update( {
	simple.GL_UNSIGNED_BYTE_3_3_2 : simple.GL_UNSIGNED_BYTE,
	simple.GL_UNSIGNED_BYTE_2_3_3_REV : simple.GL_UNSIGNED_BYTE,
	simple.GL_UNSIGNED_SHORT_4_4_4_4 : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_SHORT_4_4_4_4_REV : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_SHORT_5_5_5_1 : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_SHORT_1_5_5_5_REV : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_SHORT_5_6_5 : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_SHORT_5_6_5_REV : simple.GL_UNSIGNED_SHORT,
	simple.GL_UNSIGNED_INT_8_8_8_8 : simple.GL_UNSIGNED_INT,
	simple.GL_UNSIGNED_INT_8_8_8_8_REV : simple.GL_UNSIGNED_INT,
	simple.GL_UNSIGNED_INT_10_10_10_2 : simple.GL_UNSIGNED_INT,
	simple.GL_UNSIGNED_INT_2_10_10_10_REV : simple.GL_UNSIGNED_INT,
	simple.GL_UNSIGNED_BYTE : simple.GL_UNSIGNED_BYTE,
	simple.GL_BYTE: simple.GL_BYTE,
	simple.GL_UNSIGNED_SHORT : simple.GL_UNSIGNED_SHORT,
	simple.GL_SHORT :  simple.GL_SHORT,
	simple.GL_UNSIGNED_INT : simple.GL_UNSIGNED_INT,
	simple.GL_INT : simple.GL_INT,
	simple.GL_FLOAT : simple.GL_FLOAT,
	simple.GL_DOUBLE : simple.GL_DOUBLE,
} )
images.RANK_PACKINGS.update( {
	4: [
		(simple.glPixelStorei,simple.GL_PACK_SKIP_VOLUMES_SGIS, 0),
		(simple.glPixelStorei,simple.GL_PACK_IMAGE_DEPTH_SGIS, 0),
	],
	3: [
		(simple.glPixelStorei,simple.GL_PACK_SKIP_IMAGES, 0),
		(simple.glPixelStorei,simple.GL_PACK_IMAGE_HEIGHT, 0),
	],
	2: [
		(simple.glPixelStorei,simple.GL_PACK_ROW_LENGTH, 0),
		(simple.glPixelStorei,simple.GL_PACK_SKIP_ROWS, 0),
		(simple.glPixelStorei,simple.GL_PACK_ALIGNMENT, 1),
	],
	1: [
		(simple.glPixelStorei,simple.GL_PACK_SKIP_PIXELS, 0),
	],
} )


__all__ = (
	'glReadPixels',
	'glReadPixelsb',
	'glReadPixelsd',
	'glReadPixelsf',
	'glReadPixelsi',
	'glReadPixelss',
	'glReadPixelsub',
	'glReadPixelsui',
	'glReadPixelsus',
	
	'glGetTexImage',
	
	'glDrawPixels',
	'glDrawPixelsb',
	'glDrawPixelsf',
	'glDrawPixelsi',
	'glDrawPixelss',
	'glDrawPixelsub',
	'glDrawPixelsui',
	'glDrawPixelsus',
	
	
	'glTexSubImage2D',
	'glTexSubImage1D',
	#'glTexSubImage3D',
	
	'glTexImage1D',
	'glTexImage2D',
	#'glTexImage3D',
	
	'glGetTexImageb',
	'glGetTexImaged',
	'glGetTexImagef',
	'glGetTexImagei',
	'glGetTexImages',
	'glGetTexImageub',
	'glGetTexImageui',
	'glGetTexImageus',
	'glTexImage1Db',
	'glTexImage2Db',
	#'glTexImage3Db',
	'glTexSubImage1Db',
	'glTexSubImage2Db',
	#'glTexSubImage3Db',
	'glTexImage1Df',
	'glTexImage2Df',
	#'glTexImage3Df',
	'glTexSubImage1Df',
	'glTexSubImage2Df',
	#'glTexSubImage3Df',
	'glTexImage1Di',
	'glTexImage2Di',
	#'glTexImage3Di',
	'glTexSubImage1Di',
	'glTexSubImage2Di',
	#'glTexSubImage3Di',
	'glTexImage1Ds',
	'glTexImage2Ds',
	#'glTexImage3Ds',
	'glTexSubImage1Ds',
	'glTexSubImage2Ds',
	#'glTexSubImage3Ds',
	'glTexImage1Dub',
	'glTexImage2Dub',
	#'glTexImage3Dub',
	'glTexSubImage1Dub',
	'glTexSubImage2Dub',
	#'glTexSubImage3Dub',
	'glTexImage1Dui',
	'glTexImage2Dui',
	#'glTexImage3Dui',
	'glTexSubImage1Dui',
	'glTexSubImage2Dui',
	#'glTexSubImage3Dui',
	'glTexImage1Dus',
	'glTexImage2Dus',
	#'glTexImage3Dus',
	'glTexSubImage1Dus',
	'glTexSubImage2Dus',
	#'glTexSubImage3Dus',
	
	#'glColorTable',
	#'glGetColorTable',
	#'glColorSubTable',
	
	#'glConvolutionFilter1D',
	#'glConvolutionFilter2D',
	#'glGetConvolutionFilter',
	#'glSeparableFilter2D',
	#'glGetSeparableFilter',
	
	#'glGetMinmax',
)

for suffix,type in [
	('b',simple.GL_BYTE),
	('d',simple.GL_DOUBLE),
	('f',simple.GL_FLOAT),
	('i',simple.GL_INT),
	('s',simple.GL_SHORT),
	('ub',simple.GL_UNSIGNED_BYTE),
	('ui',simple.GL_UNSIGNED_INT),
	('us',simple.GL_UNSIGNED_SHORT),
]:
	def glReadPixels( x,y,width,height,format,type=type ):
		"""Read specified pixels from the current display buffer
		
		This typed version returns data in your specified default 
		array data-type format
		"""
		x,y,width,height = asInt(x),asInt(y),asInt(width),asInt(height)
		array = images.SetupPixelRead( format, (width,height), type )
		arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ]
		imageData = arrayType.dataPointer(array)
		simple.glReadPixels( 
			x,y,
			width, height,
			format,type, 
			ctypes.c_void_p( imageData )
		)
		return array
	globals()["glReadPixels%s"%(suffix,)] = glReadPixels
	def glGetTexImage( target, level,format,type=type ):
		"""Get a texture-level as an image"""
		from OpenGL.GL import glget
		dims = [glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_WIDTH )]
		if target != simple.GL_TEXTURE_1D:
			dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_HEIGHT ) )
			if target != simple.GL_TEXTURE_2D:
				dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_DEPTH ) )
		array = images.SetupPixelRead( format, tuple(dims), type )
		arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ]
		simple.glGetTexImage( 
			target, level, format, type, ctypes.c_void_p( arrayType.dataPointer(array)) 
		)
		return array
	globals()["glGetTexImage%s"%(suffix,)] = glGetTexImage
##	def glGetTexSubImage( target, level,format,type ):
##		"""Get a texture-level as an image"""
##		from OpenGL.GL import glget
##		dims = [glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_WIDTH )]
##		if target != simple.GL_TEXTURE_1D:
##			dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_HEIGHT ) )
##			if target != simple.GL_TEXTURE_2D:
##				dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_DEPTH ) )
##		array = images.SetupPixelRead( format, tuple(dims), type )
##		arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ]
##		simple.glGetTexImage( 
##			target, level, format, type, ctypes.c_void_p( arrayType.dataPointer(array)) 
##		)
##		return array
##	"%s = glGetTexImage"%(suffix)
	del suffix,type
# Now the real glReadPixels...
def glReadPixels( x,y,width,height,format,type, outputType=str ):
	"""Read specified pixels from the current display buffer
	
	x,y,width,height -- location and dimensions of the image to read 
		from the buffer
	format -- pixel format for the resulting data
	type -- data-format for the resulting data
	outputType -- default (str) provides string output of the 
		results iff OpenGL.UNSIGNED_BYTE_IMAGES_AS_STRING is True 
		and type == GL_UNSIGNED_BYTE.  Any other value will cause 
		output in the default array output format.
	
	returns the pixel data array in the format defined by the 
	format, type and outputType
	"""
	x,y,width,height = asInt(x),asInt(y),asInt(width),asInt(height)
	array = images.SetupPixelRead( format, (width,height), type )
	arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ]
	imageData = arrayType.dataPointer(array)
	simple.glReadPixels( 
		x,y,width,height,
		format,type, 
		ctypes.c_void_p( imageData ) 
	)
	if outputType is str:
		return images.returnFormat( array, type )
	else:
		return array

def glGetTexImage( target, level,format,type, outputType=str ):
	"""Get a texture-level as an image
	
	target -- enum constant for the texture engine to be read 
	level -- the mip-map level to read 
	format -- image format to read out the data 
	type -- data-type into which to read the data
	
	outputType -- default (str) provides string output of the 
		results iff OpenGL.UNSIGNED_BYTE_IMAGES_AS_STRING is True 
		and type == GL_UNSIGNED_BYTE.  Any other value will cause 
		output in the default array output format.
	
	returns the pixel data array in the format defined by the 
	format, type and outputType
	"""
	from OpenGL.GL import glget
	dims = [glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_WIDTH )]
	if target != simple.GL_TEXTURE_1D:
		dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_HEIGHT ) )
		if target != simple.GL_TEXTURE_2D:
			dims.append( glget.glGetTexLevelParameteriv( target, level, simple.GL_TEXTURE_DEPTH ) )
	array = images.SetupPixelRead( format, tuple(dims), type )
	arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE.get(type,type) ]
	simple.glGetTexImage( 
		target, level, format, type, ctypes.c_void_p( arrayType.dataPointer(array)) 
	)
	if outputType is str:
		return images.returnFormat( array, type )
	else:
		return array


INT_DIMENSION_NAMES = [
	'width','height','depth','x','y','z',
	'xoffset','yoffset','zoffset',
	'start', 'count',
]
def asWrapper( value ):
	if not isinstance( value, wrapper.Wrapper ):
		return wrapper.wrapper( value )
	return value

def asIntConverter( value, *args ):
	if isinstance( value, float ):
		return int(round(value,0))
	return value

def setDimensionsAsInts( baseOperation ):
	"""Set arguments with names in INT_DIMENSION_NAMES to asInt processing"""
	baseOperation = asWrapper( baseOperation )
	argNames = getattr( baseOperation, 'pyConverterNames', baseOperation.argNames )
	for i,argName in enumerate(argNames):
		if argName in INT_DIMENSION_NAMES:
			baseOperation.setPyConverter( argName, asIntConverter )
	return baseOperation

	

class ImageInputConverter( object ):
	def __init__( self, rank, pixelsName=None, typeName='type' ):
		self.rank = rank
		self.typeName = typeName
		self.pixelsName = pixelsName
	def finalise( self, wrapper ):
		"""Get our pixel index from the wrapper"""
		self.typeIndex = wrapper.pyArgIndex( self.typeName )
		self.pixelsIndex = wrapper.pyArgIndex( self.pixelsName )
	def __call__( self, arg, baseOperation, pyArgs ):
		"""pyConverter for the pixels argument"""
		images.setupDefaultTransferMode()
		images.rankPacking( self.rank )
		type = pyArgs[ self.typeIndex ]
		try:
			arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ images.TYPE_TO_ARRAYTYPE[ type ] ]
		except TypeError, err:
			import pdb
			pdb.set_trace()
		return arrayType.asArray( arg )
	def cResolver( self, array ):
		return ctypes.c_void_p( arrays.ArrayDatatype.dataPointer( array ) )
	
class TypedImageInputConverter( ImageInputConverter ):
	def __init__( self, rank, pixelsName, arrayType, typeName=None ):
		self.rank = rank
		self.arrayType = arrayType
		self.pixelsName = pixelsName
		self.typeName = typeName
	def __call__( self, arg, baseOperation, pyArgs ):
		"""The pyConverter for the pixels"""
		images.setupDefaultTransferMode()
		images.rankPacking( self.rank )
		return self.arrayType.asArray( arg )
	def finalise( self, wrapper ):
		"""Get our pixel index from the wrapper"""
		self.pixelsIndex = wrapper.pyArgIndex( self.pixelsName )
	def width( self, pyArgs, index, wrappedOperation ):
		"""Extract the width from the pixels argument"""
		return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[0]
	def height( self, pyArgs, index, wrappedOperation ):
		"""Extract the height from the pixels argument"""
		return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[1]
	def depth( self, pyArgs, index, wrappedOperation ):
		"""Extract the depth from the pixels argument"""
		return self.arrayType.dimensions( pyArgs[self.pixelsIndex] )[2]
	def type( self, pyArgs, index, wrappedOperation ):
		"""Provide the item-type argument from our stored value
		
		This is used for pre-bound processing where we want to provide 
		the type by implication...
		"""
		return self.typeName

class CompressedImageConverter( object ):
	def finalise( self, wrapper ):
		"""Get our pixel index from the wrapper"""
		self.dataIndex = wrapper.pyArgIndex( 'data' )
	def __call__( self, pyArgs, index, wrappedOperation ):
		"""Create a data-size measurement for our image"""
		arg = pyArgs[ self.dataIndex ]
		return arrays.ArrayType.arrayByteCount( arg )



DIMENSION_NAMES = (
	'width','height','depth'
)
PIXEL_NAMES = (
	'pixels', 'row', 'column',
)
DATA_SIZE_NAMES = (
	'imageSize',
)

def setImageInput( 
	baseOperation, arrayType=None, dimNames=DIMENSION_NAMES, 
	pixelName="pixels", typeName=None 
):
	"""Determine how to convert "pixels" into an image-compatible argument"""
	baseOperation = asWrapper( baseOperation )
	# rank is the count of width,height,depth arguments...
	rank = len([
		# rank is the number of dims we want, not the number we give...
		argName for argName in baseOperation.argNames 
		if argName in dimNames
	]) + 1
	if arrayType:
		converter = TypedImageInputConverter( rank, pixelName, arrayType, typeName=typeName )
		for i,argName in enumerate(baseOperation.argNames):
			if argName in dimNames:
				baseOperation.setPyConverter( argName )
				baseOperation.setCConverter( argName, getattr(converter,argName) )
			elif argName == 'type' and typeName is not None:
				baseOperation.setPyConverter( argName )
				baseOperation.setCConverter( argName, converter.type )
	else:
		converter = ImageInputConverter( rank, pixelsName=pixelName, typeName=typeName or 'type' )
	for argName in baseOperation.argNames:
		if argName in DATA_SIZE_NAMES:
			baseOperation.setPyConverter( argName )
			baseOperation.setCConverter( argName, converter.imageDataSize )
	baseOperation.setPyConverter(
		pixelName, converter,
	)
	baseOperation.setCResolver(
		pixelName, converter.cResolver
	)
	return baseOperation

glDrawPixels = setDimensionsAsInts(
	setImageInput(
		simple.glDrawPixels
	)
)
glTexSubImage2D = setDimensionsAsInts(
	setImageInput(
		simple.glTexSubImage2D
	)
)
glTexSubImage1D = setDimensionsAsInts(
	setImageInput(
		simple.glTexSubImage1D
	)
)
glTexImage2D = setDimensionsAsInts(
	setImageInput(
		simple.glTexImage2D
	)
)
glTexImage1D = setDimensionsAsInts(
	setImageInput(
		simple.glTexImage1D
	)
)

def typedImageFunction( suffix, arrayConstant,  baseFunction ):
	"""Produce a typed version of the given image function"""
	functionName = baseFunction.__name__
	functionName = '%(functionName)s%(suffix)s'%locals()
	if baseFunction:
		arrayType = arrays.GL_CONSTANT_TO_ARRAY_TYPE[ arrayConstant ]
		function = setDimensionsAsInts(
			setImageInput(
				baseFunction, 
				arrayType,
				typeName = arrayConstant,
			)
		)
		return functionName, function
	else:
		return functionName, baseFunction

def _setDataSize( baseFunction, argument='imageSize' ):
	"""Set the data-size value to come from the data field"""
	if baseFunction:
		converter = CompressedImageConverter()
		return asWrapper( baseFunction ).setPyConverter(
			argument
		).setCConverter( argument, converter )
	else:
		return baseFunction

def compressedImageFunction( baseFunction ):
	"""Set the imageSize and dimensions-as-ints converters for baseFunction"""
	if baseFunction:
		return setDimensionsAsInts(
			_setDataSize( 
				baseFunction, argument='imageSize'
			)
		)
	else:
		return baseFunction

for suffix,arrayConstant in [
	('b', simple.GL_BYTE),
	('f', simple.GL_FLOAT),
	('i', simple.GL_INT),
	('s', simple.GL_SHORT),
	('ub', simple.GL_UNSIGNED_BYTE),
	('ui', simple.GL_UNSIGNED_INT),
	('us', simple.GL_UNSIGNED_SHORT),
]:
	for functionName in (
		'glTexImage1D','glTexImage2D',
		'glTexSubImage1D','glTexSubImage2D',
		'glDrawPixels',
		#'glTexSubImage3D','glTexImage3D', # extension/1.2 standard
	):
		functionName, function = typedImageFunction(
			suffix, arrayConstant, getattr(simple,functionName),
		)
		globals()[functionName] = function
		del function, functionName
	del suffix,arrayConstant

