/*
	GameEngine and Editor
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Schema Edit

#include "SchemaEdit.h"
#include "Utils.h"
#include <cassert>
#include <cstring>
#include <string>
#include <unordered_map>

// TODO : move to toolbox
template <class T, size_t N>
constexpr size_t array_size(const T (&array)[N]) noexcept
{
    return N;
}

std::unordered_map<std::string,NumberTypeEnum> numberTypeEnumMap
{
	{ "UInt8",         E_UInt8   },
	{ "UInt16",        E_UInt16  },
	{ "UInt32",        E_UInt32  },
	{ "UInt64",        E_UInt64  },
	{ "Int8",          E_Int8    },
	{ "Int16",         E_Int16   },
	{ "Int32",         E_Int32   },
	{ "Int64",         E_Int64   },
	{ "Float32",       E_Float32 },
	{ "Float64",       E_Float64 },
	{ "Ratio",         E_Ratio   },
	{ "Angle",         E_Angle   }
};

std::unordered_map<std::string,ColorTypeEnum> colorTypeEnumMap
{
	{ "RGB32",         E_RGB32   },
	{ "RGBA32",        E_RGBA32  }
};

std::unordered_map<NumberTypeEnum,std::string> numberTypeEnumToString
{
	{ E_UInt8,         "UInt8"         },
	{ E_UInt16,        "UInt16"        },
	{ E_UInt32,        "UInt32"        },
	{ E_UInt64,        "UInt64"        },
	{ E_Int8,          "Int8"          },
	{ E_Int16,         "Int16"         },
	{ E_Int32,         "Int32"         },
	{ E_Int64,         "Int64"         },
	{ E_Float32,       "Float32"       },
	{ E_Float64,       "Float64"       },
	{ E_Ratio,         "Ratio"         },
	{ E_Angle,         "Angle"         }
};

// Idea is that the unordered_maps can be initialized from this
struct
{
    VariableTypeEnum               m_enumValue;
    const char*                    m_toString;
    const char*                    m_xmlTag;
    const char*                    m_asCode;
}
variableTypeEnumNames[] =
{
	{ VariableTypeEnum::E_Void          , "Void"          , "void"          , "void     " },
	{ VariableTypeEnum::E_Bool          , "Bool"          , "bool"          , "bool     " },
	{ VariableTypeEnum::E_Enum          , "Enum"          , "enum"          , "enum     " },
	{ VariableTypeEnum::E_String        , "String"        , "string"        , "char     " },
	{ VariableTypeEnum::E_Number        , "Number"        , "number"        , ""          },
	{ VariableTypeEnum::E_EncodedNumber , "EncodedNumber" , ""              , ""          },
	{ VariableTypeEnum::E_Color         , "Color"         , "color"         , ""          },
	{ VariableTypeEnum::E_Entity        , "Entity"        , "entity"        , "EntityId " },
	{ VariableTypeEnum::E_Asset         , "Asset"         , ""              , ""          },
	{ VariableTypeEnum::E_Struct        , "Struct"        , "struct"        , "struct "   },
	{ VariableTypeEnum::E_Component     , "Component"     , "component"     , "struct "   },
};

static_assert(array_size(variableTypeEnumNames) == (size_t)VariableTypeEnum::E_Max, "Please update above to match the enum");

//// TODO:  BEGIN     begin some experimental code ideas on string<->enum mapping
#if 0

struct VTIt
{
	int i;
	VTIt& operator++() { ++i; return *this; }
	std::pair<std::string,VariableTypeEnum> operator*()
	{
		return std::pair<std::string,VariableTypeEnum>{
			variableTypeEnumNames[i].m_toString, variableTypeEnumNames[i].m_enumValue };
	}
};

struct GenericIterator
{
	int i;
	inline GenericIterator& operator++() { ++i; return *this; }
	inline bool operator!=(const GenericIterator& other) { return i!=other.i; }
};

struct VTIt2 : public GenericIterator
{
	VTIt2(int x) { i = x; }
	std::pair<VariableTypeEnum,const char*> operator*()
	{
		return std::make_pair<VariableTypeEnum,const char*>(std::move(variableTypeEnumNames[i].m_enumValue), std::move(variableTypeEnumNames[i].m_toString));
	}
};

VariableTypeEnum VariableTypeEnumFromString(const std::string& val)
{
	static std::unordered_map<std::string,VariableTypeEnum> sVariableTypeEnumFromString;
	static bool sInited = false;
	if (!sInited)
		for (int i = 0; i < array_size(variableTypeEnumNames); ++i)
			sVariableTypeEnumFromString[variableTypeEnumNames[i].m_toString] = variableTypeEnumNames[i].m_enumValue;
	sInited = true;
	return sVariableTypeEnumFromString[val];
}

std::string VariableTypeEnumToString(VariableTypeEnum val)
{
	static std::unordered_map<VariableTypeEnum,const char*> sVariableTypeEnumToString(VTIt2{0}, VTIt2{array_size(variableTypeEnumNames)}, array_size(variableTypeEnumNames));
	return sVariableTypeEnumToString[val];
}

#endif
//// TODO:  END     end some experimental code ideas on string<->enum mapping

std::unordered_map<VariableTypeEnum,std::string> variableTypeEnumToString
{
	{ VariableTypeEnum::E_Void,          "Void"          },
	{ VariableTypeEnum::E_Bool,          "Bool"          },
	{ VariableTypeEnum::E_Enum,          "Enum"          },
	{ VariableTypeEnum::E_String,        "String"        },
	{ VariableTypeEnum::E_Number,        "Number"        },
	{ VariableTypeEnum::E_EncodedNumber, "EncodedNumber" },
	{ VariableTypeEnum::E_Entity,        "Entity"        },
	{ VariableTypeEnum::E_Struct,        "Struct"        },
	{ VariableTypeEnum::E_Component,     "Component"     },
};

std::unordered_map<VariableTypeEnum,std::string> variableTypeEnumToCType
{
	{ VariableTypeEnum::E_Void,          "void     "     },
	{ VariableTypeEnum::E_Bool,          "bool     "     },
	{ VariableTypeEnum::E_Enum,          "enum     "     },
	{ VariableTypeEnum::E_String,        "char     "     },
	{ VariableTypeEnum::E_Color,         ""              },
	{ VariableTypeEnum::E_Number,        ""              },
	{ VariableTypeEnum::E_EncodedNumber, ""              },
	{ VariableTypeEnum::E_Entity,        "EntityId "     },
	{ VariableTypeEnum::E_Struct,        "struct "       },
	{ VariableTypeEnum::E_Component,     "struct "       },
};

std::unordered_map<ColorTypeEnum,std::string> colorTypeEnumToCType
{
	{ E_RGB32,         "rgb32    "     },
	{ E_RGBA32,        "rgba32   "     }
};

std::unordered_map<NumberTypeEnum,std::string> numberTypeEnumToCType
{
	{ E_UInt8,         "uint8_t  "     },
	{ E_UInt16,        "uint16_t "     },
	{ E_UInt32,        "uint32_t "     },
	{ E_UInt64,        "uint64_t "     },
	{ E_Int8,          "int8_t   "     },
	{ E_Int16,         "int16_t  "     },
	{ E_Int32,         "int32_t  "     },
	{ E_Int64,         "int64_t  "     },
	{ E_Float32,       "float    "     },
	{ E_Float64,       "double   "     },
	{ E_Ratio,         "float    "     },
	{ E_Angle,         "float    "     }
};

DynNameValue CreateEnumValue(std::string name, uint64_t value)
{
	DynNameValue val{ name, { {}, value } };
	return val;
}

EnumDescription CreateEnum(DynSchema* schema, std::vector<DynNameValue> enumValues)
{
	EnumDescription desc{ .numberOfEnumValues = (uint32_t)enumValues.size(), .nameValueTableOffset = (uint32_t)schema->enums.size() };
	schema->enums.insert(schema->enums.end(), enumValues.begin(), enumValues.end());
	return desc;
}

StructDescription CreateStruct(DynSchema* schema, std::vector<DynVariableDescription> fields)
{
	StructDescription desc{ .numberOfFields = (uint32_t)fields.size(), .variableDescriptionTableOffset = (uint32_t)schema->variables.size() };
	schema->variables.insert(schema->variables.end(), fields.begin(), fields.end());
	return desc;
}

DynVariableDescription AddNumber(DynSchema* schema, std::string name, NumberDescription numberDescription)
{
	DynVariableDescription desc{ name, { 0, {}, VariableTypeEnum::E_Number, .numberDescription = numberDescription } };
	//schema->variables.push_back(desc);
	return desc;
}

/*
DynVariableDescription AddEncodedNumber(DynSchema* schema, std::string name, EncodedNumberDescription encodedNumberDescription)
{
	DynVariableDescription desc{ name, { {}, E_EncodedNumber, .encodedNumberDescription = encodedNumberDescription } };
	//schema->variables.push_back(desc);
	return desc;
}
*/

DynVariableDescription AddString(DynSchema* schema, std::string name, StringDescription stringDescription)
{
	DynVariableDescription desc{ name, { 0,  {}, VariableTypeEnum::E_String, .stringDescription = stringDescription } };
	//schema->variables.push_back(desc);
	return desc;
}

DynVariableDescription AddEnum(DynSchema* schema, std::string name, EnumDescription enumDescription)
{
	DynVariableDescription desc{ name, { 0, {}, VariableTypeEnum::E_Enum, .enumDescription = enumDescription } };
	//schema->variables.push_back(desc);
	return desc;
}

DynVariableDescription AddBool(DynSchema* schema, std::string name)
{
	return DynVariableDescription{ name, { 0, {}, VariableTypeEnum::E_Bool } };
}

DynVariableDescription AddColor(DynSchema* schema, std::string name, ColorDescription colorDescription)
{
	return DynVariableDescription{ name, { 0, {}, VariableTypeEnum::E_Color, .colorDescription = colorDescription } };
}

DynVariableDescription AddStruct(DynSchema* schema, std::string name, StructDescription structDescription)
{
	DynVariableDescription desc{ name, { 0, {}, VariableTypeEnum::E_Struct, .structDescription = structDescription } };
	schema->variables.push_back(desc);
	return desc;
}

DynVariableDescription AddComponent(DynSchema* schema, std::string name, StructDescription structDescription)
{
	DynVariableDescription desc{ name, { 0, {}, VariableTypeEnum::E_Component, .structDescription = structDescription } };
	schema->variables.push_back(desc);
	return desc;
}

static
size_t CalculateStringsTotalSize(DynSchema* schema)
{
	size_t stringsSize = 0;
	for (const auto& val : schema->enums)
	{
		stringsSize += val.name.size();
	}
	for (const auto& val : schema->variables)
	{
		stringsSize += val.name.size();
	}
	return stringsSize;
}

static
void ConvertDynSchemaToFileSchema(DynSchema* schema, Schema* fileSchema)
{
	size_t stringsSize = 0;
	size_t elementIndex = 0;
	for (auto& val : schema->enums)
	{
		size_t strSize = val.name.size();
		memcpy(fileSchema->strings->text + stringsSize, val.name.c_str(), strSize);
		val.desc.name.stringTableOffset = (uint32_t)stringsSize;
		val.desc.name.stringLength = (uint32_t)strSize;
		fileSchema->enums->values[elementIndex] = val.desc;
		stringsSize += strSize;
		elementIndex++;
	}
	elementIndex = 0;
	for (auto& val : schema->variables)
	{
		size_t strSize = val.name.size();
		memcpy(fileSchema->strings->text + stringsSize, val.name.c_str(), strSize);
		val.desc.name.stringTableOffset = (uint32_t)stringsSize;
		val.desc.name.stringLength = (uint32_t)strSize;
		fileSchema->variables->variables[elementIndex] = val.desc;
		stringsSize += strSize;
		elementIndex++;
	}
}

bool WriteSchemeFile(DynSchema* schema, const char* schemaFileName)
{
	FILE* schemaFile = fopen(schemaFileName, "w+");
	if (schemaFile)
	{
		size_t stringTableSize = CalculateStringsTotalSize(schema);
		size_t nameValueTableSize = schema->enums.size();
		size_t variableDescriptionTableSize = schema->variables.size();
		size_t headerSize = sizeof(SchemaFileFormatHeader);
		size_t stringTableFileOffset = headerSize;
		size_t nameValueTableFileOffset = stringTableFileOffset + stringTableSize * sizeof(char);
		size_t variableDescriptionTableFileOffset = nameValueTableFileOffset + nameValueTableSize * sizeof(NameValue);
		size_t fileSize = variableDescriptionTableFileOffset + variableDescriptionTableSize * sizeof(VariableDescription);
		SchemaFileFormatHeader fileHeader
		{
			.fileFormatCode				= 0x24555542,
			.fileSize                               = (uint32_t)fileSize,
			.headerSize                             = (uint32_t)headerSize,
			.stringTableSize                        = (uint32_t)stringTableSize,
			.stringTableFileOffset                  = (uint32_t)stringTableFileOffset,
			.nameValueTableSize                     = (uint32_t)nameValueTableSize,
			.nameValueTableFileOffset               = (uint32_t)nameValueTableFileOffset,
			.variableDescriptionTableSize           = (uint32_t)variableDescriptionTableSize,
			.variableDescriptionTableFileOffset     = (uint32_t)variableDescriptionTableFileOffset
		};
		auto res = fwrite(&fileHeader, sizeof(fileHeader), 1, schemaFile);
		if (res != 1)
		{
			printf("write res: %lu\n", res);
		}
		else
		{
			Schema* fileSchema = (Schema*)malloc(sizeof(Schema));
			fileSchema->strings = (StringTable*)malloc(sizeof(uint64_t) + sizeof(char) * fileHeader.stringTableSize);
			fileSchema->enums = (NameValueTable*)malloc(sizeof(uint64_t) + sizeof(NameValue) * fileHeader.nameValueTableSize);
			fileSchema->variables = (VariableDescriptionTable*)malloc(sizeof(uint64_t) + sizeof(VariableDescription) * fileHeader.variableDescriptionTableSize);

			ConvertDynSchemaToFileSchema(schema, fileSchema);

			//fseek(schemaFile, fileHeader.stringTableFileOffset, SEEK_SET);
			fwrite(fileSchema->strings->text, fileHeader.stringTableSize * sizeof(char), 1, schemaFile);

			//fseek(schemaFile, fileHeader.nameValueTableSize, SEEK_SET);
			fwrite(fileSchema->enums->values, fileHeader.nameValueTableSize * sizeof(NameValue), 1, schemaFile);

			//fseek(schemaFile, fileHeader.variableDescriptionTableFileOffset, SEEK_SET);
			fwrite(fileSchema->variables->variables, fileHeader.variableDescriptionTableSize * sizeof(VariableDescription), 1, schemaFile);

			DestroySchema(fileSchema);
		}
		fclose(schemaFile);
		return true;
	}
	return false;

}

static
DynVariableDescription ConvertXmlToVariable(XmlTag* tag, DynSchema* dynSchema)
{
	if (tag->tag == "struct" || tag->tag == "component")
	{
		std::vector<DynVariableDescription> fields;
		for (auto& child : tag->children)
		{
			fields.push_back(ConvertXmlToVariable(&child, dynSchema));
		}
		if (tag->tag == "struct")
			return AddStruct(dynSchema, tag->attribValues["name"], CreateStruct(dynSchema, fields));
		else
			return AddComponent(dynSchema, tag->attribValues["name"], CreateStruct(dynSchema, fields));
	}
	else if (tag->tag == "void")
	{
		// TODO
	}
	else if (tag->tag == "bool")
	{
		return AddBool(dynSchema, tag->attribValues["name"]);
	}
	else if (tag->tag == "color")
	{
		return AddColor(dynSchema, tag->attribValues["name"], { colorTypeEnumMap[tag->attribValues["type"]] });
	}
	else if (tag->tag == "enum")
	{
		std::vector<DynNameValue> enumValues;
		for (auto& attrib : tag->attribValues)
		{
			if (attrib.first != "name")
			{
				enumValues.push_back(CreateEnumValue(attrib.first, StringToUInt64(attrib.second)));
			}
		}
		return AddEnum(dynSchema, tag->attribValues["name"], CreateEnum(dynSchema, enumValues));
	}
	/*
	else if (tag->tag == "encoded")
	{
		// TODO
		return AddNumber(dynSchema, tag->attribValues["name"], { numberTypeEnumMap[tag->attribValues["type"]] });
	}
	*/
	else if (tag->tag == "number")
	{
		return AddNumber(dynSchema, tag->attribValues["name"], { numberTypeEnumMap[tag->attribValues["type"]] });
	}
	else if (tag->tag == "entity")
	{
		return DynVariableDescription{ tag->attribValues["name"], { 0, {}, VariableTypeEnum::E_Entity, .entityDescription = {} } };
	}
	else if (tag->tag == "string")
	{
		return AddString(dynSchema, tag->attribValues["name"], { StringToUInt32(tag->attribValues["size"]) });
	}
	else
	{
		printf("Error, unknown tag type: %s\n", tag->tag.c_str());
	}

	DynVariableDescription nullVar;
	return nullVar;
}

void ConvertXmlToSchema(XmlTag* rootTag, DynSchema* dynSchema)
{
	printf("Root tag: %s\n", rootTag->tag.c_str());
	assert(rootTag->tag == "root");
	printf("Root tag: %s\n", rootTag->children[0].tag.c_str());
	for (auto& child : rootTag->children)
	{
		assert(child.tag == "xml");
		for (auto& grandChild : child.children)
		{
			ConvertXmlToVariable(&grandChild, dynSchema);
		}
	}
}

std::string ConvertRuntimeString(Schema* schema, String str)
{
	const char* cstr = schema->strings->text + str.stringTableOffset;
	return std::string(cstr, cstr + str.stringLength);
}

static
void DumpStructAsC(Schema* schema, const char* indent, const char* indentAdd, const char* name, StructDescription& structDescription)
{
	printf("%sstruct %s\n%s{\n", indent, name, indent);//, str.c_str());
	for (int i = 0; i < structDescription.numberOfFields; ++i)
	{
		auto* varDec = schema->variables->variables + structDescription.variableDescriptionTableOffset + i;
		std::string str2 = ConvertRuntimeString(schema, varDec->name);
		//std::string varType = variableTypeEnumToCType[varDec->variableType];
		if (varDec->variableType == VariableTypeEnum::E_Number)
		{
			printf("%s%s%s      %s;\n", indent, indentAdd, numberTypeEnumToCType[varDec->numberDescription.numberType].c_str(), str2.c_str());
		}
		else if (varDec->variableType == VariableTypeEnum::E_Color)
		{
			printf("%s%s%s      %s;\n", indent, indentAdd, colorTypeEnumToCType[varDec->colorDescription.colorType].c_str(), str2.c_str());
		}
		else if (varDec->variableType == VariableTypeEnum::E_Struct)
		{
			std::string indent2 = indent;
			indent2 += indentAdd;
			DumpStructAsC(schema, indent2.c_str(), indentAdd, "", varDec->structDescription);
			printf(" %s;\n", str2.c_str());
		}
		else if (varDec->variableType == VariableTypeEnum::E_String)
		{
			printf("%s%schar           %s[%i];\n", indent, indentAdd, str2.c_str(), varDec->stringDescription.maximumCharacterCount);
		}
		else if (varDec->variableType == VariableTypeEnum::E_Enum)
		{
			std::string space = "                  ";
			space.resize(str2.size() < 11 ? 11 - str2.size() : 1);
			printf("%s%sEnum%s%s%s;\n", indent, indentAdd, str2.c_str(), space.c_str(), str2.c_str());
		}
		else 
		{
			printf("%s%s%s      %s;\n", indent, indentAdd, variableTypeEnumToCType[varDec->variableType].c_str(), str2.c_str());
			//printf("%s%s%s      %s;\n", indent, indentAdd, VariableTypeEnumToString(varDec->variableType).c_str(), str2.c_str());
		}
	}
    printf("%s}", indent);
}

void DumpSchema(Schema* schema)
{
/*
	printf("Enum value table:\n");
	for (int i = 0; i < schema->enums->count; ++i)
	{
		auto& enumVal = schema->enums->values[i];
		std::string str = ConvertRuntimeString(schema, enumVal.name);
		printf("  '%s' = 0x%08lx\n", str.c_str(), enumVal.value);
	}

	printf("Variable type table:\n");
	for (int i = 0; i < schema->variables->count; ++i)
	{
		auto& varDec = schema->variables->variables[i];
		std::string str = ConvertRuntimeString(schema, varDec.name);
		switch (varDec.variableType)
		{
			case E_Void:
				printf("  '%s' = void\n", str.c_str());
				break;
			case E_Bool:
				printf("  '%s' = bool\n", str.c_str());
				break;
			case E_Enum:
				printf("  '%s' = enum[%i] {\n", str.c_str(), varDec.enumDescription.numberOfEnumValues);
				for (int i = 0; i < varDec.enumDescription.numberOfEnumValues; ++i)
				{
					NameValue* nameValue = schema->enums->values + varDec.enumDescription.nameValueTableOffset + i;
					std::string str2 = ConvertRuntimeString(schema, nameValue->name);
					printf("     '%s' = %lu,\n", str2.c_str(), nameValue->value);
				}
				printf("  };\n");
				break;
			case E_String:
				printf("  '%s' = string[%i]\n", str.c_str(), varDec.stringDescription.maximumCharacterCount);
				break;
			case E_Number:
				printf("  '%s' = number: %s\n", str.c_str(), numberTypeEnumToString[varDec.numberDescription.numberType].c_str());
				break;
			case E_Entity:
				printf("  '%s' = entity\n", str.c_str());
				break;
			case E_EncodedNumber:
				printf("  '%s' = encoded_number\n", str.c_str());
				break;
			case E_Struct:
			case E_Component:
				if (varDec.variableType == VariableTypeEnum::E_Struct)
					printf("  '%s' = struct {\n", str.c_str());
				else
					printf("  '%s' = component {\n", str.c_str());
				for (int i = 0; i < varDec.structDescription.numberOfFields; ++i)
				{
					auto* varDec2 = schema->variables->variables + varDec.structDescription.variableDescriptionTableOffset + i;
					std::string str2 = ConvertRuntimeString(schema, varDec2->name);
					std::string varType = variableTypeEnumToString[varDec2->variableType];
					printf("     %s '%s';\n", varType.c_str(), str2.c_str());
				}
				printf("  };\n");
				break;
		}
	}
*/
	printf("// Schema converted to C code:\n\n");
	const char* indent = "";         // Current indent
	const char* indentAdd = "    ";  // Amount to add when entering new scope
	for (int i = 0; i < schema->variables->count; ++i)
	{
		auto& varDec = schema->variables->variables[i];
		if (varDec.variableType == VariableTypeEnum::E_Enum)
		{
			std::string str = ConvertRuntimeString(schema, varDec.name);
			printf("%senum Enum%s\n%s{\n", indent, str.c_str(), indent);
			// vals
			for (int i = 0; i < varDec.enumDescription.numberOfEnumValues; ++i)
			{
				NameValue* nameValue = schema->enums->values + varDec.enumDescription.nameValueTableOffset + i;
				std::string str2 = ConvertRuntimeString(schema, nameValue->name);
				std::string space = "                        ";
				space.resize(str2.size() < 14 ? 14 - str2.size() : 1);
				printf("%s%s%s%s = %lu,\n", indent, indentAdd, str2.c_str(), space.c_str(), nameValue->value);
			}
			printf("%s};\n\n", indent);
		}
	}
	for (int i = 0; i < schema->variables->count; ++i)
	{
		auto& varDec = schema->variables->variables[i];
		if (varDec.variableType == VariableTypeEnum::E_Component)
		{
			DumpStructAsC(schema, indent, indentAdd, ConvertRuntimeString(schema, varDec.name).c_str(), varDec.structDescription);
			printf(";\n\n");
		}
	}
}
