/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).

   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{
namespace dsp
{

namespace SIMDRegister_test_internal
{
    template <typename type, typename = void> struct RandomPrimitive {};

    template <typename type>
    struct RandomPrimitive<type, typename std::enable_if<std::is_floating_point<type>::value>::type>
    {
        static type next (Random& random)
        {
            return static_cast<type> (std::is_signed<type>::value ? (random.nextFloat() * 16.0) - 8.0
                                                                  : (random.nextFloat() * 8.0));

        }
    };

    template <typename type>
    struct RandomPrimitive<type, typename std::enable_if<std::is_integral<type>::value>::type>
    {
        static type next (Random& random)
        {
            return static_cast<type> (random.nextInt64());

        }
    };

    template <typename type> struct RandomValue { static type next (Random& random) { return RandomPrimitive<type>::next (random); } };
    template <typename type>
    struct RandomValue<std::complex<type>>
    {
        static std::complex<type> next (Random& random)
        {
            return {RandomPrimitive<type>::next (random), RandomPrimitive<type>::next (random)};
        }
    };


    template <typename type>
    struct VecFiller
    {
        static void fill (type* dst, const int size, Random& random)
        {
            for (int i = 0; i < size; ++i)
                dst[i] = RandomValue<type>::next (random);
        }
    };

    // We need to specialise for complex types: otherwise GCC 6 gives
    // us an ICE internal compiler error after which the compiler seg faults.
    template <typename type>
    struct VecFiller<std::complex<type>>
    {
        static void fill (std::complex<type>* dst, const int size, Random& random)
        {
            for (int i = 0; i < size; ++i)
                dst[i] = std::complex<type> (RandomValue<type>::next (random), RandomValue<type>::next (random));
        }
    };

    template <typename type>
    struct VecFiller<SIMDRegister<type>>
    {
        static SIMDRegister<type> fill (Random& random)
        {
            constexpr int size = (int) SIMDRegister<type>::SIMDNumElements;
           #ifdef _MSC_VER
            __declspec(align(sizeof (SIMDRegister<type>))) type elements[size];
           #else
            type elements[(size_t) size] __attribute__((aligned(sizeof (SIMDRegister<type>))));
           #endif

            VecFiller<type>::fill (elements, size, random);
            return SIMDRegister<type>::fromRawArray (elements);
        }
    };

    // Avoid visual studio warning
    template <typename type>
    static type safeAbs (type a)
    {
        return static_cast<type> (std::abs (static_cast<double> (a)));
    }

    template <typename type>
    static type safeAbs (std::complex<type> a)
    {
        return std::abs (a);
    }

    template <typename type>
    static double difference (type a)
    {
        return static_cast<double> (safeAbs (a));
    }

    template <typename type>
    static double difference (type a, type b)
    {
        return difference (a - b);
    }
}

// These tests need to be strictly run on all platforms supported by JUCE as the
// SIMD code is highly platform dependent.

class SIMDRegisterUnitTests   : public UnitTest
{
public:
    SIMDRegisterUnitTests()
        : UnitTest ("SIMDRegister UnitTests", UnitTestCategories::dsp)
    {}

    //==============================================================================
    // Some helper classes
    template <typename type>
    static bool allValuesEqualTo (const SIMDRegister<type>& vec, const type scalar)
    {
       #ifdef _MSC_VER
        __declspec(align(sizeof (SIMDRegister<type>))) type elements[SIMDRegister<type>::SIMDNumElements];
       #else
        type elements[SIMDRegister<type>::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister<type>))));
       #endif

        vec.copyToRawArray (elements);

        // as we do not want to rely on the access operator we cast this to a primitive pointer
        for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
            if (elements[i] != scalar) return false;

        return true;
    }

    template <typename type>
    static bool vecEqualToArray (const SIMDRegister<type>& vec, const type* array)
    {
        HeapBlock<type> vecElementsStorage (SIMDRegister<type>::SIMDNumElements * 2);
        auto* ptr = SIMDRegister<type>::getNextSIMDAlignedPtr (vecElementsStorage.getData());
        vec.copyToRawArray (ptr);

        for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
        {
            double delta = SIMDRegister_test_internal::difference (ptr[i], array[i]);
            if (delta > 1e-4)
            {
                DBG ("a: " << SIMDRegister_test_internal::difference (ptr[i]) << " b: " << SIMDRegister_test_internal::difference (array[i]) << " difference: " << delta);
                return false;
            }
        }

        return true;
    }

    template <typename type>
    static void copy (SIMDRegister<type>& vec, const type* ptr)
    {
        if (SIMDRegister<type>::isSIMDAligned (ptr))
        {
            vec = SIMDRegister<type>::fromRawArray (ptr);
        }
        else
        {
            for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                vec[i] = ptr[i];
        }
    }

    //==============================================================================
    // Some useful operations to test
    struct Addition
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a += b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a + b;
        }
    };

    struct Subtraction
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a -= b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a - b;
        }
    };

    struct Multiplication
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a *= b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a * b;
        }
    };

    struct BitAND
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a &= b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a & b;
        }
    };

    struct BitOR
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a |= b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a | b;
        }
    };

    struct BitXOR
    {
        template <typename typeOne, typename typeTwo>
        static void inplace (typeOne& a, const typeTwo& b)
        {
            a ^= b;
        }

        template <typename typeOne, typename typeTwo>
        static typeOne outofplace (const typeOne& a, const typeTwo& b)
        {
            return a ^ b;
        }
    };

    //==============================================================================
    // the individual tests
    struct InitializationTest
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            u.expect (allValuesEqualTo<type> (SIMDRegister<type>::expand (static_cast<type> (23)), 23));

            {
               #ifdef _MSC_VER
                __declspec(align(sizeof (SIMDRegister<type>))) type elements[SIMDRegister<type>::SIMDNumElements];
               #else
                type elements[SIMDRegister<type>::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister<type>))));
               #endif
                SIMDRegister_test_internal::VecFiller<type>::fill (elements, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister<type> a (SIMDRegister<type>::fromRawArray (elements));

                u.expect (vecEqualToArray (a, elements));

                SIMDRegister<type> b (a);
                a *= static_cast<type> (2);

                u.expect (vecEqualToArray (b, elements));
            }
        }
    };

    struct AccessTest
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            // set-up
            SIMDRegister<type> a;
            type array [SIMDRegister<type>::SIMDNumElements];

            SIMDRegister_test_internal::VecFiller<type>::fill (array, SIMDRegister<type>::SIMDNumElements, random);

            // Test non-const access operator
            for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                a[i] = array[i];

            u.expect (vecEqualToArray (a, array));

            // Test const access operator
            const SIMDRegister<type>& b = a;

            for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                u.expect (b[i] == array[i]);
        }
    };

    template <class Operation>
    struct OperatorTests
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            for (int n = 0; n < 100; ++n)
            {
                // set-up
                SIMDRegister<type> a (static_cast<type> (0));
                SIMDRegister<type> b (static_cast<type> (0));
                SIMDRegister<type> c (static_cast<type> (0));

                type array_a [SIMDRegister<type>::SIMDNumElements];
                type array_b [SIMDRegister<type>::SIMDNumElements];
                type array_c [SIMDRegister<type>::SIMDNumElements];

                SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_c, SIMDRegister<type>::SIMDNumElements, random);

                copy (a, array_a); copy (b, array_b); copy (c, array_c);

                // test in-place with both params being vectors
                for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                    Operation::template inplace<type, type> (array_a[i], array_b[i]);

                Operation::template inplace<SIMDRegister<type>, SIMDRegister<type>> (a, b);

                u.expect (vecEqualToArray (a, array_a));
                u.expect (vecEqualToArray (b, array_b));

                SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_c, SIMDRegister<type>::SIMDNumElements, random);

                copy (a, array_a); copy (b, array_b); copy (c, array_c);

                // test in-place with one param being scalar
                for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                    Operation::template inplace<type, type> (array_b[i], static_cast<type> (2));

                Operation::template inplace<SIMDRegister<type>, type> (b, 2);

                u.expect (vecEqualToArray (a, array_a));
                u.expect (vecEqualToArray (b, array_b));

                // set-up again
                SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_c, SIMDRegister<type>::SIMDNumElements, random);
                copy (a, array_a); copy (b, array_b); copy (c, array_c);

                // test out-of-place with both params being vectors
                for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                    array_c[i] = Operation::template outofplace<type, type> (array_a[i], array_b[i]);

                c = Operation::template outofplace<SIMDRegister<type>, SIMDRegister<type>> (a, b);

                u.expect (vecEqualToArray (a, array_a));
                u.expect (vecEqualToArray (b, array_b));
                u.expect (vecEqualToArray (c, array_c));

                // test out-of-place with one param being scalar
                for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                    array_c[i] = Operation::template outofplace<type, type> (array_b[i], static_cast<type> (2));

                c = Operation::template outofplace<SIMDRegister<type>, type> (b, 2);

                u.expect (vecEqualToArray (a, array_a));
                u.expect (vecEqualToArray (b, array_b));
                u.expect (vecEqualToArray (c, array_c));
            }
        }
    };

    template <class Operation>
    struct BitOperatorTests
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            typedef typename SIMDRegister<type>::vMaskType vMaskType;
            typedef typename SIMDRegister<type>::MaskType MaskType;

            for (int n = 0; n < 100; ++n)
            {
                // Check flip sign bit and using as a union
                {
                    type array_a [SIMDRegister<type>::SIMDNumElements];

                    union ConversionUnion
                    {
                        inline ConversionUnion() : floatVersion (static_cast<type> (0)) {}
                        inline ~ConversionUnion() {}
                        SIMDRegister<type> floatVersion;
                        vMaskType intVersion;
                    } a, b;

                    vMaskType bitmask = vMaskType::expand (static_cast<MaskType> (1) << (sizeof (MaskType) - 1));
                    SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                    copy (a.floatVersion, array_a);
                    copy (b.floatVersion, array_a);

                    Operation::template inplace<SIMDRegister<type>, vMaskType> (a.floatVersion, bitmask);
                    Operation::template inplace<vMaskType, vMaskType> (b.intVersion, bitmask);

                   #ifdef _MSC_VER
                    __declspec(align(sizeof (SIMDRegister<type>))) type elements[SIMDRegister<type>::SIMDNumElements];
                   #else
                    type elements[SIMDRegister<type>::SIMDNumElements] __attribute__((aligned(sizeof (SIMDRegister<type>))));
                   #endif
                    b.floatVersion.copyToRawArray (elements);

                    u.expect (vecEqualToArray (a.floatVersion, elements));
                }

                // set-up
                SIMDRegister<type> a, c;
                vMaskType b;

                MaskType array_a [SIMDRegister<MaskType>::SIMDNumElements];
                MaskType array_b [SIMDRegister<MaskType>::SIMDNumElements];
                MaskType array_c [SIMDRegister<MaskType>::SIMDNumElements];

                type float_a [SIMDRegister<type>::SIMDNumElements];
                type float_c [SIMDRegister<type>::SIMDNumElements];

                SIMDRegister_test_internal::VecFiller<type>::fill (float_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<MaskType>::fill (array_b, SIMDRegister<MaskType>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (float_c, SIMDRegister<type>::SIMDNumElements, random);

                memcpy (array_a, float_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                memcpy (array_c, float_c, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                copy (a, float_a); copy (b, array_b); copy (c, float_c);

                // test in-place with both params being vectors
                for (size_t i = 0; i < SIMDRegister<MaskType>::SIMDNumElements; ++i)
                    Operation::template inplace<MaskType, MaskType> (array_a[i], array_b[i]);
                memcpy (float_a, array_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);

                Operation::template inplace<SIMDRegister<type>, vMaskType> (a, b);

                u.expect (vecEqualToArray (a, float_a));
                u.expect (vecEqualToArray (b, array_b));

                SIMDRegister_test_internal::VecFiller<type>::fill (float_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<MaskType>::fill (array_b, SIMDRegister<MaskType>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (float_c, SIMDRegister<type>::SIMDNumElements, random);
                memcpy (array_a, float_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                memcpy (array_c, float_c, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                copy (a, float_a); copy (b, array_b); copy (c, float_c);

                // test in-place with one param being scalar
                for (size_t i = 0; i < SIMDRegister<MaskType>::SIMDNumElements; ++i)
                    Operation::template inplace<MaskType, MaskType> (array_a[i], static_cast<MaskType> (9));
                memcpy (float_a, array_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);

                Operation::template inplace<SIMDRegister<type>, MaskType> (a, static_cast<MaskType> (9));

                u.expect (vecEqualToArray (a, float_a));
                u.expect (vecEqualToArray (b, array_b));

                // set-up again
                SIMDRegister_test_internal::VecFiller<type>::fill (float_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<MaskType>::fill (array_b, SIMDRegister<MaskType>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (float_c, SIMDRegister<type>::SIMDNumElements, random);
                memcpy (array_a, float_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                memcpy (array_c, float_c, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                copy (a, float_a); copy (b, array_b); copy (c, float_c);

                // test out-of-place with both params being vectors
                for (size_t i = 0; i < SIMDRegister<MaskType>::SIMDNumElements; ++i)
                {
                    array_c[i] =
                        Operation::template outofplace<MaskType, MaskType> (array_a[i], array_b[i]);
                }
                memcpy (float_a, array_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                memcpy (float_c, array_c, sizeof (type) * SIMDRegister<type>::SIMDNumElements);

                c = Operation::template outofplace<SIMDRegister<type>, vMaskType> (a, b);

                u.expect (vecEqualToArray (a, float_a));
                u.expect (vecEqualToArray (b, array_b));
                u.expect (vecEqualToArray (c, float_c));

                // test out-of-place with one param being scalar
                for (size_t i = 0; i < SIMDRegister<MaskType>::SIMDNumElements; ++i)
                    array_c[i] = Operation::template outofplace<MaskType, MaskType> (array_a[i], static_cast<MaskType> (9));
                memcpy (float_a, array_a, sizeof (type) * SIMDRegister<type>::SIMDNumElements);
                memcpy (float_c, array_c, sizeof (type) * SIMDRegister<type>::SIMDNumElements);

                c = Operation::template outofplace<SIMDRegister<type>, MaskType> (a, static_cast<MaskType> (9));

                u.expect (vecEqualToArray (a, float_a));
                u.expect (vecEqualToArray (b, array_b));
                u.expect (vecEqualToArray (c, float_c));
            }
        }
    };

    struct CheckComparisonOps
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            typedef typename SIMDRegister<type>::vMaskType vMaskType;
            typedef typename SIMDRegister<type>::MaskType MaskType;

            for (int i = 0; i < 100; ++i)
            {
                // set-up
                type array_a   [SIMDRegister<type>::SIMDNumElements];
                type array_b   [SIMDRegister<type>::SIMDNumElements];
                MaskType array_eq  [SIMDRegister<type>::SIMDNumElements];
                MaskType array_neq [SIMDRegister<type>::SIMDNumElements];
                MaskType array_lt  [SIMDRegister<type>::SIMDNumElements];
                MaskType array_le  [SIMDRegister<type>::SIMDNumElements];
                MaskType array_gt  [SIMDRegister<type>::SIMDNumElements];
                MaskType array_ge  [SIMDRegister<type>::SIMDNumElements];


                SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);

                // do check
                for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
                {
                    array_eq  [j] = (array_a[j] == array_b[j]) ? static_cast<MaskType> (-1) : 0;
                    array_neq [j] = (array_a[j] != array_b[j]) ? static_cast<MaskType> (-1) : 0;
                    array_lt  [j] = (array_a[j] <  array_b[j]) ? static_cast<MaskType> (-1) : 0;
                    array_le  [j] = (array_a[j] <= array_b[j]) ? static_cast<MaskType> (-1) : 0;
                    array_gt  [j] = (array_a[j] >  array_b[j]) ? static_cast<MaskType> (-1) : 0;
                    array_ge  [j] = (array_a[j] >= array_b[j]) ? static_cast<MaskType> (-1) : 0;
                }

                SIMDRegister<type> a (static_cast<type> (0));
                SIMDRegister<type> b (static_cast<type> (0));

                vMaskType eq, neq, lt, le, gt, ge;

                copy (a, array_a);
                copy (b, array_b);

                eq  = SIMDRegister<type>::equal              (a, b);
                neq = SIMDRegister<type>::notEqual           (a, b);
                lt  = SIMDRegister<type>::lessThan           (a, b);
                le  = SIMDRegister<type>::lessThanOrEqual    (a, b);
                gt  = SIMDRegister<type>::greaterThan        (a, b);
                ge  = SIMDRegister<type>::greaterThanOrEqual (a, b);

                u.expect (vecEqualToArray (eq,  array_eq ));
                u.expect (vecEqualToArray (neq, array_neq));
                u.expect (vecEqualToArray (lt,  array_lt ));
                u.expect (vecEqualToArray (le,  array_le ));
                u.expect (vecEqualToArray (gt,  array_gt ));
                u.expect (vecEqualToArray (ge,  array_ge ));

                do
                {
                    SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                    SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);
                } while (std::equal (array_a, array_a + SIMDRegister<type>::SIMDNumElements, array_b));

                copy (a, array_a);
                copy (b, array_b);
                u.expect (a != b);
                u.expect (b != a);
                u.expect (! (a == b));
                u.expect (! (b == a));

                SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
                copy (a, array_a);
                copy (b, array_a);

                u.expect (a == b);
                u.expect (b == a);
                u.expect (! (a != b));
                u.expect (! (b != a));

                type scalar = a[0];
                a = SIMDRegister<type>::expand (scalar);

                u.expect (a == scalar);
                u.expect (! (a != scalar));

                scalar--;

                u.expect (a != scalar);
                u.expect (! (a == scalar));
            }
        }
    };

    struct CheckMultiplyAdd
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            // set-up
            type array_a [SIMDRegister<type>::SIMDNumElements];
            type array_b [SIMDRegister<type>::SIMDNumElements];
            type array_c [SIMDRegister<type>::SIMDNumElements];
            type array_d [SIMDRegister<type>::SIMDNumElements];

            SIMDRegister_test_internal::VecFiller<type>::fill (array_a, SIMDRegister<type>::SIMDNumElements, random);
            SIMDRegister_test_internal::VecFiller<type>::fill (array_b, SIMDRegister<type>::SIMDNumElements, random);
            SIMDRegister_test_internal::VecFiller<type>::fill (array_c, SIMDRegister<type>::SIMDNumElements, random);
            SIMDRegister_test_internal::VecFiller<type>::fill (array_d, SIMDRegister<type>::SIMDNumElements, random);

            // check
            for (size_t i = 0; i < SIMDRegister<type>::SIMDNumElements; ++i)
                array_d[i] = array_a[i] + (array_b[i] * array_c[i]);

            SIMDRegister<type> a, b, c, d;

            copy (a, array_a);
            copy (b, array_b);
            copy (c, array_c);

            d = SIMDRegister<type>::multiplyAdd (a, b, c);

            u.expect (vecEqualToArray (d, array_d));
        }
    };

    struct CheckMinMax
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            for (int i = 0; i < 100; ++i)
            {
                type array_a [SIMDRegister<type>::SIMDNumElements];
                type array_b [SIMDRegister<type>::SIMDNumElements];
                type array_min [SIMDRegister<type>::SIMDNumElements];
                type array_max [SIMDRegister<type>::SIMDNumElements];

                for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
                {
                    array_a[j] = static_cast<type> (random.nextInt (127));
                    array_b[j] = static_cast<type> (random.nextInt (127));
                }

                for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
                {
                    array_min[j] = (array_a[j] < array_b[j]) ? array_a[j] : array_b[j];
                    array_max[j] = (array_a[j] > array_b[j]) ? array_a[j] : array_b[j];
                }

                SIMDRegister<type> a (static_cast<type> (0));
                SIMDRegister<type> b (static_cast<type> (0));
                SIMDRegister<type> vMin (static_cast<type> (0));
                SIMDRegister<type> vMax (static_cast<type> (0));

                copy (a, array_a);
                copy (b, array_b);

                vMin = jmin (a, b);
                vMax = jmax (a, b);

                u.expect (vecEqualToArray (vMin, array_min));
                u.expect (vecEqualToArray (vMax, array_max));

                copy (vMin, array_a);
                copy (vMax, array_a);

                vMin = SIMDRegister<type>::min (a, b);
                vMax = SIMDRegister<type>::max (a, b);

                u.expect (vecEqualToArray (vMin, array_min));
                u.expect (vecEqualToArray (vMax, array_max));
            }
        }
    };

    struct CheckSum
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            type array [SIMDRegister<type>::SIMDNumElements];
            type sumCheck = 0;

            SIMDRegister_test_internal::VecFiller<type>::fill (array, SIMDRegister<type>::SIMDNumElements, random);

            for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
            {
                sumCheck += array[j];
            }

            SIMDRegister<type> a;
            copy (a, array);

            u.expect (SIMDRegister_test_internal::difference (sumCheck, a.sum()) < 1e-4);
        }
    };

    struct CheckAbs
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            type inArray[SIMDRegister<type>::SIMDNumElements];
            type outArray[SIMDRegister<type>::SIMDNumElements];

            SIMDRegister_test_internal::VecFiller<type>::fill (inArray, SIMDRegister<type>::SIMDNumElements, random);

            SIMDRegister<type> a;
            copy (a, inArray);
            a = SIMDRegister<type>::abs (a);

            auto calcAbs = [] (type x) -> type { return x >= type (0) ? x : type (-x); };

            for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
                outArray[j] = calcAbs (inArray[j]);

            u.expect (vecEqualToArray (a, outArray));
        }
    };

    struct CheckTruncate
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            type inArray[SIMDRegister<type>::SIMDNumElements];
            type outArray[SIMDRegister<type>::SIMDNumElements];

            SIMDRegister_test_internal::VecFiller<type>::fill (inArray, SIMDRegister<type>::SIMDNumElements, random);

            SIMDRegister<type> a;
            copy (a, inArray);
            a = SIMDRegister<type>::truncate (a);

            for (size_t j = 0; j < SIMDRegister<type>::SIMDNumElements; ++j)
                outArray[j] = (type) (int) inArray[j];

            u.expect (vecEqualToArray (a, outArray));
        }
    };

    struct CheckBoolEquals
    {
        template <typename type>
        static void run (UnitTest& u, Random& random)
        {
            bool is_signed = std::is_signed<type>::value;
            type array [SIMDRegister<type>::SIMDNumElements];

            auto value = is_signed ? static_cast<type> ((random.nextFloat() * 16.0) - 8.0)
                                   : static_cast<type> (random.nextFloat() * 8.0);

            std::fill (array, array + SIMDRegister<type>::SIMDNumElements, value);
            SIMDRegister<type> a, b;
            copy (a, array);

            u.expect (a == value);
            u.expect (! (a != value));
            value += 1;

            u.expect (a != value);
            u.expect (! (a == value));

            SIMDRegister_test_internal::VecFiller<type>::fill (array, SIMDRegister<type>::SIMDNumElements, random);
            copy (a, array);
            copy (b, array);

            u.expect (a == b);
            u.expect (! (a != b));

            SIMDRegister_test_internal::VecFiller<type>::fill (array, SIMDRegister<type>::SIMDNumElements, random);
            copy (b, array);

            u.expect (a != b);
            u.expect (! (a == b));
        }
    };

    //==============================================================================
    template <class TheTest>
    void runTestFloatingPoint (const char* unitTestName)
    {
        beginTest (unitTestName);

        Random random = getRandom();

        TheTest::template run<float>  (*this, random);
        TheTest::template run<double> (*this, random);
    }

    //==============================================================================
    template <class TheTest>
    void runTestForAllTypes (const char* unitTestName)
    {
        beginTest (unitTestName);

        Random random = getRandom();

        TheTest::template run<float>   (*this, random);
        TheTest::template run<double>  (*this, random);
        TheTest::template run<int8_t>  (*this, random);
        TheTest::template run<uint8_t> (*this, random);
        TheTest::template run<int16_t> (*this, random);
        TheTest::template run<uint16_t>(*this, random);
        TheTest::template run<int32_t> (*this, random);
        TheTest::template run<uint32_t>(*this, random);
        TheTest::template run<int64_t> (*this, random);
        TheTest::template run<uint64_t>(*this, random);
        TheTest::template run<std::complex<float>>   (*this, random);
        TheTest::template run<std::complex<double>>  (*this, random);
    }

    template <class TheTest>
    void runTestNonComplex (const char* unitTestName)
    {
        beginTest (unitTestName);

        Random random = getRandom();

        TheTest::template run<float>   (*this, random);
        TheTest::template run<double>  (*this, random);
        TheTest::template run<int8_t>  (*this, random);
        TheTest::template run<uint8_t> (*this, random);
        TheTest::template run<int16_t> (*this, random);
        TheTest::template run<uint16_t>(*this, random);
        TheTest::template run<int32_t> (*this, random);
        TheTest::template run<uint32_t>(*this, random);
        TheTest::template run<int64_t> (*this, random);
        TheTest::template run<uint64_t>(*this, random);
    }

    template <class TheTest>
    void runTestSigned (const char* unitTestName)
    {
        beginTest (unitTestName);

        Random random = getRandom();

        TheTest::template run<float>   (*this, random);
        TheTest::template run<double>  (*this, random);
        TheTest::template run<int8_t>  (*this, random);
        TheTest::template run<int16_t> (*this, random);
        TheTest::template run<int32_t> (*this, random);
        TheTest::template run<int64_t> (*this, random);
    }

    void runTest()
    {
        runTestForAllTypes<InitializationTest> ("InitializationTest");

        runTestForAllTypes<AccessTest> ("AccessTest");

        runTestForAllTypes<OperatorTests<Addition>> ("AdditionOperators");
        runTestForAllTypes<OperatorTests<Subtraction>> ("SubtractionOperators");
        runTestForAllTypes<OperatorTests<Multiplication>> ("MultiplicationOperators");

        runTestForAllTypes<BitOperatorTests<BitAND>> ("BitANDOperators");
        runTestForAllTypes<BitOperatorTests<BitOR>>  ("BitOROperators");
        runTestForAllTypes<BitOperatorTests<BitXOR>> ("BitXOROperators");

        runTestNonComplex<CheckComparisonOps> ("CheckComparisons");
        runTestNonComplex<CheckBoolEquals> ("CheckBoolEquals");
        runTestNonComplex<CheckMinMax> ("CheckMinMax");

        runTestForAllTypes<CheckMultiplyAdd> ("CheckMultiplyAdd");
        runTestForAllTypes<CheckSum> ("CheckSum");

        runTestSigned<CheckAbs> ("CheckAbs");

        runTestFloatingPoint<CheckTruncate> ("CheckTruncate");
    }
};

static SIMDRegisterUnitTests SIMDRegisterUnitTests;

} // namespace dsp
} // namespace juce
