#include <fesa/fem/line2_shape.hpp>

#include <array>
#include <limits>
#include <stdexcept>

#include <gtest/gtest.h>

namespace {

TEST(ShapeFunction, InterpolatesBothEndpoints) {
    const auto first_endpoint = fesa::line2_shape(-1.0);
    const auto second_endpoint = fesa::line2_shape(1.0);

    EXPECT_DOUBLE_EQ(first_endpoint[0], 1.0);
    EXPECT_DOUBLE_EQ(first_endpoint[1], 0.0);
    EXPECT_DOUBLE_EQ(second_endpoint[0], 0.0);
    EXPECT_DOUBLE_EQ(second_endpoint[1], 1.0);
}

TEST(ShapeFunction, FormsPartitionOfUnityWithinRoundoff) {
    for (const double xi : std::array{-1.0, -0.25, 0.0, 0.4, 1.0}) {
        const auto shape = fesa::line2_shape(xi);

        // Four ulps at unit scale cover the two affine evaluations and sum.
        constexpr double tolerance =
            4.0 * std::numeric_limits<double>::epsilon();
        EXPECT_NEAR(shape[0] + shape[1], 1.0, tolerance) << "xi=" << xi;
    }
}

TEST(ShapeFunction, DerivativesSumToZeroAtEveryNaturalCoordinate) {
    for (const double xi : std::array{-1.0, -0.25, 0.0, 0.4, 1.0}) {
        const auto derivative = fesa::line2_shape_derivative(xi);

        EXPECT_DOUBLE_EQ(derivative[0], -0.5);
        EXPECT_DOUBLE_EQ(derivative[1], 0.5);
        EXPECT_DOUBLE_EQ(derivative[0] + derivative[1], 0.0);
    }
}

TEST(Jacobian, ReturnsHalfThePhysicalLength) {
    EXPECT_DOUBLE_EQ(fesa::line2_jacobian(4.0), 2.0);
    EXPECT_DOUBLE_EQ(fesa::line2_jacobian(0.25), 0.125);
}

TEST(Jacobian, DoesNotAdjustSmallPositiveFiniteLengths) {
    constexpr double length = 1.0e-300;

    EXPECT_DOUBLE_EQ(fesa::line2_jacobian(length), length / 2.0);
}

TEST(Jacobian, RejectsNonpositiveAndNonfiniteLengths) {
    const double infinity = std::numeric_limits<double>::infinity();
    const double nan = std::numeric_limits<double>::quiet_NaN();

    for (const double length : std::array{0.0, -1.0, infinity, nan}) {
        EXPECT_THROW(
            static_cast<void>(fesa::line2_jacobian(length)),
            std::invalid_argument);
    }
}

TEST(Jacobian, RejectsLengthWhoseHalfUnderflowsToZero) {
    EXPECT_THROW(
        static_cast<void>(fesa::line2_jacobian(
            std::numeric_limits<double>::denorm_min())),
        std::invalid_argument);
}

}  // namespace
