"""Refactored version of the opengl generator using ctypeslib
"""
try:
	from ctypeslib.codegen import codegenerator
	from ctypeslib import xml2py
except ImportError, err:
	try:
		from ctypes_codegen import codegenerator, xml2py
	except ImportError, err:
		from ctypes.wrap import codegenerator, xml2py
try:
	from cStringIO import StringIO
except ImportError, err:
	from StringIO import StringIO

import sys, logging
log = logging.getLogger( 'openglgenerator' )
import ctypes
from OpenGL.platform import GL, GLU, GLUT, GLE
from OpenGL import constant

def indent( code, indentation='\t' ):
	"""Indent given code by given indentation"""
	lines = code.splitlines()
	return "\n".join( [ '%s%s'%(indentation,line) for line in lines] )

class OpenGLGenerator( codegenerator.Generator ):
	"""Subclass of code generator providing PyOpenGL integration"""
	_super = codegenerator.Generator
	MODULE_HEADER = """from ctypes import *
from OpenGL import platform, arrays
from OpenGL.constant import Constant
from OpenGL import constants as GLconstants
GLvoid = GLconstants.GLvoid
"""
	def defaultEmitters( cls ):
		"""Produce the set of default emitter classes
		"""
		return [
			OpenGLFunction(),
			OpenGLConstant(),
		] + cls._super.defaultEmitters()
	def importAble( cls, name, value ):
		"""Determine whether this name/object should be imported from known symbols"""
		return (
			isinstance( value, type ) or 
			isinstance( value, constant.Constant ) or 
			value.__class__.__name__.endswith( 'CFunctionType') # this should be available *somewhere*!
		)
	importAble = classmethod( importAble )

	def filter_items( self, items, expressions=None,symbols=None, types=None ):
		"""Filter out PFN functions"""
		items = [
			i for i in items 
			# skip the pointer-to-function meta-types...
			if not getattr( i,'name','').startswith( 'PFN' )
		]
		return self._super.filter_items( self, items, expressions=expressions, symbols=symbols, types=types )
	def get_sharedlib(self, dllname, cc):
		"""Override so that all references to shared libraries go through "platform" module"""
		if dllname in ('libGL','GL','libGL.so.1'):
			return 'platform.GL'
		elif dllname in ('libGLU','GLU','libGLU.so.1'):
			return 'platform.GLU'
		elif dllname in ('libglut','glut','libglut.so.3'):
			return 'platform.GLUT'
		elif dllname in ('libgle','gle','libgle.so.3' ):
			return 'platform.GLE'
		else:
			raise NotImplementedError( """Haven't done %s yet!"""%(dllname) )
	def cmpitems( self, a, b ):
		"""Dumb sorting helper to order by name instead of position"""
		try:
			return cmp( (a.name,getattr(a, "location", -1), a.__class__), (b.name,getattr(b, "location", 1),b.__class__))
		except (AttributeError,TypeError,ValueError), err:
			return cmp( a, b )

		

class OpenGLFunction( codegenerator.Function ):
	"""Replaces the ctypes default code generator for functions"""
	TEMPLATE = """%(location)s%(name)s = platform.createBaseFunction( 
	%(name)r, dll=%(libname)s, resultType=%(returnType)s, 
	argTypes=[%(argTypes)s],
	doc=%(documentation)r, 
	argNames=%(argNames)r,
)
"""
	def emit(self, generator, func):
		"""Produce a function via a call to platform-provided function"""
		result = []
		libname = self.libName( generator, func )
		if libname:
			self.increment()
			result.append( self.generateHeader( generator, func ))
			args = self.getArgs( generator, func )
			argTypes  = ",".join( args )
			argNames = self.getArgNames( generator, func )
			location = self.locationComment( generator, func )
			name = func.name
			returnType = generator.type_name(func.returns)

			documentation = self.documentFunction( generator, func )

			generator.names.add(func.name)
			result.append( self.TEMPLATE %locals() )
			return result
		elif not func.name.startswith( '__builtin_' ):
			log.warn( """Could not find DLL name for function: %r""", func.name )
			return ''

	def arrayTypeName( self, generator, argType ):
		"""Retrieve the array type name for argType or None"""
		if generator.type_name(argType).startswith( 'POINTER' ):
			# side effect should be to make the type available,
			# but doesn't work with GLvoid
			typeName = generator.type_name(argType.typ)
			if self.CTYPE_TO_ARRAY_TYPE.has_key( typeName ):
				return 'arrays.%s'%(self.CTYPE_TO_ARRAY_TYPE[typeName])
			elif (typeName == 'GLvoid'):
				# normal to not have pointers to it...
				log.info( 'GLvoid pointer %r, using POINTER(%s)', typeName, typeName )
			else:
				log.warn( 'No mapping for %r, using POINTER(%s)', typeName, typeName )
		return None
	def getArgs( self, generator, func ):
		"""Retrieve arg type-names for all arguments in function typedef"""
		return [
			self.arrayTypeName( generator, a ) or generator.type_name(a) 
			for a in func.iterArgTypes()
		]
	def documentFunction( self, generator, func ):
		"""Customisation point for documenting a given function"""
		args = self.getArgs(generator,func)
		argnames = self.getArgNames( generator, func )
		return str("%s( %s ) -> %s"%(
			func.name,
			", ".join(
				[ '%s(%s)'%( name, typ) for (name,typ) in zip(args,argnames) ]
			),
			generator.type_name(func.returns),
		))
	SUFFIX_TO_ARRAY_DATATYPE = [
		('ub','GLconstants.GL_UNSIGNED_BYTE'),
		('us','GLconstants.GL_UNSIGNED_SHORT'),
		('ui','GLconstants.GL_UNSIGNED_INT'),
		('f','GLconstants.GL_FLOAT'),
		('d','GLconstants.GL_DOUBLE'),
		('i','GLconstants.GL_INT'),
		('s','GLconstants.GL_SHORT'),
		('b','GLconstants.GL_BYTE'),
	]
	CTYPE_TO_ARRAY_TYPE = {
		'GLfloat': 'GLfloatArray',
		'float': 'GLfloatArray',
		'GLclampf': 'GLclampfArray',
		'GLdouble': 'GLdoubleArray',
		'double': 'GLdoubleArray',
		'int': 'GLintArray',
		'GLint': 'GLintArray',
		'GLuint': 'GLuintArray',
		'unsigned int':'GLuintArray',
		'unsigned char': 'GLbyteArray',
		'uint': 'GLuintArray',
		'GLshort': 'GLshortArray',
		'GLushort': 'GLushortArray',
		'short unsigned int':'GLushortArray',
		'GLubyte': 'GLubyteArray',
		'GLbyte': 'GLbyteArray',
		'char': 'GLbyteArray',
		'gleDouble': 'GLdoubleArray',
		# following should all have special sub-classes that enforce dimensions
		'gleDouble * 4': 'GLdoubleArray',
		'gleDouble * 3': 'GLdoubleArray',
		'gleDouble * 2': 'GLdoubleArray',
		'c_float * 3': 'GLfloatArray',
		'gleDouble * 3 * 2': 'GLdoubleArray',
	}

class OpenGLConstant( codegenerator.Variable ):
	"""Override to produce OpenGL.constant.Constant instances"""
	TEMPLATE = """%(name)s = Constant( %(name)r, %(value)r)"""
	def emit( self, generator, typedef ):
		"""Filter out constants that don't have all-uppercase names"""
		if typedef.name.upper() != typedef.name:
			return ""
		return super( OpenGLConstant, self ).emit( generator, typedef )

class OpenGLDecorator( OpenGLFunction ):
	"""Produces decorated versions of the functions in a separate module
	
	This is passed in as an emitter for a separate pass, so that only the
	annotations get into the separate module.
	"""
	def isPointer( self, generator, arg ):
		"""Is given arg-type a pointer?"""
		return generator.type_name( arg ).startswith( 'POINTER' )
	def hasPointer( self, generator, args ):
		"""Given set of arg-types, is one a pointer?"""
		return [ arg for arg in args if self.isPointer( generator, arg ) ]
	def emit( self, generator, func ):
		"""Emit code to create a copy of the function with pointer-size annotations"""
		name = func.name 
		size = None 
		typ = None
		if not self.hasPointer( generator, func.iterArgTypes() ):
			return None
		libname = self.libName( generator, func )
		if not libname:
			return None
		base = name 
		if name.endswith( 'ARB' ):
			base = base[:-3]
		if base.endswith( 'v' ):
			base = base[:-1]
			found = 0
			for suffix,typ in self.SUFFIX_TO_ARRAY_DATATYPE:
				if base.endswith(suffix):
					found = 1
					base = base[:-len(suffix)]
					try:
						size = int(base[-1])
					except ValueError, err:
						size = None
					break
		elif base[:-1].endswith( 'Matrix' ):
			# glLoadMatrix, glMultMatrix
			for suffix,typ in self.SUFFIX_TO_ARRAY_DATATYPE:
				if name.endswith( suffix ):
					size = 16
					break
		result = ''
		for index,(arg,argName) in enumerate( zip(func.iterArgTypes(),func.iterArgNames()) ):
			type = self.arrayTypeName( generator, arg )
			argName = str(argName )
			if type:
				generator.names.add(func.name)
			if result:
				previous = indent( result, '\t' )
			else:
				previous = '\traw.%(name)s'%locals()
			if type and size is None:
				# should only print this if it's a normal array type...
				result = """arrays.setInputArraySizeType(
%(previous)s,
	None, # XXX Could not determine size of argument %(argName)s for %(name)s %(type)s
	%(type)s, 
	%(argName)r,
)
"""%locals()
			elif type:
				result = """arrays.setInputArraySizeType(
%(previous)s,
	%(size)s,
	%(type)s,
	%(argName)r,
)
"""%locals()
		if result:
			return '%(name)s = %(result)s'%locals()
		return None
	

if __name__ == "__main__":
	import sys, logging
	logging.basicConfig()
	codegenerator.Generator = OpenGLGenerator
	sys.exit(xml2py.main())
