#pragma once

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

#include <cstddef>
#include <cstdint>

namespace GameRuntime {

enum class VariableTypeEnum
{
    E_Void,
    E_Bool,
    E_Enum,
    E_String,
    E_Number,
    E_EncodedNumber,
    E_Color,
    E_Entity,
    E_Asset,
    E_Struct,
    E_Component,
    E_Max
};

enum UnifiedTypeEnum
{
    // Extended/unified variable+number types
    //E_Void, // label
    //E_Bool8,
    //E_Entity64,
    //E_UInt8,
    //E_UInt16,
    //E_UInt32,
    //E_UInt64,
    //E_Int8,
    //E_Int16,
    //E_Int32,
    //E_Int64,
    //E_Enum8,
    //E_Enum16,
    //E_Enum32,
    //E_Enum64,
    //E_String64,   // 8 characters
    //E_String128,  // 16 characters
    //E_String256,  // 32 characters
    //E_String512,  // 64 characters
    //E_String1024, // 128 characters
    //E_String2048, // 256 characters
    //E_Color16_R5G6B5,
    //E_Color16_R5G5B5A1,
    //E_Color24_R8G8B8,
    //E_Color32_R8G8B8,
    //E_Color32_R8G8B8A8,
    //E_ColorFloat128_R32G32B32A32,
    //E_ColorFloat256_R64G64B64A64,
    //E_Float16,
    //E_Float32,
    //E_Float64,               // Scalar
    //E_Float128_X32Y32Z32W32, // Vector
    //E_Float256_X64Y64Z64W64, // Vector
    //E_Scientific32,
    //E_Scientific64,
    //E_Coordinate128_X32Y32Z32W32
    //E_Direction128_X32Y32Z32W32
    //E_Normal128_X32Y32Z32W32
    //E_EulerRadianAngles128_X32Y32Z32W32
    //E_EulerDegreeAngles128_X32Y32Z32W32
    //E_Quaternion128_X32Y32Z32W32
    //E_Angle8,
    //E_Angle16,
    //E_Angle32,
    //E_Angle64,
    //E_Ratio8,
    //E_Ratio16,
    //E_Ratio32,
    //E_Ratio64,
    //E_GeometryShaderAssetId32,
    //E_VertexShaderAssetId32,
    //E_FragmentShaderAssetId32,
    //E_TessellationShaderAssetId32,
    //E_ComputeShaderAssetId32,
    //E_SPIRVShaderAssetId32,
    //E_AudioAssetId32,
    //E_MeshAssetId32,
    //E_TextureAssetId32,
    //E_Struct8,   // 1 byte alignment
    //E_Struct16,  // 2 byte alignment
    //E_Struct32,
    //E_Struct64,
    //E_Struct128, // 16 byte alignment
    //E_Struct256, // 32 byte alignment
    //E_Struct512  // 64 byte alignment (cache line)

    //E_UnixTimeNano64   // Nanosceonds since UNIX epoch as 64bit value   1 January 1970


    // DateTime ideas:
    //   Age of the universe: 13.7 billion years old
    //   Epoch of 15 billion years ago ->  15,000,000,000 yrs  (36-bits)
    //   2^32 is                            4,294,967,296
    //   2^64 is               18,446,744,073,709,551,616
    //   60*60*24 = 86400 second / day
    //   31,536,000 seconds / year  (25-bits)
    //                             31,536,000,000,000,000 nanoseconds / year (55-bits)
    //   18,446,744,073,709,551,616 / 31,536,000,000,000,000 = 584
    //   584 years without rollover of nanoseconds in a 64bit value
    //   Idea:
    //     1-bit as flag for nanosecond datetime or seconds
    //       if 1 -> 38-bits for the year,  25-bits for the second of the year      (epoch of -15 billion years)
    //       if 0 ->  8-bits for the year,  55-bits for the nanosecond of the year  (epoch of 1 Jan 1970)  (only contemporary times need nanoseconds)
    //    might be easier to deal with leap seconds etc if the year is split out
};

enum NumberTypeEnum
{
    E_UInt8,
    E_UInt16,
    E_UInt32,
    E_UInt64,
    E_Int8,
    E_Int16,
    E_Int32,
    E_Int64,
    E_Float32,
    E_Float64,
    E_Angle,
    E_Ratio
};

/*
enum NumberPurposeEnum
{
    E_Numeric,   // Scalar
    E_Count,
    E_Scientific,
    E_Normal,
    E_Ratio,
    E_Color,
    E_Boolean,
    E_Void,
};

enum NumberRangeEnum
{
    E_Zero,                     // void
    E_BinaryZeroOrOne,          // bool
    E_ZeroToOne,                // 0.0 to 1.0 ratio
    E_Ratio = E_ZeroToOne,
    E_MinusOneToPlusOne,        // -1.0 to 1.0 normalized range
    E_NormalComponent = E_MinusOneToPlusOne,
    E_MinusPiToPlusPi,          // radian angle
    E_Byte,
    E_Character = E_Byte,
    E_Octet = E_Byte,
};
*/

enum ColorTypeEnum
{
    E_RGB32,
    E_RGBA32,

    // This might be more file format related
    E_R8G8B8,
    E_R16G15B16,
    E_R32G32B32,
    E_R8G8B8A8,
    E_B8G8R8A8,
    E_R16G15B16A16,
    E_R32G32B32A32,
    E_R32G32B32A32_FLOAT,
    E_R32G32B32A32_DOUBLE,
};

enum ShaderTypeEnum
{
    E_GeometryShader,
    E_VertexShader,
    E_FragmentShader,
    E_TessellationShader,
    E_ComputeShader,
    E_SPIRVShader
};

enum AssetTypeEnum
{
    E_Audio,
    E_Mesh,
    E_Shader,
    E_Texture
};

struct StringTable
{
    uint64_t                         count;
    char                             text[];
};

struct String
{
    uint32_t                         stringTableOffset;
    uint32_t                         stringLength;
};

struct NameValue
{
    String                           name;
    uint64_t                         value;
};

struct NameValueTable
{
    uint64_t                         count;
    NameValue                        values[];
};

struct NumberDescription
{
    NumberTypeEnum                   numberType;
};

struct EntityDescription
{
};

struct StringDescription
{
    uint32_t                         maximumCharacterCount;
};

struct EnumDescription
{
    uint32_t                         numberOfEnumValues;
    uint32_t                         nameValueTableOffset;
};

struct StructDescription
{
    uint32_t                         numberOfFields;
    uint32_t                         variableDescriptionTableOffset;
};

struct ColorDescription
{
    ColorTypeEnum                    colorType;
};

struct AssetDescription
{
    AssetTypeEnum                    assetType;
};

// This might be better for file formats than for component types
struct EncodedNumberDescription
{
    bool                             hasNullValue;  // Can it encode a special 'NoValue' value
    bool                             hasSignBit;
    uint8_t                          exponentBits;
    uint8_t                          mantissaBits;  // significant
    int32_t                          exponentBias;  // For 128bit wide floats, the exponent can be more than 16 bits so we need a 32bit value for this
    double                           scale;         // Can this handle the exponentBias for us? I am thinking it can't.
    double                           bias;
};

struct VariableDescription
{
    uint64_t                         uid;
    String                           name;
    VariableTypeEnum                 variableType;
    union
    {
        NumberDescription            numberDescription;
        // EncodedNumberDescription     encodedNumberDescription;
        StringDescription            stringDescription;
        EnumDescription              enumDescription;
        EntityDescription            entityDescription;
        StructDescription            structDescription;
        StructDescription            componentDescription;
        ColorDescription             colorDescription;
        AssetDescription             assetDescription;
    };
};

struct VariableDescriptionTable
{
    uint64_t                         count;
    VariableDescription              variables[];
};

struct Schema
{
    StringTable*                     strings;
    NameValueTable*                  enums;
    VariableDescriptionTable*        variables;
};

struct SchemaFileFormatHeader
{
    uint32_t                         fileFormatCode;
    uint32_t                         formatVersion;
    uint32_t                         fileSize;
    uint32_t                         headerSize;
    uint32_t                         stringTableSize;
    uint32_t                         stringTableFileOffset;
    uint32_t                         nameValueTableSize;
    uint32_t                         nameValueTableFileOffset;
    uint32_t                         variableDescriptionTableSize;
    uint32_t                         variableDescriptionTableFileOffset;
};

Schema* LoadSchemaFromFile(const char* schemaFileName);
void DestroySchema(Schema* schema);

size_t CalculateEnumSize(const Schema& schema, const EnumDescription& enumDesc);
size_t CalculateNumberSize(const Schema& schema, const NumberTypeEnum& numberDesc);
size_t CalculateFieldSize(const Schema& schema, size_t structOffset, const VariableDescription& fieldDesc);
size_t CalculateComponentSize(const Schema& schema, const StructDescription& fields);

} // GameRuntime namespace
