import os
import cog
import property_handlers
import jsonpickle

def generate_element(schema_path):
	file_path = schema_path
	schema = ''
	
	with open(file_path, 'r') as file:
		file_content = file.read()
		schema = jsonpickle.decode(file_content)

	cog.outl("/* #region GENERATED SECTION, DO NOT HAND EDIT */")

	property_handlers.generate_init(schema, property_handlers.extract_superclass(schema))
	
	cog.outl("")
	cog.outl("// [ Variable Declarations ]")

	for property in schema['properties']:
		property_handlers.generate_variable_declaration(property, schema['properties'][property])

	cog.outl("")
	cog.outl("// [ Data Getter / Setter ]")

	# generate functions
	for property in schema['properties']:
		property_handlers.generate_getters_setters(property, schema['properties'][property])
	
	# generate getter wrapper
	cog.outl("virtual int(string _key, __out cm2_argument _val, __out int _type) _getproperty")
	cog.outl("{")
	cog.outl("\tswitch(_key)")
	cog.outl("\t{")
	for property in schema['properties']:
		if ('dontDeclare' in schema['properties'][property]):
			should_ignore = schema['properties'][property]['dontDeclare']
			if (should_ignore == True):
				continue
		cog.outl(f"\t\tcase \"{property}\": _get_{property}(_val, _type); return TRUE;")
	cog.outl(f"\t\tdefault: break;")
	cog.outl("\t}")
	if (property_handlers.extract_superclass(schema) != ""):
		cog.outl("\tif (super::_getproperty(_key, _val, _type))") # call super if we have one
		cog.outl("\t\treturn TRUE;")
	cog.outl("\treturn FALSE;")
	cog.outl("};")
	cog.outl("")

	# generate setter wrapper
	cog.outl("virtual int(string _key, string _value) _setproperty")
	cog.outl("{")
	cog.outl("\tswitch(_key)")
	cog.outl("\t{")
	for property in schema['properties']:
		prop_list = schema['properties'][property]
		prop_type = prop_list['type']
		if ('dontDeclare' in prop_list):
			should_ignore = schema['properties'][property]['dontDeclare']
			if (should_ignore == True):
				continue
		cog.outl(f"\t\t// {property} ({prop_type})")
		cog.outl(f"\t\tcase \"{property}\": _set_{property}(_value); return TRUE;")
		if (prop_type == "array"):
			maxItems = property_handlers.extract_max_items(prop_list)
			for i in range(0, maxItems):
				cog.outl(f"\t\tcase \"{property}[{i}]\": _set_{property}_index(_value, {i}); return TRUE;")
			if (maxItems <= 3): # vectors
				list_chars = ['x', 'y', 'z']
				for i in range(0, min(maxItems, 3)):
					cog.outl(f"\t\tcase \"{property}_{list_chars[i]}\": _set_{property}_index(_value, {i}); return TRUE;")

	cog.outl(f"\t\tdefault: break;")
	cog.outl("\t}")
	if (property_handlers.extract_superclass(schema) != ""):
		cog.outl("\tif (super::_setproperty(_key, _value))") # call super if we have one
		cog.outl("\t\treturn TRUE;")
	cog.outl("\treturn FALSE;")
	cog.outl("};")
	cog.outl("")

	# generate preprocess step
	cog.outl("#ifdef CSQC")
	cog.outl("virtual void() _preprocess")
	cog.outl("{")
	if (property_handlers.extract_superclass(schema) != ""):
		cog.outl("\tsuper::_preprocess();") # call super if we have one
	for property in schema['properties']:
		property_handlers.generate_preprocess(property, schema['properties'][property])
	cog.outl("};")
	cog.outl("#endif // CSQC")
	
	# generate commands, if we have any
	if 'commands' in schema:
		for command in schema['commands']:
			classname = property_handlers.extract_classname(schema).lower()
			cog.outl(f'nonvirtual void(uielement_c, cm2_argument*) cmd_{command};')
		
		cog.outl(f"virtual int(cm2_commandcache_t *_cmd, string _cmdstr) _string_to_commandfunc")
		cog.outl("{")
		cog.outl("\tswitch(_cmdstr)")
		cog.outl("\t{")
		for command in schema['commands']:
			cmd_schema = schema['commands'][command]
			cog.outl(f"\t\tcase \"{command}\":")
			cog.outl(f"\t\t\t_cmd->func = cmd_{command};")
			if ('argumentLayout' in cmd_schema):
				i = 0 # argument index
				typedict = {
					'number': 'f',
					'integer': 'i',
					'boolean': 'i',
					'vector': 'v',
					'string': 's'
				}
				accessdict = {
					'number': 'stof',
					'integer': 'stoi',
					'boolean': 'stoi',
					'vector': 'stov',
					'string': ''
				}
				for argtype in cmd_schema['argumentLayout']:
					cog.outl(f"\t\t\t_cmd->arguments[{i}].{typedict[argtype]} = {accessdict[argtype]}(argv({i + 1}));")
					i += 1
			cog.outl("\t\t\treturn TRUE;")
		cog.outl("\t}")
		if (property_handlers.extract_superclass(schema) != ""):
			cog.outl("\tif (super::_string_to_commandfunc(_cmd, _cmdstr))") # call super if we have one
			cog.outl("\t\treturn TRUE;")
		cog.outl("\treturn FALSE;")
		cog.outl("};")

	cog.outl("/* #endregion END GENERATED SECTION */")

def generate_spawner(folder_path):
	print("")
	cog.outl("/* #region GENERATED SECTION, DO NOT HAND EDIT */")
	
	class classType:
		def __init__(self, classname, alias):
			self.classname = classname
			self.alias = alias

	class_list = []
	# crawl our directory and generate a list of schema files (which should map directly to classes)
	folder_path = os.path.normpath(folder_path)
	print(f"searching for files in path \"{folder_path}\"")
	for item in os.listdir(folder_path):
		if os.path.isfile(folder_path + "/" + item) == False:
			continue
		if (item[-12:] == "_schema.json"): # (presumably) valid schema file
			classname = alias = item[:-12]
			print(f'found class {classname}')
			schema = property_handlers.extract_schema(classname)
			if ('alias' in schema):
				alias = schema['alias']
				print(f"\t-> {alias}")
			else:
				print("\tno alias found")
			class_list.append(classType(classname, alias))

	# generate macros
	cog.outl('// [MACROS]')
	cog.outl('#define JSON_HELPER_STRING(node, x) jsonnode x##_node = json_find_object_child(node, #x); string x = __NULL__; if (##x##_node) { x = json_get_string(##x##_node); }')
	cog.outl('')

	# generate our class spawners
	cog.outl('// [SPAWNERS]')
	cog.outl('void _parse_json_field(jsonnode node, int(string key, string value) set_handler)')
	cog.outl('{')
	cog.outl('\tjsonnode child;')
	cog.outl('\tfor(int i = 0; child = json_get_child_at_index(node, i); i++)')
	cog.outl('\t{')
	cog.outl('\t\tstring key = json_get_name(child);')
	cog.outl('\t\tjson_type_e type = json_get_value_type(child);')
	cog.outl('\t\tstring value = __NULL__;')
	cog.outl('\t\tswitch(type)')
	cog.outl('\t\t{')
	cog.outl('\t\t\tcase JSON_TYPE_TRUE: value = "1"; break;')
	cog.outl('\t\t\tcase JSON_TYPE_FALSE: value = "0"; break;')
	cog.outl('\t\t\tcase JSON_TYPE_NUMBER: value = ftos(json_get_float(child)); break;')
	cog.outl('\t\t\tcase JSON_TYPE_ARRAY: ') # sigh, this is a bit of a faff
	cog.outl('\t\t\t\tvalue = ""; // initialize the string')
	cog.outl('\t\t\t\tfor(jsonnode array_node, int j = 0; array_node = json_get_child_at_index(child, j); j++)')
	cog.outl('\t\t\t\t{')
	cog.outl('\t\t\t\t\tif (json_get_value_type(array_node) == JSON_TYPE_OBJECT) // array of objects, probably "elements": []')
	cog.outl('\t\t\t\t\t{')
	cog.outl('\t\t\t\t\t\tvalue = __NULL__;')
	cog.outl('\t\t\t\t\t\tbreak;')
	cog.outl('\t\t\t\t\t}')
	cog.outl('\t\t\t\t\tvalue = strcat(value, ftos(json_get_float(array_node)), " ");')
	cog.outl('\t\t\t\t}')
	cog.outl('\t\t\t\tbreak;')
	cog.outl('\t\t\tcase JSON_TYPE_STRING:')
	cog.outl('\t\t\tdefault: value = json_get_string(child); break;')
	cog.outl('\t\t}')
	cog.outl('\t\tif (value)')
	cog.outl('\t\t\tset_handler(key, value);')
	cog.outl('\t}')
	cog.outl('}')

	for type in class_list:
		cog.outl(f'uielement_c _spawn_json_{type.classname}_c(jsonnode node, uielement_c parent)')
		cog.outl('{')
		cog.outl(f'\t{type.classname}_c obj = spawn({type.classname}_c, flags: UIFLAG::IGNORECONSTRUCTOR); // initialize our base entity')
		cog.outl('\tself = obj; // make sure "this" is proper')
		cog.outl(f'\t_parse_json_field(node, {type.classname}_c::_setproperty);')
		cog.outl('\tobj.owner = parent;')
		cog.outl('\tobj._init(); // constructor')
		cog.outl('\treturn obj;')
		cog.outl('}')

	cog.outl('')
	
	# generate the spawn handler
	print('spawning spawn handler')
	cog.outl('// [SPAWN HANDLER]')
	cog.outl(f'uielement_c CM2_SpawnFromJsonNode(jsonnode node, uielement_c parent)')
	cog.outl('{')
	cog.outl('\tuielement_c obj = 0;')
	cog.outl('\tJSON_HELPER_STRING(node, type)')
	cog.outl('\tswitch(type)')
	cog.outl('\t{')
	for type in class_list:
		cog.outl(f'\t\tcase "{type.alias}": obj = _spawn_json_{type.classname}_c(node, parent); break;')
	cog.outl('\t}')
	cog.outl('\t')
	cog.outl('\tif (obj)')
	cog.outl('\t{')
	cog.outl('\t\tif (obj.name)')
	cog.outl('\t\t{')
	cog.outl('\t\t\tif (obj.isGlobal || !cm2_localnames) // if we don\'t currently have a namespace, use global as fallback')
	cog.outl('\t\t\t{')
	cog.outl('\t\t\t\thash_add(cm2_globalnames, obj.name, obj, HASH_REPLACE|EV_ENTITY);')
	cog.outl('\t\t\t\tobj.cm2_namespace = cm2_globalnames;')
	cog.outl('\t\t\t}')
	cog.outl('\t\t\tif (cm2_localnames)')
	cog.outl('\t\t\t{')
	cog.outl('\t\t\t\thash_add(cm2_localnames, obj.name, obj, HASH_REPLACE|EV_ENTITY);')
	cog.outl('\t\t\t\tobj.cm2_namespace = cm2_localnames;')
	cog.outl('\t\t\t}')
	cog.outl('\t\t}')
	cog.outl('\t\t')
	cog.outl('\t\tjsonnode elements = json_find_object_child(node, "elements");')
	cog.outl('\t\tif (elements && json_get_value_type(elements) == JSON_TYPE_ARRAY)')
	cog.outl('\t\t{')
	cog.outl('\t\t\tfor(jsonnode array_node, int j = 0; array_node = json_get_child_at_index(elements, j); j++)')
	cog.outl('\t\t\t{')
	cog.outl('\t\t\t\tif (json_get_value_type(array_node) != JSON_TYPE_OBJECT)')
	cog.outl('\t\t\t\t\tcontinue;')
	cog.outl('\t\t\t\tCM2_SpawnFromJsonNode(array_node, obj);')
	cog.outl('\t\t\t}')
	cog.outl('\t\t}')
	cog.outl('\t}')
	cog.outl('\t')
	cog.outl('\treturn obj;')
	cog.outl('}')
	cog.outl('#undef JSON_HELPER_STRING')
	cog.outl("/* #endregion END GENERATED SECTION */")
