// Description: Testing for HalfMesh
// DocumentationOf: halfmesh.h
#include "test/test_init.h"
#include "pastel/geometry/halfmesh/halfmesh.h"
#include "pastel/sys/random.h"
#include <algorithm>
#include <iostream>
namespace
{
    using Mesh = HalfEdge<int, int, int, int, false, true>;
    using Vertex_Iterator = Mesh::Vertex_Iterator;
    using Half_Iterator = Mesh::Half_Iterator;
    using Edge_Iterator = Mesh::Edge_Iterator;
    using Polygon_Iterator = Mesh::Polygon_Iterator;
    using Vertex_ConstIterator = Mesh::Vertex_ConstIterator;
    using Half_ConstIterator = Mesh::Half_ConstIterator;
    using Edge_ConstIterator = Mesh::Edge_ConstIterator;
    using Polygon_ConstIterator = Mesh::Polygon_ConstIterator;
    template <typename InputIterator,
        typename OtherInputIterator>
    bool rotationEqual(
    InputIterator from, InputIterator to,
    OtherInputIterator otherFrom)
    {
        if (from == to)
        {
            return true;
        }
        InputIterator pivot(std::find(from, to, *otherFrom));
        int delta = std::distance(from, pivot);
        OtherInputIterator otherPivot(otherFrom);
        std::advance(otherPivot, delta);
        return std::equal(pivot, to, otherFrom) ||
            std::equal(from, pivot, otherPivot);
    }
}
TEST_CASE("Basic (HalfMesh)")
{
    Mesh mesh;
    REQUIRE(testInvariants(mesh));
    mesh.clear();
    REQUIRE(testInvariants(mesh));
    Mesh otherMesh(mesh);
    REQUIRE(testInvariants(mesh));
    REQUIRE(testInvariants(otherMesh));
    otherMesh.swap(mesh);
    REQUIRE(testInvariants(mesh));
    REQUIRE(testInvariants(otherMesh));
    otherMesh.clear();
    REQUIRE(testInvariants(otherMesh));
    mesh.clear();
    REQUIRE(testInvariants(mesh));
}
TEST_CASE("VertexAdd (HalfMesh)")
{
    Mesh mesh;
    integer n = 100;
    for (integer i = 0;i < n;++i)
    {
        mesh.insertVertex();
        REQUIRE(testInvariants(mesh));
    }
    Mesh otherMesh(mesh);
    REQUIRE(testInvariants(mesh));
    REQUIRE(testInvariants(otherMesh));
}
TEST_CASE("Edge (HalfMesh)")
{
    Mesh mesh;
    integer VertexCount = 100;
    std::vector<Vertex_Iterator> vertex(VertexCount);
    for (integer i = 0;i < VertexCount;++i)
    {
        vertex[i] = mesh.insertVertex();
        REQUIRE(testInvariants(mesh));
    }
    {
        Mesh otherMesh(mesh);
        REQUIRE(testInvariants(mesh));
        REQUIRE(testInvariants(otherMesh));
    }
    integer BucketSize = 100;
    integer BucketCount = 10;
    integer EdgeCount = BucketSize * BucketCount;
    std::vector<Edge_Iterator> edgeList(EdgeCount);
    for (integer i = 0;i < BucketCount;++i)
    {
        for (integer j = 0;j < BucketSize;++j)
        {
            integer aVertex = randomInteger(VertexCount);
            integer bVertex = randomInteger(VertexCount);
            auto edgePair = 
                mesh.insertEdge(vertex[aVertex], vertex[bVertex]);
            REQUIRE(testInvariants(mesh));
            auto edge = edgePair.first;
            ENSURE(edge.isNormal());
            if (edgePair.second)
            {
                edgeList[i * BucketSize + j] = edge;
            }
        }
    }
    REQUIRE(testInvariants(mesh));
    {
        Mesh otherMesh(mesh);
        REQUIRE(testInvariants(mesh));
        REQUIRE(testInvariants(otherMesh));
    }
    for (integer i = 0;i < EdgeCount;++i)
    {
        integer index = randomInteger(EdgeCount);
        if (edgeList[index].isNormal())
        {
            mesh.removeEdge(edgeList[index]);
            edgeList[index].clear();
        }
        REQUIRE(testInvariants(mesh));
    }
}
TEST_CASE("Polygon (HalfMesh)")
{
    Mesh mesh;
    Vertex_Iterator vertex[4][4];
    for (integer i = 0;i < 4;++i)
    {
        for (integer j = 0;j < 4;++j)
        {
            vertex[i][j] = mesh.insertVertex();
            REQUIRE(testInvariants(mesh));
            REQUIRE(!vertex[i][j].empty());
            (vertex[i][j])->data() = i * 10 + j;
        }
    }
    // o   o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    Polygon_Iterator polygon[16];
    std::vector<Vertex_Iterator> points;
    points.clear();
    points.push_back(vertex[0][0]);
    points.push_back(vertex[1][0]);
    points.push_back(vertex[1][1]);
    polygon[0] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o   o   o   o
    // |0\.
    // o---o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    points.clear();
    points.push_back(vertex[0][0]);
    points.push_back(vertex[1][1]);
    points.push_back(vertex[0][1]);
    polygon[1] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o   o   o
    // |0\1|
    // o---o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    points.clear();
    points.push_back(vertex[0][1]);
    points.push_back(vertex[1][1]);
    points.push_back(vertex[1][0]);
    points.push_back(vertex[2][0]);
    points.push_back(vertex[2][1]);
    points.push_back(vertex[2][2]);
    points.push_back(vertex[1][2]);
    points.push_back(vertex[0][2]);
    polygon[2] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o   o
    // |0\1|###|
    // o---o#2#o   o
    // |#######|
    // o---o---o   o
    //
    // o   o   o   o
    points.clear();
    points.push_back(vertex[2][2]);
    points.push_back(vertex[3][2]);
    points.push_back(vertex[3][3]);
    points.push_back(vertex[2][3]);
    polygon[3] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o   o
    // |0\1|###|
    // o---o#2#o   o
    // |#######|
    // o---o---o---o
    //         |#3#|
    // o   o   o---o
    points.clear();
    points.push_back(vertex[1][2]);
    points.push_back(vertex[2][3]);
    points.push_back(vertex[1][3]);
    polygon[4] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o   o
    // |0\1|###|
    // o---o#2#o---o
    // |#######| \4|
    // o---o---o---o
    //         |#3#|
    // o   o   o---o
    points.clear();
    points.push_back(vertex[1][2]);
    points.push_back(vertex[2][2]);
    points.push_back(vertex[2][3]);
    polygon[5] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o   o
    // |0\1|###|
    // o---o#2#o---o
    // |#######|5\4|
    // o---o---o---o
    //         |#3#|
    // o   o   o---o
    points.clear();
    points.push_back(vertex[0][2]);
    points.push_back(vertex[1][2]);
    points.push_back(vertex[1][3]);
    points.push_back(vertex[0][3]);
    polygon[6] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o---o
    // |0\1|###|#6#|
    // o---o#2#o---o
    // |#######|5\4|
    // o---o---o---o
    //         |#3#|
    // o   o   o---o
    points.clear();
    points.push_back(vertex[2][0]);
    points.push_back(vertex[3][0]);
    points.push_back(vertex[3][1]);
    points.push_back(vertex[3][2]);
    points.push_back(vertex[2][2]);
    points.push_back(vertex[2][1]);
    polygon[7] = mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    // o---o---o---o
    // |0\1|###|#6#|
    // o---o#2#o---o
    // |#######|5\4|
    // o---o---o---o
    // |###7###|#3#|
    // o---o---o---o
    mesh.removePolygon(polygon[5]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[0]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[7]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[6]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[4]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[1]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[2]);
    REQUIRE(testInvariants(mesh));
    mesh.removePolygon(polygon[3]);
    REQUIRE(testInvariants(mesh));
}
TEST_CASE("Polygon2 (HalfMesh)")
{
    Mesh mesh;
    Vertex_Iterator vertex[5][4];
    for (integer i = 0;i < 5;++i)
    {
        for (integer j = 0;j < 4;++j)
        {
            vertex[i][j] = mesh.insertVertex();
            REQUIRE(testInvariants(mesh));
            REQUIRE(!vertex[i][j].empty());
            (vertex[i][j])->data() = i * 10 + j;
        }
    }
    // o   o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    //
    // o   o   o   o
    std::vector<Vertex_Iterator> points;
    points.clear();
    points.push_back(vertex[3][1]);
    points.push_back(vertex[4][1]);
    points.push_back(vertex[4][0]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[2][1]);
    points.push_back(vertex[2][2]);
    points.push_back(vertex[3][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[2][2]);
    points.push_back(vertex[2][3]);
    points.push_back(vertex[3][3]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[1][2]);
    points.push_back(vertex[1][3]);
    points.push_back(vertex[2][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[0][1]);
    points.push_back(vertex[0][2]);
    points.push_back(vertex[1][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[0][1]);
    points.push_back(vertex[1][2]);
    points.push_back(vertex[1][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[1][3]);
    points.push_back(vertex[2][3]);
    points.push_back(vertex[2][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[3][2]);
    points.push_back(vertex[4][2]);
    points.push_back(vertex[4][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[3][2]);
    points.push_back(vertex[3][3]);
    points.push_back(vertex[4][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[2][2]);
    points.push_back(vertex[3][3]);
    points.push_back(vertex[3][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[0][2]);
    points.push_back(vertex[1][3]);
    points.push_back(vertex[1][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[3][1]);
    points.push_back(vertex[3][2]);
    points.push_back(vertex[4][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[2][2]);
    points.push_back(vertex[3][2]);
    points.push_back(vertex[3][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[1][1]);
    points.push_back(vertex[2][1]);
    points.push_back(vertex[2][0]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[1][1]);
    points.push_back(vertex[2][2]);
    points.push_back(vertex[2][1]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[0][0]);
    points.push_back(vertex[1][1]);
    points.push_back(vertex[1][0]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
    points.clear();
    points.push_back(vertex[1][1]);
    points.push_back(vertex[1][2]);
    points.push_back(vertex[2][2]);
    mesh.insertPolygon(points);
    REQUIRE(testInvariants(mesh));
}
TEST_CASE("Merge (HalfMesh)")
{
    Mesh mesh;
    Vertex_Iterator vertex[4][4];
    for (integer i = 0; i < 4; ++i)
    {
        for (integer j = 0; j < 4; ++j)
        {
            vertex[i][j] = mesh.insertVertex();
            REQUIRE(testInvariants(mesh));
            REQUIRE(!vertex[i][j].empty());
            (vertex[i][j])->data() = i * 10 + j;
        }
    }
    // o   o   o   o
    //
    // o   o   o   o
    // 
    // o---o---o---o
    // |   |       |
    // o---o---o---o
    {
        Polygon_Iterator aPolygon = 
            mesh.insertPolygon(
            range({
                vertex[0][0],
                vertex[1][0],
                vertex[1][1],
                vertex[0][1]
            }));
        unused(aPolygon);
        REQUIRE(testInvariants(mesh));
        REQUIRE(mesh.vertices() == 16);
        REQUIRE(mesh.edges() == 4);
        REQUIRE(mesh.polygons() == 1);
        Polygon_Iterator bPolygon =
            mesh.insertPolygon(
            range({
                vertex[1][0],
                vertex[2][0],
                vertex[3][0],
                vertex[3][1],
                vertex[2][1],
                vertex[1][1]
            }));
        unused(bPolygon);
        REQUIRE(testInvariants(mesh));
        REQUIRE(mesh.vertices() == 16);
        REQUIRE(mesh.edges() == 9);
        REQUIRE(mesh.polygons() == 2);
        mesh.merge(mesh.findHalf(vertex[1][0], vertex[1][1]));
        REQUIRE(testInvariants(mesh));
        REQUIRE(mesh.vertices() == 16);
        REQUIRE(mesh.edges() == 8);
        REQUIRE(mesh.polygons() == 1);
    }
}