#include <nanobench.h>
#include <gtest/gtest.h>
#include <vis/vis.hh>
#include <common/qvec.hh>
#include <common/polylib.hh>

#include <array>
#include <vector>

TEST(benchmark, winding)
{
    ankerl::nanobench::Bench bench;

    bench.run("std::vector<double> reserve(3*4*6)", [&] {
        std::vector<double> temp;
        temp.reserve(3 * 4 * 6);
        ankerl::nanobench::doNotOptimizeAway(temp);
    });
    bench.run("std::vector<qvec3d> reserve(4*6)", [&] {
        std::vector<qvec3d> temp;
        temp.reserve(4 * 6);
        ankerl::nanobench::doNotOptimizeAway(temp);
    });
    bench.run("std::array<double, 3*4*6>", [&] {
        std::array<double, 3 * 4 * 6> temp;
        ankerl::nanobench::doNotOptimizeAway(temp);
    });
    bench.run("std::array<qvec3d, 4*6>", [&] {
        std::array<qvec3d, 4 * 6> temp;
        ankerl::nanobench::doNotOptimizeAway(temp);
    });
    bench.run("polylib::winding_base_t<6> construct", [&] {
        polylib::winding_base_t<polylib::winding_storage_hybrid_t<double, 6>> temp;
        ankerl::nanobench::doNotOptimizeAway(temp);
    });
}

static void test_polylib(bool check_results)
{
    polylib::winding_t w(4);

    // top face to TB default brush
    w[0] = {-64, 64, 16};
    w[1] = {64, 64, 16};
    w[2] = {64, -64, 16};
    w[3] = {-64, -64, 16};

    qplane3d splitplane{{1, 0, 0}, 0};
    auto [front, back] = w.clip(splitplane);

    ankerl::nanobench::doNotOptimizeAway(front);
    ankerl::nanobench::doNotOptimizeAway(back);

    if (check_results) {
        ASSERT_TRUE(front);
        ASSERT_TRUE(back);

        EXPECT_EQ(front->size(), 4);
        EXPECT_EQ(back->size(), 4);

        // check front polygon
        EXPECT_EQ(front->at(0), qvec3d(0, 64, 16));
        EXPECT_EQ(front->at(1), qvec3d(64, 64, 16));
        EXPECT_EQ(front->at(2), qvec3d(64, -64, 16));
        EXPECT_EQ(front->at(3), qvec3d(0, -64, 16));

        // check back polygon
        EXPECT_EQ(back->at(0), qvec3d(-64, 64, 16));
        EXPECT_EQ(back->at(1), qvec3d(0, 64, 16));
        EXPECT_EQ(back->at(2), qvec3d(0, -64, 16));
        EXPECT_EQ(back->at(3), qvec3d(-64, -64, 16));
    }
}

TEST(benchmark, SplitFace)
{
    ankerl::nanobench::Bench().run("create and split a face (polylib)", [&]() { test_polylib(false); });

    // run with doctest assertions, to validate that they actually work
    test_polylib(true);
}

TEST(benchmark, visWindings)
{
    ankerl::nanobench::Bench b;
    b.run("create pstack_t", [&]() {
        pstack_t stack;
        for (int i = 0; i < 3; ++i)
            stack.windings_used[i] = false;

        ankerl::nanobench::doNotOptimizeAway(stack);
    });

    b.run("create pstack_t + 1x AllocStackWinding", [&]() {
        pstack_t stack;
        for (int i = 0; i < 3; ++i)
            stack.windings_used[i] = false;

        auto *w1 = AllocStackWinding(stack);
        ankerl::nanobench::doNotOptimizeAway(*w1);

        FreeStackWinding(w1, stack);

        ankerl::nanobench::doNotOptimizeAway(stack);
    });

    b.run("create pstack_t + 2x AllocStackWinding", [&]() {
        pstack_t stack;
        for (int i = 0; i < 3; ++i)
            stack.windings_used[i] = false;

        auto *w1 = AllocStackWinding(stack);
        ankerl::nanobench::doNotOptimizeAway(*w1);

        auto *w2 = AllocStackWinding(stack);
        ankerl::nanobench::doNotOptimizeAway(*w2);

        FreeStackWinding(w1, stack);
        FreeStackWinding(w2, stack);

        ankerl::nanobench::doNotOptimizeAway(stack);
    });

    b.run("setup + ClipStackWinding", [&]() {
        visstats_t stats;
        pstack_t stack;
        for (int i = 0; i < 3; ++i)
            stack.windings_used[i] = false;

        auto *w1 = AllocStackWinding(stack);
        w1->numpoints = 4;
        w1->points[0] = {0, 0, 0};
        w1->points[1] = {32, 0, 0};
        w1->points[2] = {32, 0, -32};
        w1->points[3] = {0, 0, -32};
        w1->set_winding_sphere();

        w1 = ClipStackWinding(stats, w1, stack, qplane3d({-1, 0, 0}, -16));
        ankerl::nanobench::doNotOptimizeAway(*w1);

        FreeStackWinding(w1, stack);
        ankerl::nanobench::doNotOptimizeAway(stack);
    });
}

TEST(benchmark, vectorMath)
{
    ankerl::nanobench::Bench b;
    ankerl::nanobench::Rng rng;

    qvec3d vec0{rng.uniform01(), rng.uniform01(), rng.uniform01()};
    qvec3d vec1{rng.uniform01(), rng.uniform01(), rng.uniform01()};

    b.run("dot product", [&]() { vec0[0] = qv::dot(vec0, vec1); });
    b.run("add", [&]() { vec0 = vec0 + vec1; });
    b.run("subtract", [&]() { vec0 = vec0 - vec1; });

    b.doNotOptimizeAway(vec0);
    b.doNotOptimizeAway(vec1);
}
