from .reg_modified import *
import xml.etree.ElementTree as et
from argparse import ArgumentParser

# remove APIs that can't be sandboxed
removeProc = [
	"glMapBuffer",
	"glMapBufferRange",
	"glUnmapBuffer",
	"glFlushMappedBufferRange",
	"glGetBufferPointerv"
]

def gen_gles_header(spec, filename):
	# Generates the GLES header, wrapping gl functions
	# prototypes in ORCA_IMPORT() macro

	gles2through31Pat = '2\.[0-9]|3\.[01]'
	allVersions = '.*'

	genOpts = CGeneratorOptions(
		filename=filename,
		apiname='gles2',
		profile='common',
		versions=gles2through31Pat,
		emitversions=allVersions,
		protectProto=False,
		procMacro='ORCA_IMPORT',
		removeProc = removeProc)

	reg = Registry()
	tree = et.parse(spec)
	reg.loadElementTree(tree)

	logFile = open('./build/gles_gen.log', 'w')
	gen = COutputGenerator(diagFile=logFile)
	reg.setGenerator(gen)
	reg.apiGen(genOpts)

	logFile.close()


def get_bindgen_tag_for_type(typeName):
	typeToTags = {
		"void": "v",

		"GLenum": "i",
		"GLbitfield": "i",

		"GLboolean": "i",
		"GLbyte": "i",
		"GLubyte": "i",
		"GLchar": "i",

		"GLshort": "i",
		"GLushort": "i",
		"GLhalf": "i",
		"GLhalfARB": "i",

		"GLuint": "i",
		"GLint": "i",
		"GLclampx": "i",
		"GLsizei": "i",
		"GLfixed": "i",

		"GLintptr": "i",
		"GLsizeiptr": "i",

		"GLuint64": "I",
		"GLint64": "I",

		"GLfloat": "f",
		"GLclampf": "f",

		"GLdouble": "F",
		"GLclampd": "F",

		#NOTE: we treat sync objects as opaque 64bit values
		#TODO we should _also_ make sure that Wasm code treat them as 64bit values
		"GLsync": "I"
	}

	if typeName[len(typeName)-1] == '*':
		return "p"
	else:
		tag = typeToTags.get(typeName)
		return tag


def gen_compsize_len_entry(name, argName, compsizeArgs):

	entry = '\t\t\t"len": {\n'
	entry += '\t\t\t\t"proc": "orca_'+ name +'_'+argName+'_length",\n'
	entry += '\t\t\t\t"args": ['

	for i, compsizeArg in enumerate(compsizeArgs):
		entry += '"' + compsizeArg + '"'
		if i < len(compsizeArgs)-1:
			entry += ', '
	entry += ']\n'
	entry += '\t\t\t}'
	return entry

def gen_argcount_len_entry(name, argName, tokens):

	entry = '\t\t\t"len": {'
	if len(tokens) == 2:
		if tokens[1].isnumeric() == False:
			print("Warning: function " + name + ": couldn't parse parameter '" + argName + "' lenght attribute")
		entry += '"count": "' + tokens[0] + '", "components": '+ tokens[1]
	elif len(tokens) == 1:
		if tokens[0].isnumeric():
			entry += '"components":'+ tokens[0]
		else:
			entry += '"count": "'+ tokens[0] + '"'
	else:
		print("Warning: function " + name + ": couldn't parse parameter '" + argName + "' lenght attribute")

	entry += '}'
	return entry

def gen_gles_bindgen_json(spec, filename):

	# Gather gles 3.1 required functions
	tree = et.parse(spec)
	api = []

	for	feature in tree.iterfind('feature[@api="gles2"]'):
		if float(feature.get('number')) > 3.1:
			break

		for require in feature.iter('require'):
			if require.get('profile') == 'compatibility':
				continue
			for command in require.iter('command'):
				if command.get('name') not in removeProc:
					api.append(command.get('name'))

		for remove in feature.iter('remove'):
			for command in remove.iter('command'):
				api.remove(command.get('name'))

	# put all GL commands in a dict
	commands = dict()
	commandsSpec = tree.find('./commands')
	for command in commandsSpec.iter('command'):
		name = command.find('proto/name')
		commands[name.text] = command

	# TODO: Generate json descriptions for commands in api

	manualBind = [
		"glShaderSource",
		"glGetVertexAttribPointerv",
		"glVertexAttribPointer",
		"glVertexAttribIPointer",
		"glGetString",
		"glGetStringi",
		"glGetUniformIndices"
	]

	json = '[\n'
	for name in api:
		if name in manualBind:
			continue

		command = commands.get(name)
		if command == None:
			print("Couldn't find definition for required command '" + name + "'")
			exit(-1)

		proto = command.find("proto")
		ptype = proto.find("ptype")

		retType = ''
		if proto.text != None:
			retType += proto.text

		if ptype != None:
			if ptype.text != None:
				retType += ptype.text
			if ptype.tail != None:
				retType += ptype.tail

		retType = retType.strip()

		retTag = get_bindgen_tag_for_type(retType)
		if retTag == None:
			print("Couldn't find tag for GL type '" + retType + "'")
			exit(-1)

		entry = '{\n\t"name": "' + name + '",\n'
		entry += '\t"cname": "' + name +  '",\n'
		entry += '\t"ret": { "name": "' + retType + '", "tag": "' + retTag + '"},\n'

		entry += '\t"args": [ '

		# iterate through params
		for param in command.iter('param'):

			argNode = param.find('name')
			argName = argNode.text

			typeNode = param.find('ptype')

			typeName = ''

			if param.text != None:
				typeName += param.text

			if typeNode != None:
				if typeNode.text != None:
					typeName += typeNode.text
				if typeNode.tail != None:
					typeName += typeNode.tail

			typeName = typeName.strip()

			if typeName.endswith('**'):
				print("Warning: function " + name + ": parameter " + argName + " has 2 (or more) levels of indirection")

			typeTag = get_bindgen_tag_for_type(typeName)

			if typeTag == None:
				print("Couldn't find tag for GL type '" + typeName + "' in function '"+ name +"'")
				exit(-1)

			entry += '\n'
			entry += '\t\t{\n\t\t\t"name": "'+ argName +'",\n'
			entry += '\t\t\t"type": {"name": "'+ typeName +'", "tag": "'+ typeTag +'"}'

			lenString = param.get('len')

			nullStringProcWithNoLen = [
				"glBindAttribLocation",
				"glGetAttribLocation",
				"glGetUniformLocation"
			]

			drawIndirectProc = [
				"glDrawArraysIndirect",
				"glDrawElementsIndirect"
			]

			if typeTag == "p":
				if lenString == None:
					if name in drawIndirectProc:
						entry += ',\n'
						entry += gen_compsize_len_entry(name, argName, ['indirect'])
					elif name in nullStringProcWithNoLen:
						entry += ',\n'
						entry += gen_compsize_len_entry(name, argName, ['name'])
					else:
						print("Warning: function " + name + ": parameter " + argName + " has no len attribute")

				elif lenString != None:
					entry += ',\n'

					tokens = lenString.split('*')

					if lenString.startswith("COMPSIZE"):
						tmp = lenString
						if tmp.startswith("COMPSIZE("):
							tmp = tmp[len("COMPSIZE("):]
						if tmp.endswith(")"):
							tmp = tmp[:-1]

						compsizeArgs = list(filter(None, tmp.split(",")))

						if len(compsizeArgs) == 0:
							# special case glGetUniformBlockIndex which isn't specified correctly in gl.xml
							if name == 'glGetUniformBlockIndex':
								compsizeArgs = ['uniformBlockName']

						entry += gen_compsize_len_entry(name, argName, compsizeArgs)

					else:
						entry += gen_argcount_len_entry(name, argName, tokens)

			entry += '\n\t\t},'

		entry = entry[:-1]
		entry += '\n\t]\n}'

		json += entry
		json += ',\n'

	json = json[:-2]
	json += '\n]'

	# write json to jsonFile
	f = open(filename, 'w')
	f.write(json)
	f.close()

def gles_gen(spec, json, header):
	gen_gles_header(spec, header)
	gen_gles_bindgen_json(spec, json)

#----------------------------------------
# driver
#----------------------------------------

if __name__ == "__main__":
	parser = ArgumentParser()
	parser.add_argument("-s", "--spec")
	parser.add_argument("--header")
	parser.add_argument("-j", "--json")

	args = parser.parse_args()

	glesHeader = args.header
	jsonFile = args.json

	gles_gen(args.spec, jsonFile, glesHeader)
