#include "Game/IW5/Techset/TechsetCompilerIW5.h"

#include "Game/IW5/IW5.h"
#include "SearchPath/MockSearchPath.h"
#include "Techset/TechsetCommon.h"
#include "Utils/TestMemoryManager.h"

#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <memory>

using namespace IW5;
using namespace std::string_literals;

namespace
{
    MaterialTechnique* GivenTechnique(const std::string& name, AssetCreationContext& context, MemoryManager& memory)
    {
        auto* technique = memory.Alloc<MaterialTechnique>();
        technique->name = memory.Dup(name.c_str());

        context.AddSubAsset<SubAssetTechnique>(name, technique);

        return technique;
    }
} // namespace

TEST_CASE("TechsetCompilerIW5", "[techset][iw5][compiler]")
{
    Zone zone("test", 0, GameId::IW5, GamePlatform::PC);
    AssetCreatorCollection creators(zone);
    IgnoredAssetLookup ignoredAssets;
    AssetCreationContext context(zone, &creators, &ignoredAssets);
    MockSearchPath searchPath;
    TestMemoryManager memory;
    const auto sut = techset::CreateTechsetCompilerIW5(memory, searchPath);

    SECTION("Sets correct worldVertFormat")
    {
        const auto [techsetName, expectedWorldVertFormat] = GENERATE(Catch::Generators::table<const char*, MaterialWorldVertexFormat>({
            {"default",                                     MTL_WORLDVERT_TEX_1_NRM_1},
            {"effect_zeqqz943",                             MTL_WORLDVERT_TEX_1_NRM_1},
            {"lit_r0c0_t1c1n1",                             MTL_WORLDVERT_TEX_2_NRM_1},
            {"lit_r0c0n0x0_b1c1n1s1v1",                     MTL_WORLDVERT_TEX_2_NRM_2},
            {"lit_r0c0n0x0_b1c1n1s1v1_b2c2n2x2",            MTL_WORLDVERT_TEX_3_NRM_3},
            {"lit_sm_b0c0_b1c1_b2c2",                       MTL_WORLDVERT_TEX_3_NRM_1},
            {"lit_sm_b0c0_b1c1n1x1_b2c2n2v2",               MTL_WORLDVERT_TEX_3_NRM_2},
            {"lit_sm_r0c0_b1c1_b2c2_b3c3",                  MTL_WORLDVERT_TEX_4_NRM_1},
            {"lit_sm_r0c0n0_b1c1_b2c2n2v2_m3c3",            MTL_WORLDVERT_TEX_4_NRM_2},
            {"lit_sm_r0c0n0_b1c1n1s1",                      MTL_WORLDVERT_TEX_2_NRM_2},
            {"lit_sm_r0c0n0s0_b1c1n1s1_b2c2n2",             MTL_WORLDVERT_TEX_3_NRM_3},
            {"lit_sm_r0c0n0x0_b1c1_b2c2n2s2",               MTL_WORLDVERT_TEX_3_NRM_2},
            {"lit_sm_r0c0n0x0_b1c1n1_b2c2n2s2v2",           MTL_WORLDVERT_TEX_3_NRM_3},
            {"lit_sm_r0c0n0x0_b1c1s1v1_m2c2_m3c3",          MTL_WORLDVERT_TEX_4_NRM_1},
            {"lit_sm_r0c0n0x0_b1c1v1_b2c2n2s2_m3c3",        MTL_WORLDVERT_TEX_4_NRM_2},
            {"lit_sm_r0c0s0_b1c1n1s1_m2c2",                 MTL_WORLDVERT_TEX_3_NRM_1},
            {"lit_sm_r0c0x0_b1c1",                          MTL_WORLDVERT_TEX_2_NRM_1},
            {"lit_sm_r0c0x0_b1c1n1s1_b2c2n2s2",             MTL_WORLDVERT_TEX_3_NRM_2},
            {"lit_sm_r0c0x0_m1c1_m2c2_m3c3",                MTL_WORLDVERT_TEX_4_NRM_1},
            {"lit_sm_t0c0n0_b1c1n1v1",                      MTL_WORLDVERT_TEX_2_NRM_2},
            {"lit_sm_t0c0n0s0_b1c1n1_b2c2n2s2v2",           MTL_WORLDVERT_TEX_3_NRM_3},
            {"mc_lit_sm_r0c0d0_2213939z",                   MTL_WORLDVERT_TEX_1_NRM_1},
            {"wpc_lit_sm_b0c0s0_3f3q946z",                  MTL_WORLDVERT_TEX_1_NRM_1},
            {"wpc_lit_sm_r0c0n0s0o0_qj92q1f8",              MTL_WORLDVERT_TEX_1_NRM_1},
            {"wpc_sw4_3d_burning_embers_nuketown_74j6971w", MTL_WORLDVERT_TEX_1_NRM_1},
            {"wpc_unlitdecalblend_add_j26wq580",            MTL_WORLDVERT_TEX_1_NRM_1},
        }));

        CAPTURE(techsetName);
        searchPath.AddFileData(techset::GetFileNameForTechsetName(techsetName), "");

        const auto result = sut->CreateAsset(techsetName, context);
        REQUIRE(result.HasBeenSuccessful());

        const auto* techset = static_cast<MaterialTechniqueSet*>(result.GetAssetInfo()->m_ptr);
        CHECK(techset->worldVertFormat == expectedWorldVertFormat);
    }

    SECTION("Can parse simple techset")
    {
        searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET(
"depth prepass":
  example_zprepass;

"lit":
  example_lit;
)TECHSET");

        auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory);
        auto* exampleLit = GivenTechnique("example_lit", context, memory);

        const auto result = sut->CreateAsset("simple", context);
        REQUIRE(result.HasBeenSuccessful());

        const auto* techset = static_cast<MaterialTechniqueSet*>(result.GetAssetInfo()->m_ptr);
        CHECK(techset->name == "simple"s);
        CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1);

        size_t techniqueCount = 0;
        for (auto* technique : techset->techniques)
        {
            if (technique)
                techniqueCount++;
        }

        CHECK(techniqueCount == 2);
        CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass);
        CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLit);
    }

    SECTION("Can parse techset with same technique used multiple times")
    {
        searchPath.AddFileData(techset::GetFileNameForTechsetName("simple"), R"TECHSET(
"depth prepass":
"build shadowmap depth":
  example_zprepass;

"lit":
"lit sun":
"lit instanced spot shadow cucoloris dfog":
  example_lit_sun;
)TECHSET");

        auto* exampleZPrepass = GivenTechnique("example_zprepass", context, memory);
        auto* exampleLitSun = GivenTechnique("example_lit_sun", context, memory);

        const auto result = sut->CreateAsset("simple", context);
        REQUIRE(result.HasBeenSuccessful());

        const auto* techset = static_cast<MaterialTechniqueSet*>(result.GetAssetInfo()->m_ptr);
        CHECK(techset->name == "simple"s);
        CHECK(techset->worldVertFormat == MTL_WORLDVERT_TEX_1_NRM_1);

        size_t techniqueCount = 0;
        for (auto* technique : techset->techniques)
        {
            if (technique)
                techniqueCount++;
        }

        CHECK(techniqueCount == 5);
        CHECK(techset->techniques[TECHNIQUE_DEPTH_PREPASS] == exampleZPrepass);
        CHECK(techset->techniques[TECHNIQUE_BUILD_SHADOWMAP_DEPTH] == exampleZPrepass);
        CHECK(techset->techniques[TECHNIQUE_LIT] == exampleLitSun);
        CHECK(techset->techniques[TECHNIQUE_LIT_SUN] == exampleLitSun);
        CHECK(techset->techniques[TECHNIQUE_LIT_INSTANCED_SPOT_SHADOW_CUCOLORIS_DFOG] == exampleLitSun);
    }
}
