#include "CppGen.h"
#include "Logging.h"
#include "TypeDef.h"
#include "bx/string.h"

#include <bx/file.h>
#include <cstdarg>
#include <memoryapi.h>
#include <winnt.h>

#define WIN32_LEAN_AND_MEAN
#include "Windows.h"

namespace WriteTemplates
{
    constexpr char FileHeaderStart[] =
        R"END(#pragma once
#include <cstdint>

namespace Generated
{
    struct Serializer;
    struct Deserializer;
)END";
    constexpr char FileCppStart[] = R"END(#include "Def.h"
#include "Generated.h"

namespace Generated
{
)END";

    constexpr char StructHeader2[] =
        R"END(    struct %s
    {
        static constexpr uint32_t Hash = %u;
)END";

    constexpr char StructField4[] =
        R"END(        %s %s%s = %s;
)END";

    constexpr char StructEnd[] =
        R"END(    };
)END";

    constexpr char EnumHeader4[] =
        R"END(    struct %s
    {
        static constexpr int32_t EntryCount = %u;
        static constexpr uint32_t Hash = %u;
        enum Enum : %s
        {
)END";

    constexpr char EnumField1[] =
        R"END(            %s,
)END";

    constexpr char EnumNamesStart2[] =
        R"END(        };
        static constexpr char %s[EntryCount][%u]
        {
)END";

    constexpr char EnumNamesEntry1[] =
        R"END(            "%s",
)END";

    constexpr char EnumNamesEnd[] =
        R"END(        };
    };
)END";

    constexpr char FileEnd[] =
        R"END(}
)END";

    constexpr char SaveFuncHeader1[] =
        R"END(    bool Save(const %s* obj, uint32_t count, Serializer& serializer);
)END";
    constexpr char SaveFuncBodyStart1[] = R"END(    bool Save(const %s* obj, uint32_t count, Serializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
)END";
    constexpr char SaveFuncBodyType3[] = R"END(            isOk = Save(%sobj[i].%s, %u, serializer) && isOk;
)END";
    constexpr char SaveFuncBodyEnum1[] = R"END(            auto val = (%s)obj[i];
            isOk = Save(&val, 1, serializer) && isOk;
)END";
    constexpr char SaveFuncBodyEnd[] = R"END(        }
        return isOk;
    }
)END";

    constexpr char LoadFuncHeader1[] =
        R"END(    bool Load(%s* obj, uint32_t count, Deserializer& serializer);
)END";
    constexpr char LoadFuncBodyStart1[] = R"END(    bool Load(%s* obj, uint32_t count, Deserializer& serializer)
    {
        bool isOk = true;
        for (uint32_t i = 0; i < count; ++i)
        {
)END";
    constexpr char LoadFuncBodyType3[] = R"END(            isOk = Load(%sobj[i].%s, %u, serializer) && isOk;
)END";
    constexpr char LoadFuncBodyEnum2[] = R"END(            %s& val = (%s&)obj[i];
            isOk = Load(&val, 1, serializer) && isOk;
)END";
    constexpr char LoadFuncBodyEnd[] = R"END(        }
        return isOk;
    }
)END";
} // namespace WriteTemplates

void CppFileWriter::InitBuffer(WriteBuffer& buf)
{
    if (buf.Data != nullptr)
    {
        LOG_ERROR(0, "Multiple writes not supported yet!");
        return;
    }
    buf.Data =
        reinterpret_cast<char*>(VirtualAlloc(nullptr, BufferRequestSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
    if (buf.Data == nullptr)
    {
        LOG_ERROR(0, "Failed to allocate write memory!");
        return;
    }
    buf.WrittenBytes = 0;
}

bool CppFileWriter::WriteBufferToDisk(const bx::FilePath& path, const WriteBuffer& buf)
{
    bx::Error error;
    bx::FileWriter writer;

    LOG(0, "Writing to %s", path.getCPtr());
    if (!writer.open(path, false, &error))
    {
        LOG_ERROR(0, "Failed to open output file: %s", error.getMessage().getCPtr());
        return false;
    }

    writer.write(buf.Data, buf.WrittenBytes, &error);
    if (!error.isOk())
    {
        LOG_ERROR(0, "Failed to write to output file: %s", error.getMessage().getCPtr());
    }
    writer.close();
    LOG(0, "Finished writing!");
    return true;
}

void CppFileWriter::WriteInternal(WriteBuffer& buf, const char* templateStr, va_list args)
{
    if (buf.Data == nullptr)
    {
        LOG_ERROR(0, "Wrote too early!");
        return;
    }

    buf.WrittenBytes += bx::vsnprintf(&buf.Data[buf.WrittenBytes], BufferRequestSize, templateStr, args);
}

void CppFileWriter::PrintTypeName(char* buf,
                                  int32_t bufSize,
                                  const Def::FieldType& type,
                                  const Def::DefinitionFile& definitions)
{
    if (type.FieldKind == Def::EFieldType::Native)
    {
        if (int32_t(type.Native) < Generated::KnownType::EntryCount)
        {
            bx::strCopy(buf, bufSize, Generated::KnownType::CName[size_t(type.Native)]);
        }
        else
        {
            LOG_ERROR(0, "Unknown native type index: %u", type.Native);
        }
    }
    else if (type.FieldKind == Def::EFieldType::DefinedClass)
    {
        bx::strCopy(buf, bufSize, definitions.Types[type.TypeIdx].Name);
    }
    else if (type.FieldKind == Def::EFieldType::DefinedEnum)
    {
        bx::strCopy(buf, bufSize, definitions.Enums[type.TypeIdx].Name);
        bx::strCat(buf, bufSize, "::Enum");
    }
}

void CppFileWriter::WriteEnums(const Def::DefinitionFile& definitions)
{
    for (int32_t enumIdx = 0; enumIdx < definitions.EnumCount; ++enumIdx)
    {
        const Def::Enum& e = definitions.Enums[enumIdx];

        Write(WriteTemplates::EnumHeader4,
              e.Name,
              e.EntryCount,
              e.Hash,
              Generated::KnownType::CName[(int32_t)e.EnumType.Native]);
        for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
        {
            Write(WriteTemplates::EnumField1, e.EntryNames[entryIdx]);
        }

        Write(WriteTemplates::EnumNamesStart2, "EntryNames", Def::MaxNameLength);
        for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
        {
            Write(WriteTemplates::EnumNamesEntry1, e.EntryNames[entryIdx]);
        }

        for (int32_t extraIdx = 0; extraIdx < e.ExtraStringFieldCount; ++extraIdx)
        {
            Write(WriteTemplates::EnumNamesStart2, e.ExtraStringFieldNames[extraIdx], Def::MaxNameLength);
            for (int32_t entryIdx = 0; entryIdx < e.EntryCount; ++entryIdx)
            {
                Write(WriteTemplates::EnumNamesEntry1, e.ExtraStringFields[entryIdx][extraIdx]);
            }
        }

        Write(WriteTemplates::EnumNamesEnd);
    }
}
void CppFileWriter::WriteTypes(const Def::DefinitionFile& definitions)
{
    for (int32_t typeIdx = 0; typeIdx < definitions.TypeCount; ++typeIdx)
    {
        const Def::Type& t = definitions.Types[typeIdx];

        Write(WriteTemplates::StructHeader2, t.Name, t.Hash);
        for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
        {
            char Type[64]{0};
            PrintTypeName(Type, sizeof(Type), t.FieldTypes[fieldIdx], definitions);
            char Array[32]{0};
            uint32_t ArraySize = t.FieldArraySizes[fieldIdx];
            if (ArraySize > 0)
            {
                bx::snprintf(Array, sizeof(Array), "[%u]", ArraySize);
            }
            Write(WriteTemplates::StructField4, Type, t.FieldNames[fieldIdx], Array, t.FieldValues[fieldIdx]);
        }
        Write(WriteTemplates::StructEnd);
    }
}
void CppFileWriter::WriteSaveLoadMethods(const Def::DefinitionFile& definitions)
{
    char nameBuf[64]{0};
    char fieldBuf[64]{0};
    for (int32_t enumIdx = 0; enumIdx < definitions.EnumCount; ++enumIdx)
    {
        const Def::Enum& e = definitions.Enums[enumIdx];
        bx::snprintf(nameBuf, sizeof(nameBuf), "%s::Enum", e.Name);
        PrintTypeName(fieldBuf, sizeof(fieldBuf), e.EnumType, definitions);

        Write(WriteTemplates::SaveFuncHeader1, nameBuf);
        Write(WriteTemplates::LoadFuncHeader1, nameBuf);

        WriteCpp(WriteTemplates::SaveFuncBodyStart1, nameBuf);
        WriteCpp(WriteTemplates::SaveFuncBodyEnum1, fieldBuf);
        WriteCpp(WriteTemplates::SaveFuncBodyEnd);

        WriteCpp(WriteTemplates::LoadFuncBodyStart1, nameBuf);
        WriteCpp(WriteTemplates::LoadFuncBodyEnum2, fieldBuf, fieldBuf);
        WriteCpp(WriteTemplates::LoadFuncBodyEnd);
    }
    for (int32_t typeIdx = 0; typeIdx < definitions.TypeCount; ++typeIdx)
    {
        const Def::Type& t = definitions.Types[typeIdx];
        Write(WriteTemplates::SaveFuncHeader1, t.Name);
        Write(WriteTemplates::LoadFuncHeader1, t.Name);

        WriteCpp(WriteTemplates::SaveFuncBodyStart1, t.Name);
        for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
        {
            WriteCpp(WriteTemplates::SaveFuncBodyType3,
                     t.FieldArraySizes[fieldIdx] > 0 ? "" : "&",
                     t.FieldNames[fieldIdx],
                     bx::max(1, t.FieldArraySizes[fieldIdx]));
        }
        WriteCpp(WriteTemplates::SaveFuncBodyEnd);

        WriteCpp(WriteTemplates::LoadFuncBodyStart1, t.Name);
        for (int32_t fieldIdx = 0; fieldIdx < t.FieldCount; ++fieldIdx)
        {
            WriteCpp(WriteTemplates::LoadFuncBodyType3,
                     t.FieldArraySizes[fieldIdx] > 0 ? "" : "&",
                     t.FieldNames[fieldIdx],
                     bx::max(1, t.FieldArraySizes[fieldIdx]));
        }
        WriteCpp(WriteTemplates::LoadFuncBodyEnd);
    }
}
void CppFileWriter::GenerateCpp(const bx::FilePath& outDir, const Def::DefinitionFile& definitions)
{
    LOG(0, "Generating output files...");

    InitBuffer(HeaderWrite);
    InitBuffer(CppWrite);

    Write(WriteTemplates::FileHeaderStart);
    WriteCpp(WriteTemplates::FileCppStart);
    WriteEnums(definitions);
    WriteTypes(definitions);
    WriteSaveLoadMethods(definitions);
    Write(WriteTemplates::FileEnd);
    WriteCpp(WriteTemplates::FileEnd);

    // std::filesystem::create_directory(outDir.getCPtr());
    bx::FilePath writePathHeader = outDir;
    bx::FilePath writePathCpp = outDir;
    writePathHeader.join("Generated.h");
    writePathCpp.join("Generated.cpp");
    WriteBufferToDisk(writePathHeader, HeaderWrite);
    WriteBufferToDisk(writePathCpp, CppWrite);
}

void CppFileWriter::Write(const char* templateStr, ...)
{
    va_list args;
    va_start(args, templateStr);
    WriteInternal(HeaderWrite, templateStr, args);
    va_end(args);
}

void CppFileWriter::WriteCpp(const char* templateStr, ...)
{
    va_list args;
    va_start(args, templateStr);
    WriteInternal(CppWrite, templateStr, args);
    va_end(args);
}

void CppFileWriter::WriteTypeDataFile(const bx::FilePath& outDir, const Def::DefinitionFile& definitions)
{
    constexpr const char _4cc[] = "TDAT";
    constexpr uint32_t version = 1;
    bx::FilePath typeDataFilePath = outDir;
    typeDataFilePath.join("TypeData.bin");

    bx::Error err;
    bx::FileWriter writer;
    if (!writer.open(typeDataFilePath, false, &err))
    {
        LOG_ERROR(0, "Failed to open type data file to write: %s", err.getMessage().getCPtr());
        return;
    }
    uint32_t size = sizeof(definitions);
    writer.write(_4cc, 4, &err);
    if (!err.isOk())
    {
        LOG_ERROR(0, "Failed to write type data: %s", err.getMessage().getCPtr());
        writer.close();
        return;
    }
    writer.write(&version, sizeof(version), &err);
    writer.write(&size, sizeof(size), &err);
    writer.write(&definitions, sizeof(definitions), &err);
    if (!err.isOk())
    {
        LOG_ERROR(0, "Failed to write type data: %s", err.getMessage().getCPtr());
    }
    writer.close();
    return;
}
