#include "ZoneWriteTemplate.h"

#include <cassert>
#include <iostream>
#include <sstream>

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

class ZoneWriteTemplate::Internal final : BaseTemplate
{
    enum class MemberWriteType
    {
        ARRAY_POINTER,
        DYNAMIC_ARRAY,
        EMBEDDED,
        EMBEDDED_ARRAY,
        POINTER_ARRAY,
        SINGLE_POINTER
    };

    static std::string WriterClassName(StructureInformation* asset)
    {
        std::ostringstream str;
        str << "Writer_" << asset->m_definition->m_name;
        return str.str();
    }

    static std::string VariableDecl(const DataDefinition* def)
    {
        std::ostringstream str;
        str << def->GetFullName() << "* var" << MakeSafeTypeName(def) << ";";
        return str.str();
    }

    static std::string WrittenVariableDecl(const DataDefinition* def)
    {
        std::ostringstream str;
        str << def->GetFullName() << "* var" << MakeSafeTypeName(def) << "Written;";
        return str.str();
    }

    static std::string PointerVariableDecl(const DataDefinition* def)
    {
        std::ostringstream str;
        str << def->GetFullName() << "** var" << MakeSafeTypeName(def) << "Ptr;";
        return str.str();
    }

    static std::string WrittenPointerVariableDecl(const DataDefinition* def)
    {
        std::ostringstream str;
        str << def->GetFullName() << "** var" << MakeSafeTypeName(def) << "PtrWritten;";
        return str.str();
    }

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

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

    void PrintHeaderWriteMethodDeclaration(const StructureInformation* info) const
    {
        LINE("void Write_" << MakeSafeTypeName(info->m_definition) << "(bool atStreamStart);")
    }

    void PrintHeaderTempPtrWriteMethodDeclaration(const StructureInformation* info) const
    {
        LINE("void WritePtr_" << MakeSafeTypeName(info->m_definition) << "(bool atStreamStart);")
    }

    void PrintHeaderGetNameMethodDeclaration(const StructureInformation* info) const
    {
        LINE("static std::string GetAssetName(" << info->m_definition->GetFullName() << "* pAsset);")
    }

    void PrintHeaderMainWriteMethodDeclaration(const StructureInformation* info) const
    {
        LINE("void Write(" << info->m_definition->GetFullName() << "** pAsset);")
    }

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

    void PrintVariableInitialization(const DataDefinition* def) const
    {
        LINE("var" << def->m_name << " = nullptr;")
    }

    void PrintWrittenVariableInitialization(const DataDefinition* def) const
    {
        LINE("var" << def->m_name << "Written = nullptr;")
    }

    void PrintPointerVariableInitialization(const DataDefinition* def) const
    {
        LINE("var" << def->m_name << "Ptr = nullptr;")
    }

    void PrintWrittenPointerVariableInitialization(const DataDefinition* def) const
    {
        LINE("var" << def->m_name << "PtrWritten = nullptr;")
    }

    void PrintConstructorMethod()
    {
        LINE(WriterClassName(m_env.m_asset) << "::" << WriterClassName(m_env.m_asset) << "("<<m_env.m_asset->m_definition->GetFullName()<<"* asset, Zone* zone, IZoneOutputStream* stream)")

        m_intendation++;
        LINE_START(": AssetWriter(zone->m_pools->GetAsset("<<m_env.m_asset->m_asset_enum_entry->m_name<<", GetAssetName(asset))"<<", zone, stream)")
        LINE_END("")
        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 (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 (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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType) const
    {
        if (writeType == MemberWriteType::ARRAY_POINTER)
        {
            LINE("varScriptString = " << MakeMemberAccess(info, member, modifier) << ";")
            LINE("m_stream->MarkFollowing("<<MakeWrittenMemberAccess(info, member, modifier)<<");")
            LINE("WriteScriptStringArray(true, " << MakeEvaluation(modifier.GetArrayPointerCountEvaluation()) << ");")
        }
        else if (writeType == MemberWriteType::EMBEDDED_ARRAY)
        {
            LINE("varScriptStringWritten = " << MakeWrittenMemberAccess(info, member, modifier) << ";")
            LINE("WriteScriptStringArray(false, " << MakeArrayCount(dynamic_cast<ArrayDeclarationModifier*>(modifier.GetDeclarationModifier())) << ");")
        }
        else if (writeType == MemberWriteType::EMBEDDED)
        {
            LINE(MakeWrittenMemberAccess(info, member, modifier) << " = UseScriptString(" << MakeMemberAccess(info, member, modifier) << ");")
        }
        else
        {
            assert(false);
            LINE("#error unsupported writeType " << static_cast<int>(writeType) << " for scripstring")
        }
    }

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

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

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

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

    void WriteMember_EmbeddedArray(StructureInformation* info, 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())
            {
                LINE(MakeTypeVarName(member->m_member->m_type_declaration->m_type)<<" = "<< MakeMemberAccess(info, member, modifier) <<";")
                LINE("WriteArray_" << MakeSafeTypeName(member->m_member->m_type_declaration->m_type) << "(true, " << arraySizeStr << ");")
            }
            else
            {
                LINE(MakeTypeVarName(member->m_member->m_type_declaration->m_type) << " = " << MakeMemberAccess(info, member, modifier) << ";")
                LINE(MakeTypeWrittenVarName(member->m_member->m_type_declaration->m_type) << " = " << MakeWrittenMemberAccess(info, member, modifier) << ";")
                LINE("WriteArray_" << MakeSafeTypeName(member->m_member->m_type_declaration->m_type) << "(false, " << arraySizeStr << ");")
            }
        }
        else if (computations.IsAfterPartialLoad())
        {
            LINE("m_stream->Write<" << MakeTypeDecl(member->m_member->m_type_declaration.get()) << MakeFollowingReferences(modifier.GetFollowingDeclarationModifiers())
                << ">(" << MakeMemberAccess(info, member, modifier) << ", " << arraySizeStr << ");")
        }
    }

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

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

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

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

    void WriteMember_TypeCheck(StructureInformation* info, 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:
                LINE("// t=" << static_cast<int>(writeType))
                break;
            }
        }
    }

    static bool WriteMember_ShouldMakeInsertReuse(StructureInformation* info, 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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
    {
        if (!WriteMember_ShouldMakeInsertReuse(info, member, modifier, writeType))
        {
            WriteMember_TypeCheck(info, member, modifier, writeType);
            return;
        }

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

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

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

    static bool WriteMember_ShouldMakeAlign(StructureInformation* info, 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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
    {
        if (!WriteMember_ShouldMakeAlign(info, 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)
        {
            LINE("m_stream->Align("<<MakeEvaluation(member->m_alloc_alignment.get())<<");")
        }
        else
        {
            LINE("m_stream->Align("<<modifier.GetAlignment()<<");")
        }

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

    static bool WriteMember_ShouldMakeReuse(StructureInformation* info, 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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
    {
        if (!WriteMember_ShouldMakeReuse(info, member, modifier, writeType))
        {
            WriteMember_Align(info, member, modifier, writeType);
            return;
        }

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

        WriteMember_Align(info, member, modifier, writeType);

        m_intendation--;
        LINE("}")
    }

    static bool WriteMember_ShouldMakePointerCheck(StructureInformation* info, 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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, const MemberWriteType writeType)
    {
        if (WriteMember_ShouldMakePointerCheck(info, member, modifier, writeType))
        {
            LINE("if (" << 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(StructureInformation* info, MemberInformation* member, const DeclarationModifierComputations& modifier, MemberWriteType writeType)
    {
        const MemberComputations computations(member);

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

        WriteMember_PointerCheck(info, member, modifier, writeType);

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

    void WriteMember_ReferenceArray(StructureInformation* info, 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(StructureInformation* info, 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);
            LINE("#error WriteMemberReference failed @ " << member->m_member->m_name)
        }
    }

    void WriteMember_Condition_Struct(StructureInformation* info, MemberInformation* member)
    {
        LINE("")
        if (member->m_condition)
        {
            LINE("if(" << 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(StructureInformation* info, MemberInformation* member)
    {
        const MemberComputations computations(member);

        if (computations.IsFirstMember())
        {
            LINE("")
            if (member->m_condition)
            {
                LINE("if(" << 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.IsLastMember())
        {
            if (member->m_condition)
            {
                LINE("else if(" << 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)
            {
                LINE("else if(" << MakeEvaluation(member->m_condition.get()) << ")")
                LINE("{")
                m_intendation++;

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

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

    void PrintWriteMemberIfNeedsTreatment(StructureInformation* info, 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(StructureInformation* info)
    {
        const StructureComputations computations(info);
        LINE("void " << WriterClassName(m_env.m_asset) << "::Write_" << info->m_definition->m_name << "(const bool atStreamStart)")
        LINE("{")
        m_intendation++;

        LINE("assert(" << MakeTypeVarName(info->m_definition) << " != nullptr);")
        LINE("")

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

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

            m_intendation--;

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

        if (computations.IsAsset())
        {
            LINE("")
            LINE("m_stream->PushBlock(" << m_env.m_default_normal_block->m_name << ");")
        }
        else if (info->m_block)
        {
            LINE("")
            LINE("m_stream->PushBlock(" << 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(StructureInformation* info)
    {
        const bool inTemp = info->m_block && info->m_block->m_type == FastFileBlockType::TEMP;
        LINE("void " << WriterClassName(m_env.m_asset) << "::WritePtr_" << MakeSafeTypeName(info->m_definition) << "(const bool atStreamStart)")
        LINE("{")
        m_intendation++;

        LINE("assert(" << MakeTypePtrVarName(info->m_definition) << " != nullptr);")
        LINE("")

        LINE("if(atStreamStart)")
        m_intendation++;
        LINE(MakeTypeWrittenPtrVarName(info->m_definition)<<" = m_stream->Write<" << info->m_definition->GetFullName() << "*>(" << MakeTypePtrVarName(info->m_definition) << ");")
        m_intendation--;

        LINE("")
        LINE("assert(" << MakeTypeWrittenPtrVarName(info->m_definition) << " != nullptr);")

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

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

        LINE("")
        LINE("m_stream->MarkFollowing(*"<<MakeTypeWrittenPtrVarName(info->m_definition)<<");")

        m_intendation--;
        LINE("}")

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

        m_intendation--;
        LINE("}")
    }

    void PrintMainWriteMethod()
    {
        LINE("void " << WriterClassName(m_env.m_asset) << "::Write(" << m_env.m_asset->m_definition->GetFullName() << "** pAsset)")
        LINE("{")
        m_intendation++;

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

        m_intendation--;
        LINE("}")
    }

    void PrintGetNameMethod()
    {
        LINE("std::string " << WriterClassName(m_env.m_asset) << "::GetAssetName(" << m_env.m_asset->m_definition->GetFullName() << "* pAsset)")
        LINE("{")
        m_intendation++;

        if (!m_env.m_asset->m_name_chain.empty())
        {
            LINE_START("return pAsset")

            auto first = true;
            for (auto* member : m_env.m_asset->m_name_chain)
            {
                if (first)
                {
                    first = false;
                    LINE_MIDDLE("->" << member->m_member->m_name)
                }
                else
                {
                    LINE_MIDDLE("." << member->m_member->m_name)
                }
            }
            LINE_END(";")
        }
        else
        {
            LINE("return \"" << m_env.m_asset->m_definition->m_name << "\";")
        }

        m_intendation--;
        LINE("}")
    }

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

        if (reusable)
        {
            LINE("m_stream->ReusableAddOffset(*"<<MakeTypePtrVarName(def)<< ");")
        }

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

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

        if (info && StructureComputations(info).IsAsset())
        {
            LINE(WriterClassName(info) << " writer(*"<< MakeTypePtrVarName(def)<<", m_zone, m_stream);")
            LINE("writer.Write(" << MakeTypeWrittenPtrVarName(def) << ");")
        }
        else
        {
            if (reusable)
            {
                LINE("if(m_stream->ReusableShouldWrite(" << 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, StructureInformation* info, const bool reusable)
    {
        LINE("void " << WriterClassName(m_env.m_asset) << "::WritePtrArray_" << MakeSafeTypeName(def) << "(const bool atStreamStart, const size_t count)")
        LINE("{")
        m_intendation++;

        LINE("assert(" << MakeTypePtrVarName(def) << " != nullptr);")
        LINE("")

        LINE("if(atStreamStart)")
        m_intendation++;
        LINE(MakeTypeWrittenPtrVarName(def)<<" = m_stream->Write<" << def->GetFullName() << "*>(" << MakeTypePtrVarName(def) << ", count);")
        m_intendation--;

        LINE("")
        LINE("assert(" << MakeTypeWrittenPtrVarName(def) << " != nullptr);")

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

        LINE(MakeTypePtrVarName(def) << " = var;")
        LINE(MakeTypeWrittenPtrVarName(def) << " = varWritten;")
        PrintWritePtrArrayMethod_PointerCheck(def, info, reusable);
        LINE("")
        LINE("var++;")
        LINE("varWritten++;")

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

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

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

        LINE("")
        LINE("assert(" << MakeTypeWrittenVarName(def) << " != nullptr);")

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

        LINE(MakeTypeVarName(info->m_definition) << " = var;")
        LINE(MakeTypeWrittenVarName(info->m_definition) << " = varWritten;")
        LINE("Write_" << info->m_definition->m_name << "(false);")
        LINE("var++;")
        LINE("varWritten++;")

        m_intendation--;
        LINE("}")

        m_intendation--;
        LINE("}")
    }

public:
    Internal(std::ostream& stream, 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("")
        LINE("#include \"Writing/AssetWriter.h\"")
        LINE("#include \"Game/" << m_env.m_game << "/" << m_env.m_game << ".h\"")
        LINE("#include <string>")
        LINE("")
        LINE("namespace " << m_env.m_game)
        LINE("{")
        m_intendation++;
        LINE("class " << WriterClassName(m_env.m_asset) << " final : public AssetWriter")
        LINE("{")
        m_intendation++;

        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 (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 (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))
            }
        }

        LINE("")

        // Method Declarations
        for (auto* type : m_env.m_used_types)
        {
            if (type->m_pointer_array_reference_exists)
            {
                PrintHeaderPtrArrayWriteMethodDeclaration(type->m_type);
            }
        }
        for (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 (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("")
        m_intendation--;
        LINE("public:")
        m_intendation++;
        PrintHeaderConstructor();
        PrintHeaderMainWriteMethodDeclaration(m_env.m_asset);
        PrintHeaderGetNameMethodDeclaration(m_env.m_asset);

        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("")
        LINE("#include \"" << Lower(m_env.m_asset->m_definition->m_name) << "_write_db.h\"")
        LINE("#include <cassert>")
        LINE("")

        if (!m_env.m_referenced_assets.empty())
        {
            LINE("// Referenced Assets:")
            for (auto* type : m_env.m_referenced_assets)
            {
                LINE("#include \"../" << Lower(type->m_type->m_name) << "/" << Lower(type->m_type->m_name) << "_write_db.h\"")
            }
            LINE("")
        }
        LINE("using namespace " << m_env.m_game << ";")
        LINE("")
        PrintConstructorMethod();

        for (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 (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 (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);
        LINE("")
        PrintMainWriteMethod();
        LINE("")
        PrintGetNameMethod();
    }
};

std::vector<CodeTemplateFile> ZoneWriteTemplate::GetFilesToRender(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));

    {
        std::ostringstream str;
        str << assetName << '/' << assetName << "_write_db.h";
        files.emplace_back(str.str(), TAG_HEADER);
    }

    {
        std::ostringstream str;
        str << assetName << '/' << assetName << "_write_db.cpp";
        files.emplace_back(str.str(), TAG_SOURCE);
    }

    return files;
}

void ZoneWriteTemplate::RenderFile(std::ostream& stream, const int fileTag, RenderingContext* context)
{
    Internal internal(stream, context);

    if (fileTag == TAG_HEADER)
    {
        internal.Header();
    }
    else if (fileTag == TAG_SOURCE)
    {
        internal.Source();
    }
    else
    {
        std::cout << "Unknown tag for ZoneWriteTemplate: " << fileTag << "\n";
    }
}
