import cog
import jsonpickle

# [ GENERATE SECTION ]
def generate_variable_declaration(name, property):
	# if we don't want this to be declared in QuakeC,
	should_ignore = False
	if ('dontDeclare' in property):
		should_ignore = property['dontDeclare']
	if ('flagfield' in property): # QuakeC handles this via a bitflag, so it isn't a direct variable (but getters/setters are still valid)
		if ('bitflag' not in property):
			TypeError(f'IN DECLARATION FOR {name}: "flagfield" is present, but "bitflag" is not')
		# argh, special case for these since we still want the stat
		cog.outl(f'cm2_stat_cache_t* {name}_stat; // string pointer')
		cog.outl(f'int {name}_stat_ver; // string pointer version')
		should_ignore = True

	if (should_ignore == True):
			return

	prop_type = property['type']
	if(prop_type == "string"):
		handle_string_variable_declaration(name, property)
	elif(prop_type == "number"):
		handle_number_variable_declaration(name, property)
	elif(prop_type == "boolean"):
		handle_bool_variable_declaration(name, property)
	elif(prop_type == "array"):
		handle_array_variable_declaration(name, property)
	elif(prop_type == "font"):
		handle_font_variable_declaration(name, property)
	elif(prop_type == "event"):
		handle_event_variable_declaration(name, property)


def generate_getters_setters(name, property):
	# if we don't want this to be declared in QuakeC,
	if ('dontDeclare' in property):
		if (property['dontDeclare'] == True):
			return

	prop_type = property['type']
	if(prop_type == "string"):
		handle_string_getset(name, property)
	elif(prop_type == "number"):
		handle_number_getset(name, property)
	elif(prop_type == "boolean"):
		handle_boolean_getset(name, property)
	elif(prop_type == "array"):
		handle_array_getset(name, property)
	elif(prop_type == "font"):
		handle_font_getset(name, property)
	elif(prop_type == "event"):
		handle_event_getset(name, property)
	
def generate_preprocess(name, property):
	# if we don't want this to be declared in QuakeC,
	if ('dontDeclare' in property):
		if (property['dontDeclare'] == True):
			return
	
	prop_type = property['type']
	if(prop_type == "string"):
		handle_string_preprocess(name, property)
	elif(prop_type == "number"):
		handle_number_preprocess(name, property)
	elif(prop_type == "boolean"):
		handle_boolean_preprocess(name, property)
	elif(prop_type == "array"):
		handle_array_preprocess(name, property)
	elif(prop_type == "font"):
		handle_font_preprocess(name, property)


def generate_init(schema, superclass):
	
	classname = extract_classname(schema)
	properties = extract_schema_property_names_expand_superclass(classname)

	''' ignore this for now, we'll get back to it
	cog.out(f'nonvirtual void(void) {classname.lower()}_c ') #constructor
	cog.outl("{")
	cog.outl("}")
	'''
# [ END GENERATE SECTION ]

# [ VARIABLE DECLARATION HANDLERS ]
def handle_string_variable_declaration(name, property):
	cog.outl(f'string {name}; // string')
	cog.outl('#ifdef CSQC')
	cog.outl(f'cm2_stat_string_t* {name}_strlist; // pointer')
	cog.outl('#endif // CSQC')
	
def handle_number_variable_declaration(name, property):
	is_int = False
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			is_int = True
	if is_int:
		cog.outl(f'int {name}; // number (integer)')
	else:
		cog.outl(f'float {name}; // number (float)')
	cog.outl('#ifdef CSQC')
	cog.outl(f'cm2_stat_cache_t* {name}_stat; // string pointer')
	cog.outl(f'int {name}_stat_ver; // string pointer version')
	cog.outl('#endif // CSQC')

def handle_bool_variable_declaration(name, property):
	cog.outl(f'int {name}; // bool')
	cog.outl('#ifdef CSQC')
	cog.outl(f'cm2_stat_cache_t* {name}_stat; // string pointer')
	cog.outl(f'int {name}_stat_ver; // string pointer version')
	cog.outl('#endif // CSQC')

def handle_array_variable_declaration(name, property):
	for array_type in property['items']:
		if (property['items'][array_type] == 'number'):
			maxItm = extract_max_items(property)
			minItm = extract_min_items(property)
			if (maxItm > 3):
				cog.outl(f'float {name}[{maxItm}]; // array (true array)')
			else:
				cog.outl(f'vector {name}; // array (vector)')
				cog.outl('#ifdef CSQC')
				cog.outl(f'cm2_stat_cache_t* {name}_stat; // string pointer')
				cog.outl(f'int {name}_stat_ver; // string pointer version')
				cog.outl('#endif // CSQC')

def handle_font_variable_declaration(name, property):
	cog.outl(f'int {name}; // font')
	cog.outl(f'string {name}_str; // font')

def handle_event_variable_declaration(name, property):
	cog.outl(f'string {name}; // event')
	cog.outl(f'string {name}_old; // event')
	cog.outl(f'cm2_commandcache_t *{name}_cmd; // event')
# [ END VARIABLE DECLARATION SECTION ]

# [ GET|SET HANDLERS ]
def handle_bitflag_getset(name, property):
	flagfield = property['flagfield']
	bitflag = property['bitflag']
	inverted = False
	if ('inverted' in property):
		inverted = property['inverted']

	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // bool (bitflag)')
	cog.outl('{')
	cog.outl('\tint istrue = stoi(val);')
	if inverted:
		cog.outl(f'\tif (!istrue)')
	else:
		cog.outl(f'\tif (istrue)')
	cog.outl(f'\t\tthis.{flagfield} |= {bitflag};')
	cog.outl('\telse')
	cog.outl(f'\t\tthis.{flagfield} &= ~{bitflag};')
	cog.outl('#ifdef CSQC')
	cog.outl('\tif (usestats)')
	cog.outl('\t{')
	cog.outl(f'\t\tthis.{name}_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);')
	cog.outl(f'\t\tthis.{name}_stat_ver = -1;')
	cog.outl('\t}')
	cog.outl('#endif')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // bool (bitflag)')
	cog.outl('{')
	if inverted:
		cog.outl(f'\tval.i = (this.{flagfield} & {bitflag}) ? 0 : 1;')
	else:
		cog.outl(f'\tval.i = (this.{flagfield} & {bitflag}) ? 1 : 0;')
	cog.outl('\ttype = EV_INTEGER;')
	cog.outl('};')
	cog.outl('')

def handle_boolean_getset(name, property):
	if ('flagfield' in property): # hack to allow bitflags to hijack booleans
		handle_bitflag_getset(name, property)
		return

	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // bool')
	cog.outl('{')
	cog.outl(f'\tthis.{name} = stoi(val);')
	cog.outl('\t')
	cog.outl('#ifdef CSQC')
	cog.outl('\tif (usestats)')
	cog.outl('\t{')
	cog.outl(f'\t\tthis.{name}_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);')
	cog.outl(f'\t\tthis.{name}_stat_ver = -1;')
	cog.outl('\t}')
	cog.outl('#endif // CSQC')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // bool')
	cog.outl('{')
	cog.outl(f'\tval.i = this.{name};')
	cog.outl('\ttype = EV_INTEGER;')
	cog.outl('};')
	cog.outl('')

def handle_number_getset(name, property):
	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // number')
	cog.outl('{')
	cog.outl(f'\tthis.{name} = stof(val);')
	cog.outl('\t')
	cog.outl('#ifdef CSQC')
	cog.outl('\tif (usestats)')
	cog.outl('\t{')
	cog.outl(f'\t\tthis.{name}_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);')
	cog.outl(f'\t\tthis.{name}_stat_ver = -1;')
	cog.outl('\t}')
	cog.outl('#endif // CSQC')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // number')
	cog.outl('{')
	is_float = True
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			is_float = False
	if (is_float):
		cog.outl(f'\tval.f = this.{name};')
		cog.outl('\ttype = EV_FLOAT;')
	else:
		cog.outl(f'\tval.i = this.{name};')
		cog.outl('\ttype = EV_INTEGER;')
	cog.outl('};')
	cog.outl('')

def handle_string_getset(name, property):
	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // string')
	cog.outl('{')
	cog.outl(f'\tthis.{name} = val;')
	cog.outl('\t')
	cog.outl('#ifdef CSQC')
	cog.outl(f'\tstrstats_clear_buf(this.{name}_strlist);')
	cog.outl(f'\tstrstats_to_buf(val, this.{name}_strlist);')
	cog.outl('#endif // CSQC')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // string')
	cog.outl('{')
	cog.outl(f'\tval.s = this.{name};')
	cog.outl('\ttype = EV_STRING;')
	cog.outl('};')
	cog.outl('')

def handle_array_getset(name, property):
	minItm = -1
	maxItm = -1

	# determine number of elements
	for array_type in property['items']:
		if(property['items'][array_type] == 'number'):
			maxItm = extract_max_items(property)
			minItm = extract_min_items(property)
	if (minItm == 0 and maxItm == 0):
		return # some wacky array... ignore it for this

	# generate our functions
	# set array
	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // array')
	cog.outl('{')
	if (maxItm > 3): # true array
		cog.outl('\tint argc = tokenizebyseparator(val, " ");')
		cog.outl('\tfor(int i = 0; i < argc; i++)')
		cog.outl('\t{')
		cog.outl(f'\t\tthis.{name} = argv(i);')
		cog.outl('\t}')
	else: # vector
		cog.outl(f'\tthis.{name} = stov(val);')
		cog.outl('#ifdef CSQC')
		cog.outl('\tif (usestats)')
		cog.outl('\t{')
		cog.outl(f'\t\tthis.{name}_stat = hash_get(cm2_hudstats_lookup, substring(val, 2, -2), __NULL__);')
		cog.outl(f'\t\tthis.{name}_stat_ver = -1;')
		cog.outl('\t}')
		cog.outl('#endif // CSQC')
	cog.outl('};')

	# set individual indices
	cog.outl(f'nonvirtual void(string val, int i) _set_{name}_index // array (individual index)')
	cog.outl('{')
	cog.outl(f'\tthis.{name}[i] = stof(val);')
	cog.outl('};')

	cog.outl('')
	# get array
	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // array')
	cog.outl('{')
	if (maxItm > 3): # true array
		cog.outl(f'\tval.ptr = &(this.{name});')
		cog.outl('\ttype = EV_POINTER;')
	else:
		cog.outl(f'\tval.v = this.{name};')
		cog.outl('\ttype = EV_VECTOR;')
	cog.outl('};')
	cog.outl('')

def handle_font_getset(name, property):
	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // font')
	cog.outl('{')
	cog.outl(f'\tthis.{name}_str = val;')
	cog.outl(f'\tthis.{name} = hash_get(cm2_localnames, CM2_FONTNAME({name}_str), -1);')
	cog.outl(f'\tif (this.{name} < 0)')
	cog.outl('\t{')
	cog.outl(f'\t\tthis.{name} = hash_get(cm2_localnames, CM2_FONTNAME("default"), 0);')
	cog.outl('\t}')
	cog.outl('};')
	cog.outl('')
	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // font')
	cog.outl('{')
	cog.outl(f'\tval.s = this.{name}_str;')
	cog.outl('\ttype = EV_STRING;')
	cog.outl('};')
	cog.outl('')

def handle_event_getset(name, property):
	cog.outl(f'nonvirtual void(string val, int usestats=1) _set_{name} // event')
	cog.outl('{')
	cog.outl(f'\tthis.{name} = val;')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(__out cm2_argument val, __out int type) _get_{name} // event')
	cog.outl('{')
	cog.outl(f'\tval.s = this.{name};')
	cog.outl('\ttype = EV_STRING;')
	cog.outl('};')
	cog.outl('')

	cog.outl(f'nonvirtual void(uielement_c this) cmd_{name}_cm2 // event')
	cog.outl('{')
	cog.outl(f'\tdprint(__func__, ": ", this.{name}, "\\n");')
	cog.outl(f'\tif (!this.{name})')
	cog.outl('\t\treturn;')
	cog.outl('\t')
	cog.outl(f'\tif (this.{name} != this.{name}_old)')
	cog.outl('\t{')
	cog.outl('\t\tdprint("     building new command queue\\n");')
	cog.outl(f'\t\tCM2_BuildCommandChain(this, this.{name}_cmd, this.{name});')
	cog.outl(f'\t\tthis.{name}_old = this.{name};')
	cog.outl('\t}')
	cog.outl('\t')
	cog.outl(f'\tif (this.{name}_cmd != __NULL__) // execute the string')
	cog.outl('\t{')
	cog.outl('\t\tdprint("     executing command\\n");')
	cog.outl(f'\t\tCM2_ExecuteCommandChain(this.{name}_cmd);')
	cog.outl('\t}')
	cog.outl('};')
	cog.outl('')
# [ END GET|SET SECTION ]
	
# [ PREPROCESS HANDLERS ]
def handle_standard_preprocess(name, property):
	cog.outl(f'\tif (this.{name}_stat)')
	cog.outl('\t{')
	cog.outl(f'\t\tif (this.{name}_stat_ver != this.{name}_stat->version) {{ _set_{name}(this.{name}_stat->value, 0); this.{name}_stat_ver = this.{name}_stat->version; }}')
	cog.outl('\t}')

def handle_bitflag_preprocess(name, property):
	handle_standard_preprocess(name, property)

def handle_boolean_preprocess(name, property):
	if ('flagfield' in property): # hack to allow bitflags to hijack booleans
		handle_bitflag_preprocess(name, property)
		return
	handle_standard_preprocess(name, property)

def handle_number_preprocess(name, property):
	handle_standard_preprocess(name, property)

def handle_string_preprocess(name, property):
	cog.outl(f'\tif (this.{name}_strlist) {{ if (strstats_needchange(this.{name}_strlist)) {{ this.{name} = strstats_buf_to_str(this.{name}_strlist); }} }}')

def handle_array_preprocess(name, property):
	handle_standard_preprocess(name, property)

def handle_font_preprocess(name, property):
	pass
# [ END PREPROCESS SECTION ]

# [ ARRAY UTILITIES ]
def extract_max_items(property):
	if('minItems' in property and property['maxItems']):
		return property['maxItems']

	return -1

def extract_min_items(property):
	if('minItems' in property and property['minItems']):
		return property['minItems']

	return -1
# [ END ARRAY UTILITIES SECTION ]

# [ CLASS UTILITIES ]
def extract_classname(schema):
	return schema['title']

def extract_superclass(schema):
	if "$ref" in schema:
		print("$ref found")
		fullname = schema['$ref']
		split_name = fullname.split(":")
		return split_name.pop() # return the last string in the split 'schema:element' should return 'element'
	return ""

def extract_schema(classname):
	classname = classname.lower()
	file_path = f'../schemas/{classname}_schema.json'
	schema = ''

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

def extract_schema_property_names(classname):
	schema = extract_schema(classname)
	properties = []

	for property in schema['properties']:
		properties.append(property)

	return properties

def extract_schema_property_names_expand_superclass(classname):
	print(f'Extracting properties for {classname}')
	all_properties = []
	target_class = classname

	while len(target_class) > 0:
		schema = extract_schema(target_class)
		new_properties = extract_schema_property_names(target_class)
		new_properties.reverse() # when unreversed later, matches declaration order of parent classes
		all_properties.extend(new_properties)
		target_class = extract_superclass(schema)

	all_properties.reverse()
	return all_properties
# [ END CLASS UTILITIES SECTION ]
