/*
  GameEngine and Editor
  by John Ryland
  Copyright (c) 2023
*/
#include "SchemaFile.h"
#include <cstdio>
#include <cstdlib>

static
void CheckReadResult(size_t result, size_t expected)
{
	if (result != expected)
	{
		printf("read result: %lu, expected: %lu\n", result, expected);
		exit(0);
	}
}

namespace GameRuntime {

Schema* LoadSchemaFromFile(const char* schemaFileName)
{
	Schema* schema = nullptr;
	// printf("opening file: %s\n", schemaFileName);
	FILE* schemaFile = fopen(schemaFileName, "r");
	if (schemaFile)
	{
		// printf("opened %s as fd: %p\n", schemaFileName, schemaFile);
		SchemaFileFormatHeader fileHeader;
		CheckReadResult(fread(&fileHeader, sizeof(fileHeader), 1, schemaFile), 1);

		schema = (Schema*)malloc(sizeof(Schema));
		schema->strings = (StringTable*)malloc(sizeof(uint64_t) + sizeof(char) * fileHeader.stringTableSize);
		schema->enums = (NameValueTable*)malloc(sizeof(uint64_t) + sizeof(NameValue) * fileHeader.nameValueTableSize);
		schema->variables = (VariableDescriptionTable*)malloc(sizeof(uint64_t) + sizeof(VariableDescription) * fileHeader.variableDescriptionTableSize);

		// printf("seeking to: %lu\n", fileHeader.stringTableFileOffset);
		fseek(schemaFile, fileHeader.stringTableFileOffset, SEEK_SET);
		schema->strings->count = fileHeader.stringTableSize;
		CheckReadResult(fread(schema->strings->text, fileHeader.stringTableSize * sizeof(char), 1, schemaFile), 1);

		// printf("seeking to: %lu\n", fileHeader.nameValueTableFileOffset);
		fseek(schemaFile, fileHeader.nameValueTableFileOffset, SEEK_SET);
		schema->enums->count = fileHeader.nameValueTableSize;
		CheckReadResult(fread(schema->enums->values, fileHeader.nameValueTableSize * sizeof(NameValue), 1, schemaFile), 1);

		// printf("seeking to: %lu\n", fileHeader.variableDescriptionTableFileOffset);
		fseek(schemaFile, fileHeader.variableDescriptionTableFileOffset, SEEK_SET);
		schema->variables->count = fileHeader.variableDescriptionTableSize;
		CheckReadResult(fread(schema->variables->variables, fileHeader.variableDescriptionTableSize * sizeof(VariableDescription), 1, schemaFile), 1);

		fclose(schemaFile);
	}
	return schema;
}

void DestroySchema(Schema* schema)
{
	if (schema)
	{
		free(schema->strings);
		free(schema->enums);
		free(schema->variables);
		free(schema);
	}
}

size_t CalculateEnumSize(const Schema& schema, const EnumDescription& enumDesc)
{
    NameValue* enumValues = schema.enums->values + enumDesc.nameValueTableOffset;
    uint64_t maxEnumValue = 0ULL;
    for (int i = 0; i < enumDesc.numberOfEnumValues; ++i)
        if (enumValues[i].value > maxEnumValue)
            maxEnumValue = enumValues[i].value;

    // If use indirection instead then could do this:
    // maxEnumValue = enumDesc.numberOfEnumValues;

    if (maxEnumValue <= UINT8_MAX)
        return sizeof(uint8_t);
    else if (maxEnumValue <= UINT16_MAX)
        return sizeof(uint16_t);
    else if (maxEnumValue <= UINT32_MAX)
        return sizeof(uint32_t);
    return sizeof(uint64_t);
}

size_t CalculateNumberSize(const Schema& schema, const NumberTypeEnum& numberDesc)
{
    switch (numberDesc)
    {
        case E_UInt8:
        case E_Int8:
            return 1;
        case E_UInt16:
        case E_Int16:
            return 2;
        case E_Int32:
        case E_UInt32:
        case E_Float32:
        case E_Angle:
        case E_Ratio:
            return 4;
        case E_Int64:
        case E_UInt64:
        case E_Float64:
            return 8;
    }
    return 16;
}

size_t CalculateAlignmentPadding(size_t currentStructSize, size_t alignToSize)
{
    return ((alignToSize - currentStructSize) % alignToSize);
}

size_t CalculateFieldSize(const Schema& schema, size_t structOffset, const VariableDescription& fieldDesc)
{
    size_t paddingSize = 0;
    size_t fieldSize = 0;
    switch (fieldDesc.variableType)
    {
        case VariableTypeEnum::E_Entity:
            fieldSize = sizeof(uint64_t);
            paddingSize = CalculateAlignmentPadding(structOffset, alignof(uint64_t));
            break;
        case VariableTypeEnum::E_Void:
            fieldSize = 0;
            break;
        case VariableTypeEnum::E_Bool:
            fieldSize = sizeof(bool);
            paddingSize = CalculateAlignmentPadding(structOffset, alignof(bool));
            break;
        case VariableTypeEnum::E_Enum:
            fieldSize = CalculateEnumSize(schema, fieldDesc.enumDescription);
            paddingSize = CalculateAlignmentPadding(structOffset, fieldSize);
            break;
        case VariableTypeEnum::E_String:
            fieldSize = fieldDesc.stringDescription.maximumCharacterCount;
            break;
        case VariableTypeEnum::E_Number:
            fieldSize = CalculateNumberSize(schema, fieldDesc.numberDescription.numberType);
            paddingSize = CalculateAlignmentPadding(structOffset, fieldSize);
            break;
        case VariableTypeEnum::E_EncodedNumber:
            // fieldSize = 0; // Not supported
            break;
        case VariableTypeEnum::E_Color:
            fieldSize = sizeof(uint32_t);
            paddingSize = CalculateAlignmentPadding(structOffset, alignof(uint32_t));
            break;
        case VariableTypeEnum::E_Asset:
            // fieldSize = 0; // Not supported yet
            break;
        case VariableTypeEnum::E_Struct:
            fieldSize = CalculateComponentSize(schema, fieldDesc.structDescription);
            break;
    }
    return paddingSize + fieldSize;
}

// TODO: alignment of fields
size_t CalculateComponentSize(const Schema& schema, const StructDescription& fields)
{
    size_t structSize = 0;
    size_t fieldSize = 0;
    VariableDescription* fieldsDesc = schema.variables->variables + fields.variableDescriptionTableOffset;
    for (int field = 0; field < fields.numberOfFields; ++field)
    {
        const VariableDescription& fieldDesc = fieldsDesc[field];
        structSize += CalculateFieldSize(schema, structSize, fieldDesc);
/*
        switch (fieldDesc.variableType)
        {
            case VariableTypeEnum::E_Entity:
                structSize += CalculateAlignmentPadding(structSize, alignof(uint64_t));
                structSize += sizeof(uint64_t);
                break;
            case VariableTypeEnum::E_Void:
                structSize += 0;
                break;
            case VariableTypeEnum::E_Bool:
                structSize += CalculateAlignmentPadding(structSize, alignof(bool));
                structSize += sizeof(bool);
                break;
            case VariableTypeEnum::E_Enum:
                fieldSize = CalculateEnumSize(schema, fieldDesc.enumDescription);
                structSize += CalculateAlignmentPadding(structSize, fieldSize);
                structSize += fieldSize;
                break;
            case VariableTypeEnum::E_String:
                structSize += fieldDesc.stringDescription.maximumCharacterCount;
                break;
            case VariableTypeEnum::E_Number:
                fieldSize = CalculateNumberSize(schema, fieldDesc.numberDescription.numberType);
                structSize += CalculateAlignmentPadding(structSize, fieldSize);
                structSize += fieldSize;
                break;
            case VariableTypeEnum::E_EncodedNumber:
                // structSize += 0; // Not supported
                break;
            case VariableTypeEnum::E_Color:
                structSize += CalculateAlignmentPadding(structSize, alignof(uint32_t));
                structSize += sizeof(uint32_t);
                break;
            case VariableTypeEnum::E_Asset:
                // structSize += 0; // Not supported yet
                break;
            case VariableTypeEnum::E_Struct:
                structSize += CalculateComponentSize(schema, fieldDesc.structDescription);
                break;
        }
*/
    }
    // TODO: will there be any trailing padding needed between elements of these in an array?
    return structSize;
}

} // GameRuntime namespace
