// -*- tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=8 sw=4 sts=4:

/** \file
    \brief A unit test for the BasicGeometry class
*/

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <cstddef>
#include <cstdlib>
#include <ostream>
#include <iostream>

#include <dune/common/exceptions.hh>
#include <dune/common/ios_state.hh>
#include <dune/common/mpihelper.hh>

#include <dune/geometry/type.hh>
#include <dune/geometry/genericgeometry/geometry.hh>


using namespace Dune;

void fail(int &result) { result = 1; }
void pass(int &result) { if(result == 77) result = 0; }

/** \brief Test whether the given BasicGeometry object 'affine' attribute is
 *         set correctly
 *
 * \param geometry       The Geometry object to test.
 * \param expectedAffine Whether the geometry should be affine.
 * \param result         Collect pass/fail results.
 */
template <class TestGeometry>
void testBasicGeometryAffine(const TestGeometry& geometry, bool expectedAffine,
                             int &result)
{
    bool affine = geometry.affine();
    if(affine != expectedAffine) {
        Dune::ios_base_all_saver saver(std::cerr);
        std::cerr << std::boolalpha;
        std::cerr << "Error: Affine: \"" << affine << "\", but "
                  << "\"" << expectedAffine << "\" was expected!" << std::endl;
        fail(result);
    }
    else
        pass(result);
}

/** \brief Test the volume of the given BasicGeometry object
 *
 * \param geometry       The Geometry object to test.
 * \param expectedVolume The volume to expect from the geometry.
 * \param result         Collect pass/fail results.
 */
template <class TestGeometry>
bool testBasicGeometryVolume(const TestGeometry& geometry,
                             typename TestGeometry::ctype expectedVolume,
                             int &result, bool warnOnly = false)
{
    typedef typename TestGeometry::ctype ctype;

    ctype volume = geometry.volume();
    if(std::abs(volume - expectedVolume) > 1e-8) {
        if(warnOnly)
            std::cerr << "Warning: Volume: " << volume << ", but "
                      << expectedVolume << " was expected!" << std::endl;
        else {
            std::cerr << "Error: Volume: " << volume << ", but "
                      << expectedVolume << " was expected!" << std::endl;
            fail(result);
        }
        return false;
    }
    else {
        pass(result);
        return true;
    }
}

int main (int argc , char **argv) try
{
    Dune::MPIHelper::instance(argc, argv);
    
    // 77 means "SKIP"
    int result = 77;

    double volume;
    bool affine;
    GeometryType gt;

    ////////////////////////////////////////////////////////////////////////
    //
    //  coorddim = 0
    //

    {
        static const std::size_t coorddim = 0;
        std::cout << "== coorddimension = " << coorddim << std::endl;

        std::vector<FieldVector<double, coorddim> > corners;

        //
        //  mydim = 0
        //

        {
            static const std::size_t mydim = 0;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a vertex
                std::cout << "==== Testing vertices..." << std::endl;

                affine = true;

                corners.resize(1);

                volume = 1;

                gt.makeVertex();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // vertex

        } // mydim = 0

    } // coorddim = 0

    ////////////////////////////////////////////////////////////////////////
    //
    //  coorddim = 1
    //

    {
        static const std::size_t coorddim = 1;
        std::cout << "== coorddimension = " << coorddim << std::endl;

        std::vector<FieldVector<double, coorddim> > corners;

        //
        //  mydim = 0
        //

        {
            static const std::size_t mydim = 0;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a vertex
                std::cout << "==== Testing vertices..." << std::endl;

                affine = true;

                corners.resize(1);
                corners[0][0] = 0.314;

                volume = 1;

                gt.makeVertex();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // vertex

        } // mydim = 0

        //
        //  mydim = 1
        //

        {
            static const std::size_t mydim = 1;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a line segment
                std::cout << "==== Testing line segments..."
                          << std::endl;

                affine = true;

                corners.resize(2);
                corners[0][0] = 0.33;
                corners[1][0] = 0.66;

                volume = 0.33;

                gt.makeLine();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // line segment

        } // mydim = 1

    } // coorddim = 1

    ////////////////////////////////////////////////////////////////////////
    //
    //  coorddim = 2
    //

    {
        static const std::size_t coorddim = 2;
        std::cout << "== coorddimension = " << coorddim << std::endl;

        std::vector<FieldVector<double, coorddim> > corners;

        //
        //  mydim = 0
        //

        {
            static const std::size_t mydim = 0;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a vertex
                std::cout << "==== Testing vertices..." << std::endl;

                affine = true;

                corners.resize(1);
                corners[0][0] = 0.314;  corners[0][1] = 0.27;

                volume = 1;

                gt.makeVertex();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // vertex

        } // mydim = 0

        //
        //  mydim = 1
        //

        {
            static const std::size_t mydim = 1;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a line segment
                std::cout << "==== Testing line segments..."
                          << std::endl;

                affine = true;

                corners.resize(2);
                corners[0][0] = 0.33;  corners[0][1] = 0.33;
                corners[1][0] = 0.66;  corners[1][1] = 0.66;

                volume = std::sqrt(2.0)*0.33;

                gt.makeLine();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // line segment

        } // mydim = 1

        //
        //  mydim = 2
        //

        {
            static const std::size_t mydim = 2;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a triangle
                std::cout << "==== Testing triangles..."
                          << std::endl;

                affine = true;

                corners.resize(3);
                corners[0][0] = 0.5;   corners[0][1] = 0.0;
                corners[1][0] = 1.0;   corners[1][1] = 0.5;
                corners[2][0] = 0.0;   corners[2][1] = 1.0;

                volume = 3.0/8;

                gt.makeTriangle();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // triangle

            { // Test an affine quadrilateral
                std::cout << "==== Testing quadrilaterals (affine)..."
                          << std::endl;

                affine = true;

                corners.resize(4);
                corners[0][0] = 0.5;   corners[0][1] = 0.0;
                corners[1][0] = 1.0;   corners[1][1] = 0.5;
                corners[2][0] = 0.0;   corners[2][1] = 0.5;
                corners[3][0] = 0.5;   corners[3][1] = 1.0;

                volume = 0.5;

                gt.makeQuadrilateral();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // affine quadrilateral

            { // Test a non-affine quadrilateral
                std::cout << "==== Testing quadrilaterals (non-affine)..."
                          << std::endl;

                affine = false;

                corners.resize(4);
                corners[0][0] = 0.5;   corners[0][1] = 0.0;
                corners[1][0] = 1.0;   corners[1][1] = 0.0;
                corners[2][0] = 0.5;   corners[2][1] = 0.25;
                corners[3][0] = 0.75;  corners[3][1] = 0.25;

                volume = 1.5/16;

                gt.makeQuadrilateral();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // non-affine quadrilateral

        } // mydim = 2

    } // coorddim = 2

    ////////////////////////////////////////////////////////////////////////
    //
    //  coorddim = 3
    //

    {
        static const std::size_t coorddim = 3;
        std::cout << "== coorddimension = " << coorddim << std::endl;

        std::vector<FieldVector<double, coorddim> > corners;

        //
        //  mydim = 0
        //

        {
            static const std::size_t mydim = 0;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a vertex
                std::cout << "==== Testing vertices..." << std::endl;

                affine = true;

                corners.resize(1);
                corners[0][0] = 0.314;  corners[0][1] = 0.27;  corners[0][2] = 0.71;

                volume = 1;

                gt.makeVertex();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // vertex

        } // mydim = 0

        //
        //  mydim = 1
        //

        {
            static const std::size_t mydim = 1;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a line segment
                std::cout << "==== Testing line segments..."
                          << std::endl;

                affine = true;

                corners.resize(2);
                corners[0][0] = 0.33; corners[0][1] = 0.33; corners[0][2] = 0.33;
                corners[1][0] = 0.66; corners[1][1] = 0.66; corners[1][2] = 0.66;

                volume = std::sqrt(3.0)*0.33;

                gt.makeLine();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);
            } // line segment

        } // mydim = 1

        //
        //  mydim = 2
        //

        {
            static const std::size_t mydim = 2;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a triangle
                std::cout << "==== Testing triangles..."
                          << std::endl;

                affine = true;

                corners.resize(3);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 1.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 1.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;

                volume = std::sqrt(0.125*(2.25-0.125));

                gt.makeTriangle();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // triangle

            { // Test an affine quadrilateral
                std::cout << "==== Testing quadrilaterals (affine)..."
                          << std::endl;

                affine = true;

                corners.resize(4);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 1.0;
                corners[2][0] = 0.0; corners[2][1] = 0.5; corners[2][2] = 0.0;
                corners[3][0] = 0.5; corners[3][1] = 1.0; corners[3][2] = 1.0;

                volume = std::sqrt(0.75);

                gt.makeQuadrilateral();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // affine quadrilateral

            { // Test a non-affine quadrilateral
                std::cout << "==== Testing quadrilaterals (non-affine)..."
                          << std::endl;

                affine = false;

                corners.resize(4);
                corners[0][0] = 0.0; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.0; corners[1][2] = 0.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.0; corners[3][1] = 1.0; corners[3][2] = 1.0;

                // Assuming the surface equation is z=x*y (this satisfies the
                // points above and is bilinear in x and y) I have to
                // integrate
                //
                //   \int_0^1 dx \int_0^1 dy \sqrt(x^2 + y^2 + 1)
                //
                // which evaluates to the value below according to maxima.
                // See
                // http://de.wikipedia.org/wiki/Oberfl%C3%A4chenintegral#Beispiel_2:_Explizite_Darstellung_2
                // and
                // http://de.wikibooks.org/wiki/Diffgeo:_Fl%C3%A4chentheorie:_Fl%C3%A4cheninhalt
                volume = 1.280789271462219;

                gt.makeQuadrilateral();
                ElementGeometry insideGeometry( gt, corners );

                // Downgrade to a warning for now -- it fails, but we don't
                // want to spoil all the other tests.
                if(!testBasicGeometryVolume(insideGeometry, volume, result,
                                            true))
                    std::cerr << "Warning: volume check failure downgraded to "
                              << "a warning (see FS#870)." << std::endl;
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // non-affine quadrilateral

        } // mydim = 2

        //
        //  mydim = 3
        //

        {
            static const std::size_t mydim = 3;
            std::cout << "=== mydimension = " << mydim << std::endl;

            typedef GenericGeometry::BasicGeometry<
                mydim, GenericGeometry::DefaultGeometryTraits<double,coorddim,
                                                              coorddim>
                > ElementGeometry;

            { // Test a tetrahedron
                std::cout << "==== Testing tetrahedron..."
                          << std::endl;

                affine = true;

                corners.resize(4);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 1.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 1.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.0; corners[3][1] = 0.0; corners[3][2] = 0.0;

                // to be determined
                //volume = ???;

                gt.makeTetrahedron();
                ElementGeometry insideGeometry( gt, corners );

                std::cerr << "Warning: volume check skipped (reference volume "
                          << "not known)." << std::endl;
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // tetrahedron

            { // Test an affine pyramid
                std::cout << "==== Testing pyramid (affine)..."
                          << std::endl;

                affine = true;

                corners.resize(5);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 0.0;
                corners[2][0] = 0.0; corners[2][1] = 0.5; corners[2][2] = 0.0;
                corners[3][0] = 0.5; corners[3][1] = 1.0; corners[3][2] = 0.0;
                corners[4][0] = 0.5; corners[4][1] = 0.5; corners[4][2] = 0.5*std::sqrt(0.5);

                volume = std::pow(0.5, 1.5)/6;

                gt.makePyramid();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // affine pyramid

            { // Test a non-affine pyramid
                std::cout << "==== Testing pyramid (non-affine)..."
                          << std::endl;

                affine = false;

                corners.resize(5);
                corners[0][0] = 0.0; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.0; corners[1][2] = 0.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.0; corners[3][1] = 1.0; corners[3][2] = 1.0;
                corners[4][0] = 0.0; corners[4][1] = 0.0; corners[4][2] = 0.5;

                // to be determined
                //volume = ???;

                gt.makePyramid();
                ElementGeometry insideGeometry( gt, corners );

                std::cerr << "Warning: volume check skipped (reference volume "
                          << "not known)." << std::endl;
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // non-affine pyramid

            { // Test an affine prism
                std::cout << "==== Testing prism (affine)..."
                          << std::endl;

                affine = true;

                corners.resize(6);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 1.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 1.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.5; corners[3][1] = 1.0; corners[3][2] = 2.0;
                corners[4][0] = 2.0; corners[4][1] = 1.5; corners[4][2] = 2.0;
                corners[5][0] = 1.0; corners[5][1] = 2.0; corners[5][2] = 1.0;

                // spatprodukt
                volume = 0.375;

                gt.makePrism();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // affine prism

            { // Test a non-affine prism
                std::cout << "==== Testing prism (non-affine)..."
                          << std::endl;

                affine = false;

                corners.resize(6);
                corners[0][0] = 0.5; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.5; corners[1][2] = 0.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.0; corners[3][1] = 1.0; corners[3][2] = 1.0;
                corners[4][0] = 0.0; corners[4][1] = 0.5; corners[4][2] = 1.0;
                corners[5][0] = 0.5; corners[5][1] = 0.0; corners[5][2] = 1.0;

                // to be determined
                //volume = ???;

                gt.makePrism();
                ElementGeometry insideGeometry( gt, corners );

                std::cerr << "Warning: volume check skipped (reference volume "
                          << "not known)." << std::endl;
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // non-affine prism

            { // Test an affine hexahedron
                std::cout << "==== Testing hexahedron (affine)..."
                          << std::endl;

                affine = true;

                corners.resize(8);
                corners[0][0] = 0.0; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.1; corners[1][2] = 0.1;
                corners[2][0] = 0.1; corners[2][1] = 1.0; corners[2][2] = 0.1;
                corners[3][0] = 1.1; corners[3][1] = 1.1; corners[3][2] = 0.2;
                corners[4][0] = 0.1; corners[4][1] = 0.1; corners[4][2] = 1.0;
                corners[5][0] = 1.1; corners[5][1] = 0.2; corners[5][2] = 1.1;
                corners[6][0] = 0.2; corners[6][1] = 1.1; corners[6][2] = 1.1;
                corners[7][0] = 1.2; corners[7][1] = 1.2; corners[7][2] = 1.2;

                // spatprodukt der basisvektoren
                // [(1, .1, .1) x (.1, 1, .1)] * (.1, .1, 1)
                volume = 1.0 - 3*0.1*0.1 + 2*0.1*0.1*0.1;

                gt.makeHexahedron();
                ElementGeometry insideGeometry( gt, corners );

                testBasicGeometryVolume(insideGeometry, volume, result);
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // affine hexahedron

            { // Test a non-affine hexahedron
                std::cout << "==== Testing hexahedron (non-affine)..."
                          << std::endl;

                affine = false;

                corners.resize(8);
                corners[0][0] = 0.0; corners[0][1] = 0.0; corners[0][2] = 0.0;
                corners[1][0] = 1.0; corners[1][1] = 0.0; corners[1][2] = 0.0;
                corners[2][0] = 0.0; corners[2][1] = 1.0; corners[2][2] = 0.0;
                corners[3][0] = 1.0; corners[3][1] = 1.0; corners[3][2] = 0.0;
                // turn the top by 45 degree (and make it smaller)
                corners[4][0] = 0.5; corners[4][1] = 0.0; corners[4][2] = 1.0;
                corners[5][0] = 1.0; corners[5][1] = 0.5; corners[5][2] = 1.0;
                corners[6][0] = 0.0; corners[6][1] = 0.5; corners[6][2] = 1.0;
                corners[7][0] = 0.5; corners[7][1] = 1.0; corners[7][2] = 1.0;

                // to be determined
                //volume = ???;

                gt.makeHexahedron();
                ElementGeometry insideGeometry( gt, corners );

                std::cerr << "Warning: volume check skipped (reference volume "
                          << "not known)." << std::endl;
                testBasicGeometryAffine(insideGeometry, affine, result);

            } // non-affine hexahedron

        } // mydim = 2

    } // coorddim = 3

    return result;
} catch (Dune::Exception& e) {
    std::cerr << e << std::endl;
    throw;
 } catch (...) {
    std::cerr << "Generic exception!" << std::endl;
    throw;
 }
