﻿import json
import sys
import operator
from functools import reduce

structlist = {}
typedeflist = {}
enumlist = {}

# These are not exported to the json, so we just maintain the small subset here by hand for now.
structparents = {
	'VRTextureWithPose_t' : 'Texture_t',
	'VRTextureWithDepth_t' : 'Texture_t',
	'VRTextureWithPoseAndDepth_t' : 'VRTextureWithPose_t' }

structindices = {}

def loadfile(filename, ns):
	with open(filename) as data_file:
		data = json.load(data_file)

	i = 0
	for struct in data['structs']:
		if(len(struct) > 0 and len(struct['struct'])):
			structname = struct['struct']
			structlist[structname] = 'struct'
			structindices[getclasswithoutnamespace(structname)] = i
		i += 1

	for typedef in data['typedefs']:
		if(len(typedef) > 0 and len(typedef['typedef'])):
			typedeflist[typedef['typedef']] = typedef['type']
	for enum in data['enums']:
		enumname = enum['enumname']
		namespace = getnamespace(enumname)
		if(len(enum) > 0 and len(enum['enumname'])):
			enumlist[enum['enumname'].replace('::','_')] = enum['enumname']
	return data


def paramshavelength(params, paramname):
	checkname = paramname[1:]
	for param in params:
		name = param['paramname']
		name = name[3:len(name)-4]
		sys.stdout.write ('// ' + name + ' ' + checkname + '\n');
		if(name == checkname):
			return True
	return False

def calculateinteropmethodname(classname, methodname):
	return classname.replace('vr::', 'VR::').replace('::', '_') + '_' + methodname

def unmanagedtype(thetype):
	if(thetype == 'float'):
		return 'R4'
	if(thetype == 'double'):
		return 'R8'
	if(thetype == 'char'):
		return 'I1'
	if(thetype == 'byte'):
		return 'U1'
	if(thetype == 'ulong'):
		return 'U8'
	if(thetype == 'uint'):
		return 'U4'
	if(thetype == 'CSteamID'):	# Special case for CSteamID which is a complex C def but is 64-bits.
		return 'U8'
	return 'Struct'

def ctype(thetype):
	thetype = thetype.replace('vr::','')
	if(thetype[0:6] == 'const '):
		thetype = thetype[6:]
	if(thetype[0:6] == 'class '): # for C replaces classes by pointers
		thetype = 'intptr_t'
	if(thetype == 'ulong'):
		return 'unsigned long long'
	if(thetype == 'uint'):
		return 'unsigned long'
	return thetype

def striparraysuffix(thetype):
	if(thetype[len(thetype) - 1] == ']'):
		thetype = thetype[0:thetype.find('[')-1]
	if(thetype == '_Bool'):
		thetype = 'bool'
	return thetype

def converttype(thetype):
	if(thetype[0:11] == 'struct vr::'):
		thetype = thetype[11:]
	if(thetype[0:17] == 'const struct vr::'):
		thetype = thetype[17:]
	if(thetype[0:10] == 'class vr::'):
		thetype = thetype[10:]
	if(thetype == 'const char *const'):
		thetype = 'string'
	#thetype = thetype.replace('::', '_')
	thetypewithnamespace = thetype
	thetype = getclasswithoutnamespace(thetype)
	if(thetype[0:6] == 'const '):
		thetype = thetype[6:]
	if(thetype == 'class CSteamID'):
		thetype = 'ulong'
	if(thetype == 'uint8'):
		thetype = 'byte'
	if(thetype == 'uint8_t'):
		thetype = 'byte'
	if(thetype == 'unsigned short'):
		thetype = 'ushort'
	if(thetype == 'unsigned char'):
		thetype = 'byte'
	#if(thetype == 'unsigned int'):
	#	thetype = 'uint'
	if(thetype == 'uint16'):
		thetype = 'ushort'
	if(thetype == 'uint16_t'):
		thetype = 'ushort'
	if(thetype == 'uint32'):
		thetype = 'uint'
	if(thetype == 'uint32_t'):
		thetype = 'uint'
	if(thetype == 'uint64'):
		thetype = 'ulong'
	if(thetype == 'short'):
		thetype = 'short'
	if(thetype == 'int32'):
		thetype = 'int'
	if(thetype == 'int32_t'):
		thetype = 'int'
	if(thetype == 'int64'):
		thetype = 'long'
	if(thetype == 'uint64_t'):
		thetype = 'ulong'
	if(thetype == '_Bool'):
		thetype = 'bool'
	if(thetype == 'char *'):
		thetype = 'string'
	if(thetype == 'char **'):
		thetype = 'string'
	if(thetype == 'void *'):
		thetype = 'IntPtr'
	if(thetype == 'byte *'):
		thetype = 'IntPtr'
	if(thetype == 'uint8 *'):
		thetype = 'IntPtr'
	if(thetype == 'T *'):
		thetype = 'IntPtr'
	if(thetype == 'func_t'):
		thetype = 'IntPtr'
	if(thetype == 'RenderModel_t *'):
		thetype = 'IntPtr'
	if(thetype == 'RenderModel_TextureMap_t *'):
		thetype = 'IntPtr'
	if(thetype == 'struct VkPhysicalDevice_T *'):
		thetype = 'IntPtr'
	if(thetype == 'struct VkInstance_T *'):
		thetype = 'IntPtr'
	if(thetype == 'SteamLeaderboard_t'):
		thetype = 'ulong'
	if(thetype == 'SteamLeaderboardEntries_t'):
		thetype = 'ulong'
	if(thetype == 'ScreenshotHandle'):
		thetype = 'uint'
	if(thetype == 'AudioPlayback_Status'):
		thetype = 'int'
	if(thetype == 'FriendsGroupID_t'):
		thetype = 'char'
	if(thetype == 'EOverlayToStoreFlag'):
		thetype = 'char'
	if(thetype == 'ESteamAPICallFailure'):
		thetype = 'int'
	if(thetype == 'EGamepadTextInputMode'):
		thetype = 'int'
	if(thetype == 'EGamepadTextInputLineMode'):
		thetype = 'int'
	if(thetype == 'EUniverse'):
		thetype = 'int'
	if(thetype == 'ISteamHTMLSurface::EHTMLMouseButton'):
		thetype = 'int'
	if(thetype == 'ISteamHTMLSurface::EHTMLKeyModifiers'):
		thetype = 'int'
	if(thetype == 'SteamInventoryResult_t'):
		thetype = 'int'
	if(thetype == 'RTime32'):
		thetype = 'ulong'
	if(thetype == 'AccountID_t'):
		thetype = 'uint'
	if(thetype == 'AppId_t'):
		thetype = 'uint'
	if(thetype == 'VRComponentProperties'):
		thetype = 'uint'
	if(thetype == 'SteamAPICall_t'):
		thetype = 'ulong'
	if(thetype == 'UGCQueryHandle_t'):
		thetype = 'ulong'
	if(thetype == 'UGCUpdateHandle_t'):
		thetype = 'ulong'
	if(thetype == 'ClientUnifiedMessageHandle'):
		thetype = 'ulong'
	if(thetype == 'UGCFileWriteStreamHandle_t'):
		thetype = 'ulong'
	if(thetype == 'PublishedFileUpdateHandle_t'):
		thetype = 'ulong'
	if(thetype == 'SteamAPIWarningMessageHook_t'):
		thetype = 'IntPtr'
	if(thetype == 'SteamAPI_PostAPIResultInProcess_t'):
		thetype = 'IntPtr'
	if(thetype == 'SteamAPI_CheckCallbackRegistered_t'):
		thetype = 'IntPtr'
	if(thetype == 'class CGameID'):
		thetype = 'ulong'
	if(thetype == 'PublishedFileId_t'):
		thetype = 'ulong'
	if(thetype == 'UGCHandle_t'):
		thetype = 'ulong'
	if(thetype == 'SNetListenSocket_t'):
		thetype = 'uint'
	if(thetype == 'SNetSocket_t'):
		thetype = 'uint'
	if(thetype == 'TrackedDeviceIndex_t'):
		thetype = 'uint'
	if(thetype == 'VROverlayHandle_t'):
		thetype = 'ulong'
	if(thetype == 'PropertyTypeTag_t'):
		thetype = 'uint'
	if(thetype == 'PropertyContainerHandle_t'):
		thetype = 'ulong'
	if(thetype == 'SpatialAnchorHandle_t'):
		thetype = 'uint'
	if(thetype == 'DriverHandle_t'):
		thetype = 'ulong'
	if(thetype == 'VRActionHandle_t'):
		thetype = 'ulong'
	if(thetype == 'VRActionSetHandle_t'):
		thetype = 'ulong'
	if(thetype == 'VRInputValueHandle_t'):
		thetype = 'ulong'
	if(thetype == 'VRInputOriginHandle_t'):
		thetype = 'ulong'
	if(thetype == 'PathHandle_t'):
		thetype = 'ulong'
	if(thetype == 'VRNotificationId'):
		thetype = 'uint'
	if(thetype == 'TextureID_t'):
		thetype = 'int'
	if(thetype == 'WebConsoleHandle_t'):
		thetype = 'ulong'
	if(thetype == 'vrshared_double'):
		thetype = 'double'
	if(thetype == 'vrshared_uint64_t'):
		thetype = 'ulong'

	if(thetype[0:7] == 'struct '):
		thetype = thetype[7:]
	if(thetype[0:6] == 'class '):
		thetype = thetype[6:]
	if(thetype[0:6] == 'union '):
		thetype = thetype[6:]
	if(thetype[0] == 'H' and thetype[0:3] != "Hmd" and thetype[0:16] != "HiddenAreaMesh_t"):
		thetype = 'uint'
	if(thetype[0:5] == 'enum '):
		thetype = thetype[5:]
	if(thetype[len(thetype) - 1] == '*' and thetype[len(thetype) - 2] == '*'):
		thetype = 'IntPtr'
	if(thetype[len(thetype) - 1] == '*'):
		if("VRControllerState_t" not in thetype):
			thetype = converttype(thetypewithnamespace[:len(thetypewithnamespace)-2])
		else:
			thetype = converttype(thetype[:len(thetype)-2])
#		thetype = 'IntPtr'
	if(thetype[len(thetype) - 1] == '&'):
		thetype = 'IntPtr'
	if(thetype[len(thetype) - 1] == ']'):
		thetype = thetype[0:thetype.find('[')-1]
	if(thetype in typedeflist):
		thetype = converttype(typedeflist[thetype])
	if(thetypewithnamespace in typedeflist):
		thetype = converttype(typedeflist[thetypewithnamespace])
	return thetype

def getnamespace(classname):
	if(classname.find('::') != -1):
		return classname[:classname.find('::')]
	else:
		return ''

def getclasswithoutnamespace(classname):
	if(classname.rfind('::') != -1):
		return classname[classname.rfind('::')+2:]
	else:
		return classname

def outputenums(namespace, data):
	for enum in data['enums']:
		if(len(enum) > 0 and len(enum['enumname'])):
			ns = getnamespace(enum['enumname'])
			enumname = getclasswithoutnamespace(enum['enumname'])
			if(ns == namespace or (namespace == '' and ns[:1] == 'I')):
				print('public enum '+enumname+'\n{')
				enumNameWithoutE = enumname
				if( enumname.startswith( "E" ) ):
					enumNameWithoutE = enumname[1:]
				for enumvalue in enum['values']:
					entry = enumvalue['name']
					if(entry.startswith(enumname)): entry = entry[len(enumname):] # strip off enum name
					if(entry.startswith(enumNameWithoutE)): entry = entry[len(enumNameWithoutE):] # strip off enum name
					if(entry.startswith('_')): entry = entry[1:] # strip off leading underscore
					print('\t'+entry+' = '+enumvalue['value']+',')
				print('}')

def outputstructfields(struct, data):

	# recursively add base class fields first
	basename = getclasswithoutnamespace(struct['struct'])
	if basename in structparents:
		parentname = structparents[basename]
		i = structindices[parentname];
		outputstructfields(data['structs'][i], data)

	for enumvalue in struct['fields']:
		fieldtype = enumvalue['fieldtype']
		otype = converttype(fieldtype)
		lastchar = fieldtype[len(fieldtype) - 1]
		if(lastchar == ']'):
			#print('\tpublic fixed '+otype+' '+enumvalue['fieldname']+ fieldtype[fieldtype.find('['):]+';')
			dims = map(int, fieldtype[fieldtype.find('[')+1:-1].split(']['))
			size = reduce(operator.mul, dims, 1)
			utype = unmanagedtype(otype)
			if(otype == 'char'):
				#print('\t[MarshalAs(UnmanagedType.ByValTStr, SizeConst = '+str(size)+')]')
				sys.stdout.write('public byte '+enumvalue['fieldname']+'0')
				for i in range(1, size):
					sys.stdout.write(',' + enumvalue['fieldname'] + str(i))
				print(';')
				
				
				print('public string ' + enumvalue['fieldname'] + '\n{')
				print('get\n{')
				print('return new string(new char[] {')
				for i in range(0, size-1):
					print('(char)' + enumvalue['fieldname'] + str(i) + ',')
				print('(char)' + enumvalue['fieldname'] + str(size-1) + '}).TrimEnd(\'\\0\');')
				print('}')
				print('}')
			else:
				#print('\t[MarshalAs(UnmanagedType.ByValArray, SizeConst = '+str(size)+', ArraySubType = UnmanagedType.'+utype+')]')
				for i in range(size):
					print('\tpublic '+otype+' '+enumvalue['fieldname']+str(i)+';'+(' //'+otype+fieldtype[fieldtype.find('['):] if(i==0) else ''))
		else:
			if(otype == 'bool'): print('\t[MarshalAs(UnmanagedType.I1)]')
			if(lastchar == '*'):
				print('\tpublic IntPtr '+enumvalue['fieldname']+';'+' // '+fieldtype)
			else:
				print('\tpublic '+otype+' '+enumvalue['fieldname']+';')

def outputstructs(namespace, data):
	for struct in data['structs']:
		if(len(struct) > 0 and len(struct['struct'])):
			structname = struct['struct']
			#print('//'+structname)
			ns = getnamespace(structname)
			basename = getclasswithoutnamespace(structname)
			if(basename != '(anonymous)' and (ns == namespace or (namespace == '' and ns[:1] == 'I') or (namespace == '' and ns[:1] == 'C'))):
				for key, value in typedeflist.items():
					if(converttype(value) == basename):
						basename = getclasswithoutnamespace(key)
						break
						
				print('[StructLayout(LayoutKind.Sequential)] public struct '+basename+'\n{')
				outputstructfields(struct, data)

				if (basename == 'HmdMatrix34_t'):
					print('#if UNITY_5_3_OR_NEWER')
					print('\n\tpublic Vector3 GetPosition()')
					print('\t{')
					print('\t\treturn new Vector3(m3, m7, -m11);')
					print('\t}')

					print('\n\tpublic bool IsRotationValid()')
					print('\t{')
					print('\t\treturn ((m2 != 0 || m6 != 0 || m10 != 0) && (m1 != 0 || m5 != 0 || m9 != 0));')
					print('\t}')

					print('\n\tpublic Quaternion GetRotation()')
					print('\t{')
					print('\t\tif (IsRotationValid())')
					print('\t\t{')
					print('\t\t\tfloat w = Mathf.Sqrt(Mathf.Max(0, 1 + m0 + m5 + m10)) / 2;')
					print('\t\t\tfloat x = Mathf.Sqrt(Mathf.Max(0, 1 + m0 - m5 - m10)) / 2;')
					print('\t\t\tfloat y = Mathf.Sqrt(Mathf.Max(0, 1 - m0 + m5 - m10)) / 2;')
					print('\t\t\tfloat z = Mathf.Sqrt(Mathf.Max(0, 1 - m0 - m5 + m10)) / 2;')

					print('\n\t\t\t_copysign(ref x, -m9 - -m6);')
					print('\t\t\t_copysign(ref y, -m2 - -m8);')
					print('\t\t\t_copysign(ref z, m4 - m1);')

					print('\n\t\t\treturn new Quaternion(x, y, z, w);')
					print('\t\t}')

					print('\t\treturn Quaternion.identity;')
					print('\t}')

					print('\n\tprivate static void _copysign(ref float sizeval, float signval)')
					print('\t{')
					print('\t\tif (signval > 0 != sizeval > 0)')
					print('\t\t\tsizeval = -sizeval;')
					print('\t}')
					
					print('#endif')
					print('}')
				else:
					print('}')

					# The following structures were originally compiled with pack(4) on Linux & OSX by mistake. Generate
					# a packed version of the structure so we can workaround this in the C# interop code
					if ((basename == 'RenderModel_t') or 
						(basename == 'VRControllerState_t') or 
						(basename == 'RenderModel_TextureMap_t') or
						(basename == 'VREvent_t')):
						print('// This structure is for backwards binary compatibility on Linux and OSX only')
						print('[StructLayout(LayoutKind.Sequential, Pack = 4)] public struct '+basename+'_Packed\n{')
						outputstructfields(struct, data)
						print('\tpublic '+basename+'_Packed('+basename+' unpacked)');
						print('\t{')
						for enumvalue in struct['fields']:
							fieldtype = enumvalue['fieldtype']
							otype = converttype(fieldtype)
							if fieldtype[-1] == ']':
								dims = map(int, fieldtype[fieldtype.find('[')+1:-1].split(']['))
								size = reduce(operator.mul, dims, 1)
								utype = unmanagedtype(otype)
								for i in range(size):
									print('\t\tthis.'+enumvalue['fieldname']+str(i)+' = unpacked.'+enumvalue['fieldname']+str(i)+';')

							else:
								print('\t\tthis.'+enumvalue['fieldname']+' = unpacked.'+enumvalue['fieldname']+';')
						print('\t}')

						print('\tpublic void Unpack(ref '+basename+' unpacked)');
						print('\t{')
						for enumvalue in struct['fields']:
							fieldtype = enumvalue['fieldtype']
							otype = converttype(fieldtype)
							if fieldtype[-1] == ']':
								dims = map(int, fieldtype[fieldtype.find('[')+1:-1].split(']['))
								size = reduce(operator.mul, dims, 1)
								utype = unmanagedtype(otype)
								for i in range(size):
									print('\t\tunpacked.'+enumvalue['fieldname']+str(i)+' = this.'+enumvalue['fieldname']+str(i)+';')

							else:
								print('\t\tunpacked.'+enumvalue['fieldname']+' = this.'+enumvalue['fieldname']+';')
						print('\t}')
						print('}')


def outputinterfaces(namespace, data):
	lastclass = ''
	lastmethod = ''
	for method in data['methods']:
		if (len(method) > 0):
			returntype = converttype(method['returntype'])
			if(method['returntype'][len(method['returntype']) - 1] == '*'):
				# Native methods which return pointers are cast to IntPtr
				returntype = 'IntPtr'

			methodname = method['methodname']
			if(methodname == lastmethod):
				methodname = methodname + repr(count)
				count = count + 1
			else:
				count = 0
			lastmethod = method['methodname']

			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				if(lastclass != ''):
					print("\t}\n");

				print("\t[StructLayout(LayoutKind.Sequential)]")
				print("\tpublic struct " + classname + "\n\t{")
				lastclass = classname


			paramlist = []
			if('params' in method):
				for param in method['params']:
					paramtype = converttype(param['paramtype'])
					if(param['paramtype'][len(param['paramtype']) - 1] == '*' and param['paramtype'][len(param['paramtype']) - 2] == '*'):
						paramlist.append('ref ' + paramtype + ' ' + param['paramname'])
					elif(param['paramtype'][len(param['paramtype']) - 1] == '*'):
						if('out_array_count' in param) or ('array_call' in param) or ('array_count' in param) or ('out_array_call' in param):
							paramlist.append('[In, Out] ' + paramtype + '[] ' + param['paramname'])
						elif('out_string_count' in param):
							paramlist.append('System.Text.StringBuilder ' + param['paramname'])
						elif('out_struct' in param):
							paramlist.append('ref '+ paramtype +' ' + param['paramname'])
						elif('out_string' in param):
							paramlist.append('System.Text.StringBuilder ' + param['paramname'])
#						elif(structlist.has_key(paramtype)):
#							paramlist.append(paramtype+' ' + param['paramname'])
#						elif(paramtype[0:1] == 'I' and paramtype != 'IntPtr'):
#							paramlist.append('IntPtr ' + param['paramname'])
						elif (paramtype == 'uint' or paramtype == 'int' or paramtype == 'char' or paramtype == 'bool'):
							# Output params for ints,uints,char
							paramlist.append('ref ' + paramtype + ' ' + param['paramname'])
						elif (paramtype == 'string' and param['paramtype'] == 'const char *'):
							# Managed strings on Windows need to be converted to Utf8 native ptrs
							paramlist.append('IntPtr ' + param['paramname'])
						elif (paramtype == 'string' or paramtype == 'IntPtr'):
							# Strings and IntPtrs are just straight
							paramlist.append(paramtype + ' ' + param['paramname'])
						else:
#							paramlist.append('ref ' + converttype(paramtype) + ' ' + param['paramname'])
							paramlist.append('ref ' + paramtype + ' ' + param['paramname'])
					else:
						paramlist.append(paramtype + ' ' + param['paramname'])
					if(paramtype == 'bool'):
						paramlist[-1] = '[MarshalAs(UnmanagedType.I1)] ' + paramlist[-1];

			print("\t\t[UnmanagedFunctionPointer(CallingConvention.StdCall)]")
			if(returntype == 'bool'):
				print("\t\t[return: MarshalAs(UnmanagedType.I1)]")
			print("\t\tinternal delegate "+returntype+" _"+methodname+"("+", ".join(paramlist)+");")
			print("\t\t[MarshalAs(UnmanagedType.FunctionPtr)]")
			print("\t\tinternal _"+methodname+" "+methodname+";\n")
	print("\t}\n\n");


def outputfntables(namespace, data):
	lastclass = ''
	lastmethod = ''
	for method in data['methods']:
		if (len(method) > 0):
			returntype = method['returntype']
			if(returntype == 'class CSteamID'):
				returntype = 'uint64'
			methodname = method['methodname']
			if(methodname == lastmethod):
				methodname = methodname + repr(count)
				count = count + 1
			else:
				count = 0
			lastmethod = method['methodname']

			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				if(lastclass != ''):
					print("};\n");

				fntablename = method['classname'].replace('vr::', 'VR::').replace('::', '_') + '_FnTable'
				print("struct " + fntablename + "\n{")
				lastclass = classname

			sys.stdout.write ('\t'+ ctype(returntype) + ' (OPENVR_FNTABLE_CALLTYPE *' + methodname + ')')

			paramlist = []
			if('params' in method):
				for param in method['params']:
					paramlist.append(ctype(param['paramtype']) + ' ' + param['paramname'])

			print('('+", ".join(paramlist)+");")

	print("};\n");

def outputfntabledecls(namespace, data):
	lastclass = ''
	lastmethod = ''
	fntablename = ''
	for method in data['methods']:
		if (len(method) > 0):
			returntype = method['returntype']
			if(returntype == 'class CSteamID'):
				returntype = 'uint64'
			methodname = method['methodname']
			if(methodname == lastmethod):
				methodname = methodname + repr(count)
				count = count + 1
			else:
				count = 0
			lastmethod = method['methodname']

			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				if(lastclass != ''):
					print("};\n\n");

				fntablename = method['classname'].replace('vr::', 'VR::').replace('::', '_') + '_FnTable'
				print("static " + fntablename + " g_" + fntablename + " =\n{")
				lastclass = classname

			print('\t&' + fntablename + '_' + methodname + ',')

	print("};\n\n");

def outputfntableinit(namespace, data):
	print("extern void *FindInterface( const char *pchInterfaceName );\n")
	print("void InitializeInterfaceFnTables()\n{")
	lastclass = ''
	for method in data['methods']:
		if (len(method) > 0):
			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				instancename = 'g_p' + classname
				print("\t" + instancename + " = ( " + namespace + "::" + classname + " * )FindInterface( " + namespace + "::" + classname + "_Version );")
				lastclass = classname
	print("}\n")

def outputfntableaccess(namespace, data):
	lastclass = ''
	for method in data['methods']:
		if (len(method) > 0):
			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				fntablename = method['classname'].replace('vr::', 'VR::').replace('::', '_') + '_FnTable'
				print('FnTableRegistration autoreg_'+fntablename+'( '+classname+'_Version, &g_'+fntablename+' );')
				lastclass = classname

def outputfntablefuncs(namespace, data):
	lastclass = ''
	lastmethod = ''
	instancename = ''
	for method in data['methods']:
		if (len(method) > 0):
			returntype = method['returntype']
			if(returntype == 'class CSteamID'):
				returntype = 'uint64'
			methodname = method['methodname']
			if(methodname == lastmethod):
				methodname = methodname + repr(count)
				count = count + 1
			else:
				count = 0
			lastmethod = method['methodname']

			classname = method['classname']

			if(namespace != getnamespace(classname)):
				continue

			classname = getclasswithoutnamespace(classname)
			if(classname != lastclass):
				if(lastclass != ''):
					print("\n\n");

				instancename = 'g_p' + classname
				print("/** " + classname + " FnTable functions */\n")
				print("static " + namespace + "::" + classname + " *" + instancename + " = nullptr;\n")
				lastclass = classname

			creturntype = ctype(returntype)
			sys.stdout.write ('static '+ creturntype + ' OPENVR_FNTABLE_CALLTYPE '+method['classname'].replace('vr::', 'VR::').replace('::', '_') + '_FnTable_' + methodname)
			paramlist = []
			if('params' in method):
				for param in method['params']:
					paramlist.append(ctype(param['paramtype'])+' '+param['paramname'])

			print('('+", ".join(paramlist)+")\n{")

			paramlist = []
			if('params' in method):
				for param in method['params']:
					paramtype = param['paramtype']
					cparamtype = ctype(paramtype)
					if paramtype != cparamtype:
						if paramtype.startswith('struct'):
							paramlist.append('*('+paramtype+'*)&'+param['paramname'])
						else:
							paramlist.append('('+paramtype+')'+param['paramname'])
					else:
						paramlist.append(param['paramname'])

			# if the method is a destructor
			if(method['methodname'][:8] == 'Destruct'):
				sys.stdout.write ('delete instance;')
			# void returntype does not return
			elif (returntype == 'void'):
				sys.stdout.write (instancename+'->'+method['methodname']+'(')
				if('params' in method):
					sys.stdout.write(", ".join(paramlist))
				sys.stdout.write (');')
			elif (creturntype.startswith('struct') and creturntype != returntype):
				sys.stdout.write (returntype+' result = '+instancename+'->'+method['methodname']+'(')
				if('params' in method):
					sys.stdout.write(", ".join(paramlist))
				sys.stdout.write (');\n')
				sys.stdout.write ('return *('+creturntype+'*)&result;')
			else:
				if creturntype != returntype:
					sys.stdout.write ('return ('+creturntype+')'+instancename+'->'+method['methodname']+'(')
				else:
					sys.stdout.write ('return '+instancename+'->'+method['methodname']+'(')
				if('params' in method):
					sys.stdout.write(", ".join(paramlist))
				sys.stdout.write (');')
			sys.stdout.write ('\n}\n');


###############################
# OUTPUT CLASSES
###############################

def isparamptr(paramname, paramlist):
	for param in paramlist:
		if(param['paramname'] == paramname):
			return param['paramtype'][len(param['paramtype']) - 1] == '*'
	

def outputclasses(namespace, data):

	print("""public class Utils
	{
		public static IntPtr ToUtf8(string managedString)
		{
			if (managedString == null)
			{
				return IntPtr.Zero;
			}

			int size = System.Text.Encoding.UTF8.GetByteCount(managedString) + 1;
			if (buffer.Length < size) buffer = new byte[size];
			int written = System.Text.Encoding.UTF8.GetBytes(managedString, 0, managedString.Length, buffer, 0);
			buffer[written] = 0x00; // null terminate
			IntPtr nativeUtf8 = Marshal.AllocHGlobal(written+1);
			Marshal.Copy(buffer, 0, nativeUtf8, written+1);
			return nativeUtf8;
		}
		private static byte[] buffer = new byte[1024];
	}
	""")

	# the following methods take a mispacked VRControllerState_t on Linux and OSX so we generate
	# a special hacky method to account for that on those platforms.
	controller_packed_methods = ['GetControllerState', 'GetControllerStateWithPose', 'GetComponentState']
	event_packed_methods      = ['PollNextEvent', 'PollNextEventWithPost', 'PollNextOverlayEvent']
	if(namespace == ''):
		namespaceextra = ''
	elif(namespace == 'vr'):
		namespaceextra = 'VR_'
	else:
		namespaceextra = namespace+'_'
	lastclass = ''
	lastmethod = ''
	for method in data['methods']:
		if (len(method) > 0):
			returntype = converttype(method['returntype'])

			interfacename = method['classname']

			if(namespace != getnamespace(interfacename)):
				continue

			interfacename = getclasswithoutnamespace(interfacename)
			methodname = method['methodname']
			if(methodname == lastmethod):
				methodname = methodname + repr(count)
				count = count + 1
			else:
				count = 0
			lastmethod = method['methodname']

			if(interfacename != lastclass):
				if(lastclass != ''):
					print("}\n\n");
				classname = 'C' + interfacename[1:]
				classshort = interfacename[1:]
				print("public class " + classname+"\n{")
				print(interfacename+" FnTable;")
				print("internal " + classname + "(IntPtr pInterface)")
				print("{")
				print("\tFnTable = ("+interfacename+")Marshal.PtrToStructure(pInterface, typeof("+interfacename+"));")
				print("}")

				lastclass = interfacename

			paramlist = []
			# Native params are always ref and include count params
			nativeparamlist = []
			skipparam = ''
			outarray = 0
			outstruct = 0
			twocalls = 0
			skipref = 0
			arraytype = ''
			arrayname = ''
			arrayreplace = ''
			skipcount = ''
			skiptype = ''
			paramtypelist = []
			if('params' in method):
				for param in method['params']:
					paramtype = converttype(param['paramtype'])
					if(param['paramname'] != skipparam):
						if('out_array_count' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('out ' + paramtype + ' [] ' + param['paramname'])
							nativeparamlist.append(param['paramname'])
							skipparam = param['out_array_count']
							arraytype = paramtype
							arrayname = param['paramname']
							arrayreplace = 'null'
							twocalls = 1
							outarray = 1
							skipref = 1
						elif('out_string_count' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('out string ' + param['paramname'])
							nativeparamlist.append('pStrBuffer')
							skipparam = param['out_string_count']
							arraytype = paramtype
							arrayname = 'pStrBuffer'
							arrayreplace = 'null'
							twocalls = 1
							skipref = 1
						elif('out_array_call' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('out ' + paramtype + ' [] ' + param['paramname'])
							nativeparamlist.append(param['paramname'])
							skipparam = param['out_array_call'].split(',')[0]
							arraytype = paramtype
							arrayname = param['paramname']
							arrayreplace = 'null'
							outarray = 1
							skipref = 0
						elif('out_string_count' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('out ' + paramtype + ' ' + param['paramname'])
							nativeparamlist.append('ref ' + param['paramname'])
							skipparam = param['out_string_count']
						elif('out_struct' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('out ' + paramtype + ' ' + param['paramname'])
							nativeparamlist.append('ref ' + param['paramname'])
						elif('array_count' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append(paramtype + ' [] ' + param['paramname'])
							nativeparamlist.append(param['paramname'])
							skipparam = param['array_count']
							skipcount = param['paramname']
#						elif(paramtype[0:1] == 'I' and paramtype != 'IntPtr' and not enumlist.has_key(paramtype)):
#							#print("//" + paramtype)
#							paramlist.append(param['paramname'])
#							paramtypelist.append(paramtype + ' ' + param['paramname'])
#							#nativeparamlist.append('ptr')
#							nativeparamlist.append(param['paramname'] + '.GetIntPtr()')
#						elif (structlist.has_key(paramtype)):
#							paramlist.append(' ' + param['paramname'])
#							paramtypelist.append(' ' + paramtype + ' ' + param['paramname'])
#							nativeparamlist.append(' ' + param['paramname'])
						elif (param['paramtype'][len(param['paramtype']) - 1] == '*' and param['paramtype'][len(param['paramtype']) - 2] == '*'):
							paramlist.append('ref ' + param['paramname'])
							paramtypelist.append('ref ' + paramtype + ' ' + param['paramname'])
							nativeparamlist.append('ref ' + param['paramname'])
							if(paramtype != 'uint' and paramtype != 'int' and paramtype != 'char' and paramtype != 'bool'):
								outstruct = 1
								arraytype = paramtype
								arrayname = param['paramname']
								arrayreplace = 'null'
						elif (param['paramtype'][len(param['paramtype']) - 1] == '*' and paramtype != 'string' and paramtype != 'IntPtr'):
							paramlist.append('ref ' + param['paramname'])
							paramtypelist.append('ref ' + paramtype + ' ' + param['paramname'])
							nativeparamlist.append('ref ' + param['paramname'])
							if(paramtype != 'uint' and paramtype != 'int' and paramtype != 'char' and paramtype != 'bool'):
								outstruct = 1
								arraytype = paramtype
								arrayname = param['paramname']
								arrayreplace = 'null'
						elif ('out_string' in param):
							paramlist.append(param['paramname'])
							paramtypelist.append('System.Text.StringBuilder ' + param['paramname'])
							nativeparamlist.append(param['paramname'])
						elif (paramtype == 'string' and param['paramtype'] == 'const char *'):
							paramlist.append(param['paramname'])
							paramtypelist.append(paramtype + ' ' + param['paramname'])
							nativeparamlist.append(param['paramname']+'Utf8')
						else:
							paramlist.append(param['paramname'])
							paramtypelist.append(paramtype + ' ' + param['paramname'])
							nativeparamlist.append(param['paramname'])
					else:
						skiptype = paramtype
						if(skipref):
							nativeparamlist.append('ref ' + param['paramname'])
						elif(skipcount != ''):
							nativeparamlist.append("(" + paramtype +") " + skipcount + ".Length" )
						else:
							nativeparamlist.append(param['paramname'])
						skipparam = 0
			if methodname in controller_packed_methods or methodname in event_packed_methods:
				if methodname in controller_packed_methods:
					packedlist=[w.replace('VRControllerState_t','VRControllerState_t_Packed').replace('string', 'IntPtr') for w in paramtypelist]
				else:
					packedlist=[w.replace('VREvent_t','VREvent_t_Packed') for w in paramtypelist]
				print('// This is a terrible hack to workaround the fact that VRControllerState_t and VREvent_t were ')
				print('// originally mis-compiled with the wrong packing for Linux and OSX.')
				print('[UnmanagedFunctionPointer(CallingConvention.StdCall)]')				
				print('internal delegate '+returntype+' _'+methodname+'Packed('+','.join(packedlist)+');')
				print('[StructLayout(LayoutKind.Explicit)]')
				print('struct '+methodname+'Union\n{')
				print('\t[FieldOffset(0)]\n\tpublic '+interfacename+'._'+methodname+' p'+methodname+';')
				print('\t[FieldOffset(0)]\n\tpublic _'+methodname+'Packed p'+methodname+'Packed;\n}')

			sys.stdout.write ('public '+returntype+' '+methodname+'('+','.join(paramtypelist)+')\n{\n')
	# PROCESS THE STUFF BEFORE THE API CALLS
			if('params' in method):
				for param in method['params']:
					if(skipparam != param['paramname']):
						paramtype = converttype(param['paramtype'])
						if('out_array_count' in param and skipparam != param['out_array_count']):
							skipparam = param['out_array_count']
							sys.stdout.write( '\t'+skiptype+' '+skipparam+' = 0;\n' )
						elif('out_string_count' in param and skipparam != param['out_string_count']):
							skipparam = param['out_string_count']
							#if(isparamptr(skipparam, param)):
							sys.stdout.write( '\t'+skiptype+' '+skipparam+' = 0;\n' )
						elif('out_array_call' in param):
							vals = param['out_array_call'].split(',')
							sys.stdout.write( '\tint '+vals[0]+' = '+vals[1]+' ('+vals[2]+');\n' )
							sys.stdout.write( '\t'+param['paramname']+' = new '+paramtype+'['+vals[0]+'];\n' )
						elif('out_struct' in param):
							sys.stdout.write( '\t'+param['paramname']+' = new '+paramtype+'();\n' )
						elif (param['paramtype'][len(param['paramtype']) - 1] == '*' and (paramtype == 'int' or paramtype == 'uint' or paramtype == 'float' or paramtype == 'long' or paramtype == 'ulong' or paramtype == 'double') and not 'array_count' in param):
							# Out parameters need to be initialized to zero
							sys.stdout.write( '\t'+param['paramname']+' = 0;\n' )
						elif (param['paramtype'][len(param['paramtype']) - 2:] == '**' and (paramtype == 'string')):
							# Out parameters need to be initialized to zero
							sys.stdout.write( '\t'+param['paramname']+' = "";\n' )
						elif (param['paramtype'][len(param['paramtype']) - 1] == '*' and (paramtype == 'char')):
							# Out parameters need to be initialized to zero
							sys.stdout.write( '\t'+param['paramname']+' = (char) 0;\n' )
						elif (param['paramtype'][len(param['paramtype']) - 1] == '*' and (paramtype == 'bool')):
							# Out parameters need to be initialized to zero
							sys.stdout.write( '\t'+param['paramname']+' = false;\n' )
						elif (paramtype == 'string' and param['paramtype'] == 'const char *'):
							# In strings need to be converted to UTF-8
							sys.stdout.write( '\tIntPtr '+param['paramname']+'Utf8 = Utils.ToUtf8('+param['paramname']+');\n')
						#elif(paramtype[0:1] == 'I' and paramtype != 'IntPtr' and not enumlist.has_key(paramtype)):
							# Returning an interface ptr needs ptr defined
							# sys.stdout.write( '\tIntPtr ptr = IntPtr.Zero;\n' )
					

	# PROCESS THE FIRST OPTIONAL API CALL
			if(twocalls):
				paramsnull = ','.join(map((lambda x: (arrayreplace if (x==arrayname) else x)),nativeparamlist))
				sys.stdout.write ('\t'+('' if (returntype=='void') else (returntype+' result = '))+'FnTable.'+methodname+'('+paramsnull+');\n')

	# PROCESS IN BETWEEN THE API CALLS
	# Allocate the array or buffer
			if('params' in method):
				for param in method['params']:
					if('out_array_count' in param):
						sys.stdout.write( '\t'+param['paramname']+'= new ' + converttype(param['paramtype']) + '['+param['out_array_count']+'];\n' )
					if('out_string_count' in param):
						sys.stdout.write( '\tSystem.Text.StringBuilder pStrBuffer = new System.Text.StringBuilder((int)'+skipparam+');\n' )


	# PROCESS THE FINAL API CALL
			if methodname in controller_packed_methods:
				packedparamlist = [w.replace('pControllerState','state_packed')
								    .replace('unControllerStateSize','(uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VRControllerState_t_Packed))')
									for w in nativeparamlist]

				print('#if !UNITY_METRO')
				print('\tif ((System.Environment.OSVersion.Platform == System.PlatformID.MacOSX) ||')
				print('\t    (System.Environment.OSVersion.Platform == System.PlatformID.Unix))')
				print('\t{')
				print('\t\t'+methodname+'Union u;')
				print('\t\tVRControllerState_t_Packed state_packed = new VRControllerState_t_Packed(pControllerState);')
				print('\t\tu.p'+methodname+'Packed = null;')
				print('\t\tu.p'+methodname+' = FnTable.'+methodname+';')
				print('\t\t'+returntype+' packed_result = u.p'+methodname+'Packed('+','.join(packedparamlist)+');\n')
				print('\t\tstate_packed.Unpack(ref pControllerState);')
				print('\t\treturn packed_result;')
				print('\t}')
				print('#endif')
			if methodname in event_packed_methods:
				packedparamlist = [w.replace('pEvent','event_packed')
									.replace('uncbVREvent','(uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VREvent_t_Packed))')
									for w in nativeparamlist]

				print('#if !UNITY_METRO')
				print('\tif ((System.Environment.OSVersion.Platform == System.PlatformID.MacOSX) ||')
				print('\t    (System.Environment.OSVersion.Platform == System.PlatformID.Unix))')
				print('\t{')
				print('\t\t'+methodname+'Union u;')
				print('\t\tVREvent_t_Packed event_packed = new VREvent_t_Packed();')
				print('\t\tu.p'+methodname+'Packed = null;')
				print('\t\tu.p'+methodname+' = FnTable.'+methodname+';')
				print('\t\t'+returntype+' packed_result = u.p'+methodname+'Packed('+','.join(packedparamlist)+');\n')
				print('\t\tevent_packed.Unpack(ref pEvent);')
				print('\t\treturn packed_result;')
				print('\t}')
				print('#endif')


			savereturn = returntype
			if(method['returntype'][len(method['returntype']) - 1] == '*'):
				returntype = 'IntPtr'
			if(returntype == 'void'):
				if(len(nativeparamlist) > 0):
					sys.stdout.write ('\tFnTable.'+methodname+'('+','.join(nativeparamlist)+');\n')
				else:
					sys.stdout.write ('\tFnTable.'+methodname+'();\n')
			else:
				if(len(nativeparamlist) > 0):
					#if(returntype[0:1] == 'I' and returntype != 'IntPtr'):
					#	sys.stdout.write ('\tC'+returntype[1:]+' result = new C'+returntype[1:]+'(FnTable.'+methodname+'('+','.join(nativeparamlist)+'));\n')
					if(returntype == 'string'):
						sys.stdout.write ('\tstring result = Marshal.PtrToStringAuto(FnTable.'+methodname+'('+','.join(nativeparamlist)+'));\n')
					else:
						sys.stdout.write ('\t'+('' if twocalls else returntype)+' result = FnTable.'+methodname+'('+','.join(nativeparamlist)+');\n')
				else:
					if(returntype == 'string'):
						sys.stdout.write ('\tstring result = Marshal.PtrToStringAuto(FnTable.'+methodname+'());\n')
					else:
						sys.stdout.write ('\t'+('' if twocalls else returntype)+' result = FnTable.'+methodname+'();\n')


	# PROCESS AFTER THE API CALL
			if('params' in method):
				for param in method['params']:
					paramtype = converttype(param['paramtype'])
					if('out_string_count' in param):
						sys.stdout.write( '\t'+param['paramname']+'  = pStrBuffer.ToString();\n' )
					elif (paramtype == 'string' and param['paramtype'] == 'const char *'):
						sys.stdout.write( '\tMarshal.FreeHGlobal('+param['paramname']+'Utf8);\n')
					#elif(paramtype[0:1] == 'I' and paramtype != 'IntPtr' and not enumlist.has_key(paramtype)):
						# Interface. Need to construct a class wrapper
						#sys.stdout.write( '\t'+param['paramname']+'= new C' + paramtype[1:] + '(ptr);\n' )
		
			if(method['returntype'][len(method['returntype']) - 1] == '*'):
				if(savereturn == 'string'):
					sys.stdout.write ('\treturn Marshal.PtrToStringAnsi(result);\n')
				else:
					sys.stdout.write ('\treturn ('+savereturn+') Marshal.PtrToStructure(result, typeof('+savereturn+'));\n')
			elif(returntype != 'void'):
				sys.stdout.write ('\treturn result;\n')
			sys.stdout.write ('}\n')
