/*---------------------------------------------------------------------------*\

    geophase: A C++ template library for geometrical operations on non-convex
    polyhedra with non-planar faces
     
    Copyright (C) 2019 Tomislav Maric, TU Darmstadt 

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Application
    geophaseTestAreaVolume 

Description
    Test different area and volume calculation algorithms. 
   
Author
    Tomislav Maric maric@mma.tu-darmstadt.de

\*---------------------------------------------------------------------------*/

// Tested
#include "Area.hpp"
#include "Volume.hpp"
#include "Triangulate.hpp"
#include "Polygon.hpp"
#include "AxisAlignedBoundingBox.hpp"

// Testing
#include "gtest/gtest.h" 
#include "WriteVtkPolyData.hpp"
#include "testUtilities.hpp"
#include "Perturb.hpp"
#include "Make.hpp"

using namespace geophase;

TEST(AREA, VECTOR)
{
    static_assert((area_oriented(vector{1.,2.,3.}) == 0.)); 
}

TEST(AREA_ORIENTED, TRIANGLE)
{
    auto tri = make_unit<triangle>();  
    ASSERT_EQ(area_oriented(tri), 0.5); 
}

TEST(AREA_ORIENTED, SQUARE)
{
    auto sqr = make_unit<rectangle>(); 
    ASSERT_EQ(area_oriented(sqr), 1.); 
}

TEST(AREA_ORIENTED, HEXAGON)
{
    auto hex = make_unit<hexagon>(); 
    write_vtk(hex, fullTestName() + "-hex.vtk");
    ASSERT_DOUBLE_EQ(area_oriented(hex), 3.0 * cos(30. * M_PI / 180.));
}

TEST(AREA_ORIENTED, VECTOR_POLYGON_SQUARE)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.}, 
        {1.,0.,0.}, 
        {1.,1.,0.}, 
        {0.,1.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 1.); 

}

TEST(AREA_ORIENTED, VECTOR_POLYGON_DEGENERATE_POINT)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 0.); 
}

TEST(AREA_ORIENTED, VECTOR_POLYGON_DEGENERATE_POINT_MULTIPLE)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.},
        {0.,0.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 0.); 
}

TEST(AREA_ORIENTED, VECTOR_POLYGON_DEGENERATE_TRIANGLE_POINT)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.},
        {0.,0.,0.},
        {0.,0.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 0.); 
}

TEST(AREA_ORIENTED, VECTOR_POLYGON_DEGENERATE_TRIANGLE_EDGE)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.},
        {1.,0.,0.},
        {0.,0.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 0.); 
}

TEST(AREA_ORIENTED, VECTOR_POLYGON_DEGENERATE_LINE)
{
    vectorPolygon vectorUnitSquare = {
        {0.,0.,0.},
        {1.,0.,0.}
    };
    ASSERT_EQ(area_oriented(vectorUnitSquare), 0.); 
}

TEST(AREA_ORIENTED, VECTOR_TRIANGLE)
{
    vectorPolygon vectorUnitTriangle = {
        {0.,0.,0.}, 
        {1.,0.,0.}, 
        {0.,1.,0.}
    };

    ASSERT_EQ(area_oriented(vectorUnitTriangle), 0.5); 
}

TEST(AREA_NORMAL_VECTOR, VECTOR_TRIANGLE)
{
    vectorPolygon vectorUnitTriangle = {
        {0.,0.,0.}, 
        {1.,0.,0.}, 
        {0.,1.,0.}
    };

    const vector normal {0.,0.,0.5};
    ASSERT_EQ(area_normal_vector(vectorUnitTriangle), normal); 
}
    
TEST(AREA_NORMAL_VECTOR, VECTOR_SQUARE)
{
    auto vectorSquare = make_unit_square<vectorPolygon>();

    const vector normal {0.,0.,1.};
    ASSERT_EQ(area_normal_vector(vectorSquare), normal); 
}
    
// UNIT: Small tests used in development for fast / integration testing.
class VOLUME : public ::testing::Test {
 protected:
  void SetUp() override {
      EPSILON_ = 128 * std::numeric_limits<double>::epsilon();
  }

  double EPSILON_; 
};

    
TEST_F(VOLUME, AABBOX)
{
    auto box = aabbox(point(0.,0.,0.), point(1.,1.,1.));

    ASSERT_EQ(volume(box), 1.0);
}

TEST_F(VOLUME, TETRAHEDRON_POINTS)
{
    std::vector<point> pts {
        {0.,0.,0.}, 
        {1.,0.,0.}, 
        {1.,1.,0.}, 
        {0.,0.,1.}, 
    };

    ASSERT_EQ(volume(pts[0], pts[1], pts[2], pts[3]), 1.0 / 6.0);
}

TEST_F(VOLUME, TETRAHEDRON)
{
    auto tetrahedron = make_unit_tetrahedron(); 
    auto tetrahedronTri = oriented_triangulate(tetrahedron); 

    ASSERT_DOUBLE_EQ(volume_by_vol_tri(tetrahedron), 1.0 / 6.0);
    ASSERT_DOUBLE_EQ(volume_by_surf_tri(tetrahedron), 1.0 / 6.0);
    ASSERT_DOUBLE_EQ(volume_by_vol_tri(tetrahedronTri), 1.0 / 6.0);
}


TEST_F(VOLUME, CUBE)
{
    auto cube = make_unit_cube(); 
    auto cubeOrientedTri = oriented_triangulate(cube); 
    auto cubeCentroidTri = centroid_triangulate(cube); 
    auto tetrahedra = centroid_vol_triangulate(cube); 

    ASSERT_LE(std::abs(volume_by_vol_tri(cube) - 1.0), EPSILON_);
    ASSERT_LE(std::abs(volume_by_surf_tri(cube) -1.0), EPSILON_);
    ASSERT_LE(std::abs(volume_by_surf_tri(cubeOrientedTri) - 1.0), EPSILON_);
    ASSERT_LE(std::abs(volume_by_surf_tri(cubeCentroidTri) - 1.0), EPSILON_);
    ASSERT_LE(std::abs(volume_by_vol_tri(tetrahedra) -1.0), EPSILON_);
    ASSERT_LE(std::abs(volume_by_surf_tri(tetrahedra) -1.0), EPSILON_);
}

template<typename Dodecahedron> 
void test_dodecahedron_volume(Dodecahedron const& dodecahedron, double EPSILON)
{
    auto dodecaTriangulation = centroid_triangulate(dodecahedron); 
    auto tetrahedra = centroid_vol_triangulate(dodecahedron);

    write_vtk(dodecahedron, fullTestName() + ".vtk");

    auto volSurfPoly = volume_by_surf_tri(dodecahedron);
    auto volSurfSurfTri = volume_by_surf_tri(dodecaTriangulation); 
    auto volVolPoly = volume_by_vol_tri(dodecahedron);
    auto volTetras = volume_by_surf_tri(tetrahedra);

    EXPECT_LE(std::abs(volSurfPoly - volSurfSurfTri), EPSILON) 
        << "volSurfPoly = " << volSurfPoly 
        << "\nvolSurfSurfTri = " << volSurfSurfTri;
    EXPECT_LE(std::abs(volSurfSurfTri - volVolPoly), EPSILON)
        << "volSurfSurfTri = " << volSurfSurfTri 
        << "\nvolVolPoly = " << volVolPoly;
    EXPECT_LE(std::abs(volVolPoly - volTetras), EPSILON)
        << "volVolPoly = " << volVolPoly 
        << "\nvolTetras = " << volTetras;
}

TEST_F(VOLUME, DODECAHEDRON)
{
    auto dodecahedron = make_unit_dodecahedron(); 
    test_dodecahedron_volume(dodecahedron, EPSILON_);
}

TEST_F(VOLUME, NON_PLANAR_DODECAHEDRON)
{
    auto nonPlanarDodecahedron = make_unit_dodecahedron(0.2); 
    test_dodecahedron_volume(nonPlanarDodecahedron, EPSILON_);
}

TEST_F(VOLUME, ENDO_DODECAHEDRON)
{
    auto endoDodecahedron = make_unit_endo_dodecahedron(); 
    test_dodecahedron_volume(endoDodecahedron, EPSILON_);
}


// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Main program:

int main(int argc, char *argv[])
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}


// ************************************************************************* //
