#include "ZoneWriteTemplate.h"

#include "Domain/Computations/StructureComputations.h"
#include "Internal/BaseTemplate.h"

#include <cassert>
#include <cstdint>
#include <sstream>

namespace
{
    constexpr int TAG_HEADER = 1;
    constexpr int TAG_SOURCE = 2;

    class Template final : BaseTemplate
    {
    public:
        Template(std::ostream& stream, const RenderingContext& context)
            : BaseTemplate(stream, context)
        {
        }

        void Header()
        {
            LINE("// ====================================================================")
            LINE("// This file has been generated by ZoneCodeGenerator.")
            LINE("// Do not modify.")
            LINE("// Any changes will be discarded when regenerating.")
            LINE("// ====================================================================")
            LINE("")
            LINE("#pragma once")
            LINE("")
            LINEF("#include \"Game/{0}/{0}.h\"", m_env.m_game)
            LINE("#include \"Writing/AssetWriter.h\"")
            LINE("")
            LINE("#include <string>")
            LINE("")
            LINEF("namespace {0}", m_env.m_game)
            LINE("{")
            m_intendation++;
            LINEF("class {0} final : public AssetWriter", WriterClassName(m_env.m_asset))
            LINE("{")
            m_intendation++;

            m_intendation--;
            LINE("public:")
            m_intendation++;
            PrintHeaderConstructor();
            PrintHeaderMainWriteMethodDeclaration(m_env.m_asset);

            LINE("")
            m_intendation--;
            LINE("private:")
            m_intendation++;

            // Method Declarations
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_pointer_array_reference_exists)
                {
                    PrintHeaderPtrArrayWriteMethodDeclaration(type->m_type);
                }
            }
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_array_reference_exists && type->m_info && !type->m_info->m_is_leaf && type->m_non_runtime_reference_exists)
                {
                    PrintHeaderArrayWriteMethodDeclaration(type->m_type);
                }
            }
            for (const auto* type : m_env.m_used_structures)
            {
                if (type->m_non_runtime_reference_exists && !type->m_info->m_is_leaf && !StructureComputations(type->m_info).IsAsset())
                {
                    PrintHeaderWriteMethodDeclaration(type->m_info);
                }
            }
            PrintHeaderWriteMethodDeclaration(m_env.m_asset);
            PrintHeaderTempPtrWriteMethodDeclaration(m_env.m_asset);

            LINE("")

            LINE(VariableDecl(m_env.m_asset->m_definition))
            LINE(WrittenVariableDecl(m_env.m_asset->m_definition))
            LINE(PointerVariableDecl(m_env.m_asset->m_definition))
            LINE(WrittenPointerVariableDecl(m_env.m_asset->m_definition))
            LINE("")

            // Variable Declarations: type varType;
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_info && !type->m_info->m_definition->m_anonymous && !type->m_info->m_is_leaf && !StructureComputations(type->m_info).IsAsset())
                {
                    LINE(VariableDecl(type->m_type))
                    LINE(WrittenVariableDecl(type->m_type))
                }
            }
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_pointer_array_reference_exists && !type->m_is_context_asset)
                {
                    LINE(PointerVariableDecl(type->m_type))
                    LINE(WrittenPointerVariableDecl(type->m_type))
                }
            }

            m_intendation--;
            LINE("};")
            m_intendation--;
            LINE("}")
        }

        void Source()
        {
            LINE("// ====================================================================")
            LINE("// This file has been generated by ZoneCodeGenerator.")
            LINE("// Do not modify.")
            LINE("// Any changes will be discarded when regenerating.")
            LINE("// ====================================================================")
            LINE("")
            LINEF("#include \"{0}_write_db.h\"", Lower(m_env.m_asset->m_definition->m_name))

            if (!m_env.m_referenced_assets.empty())
            {
                LINE("")
                LINE("// Referenced Assets:")
                for (const auto* type : m_env.m_referenced_assets)
                {
                    LINEF("#include \"../{0}/{0}_write_db.h\"", Lower(type->m_type->m_name))
                }
            }

            LINE("")
            LINE("#include <cassert>")
            LINE("")
            LINEF("using namespace {0};", m_env.m_game)
            LINE("")
            PrintConstructorMethod();
            LINE("")
            PrintMainWriteMethod();

            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_pointer_array_reference_exists)
                {
                    LINE("")
                    PrintWritePtrArrayMethod(type->m_type, type->m_info, type->m_pointer_array_reference_is_reusable);
                }
            }
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_array_reference_exists && type->m_info && !type->m_info->m_is_leaf && type->m_non_runtime_reference_exists)
                {
                    LINE("")
                    PrintWriteArrayMethod(type->m_type, type->m_info);
                }
            }
            for (const auto* type : m_env.m_used_structures)
            {
                if (type->m_non_runtime_reference_exists && !type->m_info->m_is_leaf && !StructureComputations(type->m_info).IsAsset())
                {
                    LINE("")
                    PrintWriteMethod(type->m_info);
                }
            }
            LINE("")
            PrintWriteMethod(m_env.m_asset);
            LINE("")
            PrintWritePtrMethod(m_env.m_asset);
        }

    private:
        enum class MemberWriteType : std::uint8_t
        {
            ARRAY_POINTER,
            DYNAMIC_ARRAY,
            EMBEDDED,
            EMBEDDED_ARRAY,
            POINTER_ARRAY,
            SINGLE_POINTER
        };

        static std::string WriterClassName(const StructureInformation* asset)
        {
            return std::format("Writer_{0}", asset->m_definition->m_name);
        }

        static std::string VariableDecl(const DataDefinition* def)
        {
            return std::format("{0}* var{1};", def->GetFullName(), MakeSafeTypeName(def));
        }

        static std::string WrittenVariableDecl(const DataDefinition* def)
        {
            return std::format("{0}* var{1}Written;", def->GetFullName(), MakeSafeTypeName(def));
        }

        static std::string PointerVariableDecl(const DataDefinition* def)
        {
            return std::format("{0}** var{1}Ptr;", def->GetFullName(), MakeSafeTypeName(def));
        }

        static std::string WrittenPointerVariableDecl(const DataDefinition* def)
        {
            return std::format("{0}** var{1}PtrWritten;", def->GetFullName(), MakeSafeTypeName(def));
        }

        void PrintHeaderPtrArrayWriteMethodDeclaration(const DataDefinition* def) const
        {
            LINEF("void WritePtrArray_{0}(bool atStreamStart, size_t count);", MakeSafeTypeName(def))
        }

        void PrintHeaderArrayWriteMethodDeclaration(const DataDefinition* def) const
        {
            LINEF("void WriteArray_{0}(bool atStreamStart, size_t count);", MakeSafeTypeName(def))
        }

        void PrintHeaderWriteMethodDeclaration(const StructureInformation* info) const
        {
            LINEF("void Write_{0}(bool atStreamStart);", MakeSafeTypeName(info->m_definition))
        }

        void PrintHeaderTempPtrWriteMethodDeclaration(const StructureInformation* info) const
        {
            LINEF("void WritePtr_{0}(bool atStreamStart);", MakeSafeTypeName(info->m_definition))
        }

        void PrintHeaderMainWriteMethodDeclaration(const StructureInformation* info) const
        {
            LINEF("void Write({0}** pAsset);", info->m_definition->GetFullName())
        }

        void PrintHeaderConstructor() const
        {
            LINEF("{0}({1}* asset, const Zone& zone, IZoneOutputStream& stream);", WriterClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName())
        }

        void PrintVariableInitialization(const DataDefinition* def) const
        {
            LINEF("var{0} = nullptr;", def->m_name)
        }

        void PrintWrittenVariableInitialization(const DataDefinition* def) const
        {
            LINEF("var{0}Written = nullptr;", def->m_name)
        }

        void PrintPointerVariableInitialization(const DataDefinition* def) const
        {
            LINEF("var{0}Ptr = nullptr;", def->m_name)
        }

        void PrintWrittenPointerVariableInitialization(const DataDefinition* def) const
        {
            LINEF("var{0}PtrWritten = nullptr;", def->m_name)
        }

        void PrintConstructorMethod()
        {
            LINEF(
                "{0}::{0}({1}* asset, const Zone& zone, IZoneOutputStream& stream)", WriterClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName())

            m_intendation++;
            LINEF(": AssetWriter(zone.m_pools->GetAssetOrAssetReference({0}::EnumEntry, AssetNameAccessor<{0}>()(*asset)), zone, stream)",
                  m_env.m_asset->m_asset_name)
            m_intendation--;

            LINE("{")
            m_intendation++;

            PrintVariableInitialization(m_env.m_asset->m_definition);
            PrintWrittenVariableInitialization(m_env.m_asset->m_definition);
            PrintPointerVariableInitialization(m_env.m_asset->m_definition);
            PrintWrittenPointerVariableInitialization(m_env.m_asset->m_definition);
            LINE("")

            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_info && !type->m_info->m_definition->m_anonymous && !type->m_info->m_is_leaf && !StructureComputations(type->m_info).IsAsset())
                {
                    PrintVariableInitialization(type->m_type);
                    PrintWrittenVariableInitialization(type->m_type);
                }
            }
            for (const auto* type : m_env.m_used_types)
            {
                if (type->m_info && type->m_pointer_array_reference_exists && !type->m_is_context_asset)
                {
                    PrintPointerVariableInitialization(type->m_type);
                    PrintWrittenPointerVariableInitialization(type->m_type);
                }
            }

            m_intendation--;
            LINE("}")
        }

        void WriteMember_ScriptString(const StructureInformation* info,
                                      const MemberInformation* member,
                                      const DeclarationModifierComputations& modifier,
                                      const MemberWriteType writeType) const
        {
            if (writeType == MemberWriteType::ARRAY_POINTER)
            {
                LINEF("varScriptString = {0};", MakeMemberAccess(info, member, modifier))
                LINEF("m_stream->MarkFollowing({0});", MakeWrittenMemberAccess(info, member, modifier))
                LINEF("WriteScriptStringArray(true, {0});", MakeEvaluation(modifier.GetArrayPointerCountEvaluation()))
            }
            else if (writeType == MemberWriteType::EMBEDDED_ARRAY)
            {
                LINEF("varScriptStringWritten = {0};", MakeWrittenMemberAccess(info, member, modifier))
                LINEF("WriteScriptStringArray(false, {0});", MakeArrayCount(dynamic_cast<ArrayDeclarationModifier*>(modifier.GetDeclarationModifier())))
            }
            else if (writeType == MemberWriteType::EMBEDDED)
            {
                LINEF("{0} = UseScriptString({1});", MakeWrittenMemberAccess(info, member, modifier), MakeMemberAccess(info, member, modifier))
            }
            else
            {
                assert(false);
                LINEF("#error unsupported writeType {0} for script string", static_cast<int>(writeType))
            }
        }

        void WriteMember_Asset(const StructureInformation* info,
                               const MemberInformation* member,
                               const DeclarationModifierComputations& modifier,
                               const MemberWriteType writeType) const
        {
            if (writeType == MemberWriteType::SINGLE_POINTER)
            {
                LINEF("{0} writer({1}, m_zone, *m_stream);", WriterClassName(member->m_type), MakeMemberAccess(info, member, modifier))
                LINEF("writer.Write(&{0});", MakeWrittenMemberAccess(info, member, modifier))
            }
            else if (writeType == MemberWriteType::POINTER_ARRAY)
            {
                WriteMember_PointerArray(info, member, modifier);
            }
            else
            {
                assert(false);
                LINEF("#error unsupported writeType {0} for asset", static_cast<int>(writeType))
            }
        }

        void WriteMember_String(const StructureInformation* info,
                                const MemberInformation* member,
                                const DeclarationModifierComputations& modifier,
                                const MemberWriteType writeType) const
        {
            if (writeType == MemberWriteType::SINGLE_POINTER)
            {
                if (member->m_member->m_type_declaration->m_is_const)
                {
                    LINEF("varXStringWritten = &{0};", MakeWrittenMemberAccess(info, member, modifier))
                }
                else
                {
                    LINEF("varXStringWritten = const_cast<const char**>(&{0});", MakeWrittenMemberAccess(info, member, modifier))
                }
                LINE("WriteXString(false);")
            }
            else if (writeType == MemberWriteType::POINTER_ARRAY)
            {
                if (modifier.IsArray())
                {
                    LINEF("varXStringWritten = {0};", MakeWrittenMemberAccess(info, member, modifier))
                    LINEF("WriteXStringArray(false, {0});", modifier.GetArraySize())
                }
                else
                {
                    LINEF("varXString = {0};", MakeMemberAccess(info, member, modifier))
                    LINEF("m_stream->MarkFollowing({0});", MakeWrittenMemberAccess(info, member, modifier))
                    LINEF("WriteXStringArray(true, {0});", MakeEvaluation(modifier.GetPointerArrayCountEvaluation()))
                }
            }
            else
            {
                assert(false);
                LINEF("#error unsupported writeType {0} for string", static_cast<int>(writeType))
            }
        }

        void WriteMember_ArrayPointer(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            const MemberComputations computations(member);
            LINEF("m_stream->MarkFollowing({0});", MakeWrittenMemberAccess(info, member, modifier))
            if (member->m_type && !member->m_type->m_is_leaf && !computations.IsInRuntimeBlock())
            {
                LINEF("{0} = {1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                LINEF("WriteArray_{0}(true, {1});",
                      MakeSafeTypeName(member->m_member->m_type_declaration->m_type),
                      MakeEvaluation(modifier.GetArrayPointerCountEvaluation()))
            }
            else
            {
                LINEF("m_stream->Write<{0}{1}>({2}, {3});",
                      MakeTypeDecl(member->m_member->m_type_declaration.get()),
                      MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers()),
                      MakeMemberAccess(info, member, modifier),
                      MakeEvaluation(modifier.GetArrayPointerCountEvaluation()))
            }
        }

        void WriteMember_PointerArray(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            LINEF("{0} = {1};", MakeTypePtrVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
            if (modifier.IsArray())
            {
                LINEF("{0} = {1};", MakeTypeWrittenPtrVarName(member->m_member->m_type_declaration->m_type), MakeWrittenMemberAccess(info, member, modifier))
                LINEF("WritePtrArray_{0}(false, {1});", MakeSafeTypeName(member->m_member->m_type_declaration->m_type), modifier.GetArraySize())
            }
            else
            {
                LINEF("m_stream->MarkFollowing({0});", MakeWrittenMemberAccess(info, member, modifier))
                LINEF("WritePtrArray_{0}(true, {1});",
                      MakeSafeTypeName(member->m_member->m_type_declaration->m_type),
                      MakeEvaluation(modifier.GetPointerArrayCountEvaluation()))
            }
        }

        void WriteMember_EmbeddedArray(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            const MemberComputations computations(member);
            std::string arraySizeStr;

            if (modifier.HasDynamicArrayCount())
                arraySizeStr = MakeEvaluation(modifier.GetDynamicArrayCountEvaluation());
            else
                arraySizeStr = std::to_string(modifier.GetArraySize());

            if (!member->m_is_leaf)
            {
                if (computations.IsAfterPartialLoad())
                {
                    LINEF("{0} = {1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                    LINEF("WriteArray_{0}(true, {1});", MakeSafeTypeName(member->m_member->m_type_declaration->m_type), arraySizeStr)
                }
                else
                {
                    LINEF("{0} = {1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                    LINEF("{0} = {1};", MakeTypeWrittenVarName(member->m_member->m_type_declaration->m_type), MakeWrittenMemberAccess(info, member, modifier))
                    LINEF("WriteArray_{0}(false, {1});", MakeSafeTypeName(member->m_member->m_type_declaration->m_type), arraySizeStr)
                }
            }
            else if (computations.IsAfterPartialLoad())
            {
                LINEF("m_stream->Write<{0}{1}>({2}, {3});",
                      MakeTypeDecl(member->m_member->m_type_declaration.get()),
                      MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers()),
                      MakeMemberAccess(info, member, modifier),
                      arraySizeStr)
            }
        }

        void WriteMember_DynamicArray(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            if (member->m_type && !member->m_type->m_is_leaf)
            {
                LINEF("{0} = {1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                LINEF("WriteArray_{0}(true, {1});",
                      MakeSafeTypeName(member->m_member->m_type_declaration->m_type),
                      MakeEvaluation(modifier.GetDynamicArraySizeEvaluation()))
            }
            else
            {
                LINEF("m_stream->Write<{0}{1}>({2}, {3});",
                      MakeTypeDecl(member->m_member->m_type_declaration.get()),
                      MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers()),
                      MakeMemberAccess(info, member, modifier),
                      MakeEvaluation(modifier.GetDynamicArraySizeEvaluation()))
            }
        }

        void WriteMember_Embedded(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            const MemberComputations computations(member);

            if (!member->m_is_leaf)
            {
                if (computations.IsAfterPartialLoad())
                {
                    LINEF("{0} = &{1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                    LINEF("Write_{0}(true);", MakeSafeTypeName(member->m_member->m_type_declaration->m_type))
                }
                else
                {
                    LINEF("{0} = &{1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                    LINEF("{0} = &{1};", MakeTypeWrittenVarName(member->m_member->m_type_declaration->m_type), MakeWrittenMemberAccess(info, member, modifier))
                    LINEF("Write_{0}(false);", MakeSafeTypeName(member->m_member->m_type_declaration->m_type))
                }
            }
            else if (computations.IsAfterPartialLoad())
            {
                LINEF("m_stream->Write<{0}{1}>(&{2});",
                      MakeTypeDecl(member->m_member->m_type_declaration.get()),
                      MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers()),
                      MakeMemberAccess(info, member, modifier))
            }
        }

        void WriteMember_SinglePointer(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier) const
        {
            const MemberComputations computations(member);
            LINEF("m_stream->MarkFollowing({0});", MakeWrittenMemberAccess(info, member, modifier))
            if (member->m_type && !member->m_type->m_is_leaf && !computations.IsInRuntimeBlock())
            {
                LINEF("{0} = {1};", MakeTypeVarName(member->m_member->m_type_declaration->m_type), MakeMemberAccess(info, member, modifier))
                LINEF("Write_{0}(true);", MakeSafeTypeName(member->m_type->m_definition))
            }
            else
            {
                LINEF("m_stream->Write<{0}{1}>({2});",
                      MakeTypeDecl(member->m_member->m_type_declaration.get()),
                      MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers()),
                      MakeMemberAccess(info, member, modifier))
            }
        }

        void WriteMember_TypeCheck(const StructureInformation* info,
                                   const MemberInformation* member,
                                   const DeclarationModifierComputations& modifier,
                                   const MemberWriteType writeType) const
        {
            if (member->m_is_string)
            {
                WriteMember_String(info, member, modifier, writeType);
            }
            else if (member->m_is_script_string)
            {
                WriteMember_ScriptString(info, member, modifier, writeType);
            }
            else if (member->m_type && StructureComputations(member->m_type).IsAsset())
            {
                WriteMember_Asset(info, member, modifier, writeType);
            }
            else
            {
                switch (writeType)
                {
                case MemberWriteType::ARRAY_POINTER:
                    WriteMember_ArrayPointer(info, member, modifier);
                    break;

                case MemberWriteType::SINGLE_POINTER:
                    WriteMember_SinglePointer(info, member, modifier);
                    break;

                case MemberWriteType::EMBEDDED:
                    WriteMember_Embedded(info, member, modifier);
                    break;

                case MemberWriteType::POINTER_ARRAY:
                    WriteMember_PointerArray(info, member, modifier);
                    break;

                case MemberWriteType::DYNAMIC_ARRAY:
                    WriteMember_DynamicArray(info, member, modifier);
                    break;

                case MemberWriteType::EMBEDDED_ARRAY:
                    WriteMember_EmbeddedArray(info, member, modifier);
                    break;

                default:
                    LINEF("// t={0}", static_cast<int>(writeType))
                    break;
                }
            }
        }

        static bool
            WriteMember_ShouldMakeInsertReuse(const MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
        {
            if (writeType != MemberWriteType::ARRAY_POINTER && writeType != MemberWriteType::SINGLE_POINTER && writeType != MemberWriteType::POINTER_ARRAY)
            {
                return false;
            }

            if (writeType == MemberWriteType::POINTER_ARRAY && modifier.IsArray())
            {
                return false;
            }

            if (member->m_is_string)
            {
                return false;
            }

            if (member->m_type && StructureComputations(member->m_type).IsAsset())
            {
                return false;
            }

            if (member->m_is_reusable)
                return true;

            if (member->m_type == nullptr || !member->m_type->m_reusable_reference_exists)
                return false;

            return true;
        }

        void WriteMember_InsertReuse(const StructureInformation* info,
                                     const MemberInformation* member,
                                     const DeclarationModifierComputations& modifier,
                                     const MemberWriteType writeType) const
        {
            if (!WriteMember_ShouldMakeInsertReuse(member, modifier, writeType))
            {
                WriteMember_TypeCheck(info, member, modifier, writeType);
                return;
            }

            if (writeType == MemberWriteType::ARRAY_POINTER)
            {
                LINEF("m_stream->ReusableAddOffset({0}, {1});",
                      MakeMemberAccess(info, member, modifier),
                      MakeEvaluation(modifier.GetArrayPointerCountEvaluation()))
            }
            else if (writeType == MemberWriteType::POINTER_ARRAY)
            {
                const auto* evaluation = modifier.GetPointerArrayCountEvaluation();

                if (evaluation)
                {
                    LINEF("m_stream->ReusableAddOffset({0}, {1});",
                          MakeMemberAccess(info, member, modifier),
                          MakeEvaluation(modifier.GetPointerArrayCountEvaluation()))
                }
                else
                {
                    LINEF("m_stream->ReusableAddOffset({0}, {1});", MakeMemberAccess(info, member, modifier), modifier.GetArraySize())
                }
            }
            else
            {
                LINEF("m_stream->ReusableAddOffset({0});", MakeMemberAccess(info, member, modifier))
            }

            WriteMember_TypeCheck(info, member, modifier, writeType);
        }

        static bool
            WriteMember_ShouldMakeAlign(const MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
        {
            if (writeType != MemberWriteType::ARRAY_POINTER && writeType != MemberWriteType::POINTER_ARRAY && writeType != MemberWriteType::SINGLE_POINTER)
            {
                return false;
            }

            if (writeType == MemberWriteType::POINTER_ARRAY)
            {
                return !modifier.IsArray();
            }

            if (member->m_is_string)
            {
                return false;
            }

            if (member->m_type && StructureComputations(member->m_type).IsAsset())
            {
                return false;
            }

            return true;
        }

        void WriteMember_Align(const StructureInformation* info,
                               const MemberInformation* member,
                               const DeclarationModifierComputations& modifier,
                               const MemberWriteType writeType) const
        {
            if (!WriteMember_ShouldMakeAlign(member, modifier, writeType))
            {
                WriteMember_InsertReuse(info, member, modifier, writeType);
                return;
            }

            const auto typeDecl = MakeTypeDecl(member->m_member->m_type_declaration.get());
            const auto followingReferences = MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers());

            if (member->m_alloc_alignment)
            {
                LINEF("m_stream->Align({0});", MakeEvaluation(member->m_alloc_alignment.get()))
            }
            else
            {
                LINEF("m_stream->Align({0});", modifier.GetAlignment())
            }

            WriteMember_InsertReuse(info, member, modifier, writeType);
        }

        static bool
            WriteMember_ShouldMakeReuse(const MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
        {
            if (writeType != MemberWriteType::ARRAY_POINTER && writeType != MemberWriteType::SINGLE_POINTER && writeType != MemberWriteType::POINTER_ARRAY)
            {
                return false;
            }

            if (writeType == MemberWriteType::POINTER_ARRAY && modifier.IsArray())
            {
                return false;
            }

            return member->m_is_reusable;
        }

        void WriteMember_Reuse(const StructureInformation* info,
                               const MemberInformation* member,
                               const DeclarationModifierComputations& modifier,
                               const MemberWriteType writeType)
        {
            if (!WriteMember_ShouldMakeReuse(member, modifier, writeType))
            {
                WriteMember_Align(info, member, modifier, writeType);
                return;
            }

            LINEF("if (m_stream->ReusableShouldWrite(&{0}))", MakeWrittenMemberAccess(info, member, modifier))
            LINE("{")
            m_intendation++;

            WriteMember_Align(info, member, modifier, writeType);

            m_intendation--;
            LINE("}")
        }

        static bool WriteMember_ShouldMakePointerCheck(const MemberInformation* member,
                                                       const DeclarationModifierComputations& modifier,
                                                       const MemberWriteType writeType)
        {
            if (writeType != MemberWriteType::ARRAY_POINTER && writeType != MemberWriteType::POINTER_ARRAY && writeType != MemberWriteType::SINGLE_POINTER)
            {
                return false;
            }

            if (writeType == MemberWriteType::POINTER_ARRAY)
            {
                return !modifier.IsArray();
            }

            if (member->m_is_string)
            {
                return false;
            }

            return true;
        }

        void WriteMember_PointerCheck(const StructureInformation* info,
                                      const MemberInformation* member,
                                      const DeclarationModifierComputations& modifier,
                                      const MemberWriteType writeType)
        {
            if (WriteMember_ShouldMakePointerCheck(member, modifier, writeType))
            {
                LINEF("if ({0})", MakeMemberAccess(info, member, modifier))
                LINE("{")
                m_intendation++;

                WriteMember_Reuse(info, member, modifier, writeType);

                m_intendation--;
                LINE("}")
            }
            else
            {
                WriteMember_Reuse(info, member, modifier, writeType);
            }
        }

        void WriteMember_Block(const StructureInformation* info,
                               const MemberInformation* member,
                               const DeclarationModifierComputations& modifier,
                               const MemberWriteType writeType)
        {
            const MemberComputations computations(member);

            const auto notInDefaultNormalBlock = computations.IsNotInDefaultNormalBlock();
            if (notInDefaultNormalBlock)
            {
                LINEF("m_stream->PushBlock({0});", member->m_fast_file_block->m_name)
            }

            WriteMember_PointerCheck(info, member, modifier, writeType);

            if (notInDefaultNormalBlock)
            {
                LINE("m_stream->PopBlock();")
            }
        }

        void WriteMember_ReferenceArray(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier)
        {
            auto first = true;
            for (const auto& entry : modifier.GetArrayEntries())
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    LINE("")
                }

                WriteMember_Reference(info, member, entry);
            }
        }

        void WriteMember_Reference(const StructureInformation* info, const MemberInformation* member, const DeclarationModifierComputations& modifier)
        {
            if (modifier.IsDynamicArray())
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::DYNAMIC_ARRAY);
            }
            else if (modifier.IsSinglePointer())
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::SINGLE_POINTER);
            }
            else if (modifier.IsArrayPointer())
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::ARRAY_POINTER);
            }
            else if (modifier.IsPointerArray())
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::POINTER_ARRAY);
            }
            else if (modifier.IsArray() && modifier.GetNextDeclarationModifier() == nullptr)
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::EMBEDDED_ARRAY);
            }
            else if (modifier.GetDeclarationModifier() == nullptr)
            {
                WriteMember_Block(info, member, modifier, MemberWriteType::EMBEDDED);
            }
            else if (modifier.IsArray())
            {
                WriteMember_ReferenceArray(info, member, modifier);
            }
            else
            {
                assert(false);
                LINEF("#error WriteMemberReference failed @ {0}", member->m_member->m_name)
            }
        }

        void WriteMember_Condition_Struct(const StructureInformation* info, const MemberInformation* member)
        {
            LINE("")
            if (member->m_condition)
            {
                LINEF("if ({0})", MakeEvaluation(member->m_condition.get()))
                LINE("{")
                m_intendation++;

                WriteMember_Reference(info, member, DeclarationModifierComputations(member));

                m_intendation--;
                LINE("}")
            }
            else
            {
                WriteMember_Reference(info, member, DeclarationModifierComputations(member));
            }
        }

        void WriteMember_Condition_Union(const StructureInformation* info, const MemberInformation* member)
        {
            const MemberComputations computations(member);

            if (computations.IsFirstUsedMember())
            {
                LINE("")
                if (member->m_condition)
                {
                    LINEF("if ({0})", MakeEvaluation(member->m_condition.get()))
                    LINE("{")
                    m_intendation++;

                    WriteMember_Reference(info, member, DeclarationModifierComputations(member));

                    m_intendation--;
                    LINE("}")
                }
                else
                {
                    WriteMember_Reference(info, member, DeclarationModifierComputations(member));
                }
            }
            else if (computations.IsLastUsedMember())
            {
                if (member->m_condition)
                {
                    LINEF("else if ({0})", MakeEvaluation(member->m_condition.get()))
                    LINE("{")
                    m_intendation++;

                    WriteMember_Reference(info, member, DeclarationModifierComputations(member));

                    m_intendation--;
                    LINE("}")
                }
                else
                {
                    LINE("else")
                    LINE("{")
                    m_intendation++;

                    WriteMember_Reference(info, member, DeclarationModifierComputations(member));

                    m_intendation--;
                    LINE("}")
                }
            }
            else
            {
                if (member->m_condition)
                {
                    LINEF("else if ({0})", MakeEvaluation(member->m_condition.get()))
                    LINE("{")
                    m_intendation++;

                    WriteMember_Reference(info, member, DeclarationModifierComputations(member));

                    m_intendation--;
                    LINE("}")
                }
                else
                {
                    LINEF("#error Middle member of union must have condition ({0})", member->m_member->m_name)
                }
            }
        }

        void PrintWriteMemberIfNeedsTreatment(const StructureInformation* info, const MemberInformation* member)
        {
            const MemberComputations computations(member);
            if (computations.ShouldIgnore())
                return;

            if (member->m_is_string || member->m_is_script_string || computations.ContainsNonEmbeddedReference() || member->m_type && !member->m_type->m_is_leaf
                || computations.IsAfterPartialLoad())
            {
                if (info->m_definition->GetType() == DataDefinitionType::UNION)
                    WriteMember_Condition_Union(info, member);
                else
                    WriteMember_Condition_Struct(info, member);
            }
        }

        void PrintWriteMethod(const StructureInformation* info)
        {
            const StructureComputations computations(info);
            LINEF("void {0}::Write_{1}(const bool atStreamStart)", WriterClassName(m_env.m_asset), info->m_definition->m_name)
            LINE("{")
            m_intendation++;

            LINEF("assert({0} != nullptr);", MakeTypeVarName(info->m_definition))
            LINE("")

            const auto* dynamicMember = computations.GetDynamicMember();
            if (!(info->m_definition->GetType() == DataDefinitionType::UNION && dynamicMember))
            {
                LINE("if (atStreamStart)")
                m_intendation++;

                if (dynamicMember == nullptr)
                {
                    LINEF("{0} = m_stream->Write<{1}>({2}); // Size: {3}",
                          MakeTypeWrittenVarName(info->m_definition),
                          info->m_definition->GetFullName(),
                          MakeTypeVarName(info->m_definition),
                          info->m_definition->GetSize())
                }
                else
                {
                    LINEF("{0} = m_stream->WritePartial<{1}>({2}, offsetof({1}, {3}));",
                          MakeTypeWrittenVarName(info->m_definition),
                          info->m_definition->GetFullName(),
                          MakeTypeVarName(info->m_definition),
                          dynamicMember->m_member->m_name)
                }

                m_intendation--;

                LINE("")
                LINEF("assert({0} != nullptr);", MakeTypeWrittenVarName(info->m_definition))
            }
            else
            {
                LINE("assert(atStreamStart);")
            }

            if (computations.IsAsset())
            {
                LINE("")
                LINEF("m_stream->PushBlock({0});", m_env.m_default_normal_block->m_name)
            }
            else if (info->m_block)
            {
                LINE("")
                LINEF("m_stream->PushBlock({0});", info->m_block->m_name)
            }

            for (const auto& member : info->m_ordered_members)
            {
                PrintWriteMemberIfNeedsTreatment(info, member.get());
            }

            if (info->m_block || computations.IsAsset())
            {
                LINE("")
                LINE("m_stream->PopBlock();")
            }

            m_intendation--;
            LINE("}")
        }

        void PrintWritePtrMethod(const StructureInformation* info)
        {
            const bool inTemp = info->m_block && info->m_block->m_type == FastFileBlockType::TEMP;
            LINEF("void {0}::WritePtr_{1}(const bool atStreamStart)", WriterClassName(m_env.m_asset), MakeSafeTypeName(info->m_definition))
            LINE("{")
            m_intendation++;

            LINEF("assert({0} != nullptr);", MakeTypePtrVarName(info->m_definition))
            LINE("")

            LINE("if (atStreamStart)")
            m_intendation++;
            LINEF("{0} = m_stream->Write<{1}*>({2});",
                  MakeTypeWrittenPtrVarName(info->m_definition),
                  info->m_definition->GetFullName(),
                  MakeTypePtrVarName(info->m_definition))
            m_intendation--;

            LINE("")
            LINEF("assert({0} != nullptr);", MakeTypeWrittenPtrVarName(info->m_definition))

            LINE("")
            if (inTemp)
            {
                LINEF("m_stream->PushBlock({0});", m_env.m_default_temp_block->m_name)
                LINE("")
            }
            LINEF("if (m_stream->ReusableShouldWrite({0}))", MakeTypeWrittenPtrVarName(info->m_definition))
            LINE("{")
            m_intendation++;

            LINEF("m_stream->Align({0});", info->m_definition->GetAlignment())
            LINEF("m_stream->ReusableAddOffset(*{0});", MakeTypePtrVarName(info->m_definition))
            LINE("")
            if (!info->m_is_leaf)
            {
                LINEF("{0} = *{1};", MakeTypeVarName(info->m_definition), MakeTypePtrVarName(info->m_definition))
                LINEF("Write_{0}(true);", MakeSafeTypeName(info->m_definition))
            }
            else
            {
                LINE("#error Ptr method cannot have leaf type")
            }

            LINE("")
            LINEF("m_stream->MarkFollowing(*{0});", MakeTypeWrittenPtrVarName(info->m_definition))

            m_intendation--;
            LINE("}")

            if (inTemp)
            {
                LINE("")
                LINE("m_stream->PopBlock();")
            }

            m_intendation--;
            LINE("}")
        }

        void PrintMainWriteMethod()
        {
            LINEF("void {0}::Write({1}** pAsset)", WriterClassName(m_env.m_asset), m_env.m_asset->m_definition->GetFullName())
            LINE("{")
            m_intendation++;

            LINE("assert(pAsset != nullptr);")
            LINE("assert(m_asset != nullptr);")
            LINE("assert(m_asset->m_ptr != nullptr);")
            LINE("")
            LINEF("auto* zoneAsset = static_cast<{0}*>(m_asset->m_ptr);", m_env.m_asset->m_definition->GetFullName())
            LINEF("{0} = &zoneAsset;", MakeTypePtrVarName(m_env.m_asset->m_definition))
            LINEF("{0} = &zoneAsset;", MakeTypeWrittenPtrVarName(m_env.m_asset->m_definition))
            LINEF("WritePtr_{0}(false);", MakeSafeTypeName(m_env.m_asset->m_definition))
            LINE("*pAsset = zoneAsset;")

            m_intendation--;
            LINE("}")
        }

        void PrintWritePtrArrayMethod_Loading(const DataDefinition* def, const StructureInformation* info, const bool reusable) const
        {
            LINEF("m_stream->Align({0});", def->GetAlignment())

            if (reusable)
            {
                LINEF("m_stream->ReusableAddOffset(*{0});", MakeTypePtrVarName(def))
            }

            if (info && !info->m_is_leaf)
            {
                LINEF("{0} = *{1};", MakeTypeVarName(info->m_definition), MakeTypePtrVarName(def))
                LINEF("Write_{0}(true);", MakeSafeTypeName(def))
            }
            else
            {
                LINEF("m_stream->Write<{0}>(*{1});", def->GetFullName(), MakeTypePtrVarName(def))
            }
            LINEF("m_stream->MarkFollowing(*{0});", MakeTypeWrittenPtrVarName(def))
        }

        void PrintWritePtrArrayMethod_PointerCheck(const DataDefinition* def, const StructureInformation* info, const bool reusable)
        {
            LINEF("if (*{0})", MakeTypePtrVarName(def))
            LINE("{")
            m_intendation++;

            if (info && StructureComputations(info).IsAsset())
            {
                LINEF("{0} writer(*{1}, m_zone, *m_stream);", WriterClassName(info), MakeTypePtrVarName(def))
                LINEF("writer.Write({0});", MakeTypeWrittenPtrVarName(def))
            }
            else
            {
                if (reusable)
                {
                    LINEF("if (m_stream->ReusableShouldWrite({0}))", MakeTypeWrittenPtrVarName(def))
                    LINE("{")
                    m_intendation++;

                    PrintWritePtrArrayMethod_Loading(def, info, reusable);

                    m_intendation--;
                    LINE("}")
                }
                else
                {
                    PrintWritePtrArrayMethod_Loading(def, info, reusable);
                }
            }

            m_intendation--;
            LINE("}")
        }

        void PrintWritePtrArrayMethod(const DataDefinition* def, const StructureInformation* info, const bool reusable)
        {
            LINEF("void {0}::WritePtrArray_{1}(const bool atStreamStart, const size_t count)", WriterClassName(m_env.m_asset), MakeSafeTypeName(def))
            LINE("{")
            m_intendation++;

            LINEF("assert({0} != nullptr);", MakeTypePtrVarName(def))
            LINE("")

            LINE("if (atStreamStart)")
            m_intendation++;
            LINEF("{0} = m_stream->Write<{1}*>({2}, count);", MakeTypeWrittenPtrVarName(def), def->GetFullName(), MakeTypePtrVarName(def))
            m_intendation--;

            LINE("")
            LINEF("assert({0} != nullptr);", MakeTypeWrittenPtrVarName(def))

            LINE("")
            LINEF("{0}** var = {1};", def->GetFullName(), MakeTypePtrVarName(def))
            LINEF("{0}** varWritten = {1};", def->GetFullName(), MakeTypeWrittenPtrVarName(def))
            LINE("for (size_t index = 0; index < count; index++)")
            LINE("{")
            m_intendation++;

            LINEF("{0} = var;", MakeTypePtrVarName(def))
            LINEF("{0} = varWritten;", MakeTypeWrittenPtrVarName(def))
            PrintWritePtrArrayMethod_PointerCheck(def, info, reusable);
            LINE("")
            LINE("var++;")
            LINE("varWritten++;")

            m_intendation--;
            LINE("}")
            m_intendation--;
            LINE("}")
        }

        void PrintWriteArrayMethod(const DataDefinition* def, const StructureInformation* info)
        {
            LINEF("void {0}::WriteArray_{1}(const bool atStreamStart, const size_t count)", WriterClassName(m_env.m_asset), MakeSafeTypeName(def))
            LINE("{")
            m_intendation++;

            LINEF("assert({0} != nullptr);", MakeTypeVarName(def))
            LINE("")
            LINE("if (atStreamStart)")
            m_intendation++;
            LINEF("{0} = m_stream->Write<{1}>({2}, count);", MakeTypeWrittenVarName(def), def->GetFullName(), MakeTypeVarName(def))
            m_intendation--;

            LINE("")
            LINEF("assert({0} != nullptr);", MakeTypeWrittenVarName(def))

            LINE("")
            LINEF("{0}* var = {1};", def->GetFullName(), MakeTypeVarName(def))
            LINEF("{0}* varWritten = {1};", def->GetFullName(), MakeTypeWrittenVarName(def))
            LINE("for (size_t index = 0; index < count; index++)")
            LINE("{")
            m_intendation++;

            LINEF("{0} = var;", MakeTypeVarName(info->m_definition))
            LINEF("{0} = varWritten;", MakeTypeWrittenVarName(info->m_definition))
            LINEF("Write_{0}(false);", info->m_definition->m_name)
            LINE("var++;")
            LINE("varWritten++;")

            m_intendation--;
            LINE("}")

            m_intendation--;
            LINE("}")
        }
    };
} // namespace

std::vector<CodeTemplateFile> ZoneWriteTemplate::GetFilesToRender(const RenderingContext& context)
{
    std::vector<CodeTemplateFile> files;

    auto assetName = context.m_asset->m_definition->m_name;
    for (auto& c : assetName)
        c = static_cast<char>(tolower(c));

    files.emplace_back(std::format("{0}/{0}_write_db.h", assetName), TAG_HEADER);
    files.emplace_back(std::format("{0}/{0}_write_db.cpp", assetName), TAG_SOURCE);

    return files;
}

void ZoneWriteTemplate::RenderFile(std::ostream& stream, const int fileTag, const RenderingContext& context)
{
    Template t(stream, context);

    if (fileTag == TAG_HEADER)
    {
        t.Header();
    }
    else
    {
        assert(fileTag == TAG_SOURCE);
        t.Source();
    }
}
