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

////////////////////////////////////////////////////////////////////////////////////
//	ECS System

#include "EcsSystem.h"
#include "XmlParser.h"
#include "SchemaEdit.h"
#include "Utils.h"
#include "Utilities.h"
#include <cassert>
#include <cstring>

namespace GameEngine {

// virtual
EcsSystem::EcsSystem()
{
}

// virtual
EcsSystem::~EcsSystem()
{
}

// virtual
void EcsSystem::Initialize()
{
}

// virtual
void EcsSystem::Shutdown()
{
}

// virtual
void EcsSystem::Prepare()
{
}

// virtual
void EcsSystem::Update()
{
}

// virtual
void EcsSystem::Render()
{
}

// virtual
void EcsSystem::Present()
{
}

const EntityRef& EcsSystem::GetEntity(EntityId entity) const
{
	return entities.at(entity);
}

const ComponentType& EcsSystem::GetComponentType(ComponentTypeId componentType) const
{
	return components.at(componentType);
}

struct SceneObject
{
	EntityId Self;
	EntityId Parent;
	EntityId FirstChild;
	EntityId NextSibling;
	char     Name[64];
	uint64_t Layer;
};

void EcsSystem::LoadEntitiesFromXml(XmlTag* rootTag)
{
	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& tag : child.children)
		{
			if (tag.tag == "entity")
			{
				assert(tag.attribValues.size() == 1);
				printf("Entity%s:\n", tag.attribValues["id"].c_str());

				uint64_t id = StringToUInt64(tag.attribValues["id"]);
				auto& entRef = entities[id];
				entRef.id = id;

				for (auto& comp : tag.children)
				{
					ComponentTypeId compId = FindComponentTypeByName(comp.tag);
					if (compId == INVALID_COMPONENT_TYPE_ID)
					{
						printf("  %s NOT FOUND\n", comp.tag.c_str());
					}
					else
					{
						// TODO: instead of malloc, the data should come from the component type's component pool
						void* entData = malloc(components[compId].size); // TODO: leaking at the moment - temp until we have component data pools working
						memset(entData, 0, components[compId].size);
						entRef.components[compId] = entData;

						auto& compType = GetComponentType(compId);
						for (int i = 0; i < compType.fields.numberOfFields; ++i)
						{
							auto& varDesc = schema->variables->variables[compType.fields.variableDescriptionTableOffset + i];
							auto varName = ConvertRuntimeString(schema, varDesc.name);
							bool found = false;
							if (varDesc.variableType == VariableTypeEnum::E_Struct)
							{
								for (auto& child : comp.children)
								{
									if (child.tag == varName)
										found = true;
								}
							}
							if (!found && !comp.attribValues.count(varName))
							{
								printf("WARNING: Can't find the field '%s' of the component type '%s' defined for entity '%s' (defaulting to 0)\n", 
									varName.c_str(), comp.tag.c_str(), tag.attribValues["id"].c_str());
							}
							else if (!found)
							{
								auto varVal = comp.attribValues[varName];
								switch (varDesc.variableType)
								{
									case VariableTypeEnum::E_Void:
										break;
									case VariableTypeEnum::E_Bool:
										if (varVal == "1" || varVal == "Yes" || varVal == "YES" || varVal == "yes"
										  || varVal == "True" || varVal == "TRUE" || varVal == "true"
										  || varVal == "On" || varVal == "ON" || varVal == "on")
										  *(uint8_t*)entData = 1;
										break;
									case VariableTypeEnum::E_Enum:
									{
										NameValue* enumValues = schema->enums->values + varDesc.enumDescription.nameValueTableOffset;
										uint64_t maxEnumValue = 0ULL;
										uint64_t setEnumValue = 0ULL;
										for (int i = 0; i < varDesc.enumDescription.numberOfEnumValues; ++i)
										{
											if (enumValues[i].value > maxEnumValue)
												maxEnumValue = enumValues[i].value;
											if (varVal == ConvertRuntimeString(schema, enumValues[i].name))
												setEnumValue = enumValues[i].value;
										}
										// If use indirection instead then could do this:
										// maxEnumValue = enumDesc.numberOfEnumValues;
										if (maxEnumValue <= UINT8_MAX)
											*(uint8_t*)entData = setEnumValue;
										else if (maxEnumValue <= UINT16_MAX)
											*(uint16_t*)entData = setEnumValue;
										else if (maxEnumValue <= UINT32_MAX)
											*(uint32_t*)entData = setEnumValue;
										else
											*(uint64_t*)entData = setEnumValue;
										break;
									}
									case VariableTypeEnum::E_String:
										::strncpy((char*)entData, varVal.c_str(), varDesc.stringDescription.maximumCharacterCount);
										break;
									case VariableTypeEnum::E_Number:
										switch (varDesc.numberDescription.numberType)
										{
											case E_UInt8:   *(uint8_t*) entData = Utilities::to_uint32(varVal); break;
											case E_UInt16:  *(uint16_t*)entData = Utilities::to_uint32(varVal); break;
											case E_UInt32:  *(uint32_t*)entData = Utilities::to_uint32(varVal); break;
											case E_UInt64:  *(uint64_t*)entData = Utilities::to_uint64(varVal); break;
											case E_Int8:    *(int8_t*)  entData = Utilities::to_int32(varVal);  break;
											case E_Int16:   *(int16_t*) entData = Utilities::to_int32(varVal);  break;
											case E_Int32:   *(int32_t*) entData = Utilities::to_int32(varVal);  break;
											case E_Int64:   *(int64_t*) entData = Utilities::to_int64(varVal);  break;
											case E_Float64: *(double*)  entData = Utilities::to_double(varVal); break;
											case E_Float32:
											case E_Angle:
											case E_Ratio:   *(float*)   entData = Utilities::to_float(varVal);  break;
										}
										break;
									case VariableTypeEnum::E_Color:
										switch (varDesc.colorDescription.colorType)
										{
											case E_RGBA32:   *(uint32_t*)entData = Utilities::to_uint32(varVal); break;
											case E_RGB32:    *(uint32_t*)entData = Utilities::to_uint32(varVal); break;
										}
										break;
									case VariableTypeEnum::E_Entity:
										*(uint64_t*)entData = StringToUInt64(varVal);
										break;
									case VariableTypeEnum::E_Asset:
										break;
									case VariableTypeEnum::E_Struct:
										break;
									case VariableTypeEnum::E_Component:
										break;
								}
							}

							entData = (uint8_t*)entData + CalculateFieldSize(*schema, (size_t)entData, varDesc);
						}
/*
						if (comp.tag == "SceneObject")
						{
							assert(components[compId].size == sizeof(SceneObject));
							SceneObject* sceneObj = (SceneObject*)entData;
							sceneObj->Self = StringToUInt64(comp.attribValues["Self"]);
							sceneObj->Parent = StringToUInt64(comp.attribValues["Parent"]);
							sceneObj->FirstChild = StringToUInt64(comp.attribValues["FirstChild"]);
							sceneObj->NextSibling = StringToUInt64(comp.attribValues["NextSibling"]);
							::strncpy(sceneObj->Name, comp.attribValues["Name"].c_str(), 64);
							sceneObj->Layer = StringToUInt64(comp.attribValues["Layer"]);
						}
*/
						printf("  %s\n", comp.tag.c_str());
					}
				}
			}
			else
			{
				printf("Error, unknown tag type: %s\n", tag.tag.c_str());
			}
		}
	}
}

void EcsSystem::SetSchema(GameRuntime::Schema* _schema)
{
	schema = _schema;

	// Populate the component types from the schema
	for (int i = 0; i < schema->variables->count; ++i)
	{
		auto& varDesc = schema->variables->variables[i];
		if (varDesc.variableType == GameRuntime::VariableTypeEnum::E_Component)
		{
			++lastUsedComponentTypeId;
    		auto& compType = components[lastUsedComponentTypeId];
			compType.name = ConvertRuntimeString(schema, varDesc.name);
			compType.fields = varDesc.componentDescription;
			compType.id = lastUsedComponentTypeId;
			compType.size = CalculateComponentSize(*schema, compType.fields);
			compType.enabled = true;
		}
	}

#ifndef _NDEBUG
	// Debug
	StringList comps = AllComponentTypeNames();
	for (auto& comp : comps)
		printf("Component: %s\n", comp.c_str());
#endif
}

void EcsSystem::LoadScene(const char* sceneFilename)
{
	// Load entities
	XmlTag root;
	root.tag = "root";
	if (ParseXmlFile(sceneFilename, &root))
	{
		LoadEntitiesFromXml(&root);
	}
	else
	{
		printf("Error loading the scene\n");
	}
}

void EcsSystem::RegisterComponentType()
{
}

StringList EcsSystem::AllComponentTypeNames() const
{
    StringList compTypeNames;
    for (auto& compType : components)
    {
        if (compType.second.enabled)
            compTypeNames.emplace_back(compType.second.name);
    }
    return compTypeNames;
}

ComponentTypeId EcsSystem::FindComponentTypeByName(String componentName) const
{
    for (auto& compType : components)
    {
        if (compType.second.enabled && compType.second.name == componentName)
            return compType.second.id;
    }
    return INVALID_COMPONENT_TYPE_ID;
}

Array<EntityId> EcsSystem::AllEntities() const
{
    Array<EntityId> ents;
    for (auto& ent : entities)
        ents.push_back(ent.first);
    return ents;
}

// TODO: this should be more efficient - should be a direct way to do this
Array<EntityId> EcsSystem::FindEntitiesWithComponentType(ComponentTypeId componentType) const
{
    Array<EntityId> ents;
    for (auto& ent : entities)
    {
		if (ent.second.components.count(componentType))
			ents.push_back(ent.first);
    }
    return ents;
}

StringList EcsSystem::EntityComponents(EntityId entity) const
{
    StringList compTypeNames;
	for (auto& compType : entities.at(entity).components)
		compTypeNames.emplace_back(components.at(compType.first).name);
    return compTypeNames;
}

// unused
void EcsSystem::NewEntity()
{
}

// unused
void EcsSystem::NewComponent()
{
}

// unused
void EcsSystem::RemoveStaleComponentReferences()
{
    for (auto& compType : components)
    {
        if (!compType.second.enabled)
        {
            for (auto& ent : entities)
            {
                if (ent.second.components.count(compType.second.id))
                {
                    free(ent.second.components[compType.second.id]);
                    ent.second.components.erase(compType.second.id);
                }
            }
        }
    }
}

} // GameEngine namespace
