#ifndef TEST_HALF_EDGE_STRUCTURE_H
#define TEST_HALF_EDGE_STRUCTURE_H

// Libraries
#include "reactphysics3d.h"
#include "Test.h"

/// Reactphysics3D namespace
namespace reactphysics3d {


// Class TestHalfEdgeStructure
/**
 * Unit test for the HalfEdgeStructure class.
 */
class TestHalfEdgeStructure : public Test {

    private :

        // ---------- Atributes ---------- //


    public :

        // ---------- Methods ---------- //

        /// Constructor
        TestHalfEdgeStructure(const std::string& name) : Test(name) {

        }

        /// Destructor
        virtual ~TestHalfEdgeStructure() {

        }

        /// Run the tests
        void run() {
            testCube();
            testTetrahedron();
        }

        void testCube() {

            // Create the half-edge structure for a cube
            rp3d::HalfEdgeStructure cubeStructure;

            rp3d::Vector3 vertices[8] = {
                rp3d::Vector3(-0.5, -0.5, 0.5),
                rp3d::Vector3(0.5, -0.5, 0.5),
                rp3d::Vector3(0.5, 0.5, 0.5),
                rp3d::Vector3(-0.5, 0.5, 0.5),
                rp3d::Vector3(-0.5, -0.5, -0.5),
                rp3d::Vector3(0.5, -0.5, -0.5),
                rp3d::Vector3(0.5, 0.5, -0.5),
                rp3d::Vector3(-0.5, 0.5, -0.5)
            };

            // Vertices
            cubeStructure.addVertex(0);
            cubeStructure.addVertex(1);
            cubeStructure.addVertex(2);
            cubeStructure.addVertex(3);
            cubeStructure.addVertex(4);
            cubeStructure.addVertex(5);
            cubeStructure.addVertex(6);
            cubeStructure.addVertex(7);

            // Faces
            std::vector<uint> face0;
            face0.push_back(0); face0.push_back(1); face0.push_back(2); face0.push_back(3);
            std::vector<uint> face1;
            face1.push_back(1); face1.push_back(5); face1.push_back(6); face1.push_back(2);
            std::vector<uint> face2;
            face2.push_back(5); face2.push_back(4); face2.push_back(7); face2.push_back(6);
            std::vector<uint> face3;
            face3.push_back(4); face3.push_back(0); face3.push_back(3); face3.push_back(7);
            std::vector<uint> face4;
            face4.push_back(0); face4.push_back(4); face4.push_back(5); face4.push_back(1);
            std::vector<uint> face5;
            face5.push_back(2); face5.push_back(6); face5.push_back(7); face5.push_back(3);

            cubeStructure.addFace(face0);
            cubeStructure.addFace(face1);
            cubeStructure.addFace(face2);
            cubeStructure.addFace(face3);
            cubeStructure.addFace(face4);
            cubeStructure.addFace(face5);

            cubeStructure.init();

            // --- Test that the half-edge structure of the cube is valid --- //

            test(cubeStructure.getNbFaces() == 6);
            test(cubeStructure.getNbVertices() == 8);
            test(cubeStructure.getNbHalfEdges() == 24);

            // Test vertices
            test(vertices[cubeStructure.getVertex(0).vertexPointIndex].x == -0.5);
            test(vertices[cubeStructure.getVertex(0).vertexPointIndex].y == -0.5);
            test(vertices[cubeStructure.getVertex(0).vertexPointIndex].z == 0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(0).edgeIndex).vertexIndex == 0);

            test(vertices[cubeStructure.getVertex(1).vertexPointIndex].x == 0.5);
            test(vertices[cubeStructure.getVertex(1).vertexPointIndex].y == -0.5);
            test(vertices[cubeStructure.getVertex(1).vertexPointIndex].z == 0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(1).edgeIndex).vertexIndex == 1);

            test(vertices[cubeStructure.getVertex(2).vertexPointIndex].x == 0.5);
            test(vertices[cubeStructure.getVertex(2).vertexPointIndex].y == 0.5);
            test(vertices[cubeStructure.getVertex(2).vertexPointIndex].z == 0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(2).edgeIndex).vertexIndex == 2);

            test(vertices[cubeStructure.getVertex(3).vertexPointIndex].x == -0.5);
            test(vertices[cubeStructure.getVertex(3).vertexPointIndex].y == 0.5);
            test(vertices[cubeStructure.getVertex(3).vertexPointIndex].z == 0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(3).edgeIndex).vertexIndex == 3);

            test(vertices[cubeStructure.getVertex(4).vertexPointIndex].x == -0.5);
            test(vertices[cubeStructure.getVertex(4).vertexPointIndex].y == -0.5);
            test(vertices[cubeStructure.getVertex(4).vertexPointIndex].z == -0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(4).edgeIndex).vertexIndex == 4);

            test(vertices[cubeStructure.getVertex(5).vertexPointIndex].x == 0.5);
            test(vertices[cubeStructure.getVertex(5).vertexPointIndex].y == -0.5);
            test(vertices[cubeStructure.getVertex(5).vertexPointIndex].z == -0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(5).edgeIndex).vertexIndex == 5);

            test(vertices[cubeStructure.getVertex(6).vertexPointIndex].x == 0.5);
            test(vertices[cubeStructure.getVertex(6).vertexPointIndex].y == 0.5);
            test(vertices[cubeStructure.getVertex(6).vertexPointIndex].z == -0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(6).edgeIndex).vertexIndex == 6);

            test(vertices[cubeStructure.getVertex(7).vertexPointIndex].x == -0.5);
            test(vertices[cubeStructure.getVertex(7).vertexPointIndex].y == 0.5);
            test(vertices[cubeStructure.getVertex(7).vertexPointIndex].z == -0.5);
            test(cubeStructure.getHalfEdge(cubeStructure.getVertex(7).edgeIndex).vertexIndex == 7);

            // Test faces
            for (uint f=0; f<6; f++) {
                test(cubeStructure.getHalfEdge(cubeStructure.getFace(f).edgeIndex).faceIndex == f);
            }

            // Test edges
            for (uint f=0; f<6; f++) {


                uint edgeIndex = cubeStructure.getFace(f).edgeIndex;
                const uint firstEdgeIndex = edgeIndex;

                // For each half-edge of the face
                for (uint e=0; e<4; e++) {

                    rp3d::HalfEdgeStructure::Edge edge = cubeStructure.getHalfEdge(edgeIndex);

                    test(cubeStructure.getHalfEdge(edge.twinEdgeIndex).twinEdgeIndex == edgeIndex);
                    test(edge.faceIndex == f);

                    // Go to the next edge
                    edgeIndex = edge.nextEdgeIndex;
                }

                test(firstEdgeIndex == edgeIndex);
            }
        }

        void testTetrahedron() {

            // Create the half-edge structure for a tetrahedron
            std::vector<std::vector<uint>> faces;
            rp3d::HalfEdgeStructure tetrahedron;

            // Vertices
            rp3d::Vector3 vertices[4] = {
                rp3d::Vector3(1, -1, -1),
                rp3d::Vector3(-1, -1, -1),
                rp3d::Vector3(0, -1, 1),
                rp3d::Vector3(0, 1, 0)
            };

            tetrahedron.addVertex(0);
            tetrahedron.addVertex(1);
            tetrahedron.addVertex(2);
            tetrahedron.addVertex(3);

            // Faces
            std::vector<uint> face0;
            face0.push_back(0); face0.push_back(1); face0.push_back(2);
            std::vector<uint> face1;
            face1.push_back(0); face1.push_back(3); face1.push_back(1);
            std::vector<uint> face2;
            face2.push_back(1); face2.push_back(3); face2.push_back(2);
            std::vector<uint> face3;
            face3.push_back(0); face3.push_back(2); face3.push_back(3);

            tetrahedron.addFace(face0);
            tetrahedron.addFace(face1);
            tetrahedron.addFace(face2);
            tetrahedron.addFace(face3);

            tetrahedron.init();

            // --- Test that the half-edge structure of the tetrahedron is valid --- //

            test(tetrahedron.getNbFaces() == 4);
            test(tetrahedron.getNbVertices() == 4);
            test(tetrahedron.getNbHalfEdges() == 12);

            // Test vertices
            test(vertices[tetrahedron.getVertex(0).vertexPointIndex].x == 1);
            test(vertices[tetrahedron.getVertex(0).vertexPointIndex].y == -1);
            test(vertices[tetrahedron.getVertex(0).vertexPointIndex].z == -1);
            test(tetrahedron.getHalfEdge(tetrahedron.getVertex(0).edgeIndex).vertexIndex == 0);

            test(vertices[tetrahedron.getVertex(1).vertexPointIndex].x == -1);
            test(vertices[tetrahedron.getVertex(1).vertexPointIndex].y == -1);
            test(vertices[tetrahedron.getVertex(1).vertexPointIndex].z == -1);
            test(tetrahedron.getHalfEdge(tetrahedron.getVertex(1).edgeIndex).vertexIndex == 1);

            test(vertices[tetrahedron.getVertex(2).vertexPointIndex].x == 0);
            test(vertices[tetrahedron.getVertex(2).vertexPointIndex].y == -1);
            test(vertices[tetrahedron.getVertex(2).vertexPointIndex].z == 1);
            test(tetrahedron.getHalfEdge(tetrahedron.getVertex(2).edgeIndex).vertexIndex == 2);

            test(vertices[tetrahedron.getVertex(3).vertexPointIndex].x == 0);
            test(vertices[tetrahedron.getVertex(3).vertexPointIndex].y == 1);
            test(vertices[tetrahedron.getVertex(3).vertexPointIndex].z == 0);
            test(tetrahedron.getHalfEdge(tetrahedron.getVertex(3).edgeIndex).vertexIndex == 3);

            // Test faces
            for (uint f=0; f<4; f++) {
                test(tetrahedron.getHalfEdge(tetrahedron.getFace(f).edgeIndex).faceIndex == f);
            }

            // Test edges
            for (uint f=0; f<4; f++) {

                uint edgeIndex = tetrahedron.getFace(f).edgeIndex;
                const uint firstEdgeIndex = edgeIndex;

                // For each half-edge of the face
                for (uint e=0; e<3; e++) {

                    rp3d::HalfEdgeStructure::Edge edge = tetrahedron.getHalfEdge(edgeIndex);

                    test(tetrahedron.getHalfEdge(edge.twinEdgeIndex).twinEdgeIndex == edgeIndex);
                    test(edge.faceIndex == f);

                    // Go to the next edge
                    edgeIndex = edge.nextEdgeIndex;
                }

                test(firstEdgeIndex == edgeIndex);
            }
        }
 };

}

#endif