//! Used to test the function ff::atomic::z_clip
#include "ff/EulerOperations.h"
#include "ff/Make.h"
#include "ff/Polyhedron.h"
#include "ff/Topology.h"
#include "test/3rdparty/catch.hpp"
namespace {
const ff::Topology topologyPyramid{{
                                       {{0, 1, 2, 3}, false},
                                       {{4, 1, 0}, false},
                                       {{4, 2, 1}, false},
                                       {{4, 3, 2}, false},
                                       {{4, 0, 3}, false},
                                   },
                                   false};
const std::vector<R3> verticesPyramid{
    {{-1, -1, -1}, {-1, 1, -1}, {1, 1, -1}, {1, -1, -1}, {0, 0, 1}}};

const ff::Box* box = ff::make::Box(1, 1, 1);

auto cube = std::unique_ptr<ff::Polyhedron>(box->asPolyhedron());
auto pyramid = std::make_unique<ff::Polyhedron>(topologyPyramid, verticesPyramid);
auto octahedron = std::unique_ptr<ff::Polyhedron>(ff::make::Octahedron(1));
auto icosahedron = std::unique_ptr<ff::Polyhedron>(ff::make::Icosahedron(1));
auto dodecahedron = std::unique_ptr<ff::Polyhedron>(ff::make::Dodecahedron(1));

void volume_require(const ff::Polyhedron& polyhedron, double z_clip, double volumeOriginal,
                    double volumeDiff)
{
    auto clipped = std::unique_ptr<ff::Polyhedron>(ff::atomic::z_clip(polyhedron, z_clip));
    REQUIRE(fabs(volumeOriginal - clipped->volume() - volumeDiff) < 1E-12);
}
void clip_location_require(const ff::Polyhedron& polyhedron, double z_clip)
{
    auto clipped = std::unique_ptr<ff::Polyhedron>(ff::atomic::z_clip(polyhedron, z_clip));
    REQUIRE(polyhedron.vertices().size() == clipped->vertices().size());
}
void vertex_below_require(const ff::Polyhedron& polyhedron)
{
    auto clipped = std::unique_ptr<ff::Polyhedron>(ff::atomic::z_clip(polyhedron, 0));
    for (const R3& vertex : clipped->vertices())
        REQUIRE(vertex.z() >= 0);
}
void vertex_above_require(const ff::Polyhedron& polyhedron)
{
    auto clipped = std::unique_ptr<ff::Polyhedron>(ff::atomic::z_clip(polyhedron, 0, true));
    for (const R3& vertex : clipped->vertices())
        REQUIRE(vertex.z() <= 0);
}
void topology_require(const ff::Polyhedron& polyhedron, double z_clip,
                      const ff::Topology& newTopology, bool below)
{
    auto clipped = std::unique_ptr<ff::Polyhedron>(ff::atomic::z_clip(polyhedron, z_clip, below));
    REQUIRE(clipped->topology() == newTopology);
}

} // namespace

//! Test whether the volume of bodies after clipping is correct

TEST_CASE("ZClip:HalfVolumeOfSymmetricPolyhedra", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double, double>> const table{
        {cube, 0, cube->volume() / 2},
        {octahedron, 0, octahedron->volume() / 2},
        {icosahedron, 0, icosahedron->volume() / 2},
        {dodecahedron, 0, dodecahedron->volume() / 2},
    };
    for (const auto& [polyhedron, z_cut, diffVolume] : table)
        volume_require(*polyhedron, z_cut, polyhedron->volume(), diffVolume);
}

//! Test that no change occurs if the clip plane is above

TEST_CASE("ZClip:VertexLocations:AboveBody", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double>> const table{
        {cube, -cube->z_bottom() * 3},
        {pyramid, -pyramid->z_bottom() * 3},
        {octahedron, -octahedron->z_bottom() * 3},
        {icosahedron, -icosahedron->z_bottom() * 3},
        {dodecahedron, -dodecahedron->z_bottom() * 3},
    };
    for (const auto& [polyhedron, z_cut] : table)
        clip_location_require(*polyhedron, z_cut);
}

//! Test that no change occurs if the clip plane is below

TEST_CASE("ZClip:VertexLocations:BelowBody", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double>> const table{
        {cube, cube->z_bottom() * 3},
        {pyramid, pyramid->z_bottom() * 3},
        {octahedron, octahedron->z_bottom() * 3},
        {icosahedron, icosahedron->z_bottom() * 3},
        {dodecahedron, dodecahedron->z_bottom() * 3},
    };
    for (const auto& [polyhedron, z_cut] : table)
        clip_location_require(*polyhedron, z_cut);
}

//! Test that no change occurs if a face is lying in the clip plane and the body below

TEST_CASE("ZClip:VertexLocations:OnTopBody", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double>> const table{
        {cube, cube->z_bottom() * 2},
    };
    for (const auto& [polyhedron, z_cut] : table)
        clip_location_require(*polyhedron, z_cut);
}

//! Test that no change occurs if a face is lying in the clip plane and the body above

TEST_CASE("ZClip:VertexLocations:OnBottomBody", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double>> const table{
        {cube, 0},
    };
    for (const auto& [polyhedron, z_cut] : table)
        clip_location_require(*polyhedron, z_cut);
}

//! Test that no change occurs if only one vertex is in the clipping plane

TEST_CASE("ZClip:VertexLocations:IntersectOneVertex", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double>> const table{
        {pyramid, -pyramid->z_bottom() * 2},
        {octahedron, -octahedron->z_bottom() * 2},
    };
    for (const auto& [polyhedron, z_cut] : table)
        clip_location_require(*polyhedron, z_cut);
}

//! Test that clipping is correct when clipping through an already existing edge

TEST_CASE("ZClip:VertexLocations:IntersectEdge", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double, ff::Topology>> const table{
        {octahedron,
         0,
         {{{{0, 2, 4}, false},
           {{3, 0, 4}, false},
           {{3, 4, 1}, false},
           {{2, 1, 4}, false},
           {{1, 2, 0, 3}, false}},
          false}},
    };
    for (const auto& [polyhedron, z_cut, topology] : table)
        topology_require(*polyhedron, z_cut, topology, false);
}

//! Test that after clipping all vertices in the clipped space vanished, therefore no edges face
//! reside in the clipped space

TEST_CASE("ZClip:VertexLocations:NoVerticesInClippedSpace", "")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&>> const table{
        {cube}, {pyramid}, {octahedron}, {icosahedron}, {dodecahedron},
    };
    for (const auto& [polyhedron] : table)
        vertex_below_require(*polyhedron);
    for (const auto& [polyhedron] : table)
        vertex_above_require(*polyhedron);
}

//! Test for correct results of the topology

TEST_CASE("ZClip:ExpectedTopology")
{
    std::vector<std::tuple<std::unique_ptr<ff::Polyhedron>&, double, ff::Topology>> const table{
        {cube,
         0,
         {{{{4, 5, 6, 7}, false},
           {{0, 2, 4, 7}, false},
           {{0, 7, 6, 3}, false},
           {{1, 5, 4, 2}, false},
           {{1, 3, 6, 5}, false},
           {{1, 2, 0, 3}, false}},
          false}},
        {pyramid,
         0,
         {{{{0, 3, 4}, false},
           {{0, 4, 1}, false},
           {{1, 4, 2}, false},
           {{2, 4, 3}, false},
           {{2, 3, 0, 1}, false}},
          false}},
        {octahedron,
         0,
         {{{{0, 2, 4}, false},
           {{3, 0, 4}, false},
           {{3, 4, 1}, false},
           {{2, 1, 4}, false},
           {{1, 2, 0, 3}, false}},
          false}},
        {dodecahedron,
         0,
         {{{{0, 1, 2}, false},
           {{3, 4, 5}, false},
           {{6, 7, 8}, false},
           {{9, 10, 11}, false},
           {{12, 13, 14}, false},
           {{3, 15, 16, 6, 8, 4}, false},
           {{1, 17, 15, 3, 5, 2}, false},
           {{0, 13, 12, 18, 17, 1}, false},
           {{9, 19, 18, 12, 14, 10}, false},
           {{6, 16, 19, 9, 11, 7}, false},
           {{15, 17, 18, 19, 16}, false},
           {{7, 11, 10, 14, 13, 0, 2, 5, 4, 8}, false}},
          false}},
    };
    for (const auto& [polyhedron, z_cut, topology] : table)
        topology_require(*polyhedron, z_cut, topology, false);
}