#include <render/gl/blur/Acrylic.hpp>
#include <render/gl/blur/Aurora.hpp>
#include <render/gl/blur/Kawase.hpp>
#include <render/gl/blur/Glass.hpp>
#include <render/gl/blur/FluidJar.hpp>
#include <render/gl/blur/HeatShimmer.hpp>
#include <render/gl/blur/Haze.hpp>
#include <render/gl/blur/Prism.hpp>
#include <render/gl/blur/Ripple.hpp>
#include <render/gl/blur/Water.hpp>
#include <render/ShaderLoader.hpp>

#include <gtest/gtest.h>

using namespace Render::GL;

TEST(BlurMaterial, DefaultUsesPlainFinish) {
    const CDefaultBlurMaterial material;
    const auto                 requirements = material.requirements();

    EXPECT_EQ(material.type(), Render::eBlurType::BLUR_DUAL_KAWASE);
    EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_BLURFINISH);
    EXPECT_FALSE(requirements.preparedInput);
    EXPECT_FALSE(requirements.liveBlur);
    EXPECT_FALSE(material.isAnimated());
    EXPECT_EQ(material.blurSizeForDamage(100), 40);
    EXPECT_FLOAT_EQ(material.sampleRadius(), 0.F);
}

TEST(BlurMaterial, GlassCapabilitiesAreConfiguredByMaterial) {
    const CGlassBlurMaterial frost(Render::eBlurType::BLUR_FROST, Render::SH_FRAG_FROSTFINISH);
    const auto               frostRequirements = frost.requirements();
    EXPECT_EQ(frost.type(), Render::eBlurType::BLUR_FROST);
    EXPECT_EQ(frostRequirements.finishFragment, Render::SH_FRAG_FROSTFINISH);
    EXPECT_FALSE(frostRequirements.preparedInput);

    const CPrismBlurMaterial prism;
    const auto               prismRequirements = prism.requirements();
    EXPECT_EQ(prism.type(), Render::eBlurType::BLUR_PRISM);
    EXPECT_EQ(prismRequirements.finishFragment, Render::SH_FRAG_PRISMFINISH);
    EXPECT_TRUE(prismRequirements.preparedInput);
}

TEST(BlurMaterial, HeatShimmerUsesAnimatedGlassFinish) {
    const CHeatShimmerBlurMaterial heatShimmer;
    const auto                     requirements = heatShimmer.requirements();

    EXPECT_EQ(heatShimmer.type(), Render::eBlurType::BLUR_HEAT_SHIMMER);
    EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_HEATSHIMMERFINISH);
    EXPECT_FALSE(requirements.preparedInput);
    EXPECT_FALSE(requirements.liveBlur);
}

TEST(BlurMaterial, AuroraUsesAnimatedGlassFinish) {
    const CAuroraBlurMaterial aurora;
    const auto                requirements = aurora.requirements();

    EXPECT_EQ(aurora.type(), Render::eBlurType::BLUR_AURORA);
    EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_AURORAFINISH);
    EXPECT_FALSE(requirements.preparedInput);
    EXPECT_FALSE(requirements.liveBlur);
}

TEST(BlurMaterial, HazeUsesStaticPearlescentFinish) {
    const CHazeBlurMaterial haze;
    const auto              requirements = haze.requirements();

    EXPECT_EQ(haze.type(), Render::eBlurType::BLUR_HAZE);
    EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_HAZEFINISH);
    EXPECT_FALSE(requirements.preparedInput);
    EXPECT_FALSE(requirements.liveBlur);
    EXPECT_FALSE(haze.isAnimated());
    EXPECT_EQ(haze.blurSizeForDamage(100), 40);
    EXPECT_FLOAT_EQ(haze.sampleRadius(), 0.F);
}

TEST(BlurMaterial, AcrylicUsesPreparedLiveFinish) {
    const CAcrylicBlurMaterial acrylic;
    const auto                 requirements = acrylic.requirements();

    EXPECT_EQ(acrylic.type(), Render::eBlurType::BLUR_ACRYLIC);
    EXPECT_EQ(requirements.finishFragment, Render::SH_FRAG_ACRYLICFINISH);
    EXPECT_TRUE(requirements.preparedInput);
    EXPECT_TRUE(requirements.liveBlur);
    EXPECT_FALSE(acrylic.isAnimated());
}

TEST(BlurDamage, DualKawaseUsesOperationalMinimums) {
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(0, 0), 2.F);
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(-10, -10), 2.F);
}

TEST(BlurDamage, DualKawaseCalculatesConfiguredRadius) {
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(8, 1), 16.F);
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(8, 2), 48.F);
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(12, 3), 168.F);
}

TEST(BlurDamage, DualKawaseUsesOperationalMaximums) {
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(40, 8), 20400.F);
    EXPECT_FLOAT_EQ(dualKawaseDamageRadius(100, 10), 51000.F);
}

TEST(BlurDamage, GlassIncludesRefractionReach) {
    EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, 3.F), 19.F);
    EXPECT_FLOAT_EQ(glassDamageRadius(12, 3, 4.25F), 173.F);
}

TEST(BlurDamage, GlassClampsRefractionReach) {
    EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, -1.F), 16.F);
    EXPECT_FLOAT_EQ(glassDamageRadius(8, 1, 100.F), 36.F);
}

TEST(BlurDamage, AcrylicIncludesFilteredRefractionReach) {
    EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, 3.F), 20.F);
    EXPECT_FLOAT_EQ(acrylicDamageRadius(12, 3, 4.25F), 174.F);
}

TEST(BlurDamage, AcrylicClampsRefractionReach) {
    EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, -1.F), 16.F);
    EXPECT_FLOAT_EQ(acrylicDamageRadius(8, 1, 100.F), 65.F);
}

TEST(BlurDamage, RippleIncludesBoundedDisplacement) {
    EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, 6.F), 22.F);
    EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, -1.F), 16.F);
    EXPECT_FLOAT_EQ(rippleDamageRadius(8, 1, 100.F), 48.F);
}

TEST(BlurDamage, RippleOutputReachIncludesWaveWidth) {
    EXPECT_FLOAT_EQ(rippleOutputReach(180.F, 24.F), 204.F);
    EXPECT_FLOAT_EQ(rippleOutputReach(10.25F, 2.25F), 13.F);
    EXPECT_FLOAT_EQ(rippleOutputReach(-1.F, -1.F), 0.F);
}

TEST(BlurDamage, WaterIncludesBoundedDisplacement) {
    EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, 6.F), 22.F);
    EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, -1.F), 16.F);
    EXPECT_FLOAT_EQ(waterDamageRadius(8, 1, 100.F), 48.F);
}

TEST(FluidJar, SimulationSizePreservesAspectAndBoundsParticles) {
    const auto wide = fluidJarSimulationSize({1920, 1080});
    EXPECT_EQ(wide, Vector2D(256, 144));
    EXPECT_LE(fluidJarParticleCapacity(wide), 2048);

    const auto square = fluidJarSimulationSize({1000, 1000});
    EXPECT_EQ(square, Vector2D(250, 250));
    EXPECT_LE(fluidJarParticleCapacity(square), 2048);
}

TEST(FluidJar, DamageIncludesBoundedRefraction) {
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1), 24.F);
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 2), 56.F);
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 0.F), 16.F);
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 2.F), 32.F);
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 4.F), 48.F);
    EXPECT_FLOAT_EQ(fluidJarDamageRadius(8, 1, 10.F), 96.F);
}

TEST(FluidJar, OutputTransformsMapFramebufferToLogicalCoordinates) {
    const std::array<SFluidJarOutputTransform, 8> expected = {
        SFluidJarOutputTransform{.xAxis = {1, 0}, .yAxis = {0, 1}, .offset = {0, 0}},   SFluidJarOutputTransform{.xAxis = {0, -1}, .yAxis = {1, 0}, .offset = {0, 1}},
        SFluidJarOutputTransform{.xAxis = {-1, 0}, .yAxis = {0, -1}, .offset = {1, 1}}, SFluidJarOutputTransform{.xAxis = {0, 1}, .yAxis = {-1, 0}, .offset = {1, 0}},
        SFluidJarOutputTransform{.xAxis = {-1, 0}, .yAxis = {0, 1}, .offset = {1, 0}},  SFluidJarOutputTransform{.xAxis = {0, 1}, .yAxis = {1, 0}, .offset = {0, 0}},
        SFluidJarOutputTransform{.xAxis = {1, 0}, .yAxis = {0, -1}, .offset = {0, 1}},  SFluidJarOutputTransform{.xAxis = {0, -1}, .yAxis = {-1, 0}, .offset = {1, 1}},
    };

    for (size_t i = 0; i < expected.size(); ++i) {
        const auto transform = fluidJarOutputTransform(sc<eTransform>(i));
        EXPECT_EQ(transform.xAxis, expected[i].xAxis);
        EXPECT_EQ(transform.yAxis, expected[i].yAxis);
        EXPECT_EQ(transform.offset, expected[i].offset);
    }
}

TEST(FluidJar, OutputTransformsKeepTheFloorAtLogicalBottom) {
    const std::array<Vector2D, 8> outputBottomCenters = {
        Vector2D{0.5, 1.0}, Vector2D{0.0, 0.5}, Vector2D{0.5, 0.0}, Vector2D{1.0, 0.5}, Vector2D{0.5, 1.0}, Vector2D{1.0, 0.5}, Vector2D{0.5, 0.0}, Vector2D{0.0, 0.5},
    };

    for (size_t i = 0; i < outputBottomCenters.size(); ++i) {
        const auto transform = fluidJarOutputTransform(sc<eTransform>(i));
        const auto point     = outputBottomCenters[i];
        const auto logical   = transform.xAxis * point.x + transform.yAxis * point.y + transform.offset;
        EXPECT_EQ(logical, Vector2D(0.5, 1.0));
    }
}

TEST(FluidJar, OutputTransformVectorsRoundTrip) {
    constexpr Vector2D LOGICAL_VECTOR = {0.25, -0.75};

    for (int i = 0; i < 8; ++i) {
        const auto     transform    = fluidJarOutputTransform(sc<eTransform>(i));
        const Vector2D outputVector = {
            transform.xAxis.x * LOGICAL_VECTOR.x + transform.xAxis.y * LOGICAL_VECTOR.y,
            transform.yAxis.x * LOGICAL_VECTOR.x + transform.yAxis.y * LOGICAL_VECTOR.y,
        };
        const auto logicalVector = transform.xAxis * outputVector.x + transform.yAxis * outputVector.y;
        EXPECT_EQ(logicalVector, LOGICAL_VECTOR);
    }
}

TEST(FluidJar, SimulationSizeRejectsEmptyExtents) {
    EXPECT_EQ(fluidJarSimulationSize({0, 100}), Vector2D());
    EXPECT_EQ(fluidJarSimulationSize({100, 0}), Vector2D());
}

TEST(FluidJar, PrecisionScalesSimulationAndClamps) {
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 0.5F), Vector2D(128, 72));
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 2.F), Vector2D(512, 288));
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 4.F), Vector2D(1024, 576));
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 8.F), Vector2D(2048, 1152));
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 0.1F), Vector2D(128, 72));
    EXPECT_EQ(fluidJarSimulationSize({1920, 1080}, 20.F), Vector2D(2048, 1152));
}

TEST(FluidJar, MaximumPrecisionBoundsParticleCapacity) {
    const auto size = fluidJarSimulationSize({4096, 4096}, 8.F);
    EXPECT_EQ(size, Vector2D(2048, 2048));
    EXPECT_EQ(fluidJarParticleCapacity(size), 131072);
}

TEST(FluidJar, FillAmountControlsInitialParticles) {
    const Vector2D size = {256, 144};
    EXPECT_EQ(fluidJarParticleCapacity(size), 1152);
    EXPECT_EQ(fluidJarInitialParticleCount(size, 0.4F), 460);
    EXPECT_EQ(fluidJarInitialParticleCount(size, -1.F), 0);
    EXPECT_EQ(fluidJarInitialParticleCount(size, 2.F), 1152);
}

TEST(FluidJar, ResizePreservesParticles) {
    EXPECT_EQ(fluidJarResizedParticleCount(460, {256, 144}), 460);
    EXPECT_EQ(fluidJarResizedParticleCount(460, {64, 64}), 460);
    EXPECT_EQ(fluidJarResizedParticleCount(64, {256, 144}), 64);
}

TEST(FluidJar, GeometryTransformPreservesWorldPositionDuringMove) {
    const auto transform = fluidJarGeometryTransform({100, 200, 800, 600}, {140, 220, 800, 600}, {200, 150}, {200, 150});
    EXPECT_EQ(transform.positionScale, Vector2D(1, 1));
    EXPECT_EQ(transform.positionOffset, Vector2D(-10, 5));
    EXPECT_EQ(transform.velocityScale, Vector2D(1, 1));
}

TEST(FluidJar, GeometryTransformPreservesStationaryResizeEdges) {
    const auto right = fluidJarGeometryTransform({0, 0, 800, 600}, {0, 0, 1000, 600}, {200, 150}, {250, 150});
    EXPECT_EQ(right.positionScale, Vector2D(1, 1));
    EXPECT_EQ(right.positionOffset, Vector2D(0, 0));

    const auto left = fluidJarGeometryTransform({0, 0, 800, 600}, {-200, 0, 1000, 600}, {200, 150}, {250, 150});
    EXPECT_EQ(left.positionScale, Vector2D(1, 1));
    EXPECT_EQ(left.positionOffset, Vector2D(50, 0));

    const auto bottom = fluidJarGeometryTransform({0, 0, 800, 600}, {0, 0, 800, 800}, {200, 150}, {200, 200});
    EXPECT_EQ(bottom.positionScale, Vector2D(1, 1));
    EXPECT_EQ(bottom.positionOffset, Vector2D(0, 50));
}

TEST(FluidJar, DiscontinuousGeometryFollowsContainer) {
    const auto transform = fluidJarGeometryTransform({0, 0, 800, 600}, {1200, 400, 1000, 800}, {200, 150}, {250, 200}, false);
    EXPECT_EQ(transform.positionScale, Vector2D(1.25, 4.0 / 3.0));
    EXPECT_EQ(transform.positionOffset, Vector2D(0, 0));
    EXPECT_EQ(transform.velocityScale, Vector2D(0, 0));
}

TEST(FluidJar, WallVelocityUsesSimulationCoordinates) {
    const auto moved = fluidJarWallVelocities({0, 0, 800, 600}, {6, 6, 800, 600}, {200, 150}, 1.F / 60.F);
    EXPECT_FLOAT_EQ(moved[0], 0.5F);
    EXPECT_FLOAT_EQ(moved[1], 0.5F);
    EXPECT_FLOAT_EQ(moved[2], -0.5F);

    const auto fasterSimulation = fluidJarWallVelocities({0, 0, 800, 600}, {6, 6, 800, 600}, {200, 150}, 1.F / 60.F, 2.F);
    EXPECT_FLOAT_EQ(fasterSimulation[0], 0.25F);
    EXPECT_FLOAT_EQ(fasterSimulation[1], 0.25F);
    EXPECT_FLOAT_EQ(fasterSimulation[2], -0.25F);

    const auto resized = fluidJarWallVelocities({0, 0, 800, 600}, {0, 0, 806, 600}, {200, 150}, 1.F / 60.F);
    EXPECT_FLOAT_EQ(resized[0], 0.F);
    EXPECT_NEAR(resized[1], 6.F * (200.F / 806.F) / 3.F, 0.0001F);
    EXPECT_FLOAT_EQ(resized[2], 0.F);
}

TEST(FluidJar, WallVelocityRejectsInvalidIntervalsAndClampsSpikes) {
    EXPECT_EQ(fluidJarWallVelocities({0, 0, 800, 600}, {10, 0, 800, 600}, {200, 150}, 0.F), (std::array<float, 4>{}));

    const auto velocity = fluidJarWallVelocities({0, 0, 800, 600}, {1000, 0, 800, 600}, {200, 150}, 1.F / 60.F);
    EXPECT_FLOAT_EQ(velocity[0], 1.25F);
    EXPECT_FLOAT_EQ(velocity[1], 1.25F);

    EXPECT_EQ(fluidJarWallVelocities({0, 0, 800, 600}, {10, 0, 800, 600}, {200, 150}, 1.F / 60.F, 0.F), (std::array<float, 4>{}));
}
