// ***************************************************************** -*- C++ -*-
/*
 * Copyright (C) 2004-2021 Exiv2 authors
 * This program is part of the Exiv2 distribution.
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
 */

#include <safe_op.hpp>
#include "gtestwrapper.h"
namespace si = Safe::Internal;

/*!
 * struct that contains test numbers for Safe::add. Since different test values
 * are required for signed and unsigned types, this struct is specialized via
 * SFINAE.
 *
 * Each overload has two arrays:
 * T summand[] - the test numbers
 * bool overflow[][] - overflow[i][j] indicates whether summand[i] + summand[j]
 *                     is expected to overflow
 *
 * The numbers in summand are chosen accordingly to test all "interesting" cases
 * and cause some overflows. The actual test should simply check all possible
 * sums of summand against the value in overflow[][]
 */
template <typename T, typename = void>
struct AdditionTestValues;

/*!
 * Overload for unsigned types.
 */
template <typename T>
struct AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>
{
    static const size_t case_count = 5;
    static const T summand[case_count];
    static const bool overflow[case_count][case_count];
};

template <typename T>
const T AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>::summand[] = {
    0, 1, 2, static_cast<T>(std::numeric_limits<T>::max() - 1), std::numeric_limits<T>::max()};

template <typename T>
const bool
    AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>::overflow[case_count][case_count] = {
        // 0
        {false, false, false, false, false},
        // 1
        {false, false, false, false, true},
        // 2
        {false, false, false, true, true},
        // max - 1
        {false, false, true, true, true},
        // max
        {false, true, true, true, true}};

/*!
 * Overload for signed integers
 */
template <typename T>
struct AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>
{
    static const size_t case_count = 8;
    static const T summand[case_count];
    static const bool overflow[case_count][case_count];
};

template <typename T>
const T AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>::summand[] = {
    std::numeric_limits<T>::min(),
    static_cast<T>(std::numeric_limits<T>::min() + 1),
    -1,
    0,
    1,
    2,
    static_cast<T>(std::numeric_limits<T>::max() - 1),
    std::numeric_limits<T>::max()};

template <typename T>
const bool
    AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>::overflow[case_count][case_count] = {
        // min
        {true, true, true, false, false, false, false, false},
        // min + 1
        {true, true, false, false, false, false, false, false},
        // -1
        {true, false, false, false, false, false, false, false},
        // 0
        {false, false, false, false, false, false, false, false},
        // 1
        {false, false, false, false, false, false, false, true},
        // 2
        {false, false, false, false, false, false, true, true},
        // max - 1
        {false, false, false, false, false, true, true, true},
        // max
        {false, false, false, false, true, true, true, true}};

/*!
 * Test the addition of all combinations of AdditionTestValues<T>::summand[i],
 * AdditionTestValues<T>::summand[j] using fallback_add_overflow::add and
 * builtin_add_overflow::add. The return value is checked against
 * AdditionTestValues<T>::overflow[i][j]. If no overflow occurs, then the result
 * is checked too.
 */
template <typename T>
void test_add()
{
    typedef AdditionTestValues<T> TestValues;

#define TEST_ADD(func)                                                                                        \
    for (size_t i = 0; i < TestValues::case_count; ++i) {                                                     \
        for (size_t j = 0; j < TestValues::case_count; ++j) {                                                 \
            T res = 0;                                                                                        \
            ASSERT_EQ(func(TestValues::summand[i], TestValues::summand[j], res), TestValues::overflow[i][j]); \
            if (!TestValues::overflow[i][j]) {                                                                \
                ASSERT_EQ(res, TestValues::summand[i] + TestValues::summand[j]);                              \
            }                                                                                                 \
        }                                                                                                     \
    }

    TEST_ADD(si::fallback_add_overflow)
    TEST_ADD(si::builtin_add_overflow)

#undef TEST_ADD
}

/*!
 * Test the addition of all combinations of AdditionTestValues<T>::summand[i],
 * AdditionTestValues<T>::summand[j] using Safe::add.
 *
 * It is checked whether an exception is thrown when
 * AdditionTestValues<T>::overflow[i][j] is true, otherwise the result is
 * checked.
 */
template <typename T>
void test_safe_add()
{
    typedef AdditionTestValues<T> TestValues;

    for (size_t i = 0; i < TestValues::case_count; ++i) {
        for (size_t j = 0; j < TestValues::case_count; ++j) {
            if (TestValues::overflow[i][j]) {
                ASSERT_THROW(Safe::add(TestValues::summand[i], TestValues::summand[j]), std::overflow_error);
            } else {
                ASSERT_EQ(Safe::add(TestValues::summand[i], TestValues::summand[j]),
                          TestValues::summand[i] + TestValues::summand[j]);
            }
        }
    }
}

TEST(lowLevelAddOverflow, checkUnsignedOverflow)
{
    test_add<unsigned char>();
    test_add<unsigned short>();
    test_add<unsigned int>();
    test_add<unsigned long>();
    test_add<unsigned long long>();
}

TEST(lowLevelAddOverflow, checkSignedOverflow)
{
    test_add<char>();
    test_add<short>();
    test_add<int>();
    test_add<long>();
    test_add<long long>();
}

TEST(safeAdd, checkUnsignedOverflow)
{
    test_safe_add<unsigned char>();
    test_safe_add<unsigned short>();
    test_safe_add<unsigned int>();
    test_safe_add<unsigned long>();
    test_safe_add<unsigned long long>();
}

TEST(safeAdd, checkSignedOverflow)
{
    test_safe_add<char>();
    test_safe_add<short>();
    test_safe_add<int>();
    test_safe_add<long>();
    test_safe_add<long long>();
}

TEST(safeAbs, checkValues)
{
    static const int values[] = {-1, 1, std::numeric_limits<int>::max(), std::numeric_limits<int>::min() + 1};
    for (size_t i = 0; i < sizeof(values) / sizeof(*values); ++i) {
        ASSERT_EQ(Safe::abs(values[i]), abs(values[i]));
    }
    ASSERT_EQ(Safe::abs(std::numeric_limits<int>::min()), std::numeric_limits<int>::max());
}
