//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// mathutil_unittest:
//   Unit tests for the utils defined in mathutil.h
//

#include "mathutil.h"

#include <gtest/gtest.h>

using namespace gl;

namespace
{

// Test the correctness of packSnorm2x16 and unpackSnorm2x16 functions.
// For floats f1 and f2, unpackSnorm2x16(packSnorm2x16(f1, f2)) should be same as f1 and f2.
TEST(MathUtilTest, packAndUnpackSnorm2x16)
{
    const float input[8][2] =
    {
        { 0.0f, 0.0f },
        { 1.0f, 1.0f },
        { -1.0f, 1.0f },
        { -1.0f, -1.0f },
        { 0.875f, 0.75f },
        { 0.00392f, -0.99215f },
        { -0.000675f, 0.004954f },
        { -0.6937f, -0.02146f }
    };
    const float floatFaultTolerance = 0.0001f;
    float outputVal1, outputVal2;

    for (size_t i = 0; i < 8; i++)
    {
        unpackSnorm2x16(packSnorm2x16(input[i][0], input[i][1]), &outputVal1, &outputVal2);
        EXPECT_NEAR(input[i][0], outputVal1, floatFaultTolerance);
        EXPECT_NEAR(input[i][1], outputVal2, floatFaultTolerance);
    }
}

// Test the correctness of packSnorm2x16 and unpackSnorm2x16 functions with infinity values,
// result should be clamped to [-1, 1].
TEST(MathUtilTest, packAndUnpackSnorm2x16Infinity)
{
    const float floatFaultTolerance = 0.0001f;
    float outputVal1, outputVal2;

    unpackSnorm2x16(packSnorm2x16(std::numeric_limits<float>::infinity(),
                                  std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(1.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(1.0f, outputVal2, floatFaultTolerance);

    unpackSnorm2x16(packSnorm2x16(std::numeric_limits<float>::infinity(),
                                  -std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(1.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(-1.0f, outputVal2, floatFaultTolerance);

    unpackSnorm2x16(packSnorm2x16(-std::numeric_limits<float>::infinity(),
                                  -std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(-1.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(-1.0f, outputVal2, floatFaultTolerance);
}

// Test the correctness of packUnorm2x16 and unpackUnorm2x16 functions.
// For floats f1 and f2, unpackUnorm2x16(packUnorm2x16(f1, f2)) should be same as f1 and f2.
TEST(MathUtilTest, packAndUnpackUnorm2x16)
{
    const float input[8][2] =
    {
        { 0.0f, 0.0f },
        { 1.0f, 1.0f },
        { -1.0f, 1.0f },
        { -1.0f, -1.0f },
        { 0.875f, 0.75f },
        { 0.00392f, -0.99215f },
        { -0.000675f, 0.004954f },
        { -0.6937f, -0.02146f }
    };
    const float floatFaultTolerance = 0.0001f;
    float outputVal1, outputVal2;

    for (size_t i = 0; i < 8; i++)
    {
        unpackUnorm2x16(packUnorm2x16(input[i][0], input[i][1]), &outputVal1, &outputVal2);
        float expected = input[i][0] < 0.0f ? 0.0f : input[i][0];
        EXPECT_NEAR(expected, outputVal1, floatFaultTolerance);
        expected = input[i][1] < 0.0f ? 0.0f : input[i][1];
        EXPECT_NEAR(expected, outputVal2, floatFaultTolerance);
    }
}

// Test the correctness of packUnorm2x16 and unpackUnorm2x16 functions with infinity values,
// result should be clamped to [0, 1].
TEST(MathUtilTest, packAndUnpackUnorm2x16Infinity)
{
    const float floatFaultTolerance = 0.0001f;
    float outputVal1, outputVal2;

    unpackUnorm2x16(packUnorm2x16(std::numeric_limits<float>::infinity(),
                                  std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(1.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(1.0f, outputVal2, floatFaultTolerance);

    unpackUnorm2x16(packUnorm2x16(std::numeric_limits<float>::infinity(),
                                  -std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(1.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(0.0f, outputVal2, floatFaultTolerance);

    unpackUnorm2x16(packUnorm2x16(-std::numeric_limits<float>::infinity(),
                                  -std::numeric_limits<float>::infinity()), &outputVal1, &outputVal2);
    EXPECT_NEAR(0.0f, outputVal1, floatFaultTolerance);
    EXPECT_NEAR(0.0f, outputVal2, floatFaultTolerance);
}

// Test the correctness of packHalf2x16 and unpackHalf2x16 functions.
// For floats f1 and f2, unpackHalf2x16(packHalf2x16(f1, f2)) should be same as f1 and f2.
TEST(MathUtilTest, packAndUnpackHalf2x16)
{
    const float input[8][2] =
    {
        { 0.0f, 0.0f },
        { 1.0f, 1.0f },
        { -1.0f, 1.0f },
        { -1.0f, -1.0f },
        { 0.875f, 0.75f },
        { 0.00392f, -0.99215f },
        { -0.000675f, 0.004954f },
        { -0.6937f, -0.02146f },
    };
    const float floatFaultTolerance = 0.0005f;
    float outputVal1, outputVal2;

    for (size_t i = 0; i < 8; i++)
    {
        unpackHalf2x16(packHalf2x16(input[i][0], input[i][1]), &outputVal1, &outputVal2);
        EXPECT_NEAR(input[i][0], outputVal1, floatFaultTolerance);
        EXPECT_NEAR(input[i][1], outputVal2, floatFaultTolerance);
    }
}

// Test the correctness of packUnorm4x8 and unpackUnorm4x8 functions.
// For floats f1 to f4, unpackUnorm4x8(packUnorm4x8(f1, f2, f3, f4)) should be same as f1 to f4.
TEST(MathUtilTest, packAndUnpackUnorm4x8)
{
    const float input[5][4] = {{0.0f, 0.0f, 0.0f, 0.0f},
                               {1.0f, 1.0f, 1.0f, 1.0f},
                               {-1.0f, 1.0f, -1.0f, 1.0f},
                               {-1.0f, -1.0f, -1.0f, -1.0f},
                               {64.0f / 255.0f, 128.0f / 255.0f, 32.0f / 255.0f, 16.0f / 255.0f}};

    const float floatFaultTolerance = 0.005f;
    float outputVals[4];

    for (size_t i = 0; i < 5; i++)
    {
        UnpackUnorm4x8(PackUnorm4x8(input[i][0], input[i][1], input[i][2], input[i][3]),
                       outputVals);
        for (size_t j = 0; j < 4; j++)
        {
            float expected = input[i][j] < 0.0f ? 0.0f : input[i][j];
            EXPECT_NEAR(expected, outputVals[j], floatFaultTolerance);
        }
    }
}

// Test the correctness of packSnorm4x8 and unpackSnorm4x8 functions.
// For floats f1 to f4, unpackSnorm4x8(packSnorm4x8(f1, f2, f3, f4)) should be same as f1 to f4.
TEST(MathUtilTest, packAndUnpackSnorm4x8)
{
    const float input[5][4] = {{0.0f, 0.0f, 0.0f, 0.0f},
                               {1.0f, 1.0f, 1.0f, 1.0f},
                               {-1.0f, 1.0f, -1.0f, 1.0f},
                               {-1.0f, -1.0f, -1.0f, -1.0f},
                               {64.0f / 127.0f, -8.0f / 127.0f, 32.0f / 127.0f, 16.0f / 127.0f}};

    const float floatFaultTolerance = 0.01f;
    float outputVals[4];

    for (size_t i = 0; i < 5; i++)
    {
        UnpackSnorm4x8(PackSnorm4x8(input[i][0], input[i][1], input[i][2], input[i][3]),
                       outputVals);
        for (size_t j = 0; j < 4; j++)
        {
            float expected = input[i][j];
            EXPECT_NEAR(expected, outputVals[j], floatFaultTolerance);
        }
    }
}

// Test the correctness of gl::isNaN function.
TEST(MathUtilTest, isNaN)
{
    EXPECT_TRUE(isNaN(bitCast<float>(0xffu << 23 | 1u)));
    EXPECT_TRUE(isNaN(bitCast<float>(1u << 31 | 0xffu << 23 | 1u)));
    EXPECT_TRUE(isNaN(bitCast<float>(1u << 31 | 0xffu << 23 | 0x400000u)));
    EXPECT_TRUE(isNaN(bitCast<float>(1u << 31 | 0xffu << 23 | 0x7fffffu)));
    EXPECT_FALSE(isNaN(0.0f));
    EXPECT_FALSE(isNaN(bitCast<float>(1u << 31 | 0xffu << 23)));
    EXPECT_FALSE(isNaN(bitCast<float>(0xffu << 23)));
}

// Test the correctness of gl::isInf function.
TEST(MathUtilTest, isInf)
{
    EXPECT_TRUE(isInf(bitCast<float>(0xffu << 23)));
    EXPECT_TRUE(isInf(bitCast<float>(1u << 31 | 0xffu << 23)));
    EXPECT_FALSE(isInf(0.0f));
    EXPECT_FALSE(isInf(bitCast<float>(0xffu << 23 | 1u)));
    EXPECT_FALSE(isInf(bitCast<float>(1u << 31 | 0xffu << 23 | 1u)));
    EXPECT_FALSE(isInf(bitCast<float>(1u << 31 | 0xffu << 23 | 0x400000u)));
    EXPECT_FALSE(isInf(bitCast<float>(1u << 31 | 0xffu << 23 | 0x7fffffu)));
    EXPECT_FALSE(isInf(bitCast<float>(0xfeu << 23 | 0x7fffffu)));
    EXPECT_FALSE(isInf(bitCast<float>(1u << 31 | 0xfeu << 23 | 0x7fffffu)));
}

TEST(MathUtilTest, CountLeadingZeros)
{
    for (unsigned int i = 0; i < 32u; ++i)
    {
        uint32_t iLeadingZeros = 1u << (31u - i);
        EXPECT_EQ(i, CountLeadingZeros(iLeadingZeros));
    }
    EXPECT_EQ(32u, CountLeadingZeros(0));
}

// Some basic tests. Tests that rounding up zero produces zero.
TEST(MathUtilTest, BasicRoundUp)
{
    EXPECT_EQ(0u, rx::roundUp(0u, 4u));
    EXPECT_EQ(4u, rx::roundUp(1u, 4u));
    EXPECT_EQ(4u, rx::roundUp(4u, 4u));
}

// Test that rounding up zero produces zero for checked ints.
TEST(MathUtilTest, CheckedRoundUpZero)
{
    auto checkedValue = rx::CheckedRoundUp(0u, 4u);
    ASSERT_TRUE(checkedValue.IsValid());
    ASSERT_EQ(0u, checkedValue.ValueOrDie());
}

// Test out-of-bounds with CheckedRoundUp
TEST(MathUtilTest, CheckedRoundUpInvalid)
{
    // The answer to this query is out of bounds.
    auto limit        = std::numeric_limits<unsigned int>::max();
    auto checkedValue = rx::CheckedRoundUp(limit, limit - 1);
    ASSERT_FALSE(checkedValue.IsValid());

    // Our implementation can't handle this query, despite the parameters being in range.
    auto checkedLimit = rx::CheckedRoundUp(limit - 1, limit);
    ASSERT_FALSE(checkedLimit.IsValid());
}

// Test BitfieldReverse which reverses the order of the bits in an integer.
TEST(MathUtilTest, BitfieldReverse)
{
    EXPECT_EQ(0u, gl::BitfieldReverse(0u));
    EXPECT_EQ(0x80000000u, gl::BitfieldReverse(1u));
    EXPECT_EQ(0x1u, gl::BitfieldReverse(0x80000000u));
    uint32_t bits     = (1u << 4u) | (1u << 7u);
    uint32_t reversed = (1u << (31u - 4u)) | (1u << (31u - 7u));
    EXPECT_EQ(reversed, gl::BitfieldReverse(bits));
}

// Test BitCount, which counts 1 bits in an integer.
TEST(MathUtilTest, BitCount)
{
    EXPECT_EQ(0, gl::BitCount(0u));
    EXPECT_EQ(32, gl::BitCount(0xFFFFFFFFu));
    EXPECT_EQ(10, gl::BitCount(0x17103121u));

#if defined(ANGLE_X64_CPU)
    EXPECT_EQ(0, gl::BitCount(0ull));
    EXPECT_EQ(32, gl::BitCount(0xFFFFFFFFull));
    EXPECT_EQ(10, gl::BitCount(0x17103121ull));
#endif  // defined(ANGLE_X64_CPU)
}

// Test ScanForward, which scans for the least significant 1 bit from a non-zero integer.
TEST(MathUtilTest, ScanForward)
{
    EXPECT_EQ(0ul, gl::ScanForward(1u));
    EXPECT_EQ(16ul, gl::ScanForward(0x80010000u));
    EXPECT_EQ(31ul, gl::ScanForward(0x80000000u));

#if defined(ANGLE_X64_CPU)
    EXPECT_EQ(0ul, gl::ScanForward(1ull));
    EXPECT_EQ(16ul, gl::ScanForward(0x80010000ull));
    EXPECT_EQ(31ul, gl::ScanForward(0x80000000ull));
#endif  // defined(ANGLE_X64_CPU)
}

// Test ScanReverse, which scans for the most significant 1 bit from a non-zero integer.
TEST(MathUtilTest, ScanReverse)
{
    EXPECT_EQ(0ul, gl::ScanReverse(1ul));
    EXPECT_EQ(16ul, gl::ScanReverse(0x00010030ul));
    EXPECT_EQ(31ul, gl::ScanReverse(0x80000000ul));
}

// Test FindLSB, which finds the least significant 1 bit.
TEST(MathUtilTest, FindLSB)
{
    EXPECT_EQ(-1, gl::FindLSB(0u));
    EXPECT_EQ(0, gl::FindLSB(1u));
    EXPECT_EQ(16, gl::FindLSB(0x80010000u));
    EXPECT_EQ(31, gl::FindLSB(0x80000000u));
}

// Test FindMSB, which finds the most significant 1 bit.
TEST(MathUtilTest, FindMSB)
{
    EXPECT_EQ(-1, gl::FindMSB(0u));
    EXPECT_EQ(0, gl::FindMSB(1u));
    EXPECT_EQ(16, gl::FindMSB(0x00010030u));
    EXPECT_EQ(31, gl::FindMSB(0x80000000u));
}

// Test Ldexp, which combines mantissa and exponent into a floating-point number.
TEST(MathUtilTest, Ldexp)
{
    EXPECT_EQ(2.5f, Ldexp(0.625f, 2));
    EXPECT_EQ(-5.0f, Ldexp(-0.625f, 3));
    EXPECT_EQ(std::numeric_limits<float>::infinity(), Ldexp(0.625f, 129));
    EXPECT_EQ(0.0f, Ldexp(1.0f, -129));
}

// Test that Range::extend works as expected.
TEST(MathUtilTest, RangeExtend)
{
    RangeI range(0, 0);

    range.extend(5);
    EXPECT_EQ(0, range.low());
    EXPECT_EQ(6, range.high());
    EXPECT_EQ(6, range.length());

    range.extend(-1);
    EXPECT_EQ(-1, range.low());
    EXPECT_EQ(6, range.high());
    EXPECT_EQ(7, range.length());

    range.extend(10);
    EXPECT_EQ(-1, range.low());
    EXPECT_EQ(11, range.high());
    EXPECT_EQ(12, range.length());
}

// Test that Range iteration works as expected.
TEST(MathUtilTest, RangeIteration)
{
    RangeI range(0, 10);
    int expected = 0;
    for (int value : range)
    {
        EXPECT_EQ(expected, value);
        expected++;
    }
    EXPECT_EQ(range.length(), expected);
}

}  // anonymous namespace
