#options GAME (IW3, IW4, IW5, T5, T6)

#filename "Game/" + GAME + "/Material/MaterialJsonDumper" + GAME + ".cpp"

#if GAME == "IW3"
#define FEATURE_IW3
#define HAS_WATER
#define GAME_LOWER "iw3"
#elif GAME == "IW4"
#define FEATURE_IW4
#define HAS_WATER
#define GAME_LOWER "iw4"
#elif GAME == "IW5"
#define FEATURE_IW5
#define HAS_WATER
#define GAME_LOWER "iw5"
#elif GAME == "T5"
#define FEATURE_T5
#define HAS_WATER
#define GAME_LOWER "t5"
#elif GAME == "T6"
#define FEATURE_T6
#define GAME_LOWER "t6"
#endif

// This file was templated.
// See MaterialJsonDumper.cpp.template.
// Do not modify, changes will be lost.

#set WRITER_HEADER "\"MaterialJsonDumper" + GAME + ".h\""
#include WRITER_HEADER

#ifdef HAS_WATER
#include "Base64.h"
#endif

#include "Material/MaterialCommon.h"
#set COMMON_HEADER "\"Game/" + GAME + "/Common" + GAME + ".h\""
#include COMMON_HEADER
#set JSON_HEADER "\"Game/" + GAME + "/Material/JsonMaterial" + GAME + ".h\""
#include JSON_HEADER
#set CONSTANTS_HEADER "\"Game/" + GAME + "/Material/MaterialConstantZoneState" + GAME + ".h\""
#include CONSTANTS_HEADER

#include <cassert>
#include <iomanip>
#include <nlohmann/json.hpp>

using namespace nlohmann;
using namespace GAME;

#set CLASS_NAME "JsonDumper" + GAME

namespace
{
    class JsonDumperImpl
    {
    public:
        JsonDumperImpl(AssetDumpingContext& context, std::ostream& stream)
            : m_stream(stream),
              m_material_constants(*context.GetZoneAssetDumperState<MaterialConstantZoneState>())
        {
        }

        void Dump(const Material& material) const
        {
            JsonMaterial jsonMaterial;
            CreateJsonMaterial(jsonMaterial, material);
            json jRoot = jsonMaterial;

            jRoot["$schema"] = "http://openassettools.dev/schema/material.v1.json";
            jRoot["_type"] = "material";
            jRoot["_version"] = 1;
            jRoot["_game"] = GAME_LOWER;

            m_stream << std::setw(4) << jRoot << "\n";
        }

    private:
        static const char* AssetName(const char* input)
        {
            if (input && input[0] == ',')
                return &input[1];

            return input;
        }

        static void CreateJsonGameFlags(JsonMaterial& jMaterial, const unsigned gameFlags)
        {
            jMaterial.gameFlags.clear();
            for (auto i = 0u; i < sizeof(gameFlags) * 8u; i++)
            {
                const auto flag = static_cast<MaterialGameFlags>(1 << i);

                if (gameFlags & flag)
                    jMaterial.gameFlags.emplace_back(flag);
            }
        }

        static void CreateJsonSamplerState(JsonSamplerState& jSamplerState, const MaterialTextureDefSamplerState& samplerState)
        {
            jSamplerState.filter = static_cast<TextureFilter>(samplerState.filter);
            jSamplerState.mipMap = static_cast<SamplerStateBitsMipMap_e>(samplerState.mipMap);
            jSamplerState.clampU = samplerState.clampU;
            jSamplerState.clampV = samplerState.clampV;
            jSamplerState.clampW = samplerState.clampW;
        }

#ifdef HAS_WATER
        static void CreateJsonWater(JsonWater& jWater, const water_t& water)
        {
            jWater.floatTime = water.writable.floatTime;
            jWater.m = water.M;
            jWater.n = water.N;
            jWater.lx = water.Lx;
            jWater.lz = water.Lz;
            jWater.gravity = water.gravity;
            jWater.windvel = water.windvel;
            jWater.winddir[0] = water.winddir[0];
            jWater.winddir[1] = water.winddir[1];
            jWater.amplitude = water.amplitude;
            jWater.codeConstant[0] = water.codeConstant[0];
            jWater.codeConstant[1] = water.codeConstant[1];
            jWater.codeConstant[2] = water.codeConstant[2];
            jWater.codeConstant[3] = water.codeConstant[3];

            if (water.H0)
            {
                const auto count = water.M * water.N;
                jWater.h0 = base64::EncodeBase64(water.H0, sizeof(complex_s) * count);
            }

            if (water.wTerm)
            {
                const auto count = water.M * water.N;
                jWater.wTerm = base64::EncodeBase64(water.wTerm, sizeof(float) * count);
            }
        }
#endif

        void CreateJsonTexture(JsonTexture& jTextureDef, const MaterialTextureDef& textureDef) const
        {
            std::string textureDefName;
            if (m_material_constants.GetTextureDefName(textureDef.nameHash, textureDefName))
            {
                jTextureDef.name = textureDefName;
            }
            else
            {
                jTextureDef.nameHash = textureDef.nameHash;
                jTextureDef.nameStart = std::string(1u, textureDef.nameStart);
                jTextureDef.nameEnd = std::string(1u, textureDef.nameEnd);
            }

            jTextureDef.semantic = static_cast<TextureSemantic>(textureDef.semantic);
#if defined(FEATURE_T5) || defined(FEATURE_T6)
            jTextureDef.isMatureContent = textureDef.isMatureContent;
#endif

            CreateJsonSamplerState(jTextureDef.samplerState, textureDef.samplerState);

#ifdef HAS_WATER
            if (textureDef.semantic == TS_WATER_MAP)
            {
                if (textureDef.u.water)
                {
                    const auto& water = *textureDef.u.water;
                    if (water.image && water.image->name)
                        jTextureDef.image = AssetName(water.image->name);

                    JsonWater jWater;
                    CreateJsonWater(jWater, water);

                    jTextureDef.water = std::move(jWater);
                }
            }
            else
            {
                if (textureDef.u.image && textureDef.u.image->name)
                    jTextureDef.image = AssetName(textureDef.u.image->name);
            }
#else
            if (textureDef.image && textureDef.image->name)
                jTextureDef.image = AssetName(textureDef.image->name);
#endif
        }

        void CreateJsonConstant(JsonConstant& jConstantDef, const MaterialConstantDef& constantDef) const
        {
            const auto fragmentLength = strnlen(constantDef.name, std::extent_v<decltype(MaterialConstantDef::name)>);
            const std::string nameFragment(constantDef.name, fragmentLength);
            std::string knownConstantName;

            if (fragmentLength < std::extent_v<decltype(MaterialConstantDef::name)> || Common::R_HashString(nameFragment.c_str(), 0) == constantDef.nameHash)
            {
                jConstantDef.name = nameFragment;
            }
            else if (m_material_constants.GetConstantName(constantDef.nameHash, knownConstantName))
            {
                jConstantDef.name = knownConstantName;
            }
            else
            {
                jConstantDef.nameHash = constantDef.nameHash;
                jConstantDef.nameFragment = nameFragment;
            }

            jConstantDef.literal = {
                constantDef.literal.x,
                constantDef.literal.y,
                constantDef.literal.z,
                constantDef.literal.w,
            };
        }

        static void CreateJsonStencil(JsonStencil& jStencil, const unsigned pass, const unsigned fail, const unsigned zFail, const unsigned func)
        {
            jStencil.pass = static_cast<GfxStencilOp>(pass);
            jStencil.fail = static_cast<GfxStencilOp>(fail);
            jStencil.zfail = static_cast<GfxStencilOp>(zFail);
            jStencil.func = static_cast<GfxStencilFunc>(func);
        }

        static void CreateJsonStateBitsTableEntry(JsonStateBitsTableEntry& jStateBitsTableEntry, const GfxStateBits& stateBitsTableEntry)
        {
            const auto& structured = stateBitsTableEntry.loadBits.structured;

            jStateBitsTableEntry.srcBlendRgb = static_cast<GfxBlend>(structured.srcBlendRgb);
            jStateBitsTableEntry.dstBlendRgb = static_cast<GfxBlend>(structured.dstBlendRgb);
            jStateBitsTableEntry.blendOpRgb = static_cast<GfxBlendOp>(structured.blendOpRgb);

            assert(structured.alphaTestDisabled
                    || structured.alphaTest == GFXS_ALPHA_TEST_GT_0
#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5)
                    || structured.alphaTest == GFXS_ALPHA_TEST_LT_128
#elif defined(FEATURE_T5)
                    || structured.alphaTest == GFXS_ALPHA_TEST_GE_255
#endif
                    || structured.alphaTest == GFXS_ALPHA_TEST_GE_128);
            if (structured.alphaTestDisabled)
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::DISABLED;
            else if (structured.alphaTest == GFXS_ALPHA_TEST_GT_0)
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::GT0;
#if defined(FEATURE_IW3) || defined(FEATURE_IW4) || defined(FEATURE_IW5)
            else if (structured.alphaTest == GFXS_ALPHA_TEST_LT_128)
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::LT128;
#elif defined(FEATURE_T5)
            else if (structured.alphaTest == GFXS_ALPHA_TEST_GE_255)
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::GE255;
#endif
            else if (structured.alphaTest == GFXS_ALPHA_TEST_GE_128)
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::GE128;
            else
                jStateBitsTableEntry.alphaTest = JsonAlphaTest::INVALID;

            assert(structured.cullFace == GFXS_CULL_NONE || structured.cullFace == GFXS_CULL_BACK || structured.cullFace == GFXS_CULL_FRONT);
            if (structured.cullFace == GFXS_CULL_NONE)
                jStateBitsTableEntry.cullFace = JsonCullFace::NONE;
            else if (structured.cullFace == GFXS_CULL_BACK)
                jStateBitsTableEntry.cullFace = JsonCullFace::BACK;
            else if (structured.cullFace == GFXS_CULL_FRONT)
                jStateBitsTableEntry.cullFace = JsonCullFace::FRONT;
            else
                jStateBitsTableEntry.cullFace = JsonCullFace::INVALID;

            jStateBitsTableEntry.srcBlendAlpha = static_cast<GfxBlend>(structured.srcBlendAlpha);
            jStateBitsTableEntry.dstBlendAlpha = static_cast<GfxBlend>(structured.dstBlendAlpha);
            jStateBitsTableEntry.blendOpAlpha = static_cast<GfxBlendOp>(structured.blendOpAlpha);
            jStateBitsTableEntry.colorWriteRgb = structured.colorWriteRgb;
            jStateBitsTableEntry.colorWriteAlpha = structured.colorWriteAlpha;
#if defined(FEATURE_IW4) || defined(FEATURE_IW5)
            jStateBitsTableEntry.gammaWrite = structured.gammaWrite;
#endif
            jStateBitsTableEntry.polymodeLine = structured.polymodeLine;
            jStateBitsTableEntry.depthWrite = structured.depthWrite;

            assert(structured.depthTestDisabled || structured.depthTest == GFXS_DEPTHTEST_ALWAYS || structured.depthTest == GFXS_DEPTHTEST_LESS
                   || structured.depthTest == GFXS_DEPTHTEST_EQUAL || structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL);
            if (structured.depthTestDisabled)
                jStateBitsTableEntry.depthTest = JsonDepthTest::DISABLED;
            else if (structured.depthTest == GFXS_DEPTHTEST_ALWAYS)
                jStateBitsTableEntry.depthTest = JsonDepthTest::ALWAYS;
            else if (structured.depthTest == GFXS_DEPTHTEST_LESS)
                jStateBitsTableEntry.depthTest = JsonDepthTest::LESS;
            else if (structured.depthTest == GFXS_DEPTHTEST_EQUAL)
                jStateBitsTableEntry.depthTest = JsonDepthTest::EQUAL;
            else if (structured.depthTest == GFXS_DEPTHTEST_LESSEQUAL)
                jStateBitsTableEntry.depthTest = JsonDepthTest::LESS_EQUAL;
            else
                jStateBitsTableEntry.depthTest = JsonDepthTest::INVALID;

            jStateBitsTableEntry.polygonOffset = static_cast<GfxPolygonOffset_e>(structured.polygonOffset);

            if (structured.stencilFrontEnabled)
            {
                JsonStencil jStencilFront;
                CreateJsonStencil(
                    jStencilFront, structured.stencilFrontPass, structured.stencilFrontFail, structured.stencilFrontZFail, structured.stencilFrontFunc);
                jStateBitsTableEntry.stencilFront = jStencilFront;
            }

            if (structured.stencilBackEnabled)
            {
                JsonStencil jStencilBack;
                CreateJsonStencil(
                    jStencilBack, structured.stencilBackPass, structured.stencilBackFail, structured.stencilBackZFail, structured.stencilBackFunc);
                jStateBitsTableEntry.stencilBack = jStencilBack;
            }
        }

        void CreateJsonMaterial(JsonMaterial& jMaterial, const Material& material) const
        {
            CreateJsonGameFlags(jMaterial, material.info.gameFlags);
            jMaterial.sortKey = material.info.sortKey;

            jMaterial.textureAtlas = JsonTextureAtlas();
            jMaterial.textureAtlas->rows = material.info.textureAtlasRowCount;
            jMaterial.textureAtlas->columns = material.info.textureAtlasColumnCount;

            jMaterial.surfaceTypeBits = material.info.surfaceTypeBits;
#if defined(FEATURE_T5) || defined(FEATURE_T6)
            jMaterial.layeredSurfaceTypes = material.info.layeredSurfaceTypes;
#endif
#if defined(FEATURE_T6)
            jMaterial.surfaceFlags = material.info.surfaceFlags;
            jMaterial.contents = material.info.contents;
#endif

            jMaterial.stateBitsEntry.resize(std::extent_v<decltype(Material::stateBitsEntry)>);
            for (auto i = 0u; i < std::extent_v<decltype(Material::stateBitsEntry)>; i++)
                jMaterial.stateBitsEntry[i] = material.stateBitsEntry[i];

            jMaterial.stateFlags = material.stateFlags;
            jMaterial.cameraRegion = static_cast<GfxCameraRegionType>(material.cameraRegion);

            if (material.techniqueSet && material.techniqueSet->name)
                jMaterial.techniqueSet = AssetName(material.techniqueSet->name);

            jMaterial.textures.resize(material.textureCount);
            for (auto i = 0u; i < material.textureCount; i++)
                CreateJsonTexture(jMaterial.textures[i], material.textureTable[i]);

            jMaterial.constants.resize(material.constantCount);
            for (auto i = 0u; i < material.constantCount; i++)
                CreateJsonConstant(jMaterial.constants[i], material.constantTable[i]);

            jMaterial.stateBits.resize(material.stateBitsCount);
            for (auto i = 0u; i < material.stateBitsCount; i++)
                CreateJsonStateBitsTableEntry(jMaterial.stateBits[i], material.stateBitsTable[i]);

#ifdef FEATURE_T6
            if (material.thermalMaterial && material.thermalMaterial->info.name)
                jMaterial.thermalMaterial = AssetName(material.thermalMaterial->info.name);
#endif
        }

        std::ostream& m_stream;
        const MaterialConstantZoneState& m_material_constants;
    };
} // namespace

namespace material
{
    void CLASS_NAME::DumpPool(AssetDumpingContext& context, AssetPool<Material>* pool)
    {
        auto* materialConstantState = context.GetZoneAssetDumperState<MaterialConstantZoneState>();
        materialConstantState->ExtractNamesFromZone();

        AbstractAssetDumper::DumpPool(context, pool);
    }

    bool CLASS_NAME::ShouldDump(XAssetInfo<Material>* asset)
    {
        return true;
    }

    void CLASS_NAME::DumpAsset(AssetDumpingContext& context, XAssetInfo<Material>* asset)
    {
        const auto assetFile = context.OpenAssetFile(GetFileNameForAssetName(asset->m_name));

        if (!assetFile)
            return;

        const auto* material = asset->Asset();
#if defined(FEATURE_T5)
        assert(material->info.gameFlags < 0x400);
        assert(material->maxStreamedMips == 0);
#elif defined(FEATURE_T6)
        assert(material->info.gameFlags < 0x8000);
        assert(material->info.hashIndex == 0);
        assert(material->probeMipBits == 0);
#endif

        JsonDumperImpl dumper(context, *assetFile);
        dumper.Dump(*material);
    }
} // namespace material

