/***************************************************************************
* Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and         *
* Martin Renou                                                             *
* Copyright (c) QuantStack                                                 *
*                                                                          *
* Distributed under the terms of the BSD 3-Clause License.                 *
*                                                                          *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "test_utils.hpp"

namespace xsimd
{
    template <class T>
    struct test_int_min_max
    {
        bool run()
        {
            return true;
        }
    };

    template <class T>
    struct test_int_min_max<batch<T, 2>>
    {
        void run()
        {
            using B = batch<T, 2>;
            using BB = batch_bool<T, 2>;
            using A = std::array<T, 2>;

            T max = std::numeric_limits<T>::max();
            T min = std::numeric_limits<T>::min();
            std::array<T, 2> maxmin_cmp{{max, min}};
            B maxmin(max, min);
            EXPECT_BATCH_EQ(maxmin, maxmin_cmp) << print_function_name("numeric max and min");

            B a(1, 3);
            B b(2);
            B c(2, 3);

            auto r1 = xsimd::max(a, c);
            auto r3 = xsimd::min(a, c);

            EXPECT_BATCH_EQ(r1, (A{{2, 3}})) << print_function_name("max");
            EXPECT_BATCH_EQ(r3, (A{{1, 3}})) << print_function_name("min");

            auto r4 = a < b; // test lt
            BB e4(1, 0);
            EXPECT_TRUE(xsimd::all(r4 == e4));
        }
    };

    template <class T>
    struct test_int_min_max<batch<T, 4>>
    {
        void run()
        {
            using B = batch<T, 4>;
            using BB = batch_bool<T, 4>;
            using A = std::array<T, 4>;

            B a(1,3,1,1);
            B b(2);
            B c(2,3,2,3);

            auto r1 = xsimd::max(a, c);
            auto r3 = xsimd::min(a, c);

            EXPECT_BATCH_EQ(r1, (A{{2, 3, 2, 3}})) << print_function_name("max");
            EXPECT_BATCH_EQ(r3, (A{{1, 3, 1, 1}})) << print_function_name("min");

            auto r4 = a < b; // test lt
            BB e4(1,0,1,1);
            EXPECT_TRUE(xsimd::all(r4 == e4));
        }
    };

    template <class T>
    struct test_int_min_max<batch<T, 8>>
    {
        void run()
        {
            using B = batch<T, 8>;
            using BB = batch_bool<T, 8>;
            using A = std::array<T, 8>;

            T max = std::numeric_limits<T>::max();
            T min = std::numeric_limits<T>::min();
            std::array<T, 8> maxmin_cmp{{0, 0, max, 0, min, 0, 0, 0}};
            B maxmin(0, 0, max, 0, min, 0, 0, 0);
            EXPECT_BATCH_EQ(maxmin, maxmin_cmp) << print_function_name("numeric max and min");

            B a(1,3,1,3, 1,1,3,3);
            B b(2);
            B c(2,3,2,3, 2,3,2,3);

            auto r1 = xsimd::max(a, c);
            auto r3 = xsimd::min(a, c);
            auto r4 = a < b; // test lt
            EXPECT_BATCH_EQ(r1, (A{{2, 3, 2, 3, 2, 3, 3, 3}})) << print_function_name("max");
            EXPECT_BATCH_EQ(r3, (A{{1, 3, 1, 3, 1, 1, 2, 3}})) << print_function_name("min");

            BB e4(1,0,1,0, 1,1,0,0);
            EXPECT_TRUE(xsimd::all(r4 == e4));
        }
    };

    template <class T>
    struct test_int_min_max<batch<T, 16>>
    {
        void run()
        {
            using B = batch<T, 16>;
            using BB = batch_bool<T, 16>;
            using A = std::array<T, 16>;

            T max = std::numeric_limits<T>::max();
            T min = std::numeric_limits<T>::min();
            std::array<T, 16> maxmin_cmp{{0, 0, max, 0, min, 0, 0, 0, 0, 0, max, 0, min, 0, 0, 0}};
            B maxmin(0, 0, max, 0, min, 0, 0, 0, 0, 0, max, 0, min, 0, 0, 0);
            EXPECT_BATCH_EQ(maxmin, maxmin_cmp) << print_function_name("numeric max and min");

            B a(1,3,1,3, 1,3,1,3, 3,3,3,3, min,max,max,min);
            B b(2);
            B c(2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3);
            auto r1 = xsimd::max(a, b);
            auto r3 = xsimd::min(a, b);
            auto r4 = a < b; // test lt
            auto r5 = a == c;
            auto r6 = a != c;

            EXPECT_BATCH_EQ(r1, (A{{2,3,2,3, 2,3,2,3, 3,3,3,3, 2,max,max,2}})) << print_function_name("max");
            EXPECT_BATCH_EQ(r3, (A{{1,2,1,2, 1,2,1,2, 2,2,2,2, min,2,2,min}})) << print_function_name("min");

            BB e4(1,0,1,0, 1,0,1,0, 0,0,0,0, 1,0,0,1);
            EXPECT_TRUE(xsimd::all(r4 == e4));

            BB e5(0,1,0,1, 0,1,0,1, 0,1,0,1, 0,0,0,0);
            EXPECT_TRUE(xsimd::all(r5 == e5));
            EXPECT_TRUE(xsimd::all(r6 == !e5));
        }
    };

    template <class T>
    struct test_int_min_max<batch<T, 32>>
    {
        void run()
        {
            using B = batch<T, 32>;
            using BB = batch_bool<T, 32>;
            using A = std::array<T, 32>;
            T max = std::numeric_limits<T>::max();
            T min = std::numeric_limits<T>::min();

            B a(1,3,1,3, 1,3,1,3, 1,3,1,3, 1,3,1,3, 1,3,1,3, 1,3,1,3, 3,3,3,3, min,max,max,min);
            B b(2);
            B c(2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3);

            auto r1 = xsimd::max(a, b);
            auto r3 = xsimd::min(a, b);
            auto r4 = a < b; // test lt
            EXPECT_BATCH_EQ(r1, (A{{2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 2,3,2,3, 3,3,3,3, 2,max,max,2}})) << print_function_name("max");
            EXPECT_BATCH_EQ(r3, (A{{1,2,1,2, 1,2,1,2, 1,2,1,2, 1,2,1,2, 1,2,1,2, 1,2,1,2, 2,2,2,2, min,2,2,min}})) << print_function_name("min");

            BB e4(1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0, 0,0,0,0, 1,0,0,1);
            EXPECT_TRUE(xsimd::all(r4 == e4));
        }
    };
}

template <class B>
class batch_int_test : public testing::Test
{
protected:

    using batch_type = B;
    using value_type = typename B::value_type;
    static constexpr size_t size = B::size;
    using array_type = std::array<value_type, size>;
    using bool_array_type = std::array<bool, size>;

    array_type lhs;
    array_type rhs;
    array_type shift;

    batch_int_test()
    {
        using signed_value_type = typename std::make_signed<value_type>::type;
        for (size_t i = 0; i < size; ++i)
        {
            bool negative_lhs = std::is_signed<value_type>::value && (i % 2 == 1);
            lhs[i] = value_type(i) * (negative_lhs ? -10 : 10);
            if (lhs[i] == value_type(0))
            {
                lhs[i] += value_type(1);
            }
            rhs[i] = value_type(i) + value_type(4);
            shift[i] = signed_value_type(i) % (CHAR_BIT * sizeof(value_type));
        }
    }

    void test_modulo() const
    {
        // batch % batch
        {
            array_type expected;
            std::transform(lhs.cbegin(), lhs.cend(), rhs.cbegin(), expected.begin(),
                            [](const value_type& l, const value_type& r) { return l % r; });
            batch_type res = batch_lhs() % batch_rhs();
            EXPECT_BATCH_EQ(res, expected) << print_function_name("batch % batch");
        }
    }

    void test_shift() const
    {
        int32_t nb_sh = 3;
        // batch << scalar
        {
            array_type expected;
            std::transform(lhs.cbegin(), lhs.cend(), expected.begin(),
                            [nb_sh](const value_type& v) { return v << nb_sh; });
            batch_type res = batch_lhs() << nb_sh;
            EXPECT_BATCH_EQ(res, expected) << print_function_name("batch << scalar");
        }
        // batch << batch
        {
            array_type expected;
            std::transform(lhs.cbegin(), lhs.cend(), shift.cbegin(), expected.begin(),
                            [](const value_type& l, const value_type& r) { return l << r; });
            batch_type res = batch_lhs() << batch_shift();
            EXPECT_BATCH_EQ(res, expected) << print_function_name("batch << batch");
        }
        // batch >> scalar
        {
            array_type expected;
            std::transform(lhs.cbegin(), lhs.cend(), expected.begin(),
                            [nb_sh](const value_type& v) { return v >> nb_sh; });
            batch_type res = batch_lhs() >> nb_sh;
            EXPECT_BATCH_EQ(res, expected) << print_function_name("batch >> scalar");
        }
        // batch >> batch
        {
            array_type expected;
            std::transform(lhs.cbegin(), lhs.cend(), shift.cbegin(), expected.begin(),
                            [](const value_type& l, const value_type& r) { return l >> r; });
            batch_type res = batch_lhs() >> batch_shift();
            EXPECT_BATCH_EQ(res, expected) << print_function_name("batch >> batch");
        }
    }

    void test_more_shift() const
    {
        int32_t s = static_cast<int32_t>(sizeof(value_type) * 8);
        batch_type lhs = batch_type(value_type(1));
        batch_type res;

        for (int32_t i = 0; i < s; ++i)
        {
            res = lhs << i;
            value_type expected = value_type(1) << i;
            for (std::size_t j = 0; j < size; ++j)
            {
                EXPECT_EQ(res[j], expected);
            }
        }
        lhs = batch_type(std::numeric_limits<value_type>::max());
        for (int32_t i = 0; i < s; ++i)
        {
            res = lhs >> value_type(i);
            value_type expected = std::numeric_limits<value_type>::max() >> i;
            for (std::size_t j = 0; j < size; ++j)
            {
                EXPECT_EQ(res[j], expected);
            }
        }
    }

    void test_min_max() const
    {
        xsimd::test_int_min_max<batch_type> t;
        t.run();
    }

    void test_less_than_underflow() const
    {
        batch_type test_negative_compare = batch_type(5) - 6;
        if (std::is_unsigned<value_type>::value)
        {
            EXPECT_FALSE(xsimd::any(test_negative_compare < 1));
        }
        else
        {
            EXPECT_TRUE(xsimd::all(test_negative_compare < 1));
        }
    }

private:

    batch_type batch_lhs() const
    {
        return batch_type(lhs.data());
    }

    batch_type batch_rhs() const
    {
        return batch_type(rhs.data());
    }

    batch_type batch_shift() const
    {
        return batch_type(shift.data());
    }
};

TYPED_TEST_SUITE(batch_int_test, batch_int_types, simd_test_names);

TYPED_TEST(batch_int_test, modulo)
{
    this->test_modulo();
}

TYPED_TEST(batch_int_test, shift)
{
    this->test_shift();
}

TYPED_TEST(batch_int_test, more_shift)
{
    this->test_more_shift();
}

TYPED_TEST(batch_int_test, min_max)
{
    this->test_min_max();
}

TYPED_TEST(batch_int_test, less_than_underflow)
{
    this->test_less_than_underflow();
}

