import cog
import jsonpickle

'''
Supported Json Types:
	* String
	* Number
		-> Float
		-> Int
	* Boolean
	* Array : element or float
	* Font : string
	* Event : string
	
Supported C# Types:
	* Float
	* Int
	* Boolean
	* String
	* Float[]
	* List<UIElement_Data> : Reserved for elements var
'''

#-------------------------------- [ GENERATE SECTION ] ---------------------------------
def generate_variable_declaration(name, property):
	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_string_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):
	#	should_ignore = property['dontDeclare']
	#	if (should_ignore == 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_string_getset(name, property)

def generate_class(schema):
	cog.out("public class %s " % extract_data_classname(schema))
	superclass = extract_data_superclass(schema)
	if len(superclass) > 5:
		cog.out(f': {superclass}')
	cog.outl('')

def generate_property_names(schema):
	classname = extract_classname(schema)
	superclass = extract_superclass(schema)
	property_names = []
	proeprty_types = []
	extract_schema_property_names(classname, property_names, proeprty_types)

	cog.out("\tpublic ")
	if(len(superclass) > 0):
		cog.out("override ")
	else:
		cog.out("virtual ")
	cog.out("void GeneratePropertyNames()\n")
	cog.outl('\t{')

	if(len(superclass) > 0):
		cog.outl('\t\tbase.GeneratePropertyNames();')
	
	for idx,property in enumerate(property_names):
		if property != 'elements':
			cog.outl(f'\t\t_{property}.DisplayName = "{property}"; ')
			
	cog.outl('\t}')
	cog.outl('')

def generate_init(schema, superclass):
    classname = extract_classname(schema)
    data_classname = extract_data_classname(schema)
    superclass = extract_superclass(schema)
    properties = []
    types = []
    
    super_properties = []
    super_types = []
    extract_schema_property_names_expand_superclass(classname, properties, types, super_properties, super_types)

    cog.out(f'\tpublic {data_classname}()')
    if len(superclass) > 0:
        cog.outl(' : base()')
    else:
        cog.outl('')
		
    cog.outl('\t{')
    cog.outl('\t}')
    
    cog.outl('')
    
    cog.out(f'\tpublic {data_classname}(')
    cog.outl('')
    
            
    for idx, property in enumerate(super_properties):
        cog.out(f'\t\t{super_types[idx] + " in_" + property}')
        
        if idx < len(super_properties)-1:
            cog.outl(',')
            
    if len(properties) > 0 and len(super_properties) > 0:
         cog.outl(',')
         
    for idx, property in enumerate(properties):
        cog.out(f'\t\t{types[idx] + " in_" + property}')
    
        if idx < len(properties)-1:
            cog.outl(',')

    cog.outl(')')
    
    if len(superclass) > 0:
        cog.outl('\t: base(')
    
        for idx, super_property in enumerate(super_properties):
            cog.out(f'\t\tin_{super_property}')
            if idx < len(super_properties)-1:
                cog.outl(',')
        cog.outl(')')
    cog.outl('\t{')
    for idx, property in enumerate(properties):
        if(property != "elements"):
            cog.outl(f'\t\t_{property}.Set(in_{property});')
        else:
            cog.outl(f'\t\t_{property} = in_{property};')
    cog.outl('\t}')
    cog.outl('')


    cog.out(f'\tpublic {data_classname}(JsonElement jsonData)')

    if len(superclass) > 0:
        cog.outl(': base(jsonData)')
    else:
        cog.outl("")
		
    cog.outl('\t{')


    for idx, property in enumerate(properties):
        cog.outl(f'\t\t// {property.capitalize()} Deserialization')
        cog.outl(f'\t\tJsonElement json{property.capitalize()};')
        cog.outl(f'\t\tif(jsonData.TryGetProperty("{property}", out json{property.capitalize()}))')
        cog.outl("\t\t{")
        if(types[idx] == "string" or types[idx] == "event"):
            cog.outl(f'\t\t\t_{property}.Set(json{property.capitalize()}.GetString());')
        elif(types[idx] == "float"):
            cog.outl(f'\t\t\tif(json{property.capitalize()}.ValueKind == JsonValueKind.Number)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\t_{property}.Set((float)json{property.capitalize()}.GetDecimal());')
            cog.outl("\t\t\t}")
            cog.outl(f'\t\t\telse if(json{property.capitalize()}.ValueKind == JsonValueKind.String)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\t_{property}.Set(json{property.capitalize()}.GetString());')
            cog.outl("\t\t\t}")
        elif(types[idx] == "bool"):
            cog.outl(f'\t\t\tif(json{property.capitalize()}.ValueKind == JsonValueKind.True \n\t\t\t\t|| json{property.capitalize()}.ValueKind == JsonValueKind.False)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\t_{property}.Set(json{property.capitalize()}.GetBoolean());')
            cog.outl("\t\t\t}")
            cog.outl(f'\t\t\telse if(json{property.capitalize()}.ValueKind == JsonValueKind.String)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\t_{property}.Set(json{property.capitalize()}.GetString());')
            cog.outl("\t\t\t}")
        elif(types[idx] == "float[]"):
            cog.outl("")
            cog.outl(f'\t\t\tif(json{property.capitalize()}.ValueKind == JsonValueKind.Array)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\tList<float> {property}Temp = new List<float>();')
            cog.outl(f'\t\t\t\tfor(int i = 0; i < json{property.capitalize()}.GetArrayLength(); i++)')
            cog.outl("\t\t\t\t{")
            cog.outl(f'\t\t\t\t\t{property}Temp.Add((float)json{property.capitalize()}[i].GetDecimal());')
            cog.outl("\t\t\t\t}")
            cog.outl(f'\t\t\t\t_{property}.Set({property}Temp.ToArray());')
            cog.outl("\t\t\t}")
            cog.outl(f'\t\t\telse if(json{property.capitalize()}.ValueKind == JsonValueKind.String)')
            cog.outl("\t\t\t{")
            cog.outl(f'\t\t\t\t_{property}.Set(json{property.capitalize()}.GetString());')
            cog.outl("\t\t\t}")
            cog.outl("")
        cog.outl("\t\t}")
        cog.outl(f'\t\t// End {property.capitalize()} Deserialization')
        cog.outl("")
    cog.outl('\t}\n')
	
def generate_serialize(schema):
	classname = extract_classname(schema)
	superclass = extract_superclass(schema)
	properties = []
	types = []

	extract_schema_property_names(classname, properties, types)

	cog.out("\tpublic ")
	if(len(superclass) > 0):
		cog.out("override ")
	else:
		cog.out("virtual ")
	cog.out("void Serialize(ref JsonObject serializeObj)\n")
	cog.outl("\t{")
	
	if(len(superclass) > 0):
		cog.outl('\t\tbase.Serialize(ref serializeObj);')
	for idx, property in enumerate(properties):
		cog.outl(f'\t\t// {property.capitalize()} Serialization')
		if(types[idx] == "string" or types[idx] == "event"):
			cog.outl(f'\t\tserializeObj.Add("{property}", _{property}.AsString());')
		elif(types[idx] == "float"):
			cog.outl(f'\t\tif(_{property}.VariantType == Variant.Type.Float)')
			cog.outl("\t\t{")
			cog.outl(f'\t\t\tJsonValue {property}Value = JsonValue.Create(_{property}.AsSingle());')
			cog.outl(f'\t\t\tserializeObj.Add("{property}", {property}Value);')
			cog.outl("\t\t}")
			cog.outl(f'\t\telse if(_{property}.VariantType == Variant.Type.String)')
			cog.outl("\t\t{")
			cog.outl(f'\t\t\tserializeObj.Add("{property}", _{property}.AsString());')
			cog.outl("\t\t}")
		elif(types[idx] == "bool"):
			cog.outl(f'\t\tJsonValue {property}Value = JsonValue.Create(_{property}.AsBool());')
			cog.outl(f'\t\tserializeObj.Add("{property}", {property}Value);')
		elif(types[idx] == "float[]"):
			cog.outl(f'\t\tif(_{property}.VariantType == Variant.Type.String)')
			cog.outl("\t\t{")
			cog.outl(f'\t\t\tserializeObj.Add("{property}", _{property}.AsString());')
			cog.outl("\t\t}")
			cog.outl(f'\t\telse')
			cog.outl("\t\t{")
			cog.outl(f'\t\t\tJsonValue {property}Value = JsonValue.Create(_{property}.AsFloat32Array());')
			cog.outl(f'\t\t\tserializeObj.Add("{property}", {property}Value);')
			cog.outl("\t\t}")
			cog.outl("")
		elif(types[idx] == "List<UIElement_Data>"):
			cog.outl(f'\t\tList<JsonObject> {property}Arr = new List<JsonObject>();')
			cog.outl(f'\t\tfor(int i = 0; i < _{property}.Count; i++)')
			cog.outl('\t\t{')
			cog.outl('\t\t\tJsonObject obj = new JsonObject();')
			cog.outl(f'\t\t\t_{property}[i].Serialize(ref obj);')
			cog.outl(f'\t\t\t{property}Arr.Add(obj);')
			cog.outl('\t\t}')
			cog.outl(f'\t\tJsonValue val = JsonValue.Create({property}Arr);')
			cog.outl(f'\t\tserializeObj.Add("{property}", val);')
			cog.outl("")
		cog.outl(f'\t\t// End {property.capitalize()} Serialization')
		cog.outl("")
	cog.outl('\t}\n')

def generate_property_accessors(schema):
	classname = extract_classname(schema)
	superclass = extract_superclass(schema)
	properties = []
	types = []

	extract_schema_property_names(classname, properties, types)
	cog.out('\tpublic ')
	if len(superclass) > 0:
		cog.out('override ')
	else:
		cog.out('virtual ')
	cog.outl('void GetElementProperties(ref List<ElementProperty> outElems)')
	cog.outl('\t{')
	if len(superclass) > 0:
		cog.outl('\t\tbase.GetElementProperties(ref outElems);')
		cog.outl('')
	
	for property in properties:
		if property != 'elements':
			cog.outl(f'\t\toutElems.Add(_{property});')
	cog.outl('\t}')
	cog.outl('')
	cog.out('\tpublic ')
	if len(superclass) > 0:
		cog.out('override ')
	else:
		cog.out('virtual ')
	cog.outl('void SubscribeToPropertyChanges(ElementProperty.ValueDelegate inFunc)')
	cog.outl('\t{')
	if len(superclass) > 0:
		cog.outl('\t\tbase.SubscribeToPropertyChanges(inFunc);')
		cog.outl('')
	for property in properties:
		if property != 'elements':
			cog.outl(f'\t\t_{property}.OnValueChanged += inFunc;')
	cog.outl('\t}')
	cog.outl('')
	
# [ END GENERATE SECTION ]

#-------------------------------- [ VARIABLE DECLARATION HANDLERS ] ---------------------------------
def handle_string_variable_declaration(name, property):
	cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(""); // string')
	
def handle_number_variable_declaration(name, property):
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(0); // number (integer)')
			return
	cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(0.0f); // number (float)')

def handle_bool_variable_declaration(name, property):
	cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(false); // bool')

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)
			cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(new float[{maxItm}]);')
		elif(array_type == '$ref'):
			if(property['items'][array_type] == '#'):
				cog.outl(f'\tprivate List<UIElement_Data> _{name} = new List<UIElement_Data>();')
				cog.outl('\tpublic delegate void ChildElementsAddRemove(bool childWasAdded, UIElement_Data elem);')
				cog.outl('\tpublic ChildElementsAddRemove OnChildElementsAddRemove;')

def handle_font_variable_declaration(name, property):
	cog.outl(f'\tprivate ElementProperty _{name} = new ElementProperty(""); // font')
# [ END VARIABLE DECLARATION SECTION ]

#-------------------------------- [ GET|SET HANDLERS ] ---------------------------------
def handle_boolean_getset(name, property):
	cog.outl(f'\tpublic bool {name.capitalize()}')
	cog.outl('\t{')
	cog.outl(f'\t\tget => _{name}.AsBool();')
	cog.outl(f'\t\tset => _{name}.Set(value);')
	cog.outl('\t}')
	cog.outl('')

def handle_number_getset(name, property):
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			cog.outl(f'\tpublic int Get{name.capitalize()}()')
		else:
			cog.outl(f'\tpublic float Get{name.capitalize()}()')
	else:
		cog.outl(f'\tpublic float Get{name.capitalize()}()')
	cog.outl('\t{')
	cog.outl(f'\t\t\tif(_{name}.VariantType == Variant.Type.String)')
	cog.outl('\t\t\t{')
	if('numberType' in property):
		if(property['numberType'] == "integer"):
			cog.outl(f'\t\t\t\treturn NamedValueContainer.GetInstance().GetNamedInt(_{name}.AsString());')
		else:
			cog.outl(f'\t\t\t\treturn NamedValueContainer.GetInstance().GetNamedFloat(_{name}.AsString());')
	else:
		cog.outl(f'\t\t\t\treturn NamedValueContainer.GetInstance().GetNamedFloat(_{name}.AsString());')
	cog.outl('\t\t\t}')
	cog.outl('\t\t\telse\n\t\t\t{')
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			cog.outl(f'\t\t\t\treturn _{name}.AsInt32();')
		else:
			cog.outl(f'\t\t\t\treturn _{name}.AsSingle();')
	else:
		cog.outl(f'\t\t\t\treturn _{name}.AsSingle();')
	cog.outl('\t\t\t}\n\t}\n')
	if ('numberType' in property):
		if (property['numberType'] == "integer"):
			cog.outl(f'\tpublic void Set{name.capitalize()}(int value)')
		else:
			cog.outl(f'\tpublic void Set{name.capitalize()}(float value)')
	else:
		cog.outl(f'\tpublic void Set{name.capitalize()}(float value)')
	cog.outl('\t{')
	cog.outl(f'\t\t _{name}.Set(value);')
	cog.outl('\t}')
	cog.outl('')

def handle_string_getset(name, property):
	cog.outl(f'\tpublic string {name.capitalize()}')
	cog.outl('\t{')
	cog.outl(f'\t\tget => NamedValueContainer.GetInstance().InterpolateNamedValue(_{name}.AsString());')
	cog.outl(f'\t\tset => _{name}.Set(value);')
	cog.outl('\t}')
	cog.outl('')

def handle_array_getset(name, property):
	for array_type in property['items']:
		a = property['items'][array_type];
		print(f'array_type -> {a}')
		if(property['items'][array_type] == 'number'):
			maxItm = extract_max_items(property)
			minItm = extract_min_items(property)
			handle_number_array_getset(name, property, minItm, maxItm)
			return # only support 1 array type
		elif(array_type == '$ref'):
			if(property['items'][array_type] == '#'):
				print("Handling element array get set")
				handle_element_array_getset(name, property)
				
def handle_element_array_getset(name, property):
	cog.outl(f'\tpublic List<UIElement_Data> {name.capitalize()}')
	cog.outl('\t{')
	cog.outl(f'\t\tget => _{name};')
	cog.outl('\t}')
	cog.outl(f'\tpublic void AddChild(UIElement_Data inElement)')
	cog.outl('\t{')
	cog.outl(f'\t\t_{name}.Add(inElement);')
	cog.outl('\t\tif(OnChildElementsAddRemove != null)')
	cog.outl('\t\t{')
	cog.outl('\t\t\tOnChildElementsAddRemove(true, inElement);')
	cog.outl('\t\t}')
	cog.outl('\t}')
	cog.outl('')
	cog.outl(f'\tpublic void RemoveChild(UIElement_Data inElement)')
	cog.outl('\t{')
	cog.outl(f'\t\t_{name}.Remove(inElement);')
	cog.outl('\t\tif(OnChildElementsAddRemove != null)')
	cog.outl('\t\t{')
	cog.outl('\t\t\tOnChildElementsAddRemove(false, inElement);')
	cog.outl('\t\t}')
	cog.outl('\t}')
	cog.outl('')
	cog.outl('\tpublic void SubscribeToChildElementAddRemove(ChildElementsAddRemove inFunc)')
	cog.outl('\t{')
	cog.outl('\t\tOnChildElementsAddRemove += inFunc;')
	cog.outl('\t}')
	cog.outl('')
	cog.outl('\tpublic void UnsubscribeToChildElementAddRemove(ChildElementsAddRemove inFunc)')
	cog.outl('\t{')
	cog.outl('\t\tOnChildElementsAddRemove -= inFunc;')
	cog.outl('\t}')
	cog.outl('')

def handle_number_array_getset(name, property, minItems, maxItems):
	cog.outl(f'\tpublic float[] Get{name.capitalize()}()')
	cog.outl('\t{')
	cog.outl(f'\t\tif(_{name}.VariantType == Variant.Type.String)')
	cog.out('\t\t{\n')
	cog.outl(f'\t\t\treturn NamedValueContainer.GetInstance().GetNamed{maxItems}Float(_{name}.AsString());')
	cog.out('\t\t}\n')
	cog.outl('\t\telse\n\t\t{')
	cog.outl(f'\t\t\treturn _{name}.AsFloat32Array();')
	cog.outl('\t\t}\n')
	cog.outl('\t}\n')
	cog.outl(f'\tpublic void Set{name.capitalize()}(float[] value)')
	cog.outl('\t{')
	if minItems != -1:
		cog.out(f'\t\tif(value.Length < {minItems})')
		cog.outl(' { return; }')
	if maxItems != -1:
		cog.out(f'\t\tif(value.Length > {maxItems})')
		cog.outl(' { return; }')
	cog.outl(f'\t\t_{name}.Set(value);')
	cog.outl('\t}')
	cog.outl('')

def handle_font_getset(name, property):
	cog.outl(f'\tpublic string {name.capitalize()}')
	cog.outl('\t{')
	cog.outl(f'\t\tget => _{name}.AsString();')
	cog.outl(f'\t\tset => _{name}.Set(value);')
	cog.outl('\t}')
	cog.outl('')
# [ END GET|SET SECTION ]

#-------------------------------- [ ARRAY UTILITIES ] ---------------------------------
def extract_max_items(property):
	if(property['maxItems']):
		return property['maxItems']

	return -1

def extract_min_items(property):
	if(property['minItems']):
		return property['minItems']

	return -1
# [ END ARRAY UTILITIES SECTION ]


#-------------------------------- [ CLASS UTILITIES ] ---------------------------------
def extract_classname(schema):
	return schema['title']

def extract_data_classname(schema):
	return extract_classname(schema) + "_Data"

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_data_superclass(schema):
	return extract_superclass(schema) + "_Data"

def extract_schema(classname):
	classname = classname.lower()
	file_path = f'../../ui_codegen/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, properties, types):
	schema = extract_schema(classname)
	

	for property in schema['properties']:
		properties.append(property)
		
		prop_type = schema['properties'][property]['type']

		if prop_type == "array":
			for array_type in schema['properties'][property]['items']:
				if (schema['properties'][property]['items'][array_type] == 'number'):
					prop_type = "float[]"
				elif(array_type == '$ref'):
					if(schema['properties'][property]['items'][array_type] == '#'):
						prop_type = "List<UIElement_Data>"
		elif prop_type == "number":
			if schema['properties'][property] == "integer":
				prop_type = "int"
			else:
				prop_type = "float"
		elif prop_type == "boolean":
			prop_type = "bool"
		elif prop_type == "font":
			prop_type = "string"
		elif prop_type == "event":
			prop_type = "string"
		types.append(prop_type)

def extract_schema_property_names_expand_superclass(classname, our_properties, our_types, super_properties, super_types):
    print(f'Extracting properties for {classname}')
    target_class = classname
    
    in_superclass = False

    while len(target_class) > 0:
        schema = extract_schema(target_class)
        new_properties = []
        new_types = []
        extract_schema_property_names(target_class, new_properties, new_types)
        new_properties.reverse() # when unreversed later, matches declaration order of parent classes
        new_types.reverse()
		
        if in_superclass:
            super_properties.extend(new_properties)
            super_types.extend(new_types)
        else:
            our_properties.extend(new_properties)
            our_types.extend(new_types)

        target_class = extract_superclass(schema)
        in_superclass = True

    our_properties.reverse()
    our_types.reverse()
    super_properties.reverse()
    super_types.reverse()
# [ END CLASS UTILITIES SECTION ]
