// -----------------------------------------------------------------------------
// Fern © Geoneric
//
// This file is part of Geoneric Fern which is available under the terms of
// the GNU General Public License (GPL), version 2. If you do not want to
// be bound by the terms of the GPL, you may purchase a proprietary license
// from Geoneric (http://www.geoneric.eu/contact).
// -----------------------------------------------------------------------------
#define BOOST_TEST_MODULE fern algorithm algebra binary_local_operation
#include <boost/test/unit_test.hpp>
#include "fern/core/data_customization_point/scalar.h"
#include "fern/core/data_customization_point/vector.h"
#include "fern/feature/core/data_customization_point/array.h"
#include "fern/feature/core/data_customization_point/masked_array.h"
#include "fern/feature/core/data_customization_point/masked_scalar.h"
#include "fern/algorithm/algebra/elementary/add.h"

namespace fa = fern::algorithm;

using ArgumentValue = int32_t;
using ResultValue = int32_t;

template<
    class Value1,
    class Value2
>
using Algorithm = fa::add::detail::Algorithm<Value1, Value2>;

template<
    class Value1,
    class Value2,
    class Result
>
using OutOfRangePolicy = fa::add::OutOfRangePolicy<Value1, Value2, Result>;

BOOST_AUTO_TEST_CASE(d0_array_d0_array)
{
    using InputNoDataPolicy = fa::InputNoDataPolicies<fa::SkipNoData,
          fa::SkipNoData>;
    using OutputNoDataPolicy = fa::DontMarkNoData;
    using Argument1 = ArgumentValue;
    using Argument2 = ArgumentValue;
    using Result = ResultValue;

    fa::ParallelExecutionPolicy parallel;
    fa::SequentialExecutionPolicy sequential;

    OutputNoDataPolicy output_no_data_policy;
    Argument1 argument1{-5};
    Argument2 argument2{-6};
    Result result{3};

    fa::binary_local_operation<
        Algorithm,
        fa::binary::DiscardDomainErrors,
        fa::binary::DiscardRangeErrors>(
            InputNoDataPolicy{{}, {}},
            output_no_data_policy,
            sequential, argument1, argument2, result);

    BOOST_CHECK_EQUAL(result, -11);

    result = 3;

    fa::binary_local_operation<
        Algorithm,
        fa::binary::DiscardDomainErrors,
        fa::binary::DiscardRangeErrors>(
            InputNoDataPolicy{{}, {}},
            output_no_data_policy,
            parallel, argument1, argument2, result);

    BOOST_CHECK_EQUAL(result, -11);
}

BOOST_AUTO_TEST_CASE(masked_d0_array_d0_array)
{
    using InputNoDataPolicy = fa::InputNoDataPolicies<
        fa::DetectNoDataByValue<bool>, fa::DetectNoDataByValue<bool>>;
    using OutputNoDataPolicy = fa::MarkNoDataByValue<bool>;
    using Argument1 = fern::MaskedScalar<ArgumentValue>;
    using Argument2 = fern::MaskedScalar<ArgumentValue>;
    using Result = fern::MaskedScalar<ResultValue>;

    fa::SequentialExecutionPolicy sequential;

    Argument1 argument1;
    Argument2 argument2;
    Result result;

    // Input is not masked.
    {
        argument1.value() = -5;
        argument1.mask() = false;
        argument2.value() = -6;
        argument2.mask() = false;
        result.value() = 3;
        result.mask() = false;

        InputNoDataPolicy input_no_data_policy{
            {argument1.mask(), true},
            {argument2.mask(), true}};
        OutputNoDataPolicy output_no_data_policy(result.mask(), true);

        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                input_no_data_policy,
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK_EQUAL(result.mask(), false);
        BOOST_CHECK_EQUAL(result.value(), -11);
    }

    // Input is masked.
    {
        argument1.value() = -5;
        argument1.mask() = true;
        argument2.value() = -6;
        argument2.mask() = false;
        result.value() = 3;
        result.mask() = false;

        InputNoDataPolicy input_no_data_policy{
            {argument1.mask(), true},
            {argument2.mask(), true}};
        OutputNoDataPolicy output_no_data_policy(result.mask(), true);

        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                input_no_data_policy,
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK_EQUAL(result.mask(), true);
        BOOST_CHECK_EQUAL(result.value(), 3);
    }

    // Input is out of domain.
    {
        // TODO  Use other operation.
    }

    // Result goes out of range.
    {
        argument1.value() = fern::max<ArgumentValue>();
        argument1.mask() = false;
        argument2.value() = 1;
        argument2.mask() = false;
        result.value() = 3;
        result.mask() = false;

        InputNoDataPolicy input_no_data_policy{
            {argument1.mask(), true},
            {argument2.mask(), true}};
        OutputNoDataPolicy output_no_data_policy(result.mask(), true);
        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            OutOfRangePolicy>(
                input_no_data_policy,
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK_EQUAL(result.mask(), true);

        // Result value is max<ArgumentValue> + 1, which equals
        // min<ArgumentValue>.
        BOOST_CHECK_EQUAL(result.value(), fern::min<ArgumentValue>());
    }
}

BOOST_AUTO_TEST_CASE(d1_array_d0_array_sequential)
{
    using InputNoDataPolicy = fa::InputNoDataPolicies<fa::SkipNoData,
          fa::SkipNoData>;
    using OutputNoDataPolicy = fa::DontMarkNoData;

    OutputNoDataPolicy output_no_data_policy;

    fa::SequentialExecutionPolicy sequential;

    // vector
    {
        using Argument1 = std::vector<ArgumentValue>;
        using Argument2 = ArgumentValue;
        using Result = std::vector<ResultValue>;

        Argument1 argument1{-5, 0, 5};
        Argument2 argument2{6};
        Result result{3, 3, 3};

        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                InputNoDataPolicy{{}, {}},
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK_EQUAL(result[0], 1);
        BOOST_CHECK_EQUAL(result[1], 6);
        BOOST_CHECK_EQUAL(result[2], 11);

        std::fill(result.begin(), result.end(), 3);

        // Switch arguments.
        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                InputNoDataPolicy{{}, {}},
                output_no_data_policy,
                sequential, argument2, argument1, result);

        BOOST_CHECK_EQUAL(result[0], 1);
        BOOST_CHECK_EQUAL(result[1], 6);
        BOOST_CHECK_EQUAL(result[2], 11);
    }

    // 1d array
    {
        using Argument1 = fern::Array<ArgumentValue, 1>;
        using Argument2 = ArgumentValue;
        using Result = fern::Array<ResultValue, 1>;

        Argument1 argument1{-5, 0, 5};
        Argument2 argument2{6};
        Result result{3, 3, 3};

        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                InputNoDataPolicy{{}, {}},
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK_EQUAL(result[0], 1);
        BOOST_CHECK_EQUAL(result[1], 6);
        BOOST_CHECK_EQUAL(result[2], 11);
    }

    // empty
    {
        using Argument1 = std::vector<ArgumentValue>;
        using Argument2 = ArgumentValue;
        using Result = std::vector<ResultValue>;

        Argument1 argument1;
        Argument2 argument2{};
        Result result;

        fa::binary_local_operation<
            Algorithm,
            fa::binary::DiscardDomainErrors,
            fa::binary::DiscardRangeErrors>(
                InputNoDataPolicy{{}, {}},
                output_no_data_policy,
                sequential, argument1, argument2, result);

        BOOST_CHECK(result.empty());
    }
}

/// BOOST_AUTO_TEST_CASE(array_1d_parallel)
/// {
///     using InputNoDataPolicy = fern::SkipNoData;
///     using OutputNoDataPolicy = fern::DontMarkNoData;
///
///     OutputNoDataPolicy output_no_data_policy;
///
///     // vector
///     {
///         using Argument = std::vector<ArgumentValue>;
///         using Result = std::vector<ResultValue>;
///
///         Argument argument{-5, 0, 5};
///         Result result{3, 3, 3};
///
///         fa::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 InputNoDataPolicy(),
///                 output_no_data_policy,
///                 parallel, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result[0], 5);
///         BOOST_REQUIRE_EQUAL(result[1], 0);
///         BOOST_REQUIRE_EQUAL(result[2], 5);
///     }
///
///     // 1d array
///     {
///         using Argument = fern::Array<ArgumentValue, 1>;
///         using Result = fern::Array<ResultValue, 1>;
///
///         Argument argument{-5, 0, 5};
///         Result result{3, 3, 3};
///
///         fa::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 InputNoDataPolicy(),
///                 output_no_data_policy,
///                 parallel, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result[0], 5);
///         BOOST_REQUIRE_EQUAL(result[1], 0);
///         BOOST_REQUIRE_EQUAL(result[2], 5);
///     }
///
///     // empty
///     {
///         using Argument = std::vector<ArgumentValue>;
///         using Result = std::vector<ResultValue>;
///
///         Argument argument;
///         Result result;
///
///         fa::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 InputNoDataPolicy(),
///                 output_no_data_policy,
///                 parallel, argument, result);
///
///         BOOST_CHECK(result.empty());
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(array_1d_masked)
/// {
///     using Argument = fern::MaskedArray<ArgumentValue, 1>;
///     using Result = fern::MaskedArray<ResultValue, 1>;
///     using InputNoDataPolicy = fern::DetectNoDataByValue<fern::Mask<1>>;
///     using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<1>>;
///
///     {
///         Argument argument{-5, 0, 5};
///         Result result(3);
///
///         InputNoDataPolicy input_no_data_policy(result.mask(), true);
///         OutputNoDataPolicy output_no_data_policy(result.mask(), true);
///
///         result.fill(3);
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result.mask()[0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[2], false);
///         BOOST_REQUIRE_EQUAL(result[0], 5);
///         BOOST_REQUIRE_EQUAL(result[1], 0);
///         BOOST_REQUIRE_EQUAL(result[2], 5);
///
///         result.fill(3);
///         result.mask()[1] = true;
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result.mask()[0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1], true);
///         BOOST_REQUIRE_EQUAL(result.mask()[2], false);
///         BOOST_REQUIRE_EQUAL(result[0], 5);
///         BOOST_REQUIRE_EQUAL(result[1], 3);
///         BOOST_REQUIRE_EQUAL(result[2], 5);
///     }
///
///     // empty
///     {
///         Argument argument;
///         Result result;
///
///         InputNoDataPolicy input_no_data_policy(result.mask(), true);
///         OutputNoDataPolicy output_no_data_policy(result.mask(), true);
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_CHECK(result.empty());
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(array_2d_sequential)
/// {
///     using InputNoDataPolicy = fern::SkipNoData;
///     using OutputNoDataPolicy = fern::DontMarkNoData;
///     using Argument = fern::Array<ArgumentValue, 2>;
///     using Result = fern::Array<ResultValue, 2>;
///
///     OutputNoDataPolicy output_no_data_policy;
///
///     Argument argument{
///         { -2, -1 },
///         {  0,  9 },
///         {  1,  2 }
///     };
///     Result result{
///         { 3, 3 },
///         { 3, 3 },
///         { 3, 3 }
///     };
///
///     fern::unary_local_operation<
///         Algorithm,
///         fern::unary::DiscardDomainErrors,
///         fern::unary::DiscardRangeErrors>(
///             InputNoDataPolicy(),
///             output_no_data_policy,
///             sequential, argument, result);
///
///     BOOST_CHECK_EQUAL(result[0][0], 2);
///     BOOST_CHECK_EQUAL(result[0][1], 1);
///     BOOST_CHECK_EQUAL(result[1][0], 0);
///     BOOST_CHECK_EQUAL(result[1][1], 9);
///     BOOST_CHECK_EQUAL(result[2][0], 1);
///     BOOST_CHECK_EQUAL(result[2][1], 2);
/// }
///
///
/// BOOST_AUTO_TEST_CASE(array_2d_parallel)
/// {
///     using InputNoDataPolicy = fern::SkipNoData;
///     using OutputNoDataPolicy = fern::DontMarkNoData;
///     using Argument = fern::Array<ArgumentValue, 2>;
///     using Result = fern::Array<ResultValue, 2>;
///
///     OutputNoDataPolicy output_no_data_policy;
///
///     Argument argument{
///         { -2, -1 },
///         {  0,  9 },
///         {  1,  2 }
///     };
///     Result result{
///         { 3, 3 },
///         { 3, 3 },
///         { 3, 3 }
///     };
///
///     fern::unary_local_operation<
///         Algorithm,
///         fern::unary::DiscardDomainErrors,
///         fern::unary::DiscardRangeErrors>(
///             InputNoDataPolicy(),
///             output_no_data_policy,
///             parallel, argument, result);
///
///     BOOST_CHECK_EQUAL(result[0][0], 2);
///     BOOST_CHECK_EQUAL(result[0][1], 1);
///     BOOST_CHECK_EQUAL(result[1][0], 0);
///     BOOST_CHECK_EQUAL(result[1][1], 9);
///     BOOST_CHECK_EQUAL(result[2][0], 1);
///     BOOST_CHECK_EQUAL(result[2][1], 2);
/// }
///
///
/// BOOST_AUTO_TEST_CASE(array_2d_masked)
/// {
///     using Argument = fern::MaskedArray<ArgumentValue, 2>;
///     using Result = fern::MaskedArray<ResultValue, 2>;
///     using InputNoDataPolicy = fern::DetectNoDataByValue<fern::Mask<2>>;
///     using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<2>>;
///
///     {
///         Argument argument{
///             { -2, -1 },
///             {  0,  9 },
///             {  1,  2 }
///         };
///         Result result{
///             { 3, 3 },
///             { 3, 3 },
///             { 3, 3 }
///         };
///
///         InputNoDataPolicy input_no_data_policy(result.mask(), true);
///         OutputNoDataPolicy output_no_data_policy(result.mask(), true);
///
///         result.fill(3);
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result.mask()[0][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[0][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1][1], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[2][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[2][1], false);
///         BOOST_CHECK_EQUAL(result[0][0], 2);
///         BOOST_CHECK_EQUAL(result[0][1], 1);
///         BOOST_CHECK_EQUAL(result[1][0], 0);
///         BOOST_CHECK_EQUAL(result[1][1], 9);
///         BOOST_CHECK_EQUAL(result[2][0], 1);
///         BOOST_CHECK_EQUAL(result[2][1], 2);
///
///         result.fill(3);
///         result.mask()[1][1] = true;
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_REQUIRE_EQUAL(result.mask()[0][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[0][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[1][1], true);
///         BOOST_REQUIRE_EQUAL(result.mask()[2][0], false);
///         BOOST_REQUIRE_EQUAL(result.mask()[2][1], false);
///         BOOST_CHECK_EQUAL(result[0][0], 2);
///         BOOST_CHECK_EQUAL(result[0][1], 1);
///         BOOST_CHECK_EQUAL(result[1][0], 0);
///         BOOST_CHECK_EQUAL(result[1][1], 3);
///         BOOST_CHECK_EQUAL(result[2][0], 1);
///         BOOST_CHECK_EQUAL(result[2][1], 2);
///     }
///
///     // empty
///     {
///         Argument argument;
///         Result result;
///
///         InputNoDataPolicy input_no_data_policy(result.mask(), true);
///         OutputNoDataPolicy output_no_data_policy(result.mask(), true);
///
///         fern::unary_local_operation<
///             Algorithm,
///             fern::unary::DiscardDomainErrors,
///             fern::unary::DiscardRangeErrors>(
///                 input_no_data_policy,
///                 output_no_data_policy,
///                 sequential, argument, result);
///
///         BOOST_CHECK(result.empty());
///     }
/// }

/// #include "fern/feature/core/array_traits.h"
/// #include "fern/feature/core/masked_array_traits.h"
/// #include "fern/feature/core/masked_constant_traits.h"
/// #include "fern/feature/core/test/masked_scalar.h"
/// #include "fern/core/vector_traits.h"
/// #include "fern/algorithm/policy/policies.h"
/// #include "fern/algorithm/statistic/count.h"
/// #include "fern/algorithm/algebra/executor.h"
/// #include "fern/algorithm/algebra/elementary/equal.h"

// BOOST_AUTO_TEST_CASE(masked_d0_array_d0_array)
// {
//     fern::MaskedScalar<int8_t> constant1;
//     fern::MaskedScalar<int32_t> constant2;
//     fern::MaskedScalar<int32_t> result;
//
//     // MaskedConstants with non-masking equal. ---------------------------------
//     // Constants are not masked.
//     constant1.mask() = false;
//     constant1.value() = 5;
//     constant2.mask() = false;
//     constant2.value() = 5;
//     result.value() = 9;
//     result.mask() = false;
//     fern::algebra::add(constant1, constant2, result);
//     BOOST_CHECK(!result.mask());
//     BOOST_CHECK_EQUAL(result.value(), 10);
//
//     // Constant is masked.
//     constant1.mask() = true;
//     constant1.value() = 5;
//     constant2.mask() = false;
//     constant2.value() = 5;
//     result.value() = 9;
//     result.mask() = false;
//     fern::algebra::add(constant1, constant2, result);
//     BOOST_CHECK(!result.mask());
//     BOOST_CHECK_EQUAL(result.value(), 10);
//
//     // MaskedScalar with masking add. ----------------------------------------
//     using InputNoDataPolicy = fern::DetectNoDataByValue<bool>;
//     using OutputNoDataPolicy = fern::MarkNoDataByValue<bool>;
//
//     // Constants are not masked.
//     constant1.mask() = false;
//     constant1.value() = 5;
//     constant2.mask() = false;
//     constant2.value() = 5;
//     result.value() = 9;
//     result.mask() = false;
//     fern::algebra::add<fern::MaskedScalar<int8_t>,
//         fern::MaskedScalar<int32_t>, fern::MaskedScalar<int32_t>,
//         fern::binary::DiscardDomainErrors, fern::binary::DiscardRangeErrors, InputNoDataPolicy,
//         OutputNoDataPolicy>(
//             InputNoDataPolicy(result.mask(), true),
//             OutputNoDataPolicy(result.mask(), true),
//             constant1, constant2, result);
//     BOOST_CHECK(!result.mask());
//     BOOST_CHECK_EQUAL(result.value(), 10);
//
//     // Constants are masked.
//     constant1.value() = 5;
//     constant1.mask() = true;
//     constant2.value() = 5;
//     constant2.mask() = false;
//     result.value() = 9;
//     result.mask() = constant1.mask() || constant2.mask();
//     fern::algebra::add<fern::MaskedScalar<int8_t>,
//         fern::MaskedScalar<int32_t>, fern::MaskedScalar<int32_t>,
//         fern::binary::DiscardDomainErrors, fern::binary::DiscardRangeErrors, InputNoDataPolicy,
//         OutputNoDataPolicy>(
//             InputNoDataPolicy(result.mask(), true),
//             OutputNoDataPolicy(result.mask(), true),
//             constant1, constant2, result);
//     BOOST_CHECK(result.mask());
//     BOOST_CHECK_EQUAL(result.value(), 9);
// }
//
//
// BOOST_AUTO_TEST_CASE(d1_array_d1_array)
// {
//     // vector
//     {
//         std::vector<int32_t> array1{1, 2, 3};
//         std::vector<uint64_t> array2{1, 4, 3};
//         std::vector<int64_t> result(3);
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK_EQUAL(result[0], 2);
//         BOOST_CHECK_EQUAL(result[1], 6);
//         BOOST_CHECK_EQUAL(result[2], 6);
//     }
//
//     // 1d array
//     {
//         fern::Array<uint8_t, 1> array1{1, 2, 3};
//         fern::Array<uint16_t, 1> array2{1, 4, 3};
//         std::vector<uint16_t> result(3);
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK_EQUAL(result[0], 2);
//         BOOST_CHECK_EQUAL(result[1], 6);
//         BOOST_CHECK_EQUAL(result[2], 6);
//     }
//
//     // empty
//     {
//         std::vector<int32_t> array1;
//         std::vector<int32_t> array2;
//         std::vector<int32_t> result;
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK(result.empty());
//     }
// }
//
//
// BOOST_AUTO_TEST_CASE(masked_d1_array_masked_d1_array)
// {
//     using InputNoDataPolicy = fern::DetectNoDataByValue<fern::Mask<1>>;
//     using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<1>>;
//
//     fern::MaskedArray<int32_t, 1> array1{1, 2, 3};
//     fern::MaskedArray<int32_t, 1> array2{1, 4, 3};
//
//     // 1d masked arrays with non-masking add
//     {
//         fern::MaskedArray<int32_t, 1> result(3);
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK_EQUAL(result.mask()[0], false);
//         BOOST_CHECK_EQUAL(result.mask()[1], false);
//         BOOST_CHECK_EQUAL(result.mask()[2], false);
//         BOOST_CHECK_EQUAL(result[0], 2);
//         BOOST_CHECK_EQUAL(result[1], 6);
//         BOOST_CHECK_EQUAL(result[2], 6);
//     }
//
//     // 1d masked arrays with masking add
//     {
//         fern::MaskedArray<int32_t, 1> result(3);
//         result.mask()[2] = true;
//         fern::algebra::add<
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::binary::DiscardDomainErrors,
//             fern::binary::DiscardRangeErrors,
//             InputNoDataPolicy,
//             OutputNoDataPolicy>(
//                 InputNoDataPolicy(result.mask(), true),
//                 OutputNoDataPolicy(result.mask(), true),
//                 array1, array2, result);
//         BOOST_CHECK_EQUAL(result.mask()[0], false);
//         BOOST_CHECK_EQUAL(result.mask()[1], false);
//         BOOST_CHECK_EQUAL(result.mask()[2], true);
//         BOOST_CHECK_EQUAL(result[0], 2);
//         BOOST_CHECK_EQUAL(result[1], 6);
//         BOOST_CHECK_EQUAL(result[2], 0);  // <-- Not touched.
//
//         // Mask the whole input. Result must be masked too.
//         result.mask_all();
//         fern::algebra::add<
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::binary::DiscardDomainErrors,
//             fern::binary::DiscardRangeErrors,
//             InputNoDataPolicy,
//             OutputNoDataPolicy>(
//                 InputNoDataPolicy(result.mask(), true),
//                 OutputNoDataPolicy(result.mask(), true),
//                 array1, array2, result);
//         BOOST_CHECK_EQUAL(result.mask()[0], true);
//         BOOST_CHECK_EQUAL(result.mask()[1], true);
//         BOOST_CHECK_EQUAL(result.mask()[2], true);
//     }
//
//     // empty
//     {
//         fern::MaskedArray<int32_t, 1> empty_array;
//         fern::MaskedArray<int32_t, 1> result;
//         fern::algebra::add<
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::MaskedArray<int32_t, 1>,
//             fern::binary::DiscardDomainErrors,
//             fern::binary::DiscardRangeErrors,
//             InputNoDataPolicy,
//             OutputNoDataPolicy>(
//                 InputNoDataPolicy(result.mask(), true),
//                 OutputNoDataPolicy(result.mask(), true),
//                 empty_array, empty_array, result);
//         BOOST_CHECK(result.empty());
//     }
// }
//
//
// BOOST_AUTO_TEST_CASE(d2_array_d2_array)
// {
//     // 2d array
//     {
//         fern::Array<int8_t, 2> array1{
//             { -2, -1 },
//             {  0,  9 },
//             {  1,  2 }
//         };
//         fern::Array<int8_t, 2> array2{
//             { -2, -1 },
//             {  5,  9 },
//             {  1,  2 }
//         };
//         fern::Array<int8_t, 2> result{
//             { 0, 0 },
//             { 0, 0 },
//             { 0, 0 }
//         };
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK_EQUAL(result[0][0], -4);
//         BOOST_CHECK_EQUAL(result[0][1], -2);
//         BOOST_CHECK_EQUAL(result[1][0], 5);
//         BOOST_CHECK_EQUAL(result[1][1], 18);
//         BOOST_CHECK_EQUAL(result[2][0], 2);
//         BOOST_CHECK_EQUAL(result[2][1], 4);
//     }
// }
//
//
// BOOST_AUTO_TEST_CASE(masked_d2_array_masked_d2_array)
// {
//     fern::MaskedArray<int8_t, 2> array1{
//         { -2, -1 },
//         {  0,  9 },
//         {  1,  2 }
//     };
//     fern::MaskedArray<int8_t, 2> array2{
//         { -2, -1 },
//         {  5,  9 },
//         {  1,  2 }
//     };
//
//     // 2d masked array with non-masking add
//     {
//         fern::MaskedArray<int8_t, 2> result{
//             { 0, 0 },
//             { 0, 0 },
//             { 0, 0 }
//         };
//         fern::algebra::add(array1, array2, result);
//         BOOST_CHECK_EQUAL(result[0][0], -4);
//         BOOST_CHECK_EQUAL(result[0][1], -2);
//         BOOST_CHECK_EQUAL(result[1][0], 5);
//         BOOST_CHECK_EQUAL(result[1][1], 18);
//         BOOST_CHECK_EQUAL(result[2][0], 2);
//         BOOST_CHECK_EQUAL(result[2][1], 4);
//     }
//
//     // 2d masked arrays with masking add
//     {
//         // TODO
//     }
// }
//
//
// BOOST_AUTO_TEST_CASE(concurrent_d2_array)
// {
//     size_t const nr_rows = 6000;
//     size_t const nr_cols = 4000;
//     auto const extents = fern::extents[nr_rows][nr_cols];
//     fern::Array<int32_t, 2> array1(extents);
//     fern::Array<int32_t, 2> array2(extents);
//     fern::Array<int32_t, 2> plus_result_we_get(extents);
//     fern::Array<int32_t, 2> plus_result_we_want(extents);
//     fern::Array<bool, 2> equal_result_we_get(extents, true);
//     size_t count_result_we_get;
//     size_t const count_result_we_want = nr_rows * nr_cols;
//     fern::algebra::Add<
//         fern::Array<int32_t, 2>,
//         fern::Array<int32_t, 2>,
//         fern::Array<int32_t, 2>> add;
//     fern::algebra::Equal<
//         fern::Array<int32_t, 2>,
//         fern::Array<int32_t, 2>,
//         fern::Array<bool, 2>> equal;
//     fern::statistic::Count<
//         fern::Array<bool, 2>,
//         size_t> count;
//
//     std::iota(array1.data(), array1.data() + array1.num_elements(), 0);
//     std::iota(array2.data(), array2.data() + array2.num_elements(), 0);
//     std::transform(array1.data(), array1.data() + array1.num_elements(),
//         plus_result_we_want.data(), [](int32_t value){ return value + value; });
//
//     // Serial.
//     {
//         fern::serial::execute(add, array1, array2, plus_result_we_get);
//         fern::serial::execute(equal, plus_result_we_get, plus_result_we_want,
//             equal_result_we_get);
//         fern::serial::execute(count, equal_result_we_get, true,
//             count_result_we_get);
//         BOOST_CHECK_EQUAL(count_result_we_get, count_result_we_want);
//     }
//
//     // Concurrent.
//     {
//         fern::concurrent::execute(add, array1, array2, plus_result_we_get);
//         fern::concurrent::execute(equal, plus_result_we_get,
//             plus_result_we_want, equal_result_we_get);
//         fern::concurrent::execute(count, equal_result_we_get, true,
//             count_result_we_get);
//         BOOST_CHECK_EQUAL(count_result_we_get, count_result_we_want);
//     }
// }
//
//
// BOOST_AUTO_TEST_CASE(concurrent_masked_d2_array)
// {
//     using InputNoDataPolicy = fern::SkipNoData;
//     using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<2>>;
//
//     size_t const nr_rows = 6000;
//     size_t const nr_cols = 4000;
//     auto const extents = fern::extents[nr_rows][nr_cols];
//     fern::MaskedArray<int32_t, 2> array1(extents);
//     fern::MaskedArray<int32_t, 2> array2(extents);
//     fern::MaskedArray<int32_t, 2> plus_result_we_get(extents);
//     fern::MaskedArray<int32_t, 2> plus_result_we_want(extents);
//     fern::MaskedArray<bool, 2> equal_result_we_get(extents, true);
//     fern::MaskedScalar<size_t> count_result_we_get;
//     fern::MaskedScalar<size_t> const count_result_we_want{nr_rows * nr_cols};
//     fern::algebra::Add<
//         fern::MaskedArray<int32_t, 2>,
//         fern::MaskedArray<int32_t, 2>,
//         fern::MaskedArray<int32_t, 2>,
//         fern::binary::DiscardDomainErrors,
//         fern::binary::DiscardRangeErrors,
//         InputNoDataPolicy,
//         OutputNoDataPolicy> add(
//             InputNoDataPolicy(),
//             OutputNoDataPolicy(plus_result_we_get.mask(), true));
//     fern::algebra::Equal<
//         fern::MaskedArray<int32_t, 2>,
//         fern::MaskedArray<int32_t, 2>,
//         fern::MaskedArray<bool, 2>,
//         fern::binary::DiscardDomainErrors,
//         fern::binary::DiscardRangeErrors,
//         InputNoDataPolicy,
//         OutputNoDataPolicy> equal(
//             InputNoDataPolicy(),
//             OutputNoDataPolicy(equal_result_we_get.mask(), true));
//     fern::statistic::Count<
//         fern::MaskedArray<bool, 2>,
//         fern::MaskedScalar<size_t>,
//         InputNoDataPolicy,
//         fern::MarkNoDataByValue<bool>> count(
//             InputNoDataPolicy(),
//             fern::MarkNoDataByValue<bool>(count_result_we_get.mask(), true));
//
//     std::iota(array1.data(), array1.data() + array1.num_elements(), 0);
//     std::iota(array2.data(), array2.data() + array2.num_elements(), 0);
//     std::transform(array1.data(), array1.data() + array1.num_elements(),
//         plus_result_we_want.data(), [](int32_t value){ return value + value; });
//
//     // Verify executor can handle masked result.
//     fern::concurrent::execute(add, array1, array2, plus_result_we_get);
//     fern::concurrent::execute(equal, plus_result_we_get,
//         plus_result_we_want, equal_result_we_get);
//     fern::concurrent::execute(count, equal_result_we_get, true,
//         count_result_we_get);
//     BOOST_CHECK_EQUAL(count_result_we_get, count_result_we_want);
// }
//
//
// // TODO Test 1d array + 0d array
// //      Test 2d array + 1d array
// //      etc
// // TODO Stuff below.

/// BOOST_AUTO_TEST_CASE(value)
/// {
///     verify_value<int8_t, int8_t, int8_t>(-5, 6, 1);
///
///     verify_value<uint16_t, int8_t>(fern::TypeTraits<uint16_t>::max, 2,
///         int32_t(fern::TypeTraits<uint16_t>::max) + int32_t(2));
///
///     verify_value<uint32_t, int8_t>(fern::TypeTraits<uint32_t>::max, 2,
///         int64_t(fern::TypeTraits<uint32_t>::max) + int64_t(2));
///
///     verify_value<uint64_t, int64_t>(
///         fern::TypeTraits<uint64_t>::max,
///         fern::TypeTraits<int64_t>::max,
///         int64_t(fern::TypeTraits<uint64_t>::max) +
///             fern::TypeTraits<int64_t>::max);
/// }
///
///
/// template<
///     class A1,
///     class A2>
/// struct DomainPolicyHost:
///     public fern::add::OutOfDomainPolicy<A1, A2>
/// {
/// };
///
///
/// BOOST_AUTO_TEST_CASE(domain)
/// {
///     {
///         DomainPolicyHost<int32_t, int32_t> domain;
///         BOOST_CHECK(domain.within_domain(-1, 2));
///     }
///     {
///         DomainPolicyHost<uint8_t, double> domain;
///         BOOST_CHECK(domain.within_domain(1, 2.0));
///     }
/// }
///
///
/// template<
///     class A1,
///     class A2>
/// struct RangePolicyHost:
///     public fern::add::OutOfRangePolicy<A1, A2>
/// {
/// };
///
///
/// template<
///     class A1,
///     class A2>
/// void verify_range_check(
///     A1 const& argument1,
///     A2 const& argument2,
///     bool const within)
/// {
///     fern::algebra::Add<A1, A2> operation;
///     typename fern::algebra::Add<A1, A2>::R result;
///     RangePolicyHost<A1, A2> range;
///
///     operation(argument1, argument2, result);
///     BOOST_CHECK_EQUAL((range.within_range(argument1, argument2, result)),
///         within);
/// }
///
///
/// BOOST_AUTO_TEST_CASE(range)
/// {
///     int8_t const min_int8 = fern::TypeTraits<int8_t>::min;
///     int8_t const max_int8 = fern::TypeTraits<int8_t>::max;
///     uint8_t const max_uint8 = fern::TypeTraits<uint8_t>::max;
///     uint16_t const max_uint16 = fern::TypeTraits<uint16_t>::max;
///     uint32_t const max_uint32 = fern::TypeTraits<uint32_t>::max;
///     int64_t const min_int64 = fern::TypeTraits<int64_t>::min;
///     int64_t const max_int64 = fern::TypeTraits<int64_t>::max;
///     uint64_t const max_uint64 = fern::TypeTraits<uint64_t>::max;
///
///     // signed + signed
///     verify_range_check<int8_t, int8_t>(-5, 6, true);
///     verify_range_check<int8_t, int8_t>(max_int8, 1, false);
///     verify_range_check<int8_t, int8_t>(min_int8, -1, false);
///     verify_range_check<int64_t, int64_t>(min_int64, -1, false);
///
///     // unsigned + unsigned
///     verify_range_check<uint8_t, uint8_t>(5, 6, true);
///     verify_range_check<uint8_t, uint8_t>(max_uint8, 1, false);
///     verify_range_check<uint8_t, uint16_t>(max_uint8, 1, true);
///
///     // signed + unsigned
///     // unsigned + signed
///     verify_range_check<int8_t, uint8_t>(5, 6, true);
///     verify_range_check<uint8_t, int8_t>(5, 6, true);
///     verify_range_check<uint16_t, int8_t>(max_uint16, 2, true);
///     verify_range_check<uint32_t, int8_t>(max_uint32, 2, true);
///     verify_range_check<uint64_t, int64_t>(max_uint64, max_int64, false);
///
///     // float + float
///     float const max_float32 = fern::TypeTraits<float>::max;
///     verify_range_check<float, float>(5.0, 6.0, true);
///     verify_range_check<float, float>(max_float32, max_float32 * 20, false);
///
///     // float + signed
///     // unsigned + float
///     verify_range_check<float, int8_t>(5.0, 6, true);
///     verify_range_check<uint8_t, float>(5, 6.0, true);
/// }
///
///
/// BOOST_AUTO_TEST_CASE(argument_types)
/// {
///     // Verify that we can pass in all kinds of collection types.
///
///     // constant + constant
///     {
///         uint8_t argument1(5);
///         uint8_t argument2(6);
///         using R = fern::Result<uint8_t, uint8_t>::type;
///         R result;
///
///         fern::algebra::add(argument1, argument2, result);
///
///         BOOST_CHECK_EQUAL(result, 11u);
///     }
///
///     // constant + vector
///     {
///         uint8_t argument1(5);
///         std::vector<uint8_t> argument2({1, 2, 3});
///         using R = fern::Result<uint8_t, uint8_t>::type;
///         std::vector<R> result(argument2.size());
///
///         fern::algebra::add(argument1, argument2, result);
///
///         BOOST_REQUIRE_EQUAL(result.size(), 3u);
///         BOOST_CHECK_EQUAL(result[0], 6u);
///         BOOST_CHECK_EQUAL(result[1], 7u);
///         BOOST_CHECK_EQUAL(result[2], 8u);
///     }
///
///     // vector + constant
///     {
///         std::vector<uint8_t> argument1({1, 2, 3});
///         uint8_t argument2(5);
///         using R = fern::Result<uint8_t, uint8_t>::type;
///         std::vector<R> result(argument1.size());
///
///         fern::algebra::add(argument1, argument2, result);
///
///         BOOST_REQUIRE_EQUAL(result.size(), 3u);
///         BOOST_CHECK_EQUAL(result[0], 6u);
///         BOOST_CHECK_EQUAL(result[1], 7u);
///         BOOST_CHECK_EQUAL(result[2], 8u);
///     }
///
///     // vector + vector
///     {
///         std::vector<uint8_t> argument1({1, 2, 3});
///         std::vector<uint8_t> argument2({4, 5, 6});
///         using R = fern::Result<uint8_t, uint8_t>::type;
///         std::vector<R> result(argument1.size());
///
///         fern::algebra::add(argument1, argument2, result);
///
///         BOOST_REQUIRE_EQUAL(result.size(), 3u);
///         BOOST_CHECK_EQUAL(result[0], 5u);
///         BOOST_CHECK_EQUAL(result[1], 7u);
///         BOOST_CHECK_EQUAL(result[2], 9u);
///     }
///
///     // array + array
///     {
///         fern::Array<int8_t, 2> argument(fern::extents[3][2]);
///         argument[0][0] = -2;
///         argument[0][1] = -1;
///         argument[1][0] =  0;
///         argument[1][1] =  9;
///         argument[2][0] =  1;
///         argument[2][1] =  2;
///         using R = fern::Result<int8_t, int8_t>::type;
///         fern::Array<R, 2> result(fern::extents[3][2]);
///
///         fern::algebra::add(argument, argument, result);
///
///         BOOST_CHECK_EQUAL(result[0][0], -4);
///         BOOST_CHECK_EQUAL(result[0][1], -2);
///         BOOST_CHECK_EQUAL(result[1][0],  0);
///         BOOST_CHECK_EQUAL(result[1][1], 18);
///         BOOST_CHECK_EQUAL(result[2][0],  2);
///         BOOST_CHECK_EQUAL(result[2][1],  4);
///     }
///
///     // masked_array + masked_array
///     {
///         fern::MaskedArray<int8_t, 2> argument(fern::extents[3][2]);
///         argument[0][0] = -2;
///         argument[0][1] = -1;
///         argument[1][0] =  0;
///         argument.mask()[1][1] =  true;
///         argument[1][1] =  9;
///         argument[2][0] =  1;
///         argument[2][1] =  2;
///         using R = fern::Result<int8_t, int8_t>::type;
///         fern::MaskedArray<R, 2> result(fern::extents[3][2]);
///
///         fern::algebra::add(argument, argument, result);
///
///         BOOST_CHECK(!result.mask()[0][0]);
///         BOOST_CHECK_EQUAL(result[0][0], -4);
///
///         BOOST_CHECK(!result.mask()[0][1]);
///         BOOST_CHECK_EQUAL(result[0][1], -2);
///
///         BOOST_CHECK(!result.mask()[1][0]);
///         BOOST_CHECK_EQUAL(result[1][0],  0);
///
///         // Although the input data has a mask, the default policy discards
///         // it. So the result doesn't have masked values.
///         BOOST_CHECK(!result.mask()[1][1]);
///         BOOST_CHECK_EQUAL(result[1][1], 18);
///
///         BOOST_CHECK(!result.mask()[2][0]);
///         BOOST_CHECK_EQUAL(result[2][0],  2);
///         BOOST_CHECK(!result.mask()[2][1]);
///         BOOST_CHECK_EQUAL(result[2][1],  4);
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(no_data)
/// {
///     size_t const nr_rows = 3;
///     size_t const nr_cols = 2;
///     auto extents = fern::extents[nr_rows][nr_cols];
///
///     fern::MaskedArray<int8_t, 2> argument1(extents);
///     argument1[0][0] = -2;
///     argument1[0][1] = -1;
///     argument1[1][0] =  0;
///     argument1.mask()[1][1] =  true;
///     argument1[2][0] =  1;
///     argument1[2][1] =  2;
///
///     fern::MaskedArray<int8_t, 2> argument2(extents);
///     argument2[0][0] = -2;
///     argument2[0][1] = -1;
///     argument2[1][0] =  0;
///     argument2[1][1] =  9;
///     argument2.mask()[2][0] =  true;
///     argument2[2][1] =  2;
///
///     int8_t argument3 = 5;
///
///     // masked_array + masked_array
///     {
///         // Create room for the result.
///         // Set the mask.
///         using R = fern::Result<int8_t, int8_t>::type;
///         fern::MaskedArray<R, 2> result(extents);
///         result.set_mask(argument1.mask(), true);
///         result.set_mask(argument2.mask(), true);
///
///         using A1 = decltype(argument1);
///         using A2 = decltype(argument2);
///         using A1Value = fern::DataTypeTraits<A1>::value_type;
///         using A2Value = fern::DataTypeTraits<A2>::value_type;
///         using OutOfDomainPolicy = fern::DiscardDomainErrors<A1Value, A2Value>;
///         using OutOfRangePolicy = fern::add::OutOfRangePolicy<A1Value, A2Value>;
///         using NoDataPolicy = fern::MarkNoDataByValue<bool, fern::Mask<2>>;
///         using Add = fern::algebra::Add<A1, A2, OutOfDomainPolicy, OutOfRangePolicy,
///             NoDataPolicy>;
///
///         Add add(NoDataPolicy(result.mask(), true));
///
///         add(argument1, argument2, result);
///
///         BOOST_CHECK(!result.mask()[0][0]);
///         BOOST_CHECK_EQUAL(result[0][0], -4);
///
///         BOOST_CHECK(!result.mask()[0][1]);
///         BOOST_CHECK_EQUAL(result[0][1], -2);
///
///         BOOST_CHECK(!result.mask()[1][0]);
///         BOOST_CHECK_EQUAL(result[1][0],  0);
///
///         BOOST_CHECK( result.mask()[1][1]);
///         // Value is masked: it is undefined.
///         // BOOST_CHECK_EQUAL(result[1][1], 18);
///
///         BOOST_CHECK(result.mask()[2][0]);
///         // Value is masked.
///         // BOOST_CHECK_EQUAL(result[2][0],  2);
///
///         BOOST_CHECK(!result.mask()[2][1]);
///         BOOST_CHECK_EQUAL(result[2][1],  4);
///     }
///
///     // masked_array + 5
///     {
///         // Create room for the result.
///         // Set the mask.
///         using R = fern::Result<int8_t, int8_t>::type;
///         fern::MaskedArray<R, 2> result(extents);
///         result.set_mask(argument1.mask(), true);
///
///         using A1 = decltype(argument1);
///         using A2 = decltype(argument3);
///         using A1Value = fern::DataTypeTraits<A1>::value_type;
///         using A2Value = fern::DataTypeTraits<A2>::value_type;
///         using OutOfDomainPolicy = fern::DiscardDomainErrors<A1Value, A2Value>;
///         using OutOfRangePolicy = fern::add::OutOfRangePolicy<A1Value, A2Value>;
///         using NoDataPolicy = fern::MarkNoDataByValue<bool, fern::Mask<2>>;
///         using Add = fern::algebra::Add<A1, A2, OutOfDomainPolicy, OutOfRangePolicy,
///             NoDataPolicy>;
///
///         Add add(NoDataPolicy(result.mask(), true));
///
///         add(argument1, argument3, result);
///
///         BOOST_CHECK(!result.mask()[0][0]);
///         BOOST_CHECK_EQUAL(result[0][0], 3);
///
///         BOOST_CHECK(!result.mask()[0][1]);
///         BOOST_CHECK_EQUAL(result[0][1], 4);
///
///         BOOST_CHECK(!result.mask()[1][0]);
///         BOOST_CHECK_EQUAL(result[1][0], 5);
///
///         BOOST_CHECK( result.mask()[1][1]);
///
///         BOOST_CHECK(!result.mask()[2][0]);
///         BOOST_CHECK_EQUAL(result[2][0], 6);
///
///         BOOST_CHECK(!result.mask()[2][1]);
///         BOOST_CHECK_EQUAL(result[2][1], 7);
///     }
///
///     // 5 + masked_array
///     {
///         // Create room for the result.
///         // Set the mask.
///         using R = fern::Result<int8_t, int8_t>::type;
///         fern::MaskedArray<R, 2> result(extents);
///         result.set_mask(argument1.mask(), true);
///
///         using A1 = decltype(argument3);
///         using A2 = decltype(argument1);
///         using A1Value = fern::DataTypeTraits<A1>::value_type;
///         using A2Value = fern::DataTypeTraits<A2>::value_type;
///         using OutOfDomainPolicy = fern::DiscardDomainErrors<A1Value, A2Value>;
///         using OutOfRangePolicy = fern::add::OutOfRangePolicy<A1Value, A2Value>;
///         using NoDataPolicy = fern::MarkNoDataByValue<bool, fern::Mask<2>>;
///         using Add = fern::algebra::Add<A1, A2, OutOfDomainPolicy, OutOfRangePolicy,
///             NoDataPolicy>;
///
///         Add add(NoDataPolicy(result.mask(), true));
///
///         add(argument3, argument1, result);
///
///         BOOST_CHECK(!result.mask()[0][0]);
///         BOOST_CHECK_EQUAL(result[0][0], 3);
///
///         BOOST_CHECK(!result.mask()[0][1]);
///         BOOST_CHECK_EQUAL(result[0][1], 4);
///
///         BOOST_CHECK(!result.mask()[1][0]);
///         BOOST_CHECK_EQUAL(result[1][0], 5);
///
///         BOOST_CHECK( result.mask()[1][1]);
///
///         BOOST_CHECK(!result.mask()[2][0]);
///         BOOST_CHECK_EQUAL(result[2][0], 6);
///
///         BOOST_CHECK(!result.mask()[2][1]);
///         BOOST_CHECK_EQUAL(result[2][1], 7);
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(threading)
/// {
///     // Create a somewhat larger array.
///     size_t const nr_rows = 6000;
///     size_t const nr_cols = 4000;
///     auto extents = fern::extents[nr_rows][nr_cols];
///     fern::Array<int8_t, 2> argument1(extents);
///
///     // Fill it with 0, 1, 2, 3, ...
///     std::iota(argument1.data(), argument1.data() + argument1.num_elements(), 0);
///
///     int8_t argument2 = 5;
///
///     // Create array with values that should be in the result.
///     fern::Array<int8_t, 2> result_we_want(extents);
///     std::iota(result_we_want.data(), result_we_want.data() +
///         result_we_want.num_elements(), 5);
///
///     {
///         // Add collection and constant.
///         fern::algebra::Add<fern::Array<int8_t, 2>, int8_t> add;
///         fern::Array<int8_t, 2> plus_result(extents);
///
///         // Compare result with result we want.
///         fern::algebra::Add<fern::Array<int8_t, 2>, fern::Array<int8_t, 2>>
///             add;
///         fern::Array<bool, 2> equal_result(extents);
///
///         // Count the number of equal values.
///         fern::statistic::Count<fern::Array<bool, 2>, bool> count;
///         size_t count_result;
///
///         // Serial.
///         {
///             fern::serial::execute(add, argument1, argument2, plus_result);
///             fern::serial::execute(equal, plus_result, result_we_want,
///                 equal_result);
///             fern::serial::execute(count, equal_result, true, count_result);
///
///             // Make sure the number of equal values equals the number of
///             // elements.
///             BOOST_CHECK_EQUAL(count_result, equal_result.num_elements());
///         }
///
///         // Concurrent.
///         {
///             fern::concurrent::execute(add, argument1, argument2, plus_result);
///             fern::concurrent::execute(equal, plus_result, result_we_want,
///                 equal_result);
///             fern::concurrent::execute(count, equal_result, true, count_result);
///
///             // Make sure the number of equal values equals the number of
///             // elements.
///             BOOST_CHECK_EQUAL(count_result, equal_result.num_elements());
///         }
///     }
///
///     // TODO Make this work for masked arrays too.
/// }

/// BOOST_AUTO_TEST_CASE(masked_d0_array_d0_array)
/// {
///     fern::MaskedScalar<int8_t> constant1;
///     fern::MaskedScalar<int32_t> constant2;
///     fern::MaskedScalar<bool> result_we_get;
///
///     // MaskedConstants with non-masking equal. ---------------------------------
///     // Constants are not masked.
///     constant1.mask() = false;
///     constant1.value() = 5;
///     constant2.mask() = false;
///     constant2.value() = 5;
///     result_we_get.value() = false;
///     result_we_get.mask() = false;
///     fern::algebra::equal(constant1, constant2, result_we_get);
///     BOOST_CHECK(!result_we_get.mask());
///     BOOST_CHECK_EQUAL(result_we_get.value(), true);
///
///     // Constant is masked.
///     constant1.mask() = true;
///     constant1.value() = 5;
///     constant2.mask() = false;
///     constant2.value() = 5;
///     result_we_get.value() = false;
///     result_we_get.mask() = false;
///     fern::algebra::equal(constant1, constant2, result_we_get);
///     BOOST_CHECK(!result_we_get.mask());
///     BOOST_CHECK_EQUAL(result_we_get.value(), true);
///
///     // MaskedScalar with masking equal. --------------------------------------
///     using InputNoDataPolicy = fern::DetectNoDataByValue<bool>;
///     using OutputNoDataPolicy = fern::MarkNoDataByValue<bool>;
///
///     // Constants are not masked.
///     constant1.mask() = false;
///     constant1.value() = 5;
///     constant2.mask() = false;
///     constant2.value() = 5;
///     result_we_get.value() = false;
///     result_we_get.mask() = constant1.mask() || constant2.mask();
///     fern::algebra::equal<fern::MaskedScalar<int8_t>,
///         fern::MaskedScalar<int32_t>, fern::MaskedScalar<bool>,
///         fern::binary::DiscardDomainErrors, fern::binary::DiscardRangeErrors,
///         InputNoDataPolicy,
///         OutputNoDataPolicy>(
///             InputNoDataPolicy(result_we_get.mask(), true),
///             OutputNoDataPolicy(result_we_get.mask(), true),
///             constant1, constant2, result_we_get);
///     BOOST_CHECK(!result_we_get.mask());
///     BOOST_CHECK_EQUAL(result_we_get.value(), true);
///
///     // Constants are masked.
///     constant1.value() = 5;
///     constant1.mask() = true;
///     constant2.value() = 5;
///     constant2.mask() = false;
///     result_we_get.value() = false;
///     result_we_get.mask() = constant1.mask() || constant2.mask();
///     fern::algebra::equal<fern::MaskedScalar<int8_t>,
///         fern::MaskedScalar<int32_t>, fern::MaskedScalar<bool>,
///         fern::binary::DiscardDomainErrors, fern::binary::DiscardRangeErrors,
///         InputNoDataPolicy,
///         OutputNoDataPolicy>(
///             InputNoDataPolicy(result_we_get.mask(), true),
///             OutputNoDataPolicy(result_we_get.mask(), true),
///             constant1, constant2, result_we_get);
///     BOOST_CHECK(result_we_get.mask());
///     BOOST_CHECK_EQUAL(result_we_get.value(), false);
/// }
///
///
/// BOOST_AUTO_TEST_CASE(d1_array_d1_array)
/// {
///     // vector
///     {
///         std::vector<int32_t> array1{1, 2, 3};
///         std::vector<uint64_t> array2{1, 4, 3};
///         std::vector<bool> result(3);
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK_EQUAL(result[0], true);
///         BOOST_CHECK_EQUAL(result[1], false);
///         BOOST_CHECK_EQUAL(result[2], true);
///     }
///
///     // 1d array
///     {
///         fern::Array<uint8_t, 1> array1{1, 2, 3};
///         fern::Array<uint16_t, 1> array2{1, 4, 3};
///         std::vector<bool> result(3);
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK_EQUAL(result[0], true);
///         BOOST_CHECK_EQUAL(result[1], false);
///         BOOST_CHECK_EQUAL(result[2], true);
///     }
///
///     // empty
///     {
///         std::vector<int32_t> array1;
///         std::vector<int32_t> array2;
///         std::vector<bool> result;
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK(result.empty());
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(masked_d1_array)
/// {
///     using InputNoDataPolicy = fern::DetectNoDataByValue<fern::Mask<1>>;
///     using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<1>>;
///
///     fern::MaskedArray<int32_t, 1> array1{1, 2, 3};
///     fern::MaskedArray<int32_t, 1> array2{1, 4, 3};
///
///     // 1d masked arrays with non-masking equal
///     {
///         fern::MaskedArray<bool, 1> result(3, false);
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK_EQUAL(result.mask()[0], false);
///         BOOST_CHECK_EQUAL(result.mask()[1], false);
///         BOOST_CHECK_EQUAL(result.mask()[2], false);
///         BOOST_CHECK_EQUAL(result[0], true);
///         BOOST_CHECK_EQUAL(result[1], false);
///         BOOST_CHECK_EQUAL(result[2], true);
///     }
///
///     // 1d masked arrays with masking equal
///     {
///         fern::MaskedArray<bool, 1> result(3, false);
///         result.mask()[2] = true;
///         fern::algebra::equal<
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<bool, 1>,
///             fern::binary::DiscardDomainErrors,
///             fern::binary::DiscardRangeErrors,
///             InputNoDataPolicy,
///             OutputNoDataPolicy>(
///                 InputNoDataPolicy(result.mask(), true),
///                 OutputNoDataPolicy(result.mask(), true),
///                 array1, array2, result);
///         BOOST_CHECK_EQUAL(result.mask()[0], false);
///         BOOST_CHECK_EQUAL(result.mask()[1], false);
///         BOOST_CHECK_EQUAL(result.mask()[2], true);
///         BOOST_CHECK_EQUAL(result[0], true);
///         BOOST_CHECK_EQUAL(result[1], false);
///         BOOST_CHECK_EQUAL(result[2], false);  // <-- Not touched.
///
///         // Mask the whole input. Result must be masked too.
///         result.mask_all();
///         fern::algebra::equal<
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<bool, 1>,
///             fern::binary::DiscardDomainErrors,
///             fern::binary::DiscardRangeErrors,
///             InputNoDataPolicy,
///             OutputNoDataPolicy>(
///                 InputNoDataPolicy(result.mask(), true),
///                 OutputNoDataPolicy(result.mask(), true),
///                 array1, array2, result);
///         BOOST_CHECK_EQUAL(result.mask()[0], true);
///         BOOST_CHECK_EQUAL(result.mask()[1], true);
///         BOOST_CHECK_EQUAL(result.mask()[2], true);
///     }
///
///     // empty
///     {
///         fern::MaskedArray<int32_t, 1> empty_array;
///         fern::MaskedArray<bool, 1> result;
///         fern::algebra::equal<
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<int32_t, 1>,
///             fern::MaskedArray<bool, 1>,
///             fern::binary::DiscardDomainErrors,
///             fern::binary::DiscardRangeErrors,
///             InputNoDataPolicy,
///             OutputNoDataPolicy>(
///                 InputNoDataPolicy(result.mask(), true),
///                 OutputNoDataPolicy(result.mask(), true),
///                 empty_array, empty_array, result);
///         BOOST_CHECK(result.empty());
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(d2_array_d2_array)
/// {
///     // 2d array
///     {
///         fern::Array<int8_t, 2> array1{
///             { -2, -1 },
///             {  0,  9 },
///             {  1,  2 }
///         };
///         fern::Array<int8_t, 2> array2{
///             { -2, -1 },
///             {  5,  9 },
///             {  1,  2 }
///         };
///         fern::Array<bool, 2> result{
///             { false, false },
///             { false, false },
///             { false, false }
///         };
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK_EQUAL(result[0][0], true);
///         BOOST_CHECK_EQUAL(result[0][1], true);
///         BOOST_CHECK_EQUAL(result[1][0], false);
///         BOOST_CHECK_EQUAL(result[1][1], true);
///         BOOST_CHECK_EQUAL(result[2][0], true);
///         BOOST_CHECK_EQUAL(result[2][1], true);
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(masked_d2_array_d2_array)
/// {
///     fern::MaskedArray<int8_t, 2> array1{
///         { -2, -1 },
///         {  0,  9 },
///         {  1,  2 }
///     };
///     fern::MaskedArray<int8_t, 2> array2{
///         { -2, -1 },
///         {  5,  9 },
///         {  1,  2 }
///     };
///
///     // 2d masked array with non-masking equal
///     {
///         fern::MaskedArray<bool, 2> result{
///             { true, true },
///             { true, true },
///             { true, true }
///         };
///         fern::algebra::equal(array1, array2, result);
///         BOOST_CHECK_EQUAL(result[0][0], true);
///         BOOST_CHECK_EQUAL(result[0][1], true);
///         BOOST_CHECK_EQUAL(result[1][0], false);
///         BOOST_CHECK_EQUAL(result[1][1], true);
///         BOOST_CHECK_EQUAL(result[2][0], true);
///         BOOST_CHECK_EQUAL(result[2][1], true);
///     }
///
///     // 2d masked arrays with masking equal
///     {
///         // TODO
///     }
/// }
///
///
/// BOOST_AUTO_TEST_CASE(concurrent)
/// {
///     // Create a somewhat larger array.
///     size_t const nr_rows = 6000;
///     size_t const nr_cols = 4000;
///     auto const extents = fern::extents[nr_rows][nr_cols];
///     fern::Array<int32_t, 2> array1(extents);
///     fern::Array<int32_t, 2> array2(extents);
///     fern::Array<bool, 2> result(extents);
///     fern::algebra::Equal<
///         fern::Array<int32_t, 2>,
///         fern::Array<int32_t, 2>,
///         fern::Array<bool, 2>> equal;
///
///     std::iota(array1.data(), array1.data() + array1.num_elements(), 0);
///     std::iota(array2.data(), array2.data() + array2.num_elements(), 0);
///
///     // Serial.
///     {
///         result.fill(false);
///         fern::serial::execute(equal, array1, array2, result);
///         BOOST_CHECK(std::all_of(result.data(), result.data() +
///             result.num_elements(), [](bool equal){ return equal; }));
///     }
///
///     // Concurrent.
///     {
///         result.fill(false);
///         fern::concurrent::execute(equal, array1, array2, result);
///         BOOST_CHECK(std::all_of(result.data(), result.data() +
///             result.num_elements(), [](bool equal){ return equal; }));
///     }
///
///     {
///         using InputNoDataPolicy = fern::SkipNoData;
///         using OutputNoDataPolicy = fern::MarkNoDataByValue<fern::Mask<2>>;
///
///         fern::MaskedArray<bool, 2> result(extents);
///         fern::algebra::Equal<
///             fern::Array<int32_t, 2>,
///             fern::Array<int32_t, 2>,
///             fern::MaskedArray<bool, 2>,
///             fern::binary::DiscardDomainErrors,
///             fern::binary::DiscardRangeErrors,
///             InputNoDataPolicy,
///             OutputNoDataPolicy> equal(
///                 InputNoDataPolicy(),
///                 OutputNoDataPolicy(result.mask(), true));
///
///         // Verify executor can handle masked result.
///         fern::concurrent::execute(equal, array1, array2, result);
///         BOOST_CHECK(std::all_of(result.data(), result.data() +
///             result.num_elements(), [](bool equal){ return equal; }));
///     }
/// }
///
/// // TODO Test 1d array == 0d array
/// //      Test 2d array == 1d array
/// //      etc


/**
 * @file spill_tree_test.cpp
 * @author Marcos Pividori
 *
 * Tests for the SpillTree class.  This should ensure that the class works
 * correctly and that subsequent changes don't break anything.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */

#include <mlpack/core.hpp>
#include <mlpack/core/tree/spill_tree.hpp>
#include <boost/test/unit_test.hpp>
#include <stack>

using namespace mlpack;
using namespace mlpack::tree;
using namespace mlpack::metric;

BOOST_AUTO_TEST_SUITE(SpillTreeTest);

/**
 * Test to make sure the tree contains the correct number of points after
 * it is constructed. Also, it checks some invariants in the relation between
 * parent and child nodes.
 */
BOOST_AUTO_TEST_CASE(SpillTreeConstructionCountTest)
{
  arma::mat dataset;
  dataset.randu(3, 1000); // 1000 points in 3 dimensions.

  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;

  // When overlapping buffer is 0, there shouldn't be repeated points.
  TreeType tree1(dataset, 0);
  TreeType tree2 = tree1;

  BOOST_REQUIRE_EQUAL(tree1.NumDescendants(), 1000);
  BOOST_REQUIRE_EQUAL(tree2.NumDescendants(), 1000);

  // When overlapping buffer is greater than 0, it is possible to have repeated
  // points. So, let's check node by node, that the number of descendants
  // equals to the addition of the number of descendants of child nodes.
  TreeType tree3(dataset, 0.5);

  std::stack<TreeType*> nodes;
  nodes.push(&tree3);
  while (!nodes.empty())
  {
    TreeType* node = nodes.top();
    nodes.pop();

    size_t numDesc = node->NumPoints();

    if (node->Left())
    {
      nodes.push(node->Left());
      numDesc += node->Left()->NumDescendants();
    }

    if (node->Right())
    {
      nodes.push(node->Right());
      numDesc += node->Right()->NumDescendants();
    }

    if (node->IsLeaf())
      BOOST_REQUIRE_EQUAL(node->NumPoints(), node->NumDescendants());
    else
      BOOST_REQUIRE_EQUAL(node->NumPoints(), 0);

    BOOST_REQUIRE_EQUAL(node->NumDescendants(), numDesc);
  }
}

/**
 * Test to check that parents and children are set correctly.
 */
BOOST_AUTO_TEST_CASE(SpillTreeConstructionParentTest)
{
  arma::mat dataset;
  dataset.randu(3, 1000); // 1000 points in 3 dimensions.

  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;

  TreeType tree(dataset, 0.5);

  std::stack<TreeType*> nodes;
  nodes.push(&tree);
  while (!nodes.empty())
  {
    TreeType* node = nodes.top();
    nodes.pop();

    if (node->Left())
    {
      nodes.push(node->Left());
      BOOST_REQUIRE_EQUAL(node, node->Left()->Parent());
    }

    if (node->Right())
    {
      nodes.push(node->Right());
      BOOST_REQUIRE_EQUAL(node, node->Right()->Parent());
    }
  }
}

/**
 * Auxiliary function to execute the same test for different flavours of Spill
 * Trees.
 */
template<typename SpillType>
void SpillTreeHyperplaneTestAux()
{
  arma::mat dataset;
  dataset.randu(3, 1000); // 1000 points in 3 dimensions.

  for (size_t cases = 0; cases < 3; cases++)
  {
    double tau = cases * 0.05;

    // Let's check node by node, that points in the left child are considered to
    // the left by the splitting hyperplane, and the same for points in the
    // right child.
    SpillType tree(dataset, tau);

    std::stack<SpillType*> nodes;
    nodes.push(&tree);
    while (!nodes.empty())
    {
      SpillType* node = nodes.top();
      nodes.pop();

      if (node->Overlap())
      {
        // We have a overlapping node.
        if (node->Left())
        {
          // Let's check that points in the left child are projected to values
          // in the range: (-inf, tau]
          size_t numDesc = node->Left()->NumDescendants();
          for (size_t i = 0; i < numDesc; i++)
          {
            size_t descIndex = node->Left()->Descendant(i);
            BOOST_REQUIRE_LE(
                node->Hyperplane().Project(node->Dataset().col(descIndex)),
                tau);
          }
        }
        if (node->Right())
        {
          // Let's check that points in the right child are projected to values
          // in the range: (-tau, inf)
          size_t numDesc = node->Right()->NumDescendants();
          for (size_t i = 0; i < numDesc; i++)
          {
            size_t descIndex = node->Right()->Descendant(i);
            BOOST_REQUIRE_GT(
                node->Hyperplane().Project(node->Dataset().col(descIndex)),
                -tau);
          }
        }
      }
      else
      {
        // We have a non-overlapping node.
        if (node->Left())
        {
          // Let's check that points in the left child are considered to the
          // left by the splitting hyperplane.
          size_t numDesc = node->Left()->NumDescendants();
          for (size_t i = 0; i < numDesc; i++)
          {
            size_t descIndex = node->Left()->Descendant(i);
            BOOST_REQUIRE(
                node->Hyperplane().Left(node->Dataset().col(descIndex)));
          }
        }
        if (node->Right())
        {
          // Let's check that points in the right child are considered to the
          // right by the splitting hyperplane.
          size_t numDesc = node->Right()->NumDescendants();
          for (size_t i = 0; i < numDesc; i++)
          {
            size_t descIndex = node->Right()->Descendant(i);
            BOOST_REQUIRE(
                node->Hyperplane().Right(node->Dataset().col(descIndex)));
          }
        }
      }

      if (node->Left())
        nodes.push(node->Left());

      if (node->Right())
        nodes.push(node->Right());
    }
  }
}

/**
 * Test to make sure that the points in the left child are considered to the
 * left by the node's splitting hyperplane, and the same for points in the
 * right child.
 */
BOOST_AUTO_TEST_CASE(SpillTreeHyperplaneTest)
{
  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> SpillType1;
  typedef NonOrtSPTree<EuclideanDistance, EmptyStatistic, arma::mat> SpillType2;
  typedef MeanSPTree<EuclideanDistance, EmptyStatistic, arma::mat> SpillType3;
  typedef NonOrtMeanSPTree<EuclideanDistance, EmptyStatistic, arma::mat>
      SpillType4;

  SpillTreeHyperplaneTestAux<SpillType1>();
  SpillTreeHyperplaneTestAux<SpillType2>();
  SpillTreeHyperplaneTestAux<SpillType3>();
  SpillTreeHyperplaneTestAux<SpillType4>();
}

/**
 * Simple test for the move constructor.
 */
BOOST_AUTO_TEST_CASE(SpillTreeMoveConstructorTest)
{
  arma::mat dataset = arma::randu<arma::mat>(3, 1000);
  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;

  TreeType tree(dataset);

  TreeType* left = tree.Left();
  TreeType* right = tree.Right();
  size_t numDesc = tree.NumDescendants();

  TreeType newTree(std::move(tree));

  BOOST_REQUIRE(tree.Left() == NULL);
  BOOST_REQUIRE(tree.Right() == NULL);
  BOOST_REQUIRE_EQUAL(tree.NumDescendants(), 0);

  BOOST_REQUIRE_EQUAL(newTree.Left(), left);
  BOOST_REQUIRE_EQUAL(newTree.Right(), right);
  BOOST_REQUIRE_EQUAL(newTree.NumDescendants(), numDesc);
  if (left)
  {
    BOOST_REQUIRE(newTree.Left() != NULL);
    BOOST_REQUIRE_EQUAL(newTree.Left()->Parent(), &newTree);
  }
  if (right)
  {
    BOOST_REQUIRE(newTree.Right() != NULL);
    BOOST_REQUIRE_EQUAL(newTree.Right()->Parent(), &newTree);
  }
}

/**
 * Simple test for the copy constructor.
 */
BOOST_AUTO_TEST_CASE(SpillTreeCopyConstructorTest)
{
  arma::mat dataset = arma::randu<arma::mat>(3, 1000);
  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;

  TreeType* tree = new TreeType(dataset);

  TreeType* left = tree->Left();
  TreeType* right = tree->Right();
  size_t numDesc = tree->NumDescendants();

  // Copy the tree.
  TreeType newTree(*tree);

  delete tree;

  BOOST_REQUIRE_EQUAL(newTree.Dataset().n_rows, 3);
  BOOST_REQUIRE_EQUAL(newTree.Dataset().n_cols, 1000);
  BOOST_REQUIRE_EQUAL(newTree.NumDescendants(), numDesc);
  if (left)
  {
    BOOST_REQUIRE(newTree.Left() != left);
    BOOST_REQUIRE(newTree.Left() != NULL);
    BOOST_REQUIRE_EQUAL(newTree.Left()->Parent(), &newTree);
  }
  if (right)
  {
    BOOST_REQUIRE(newTree.Right() != right);
    BOOST_REQUIRE(newTree.Right() != NULL);
    BOOST_REQUIRE_EQUAL(newTree.Right()->Parent(), &newTree);
  }
}

/**
 * Simple test for the constructor that takes a rvalue reference to the dataset.
 */
BOOST_AUTO_TEST_CASE(SpillTreeMoveDatasetTest)
{
  arma::mat dataset = arma::randu<arma::mat>(3, 1000);
  typedef SPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;

  TreeType tree(std::move(dataset));

  BOOST_REQUIRE_EQUAL(dataset.n_elem, 0);
  BOOST_REQUIRE_EQUAL(tree.Dataset().n_rows, 3);
  BOOST_REQUIRE_EQUAL(tree.Dataset().n_cols, 1000);
}

BOOST_AUTO_TEST_SUITE_END();


/* Copyright (c) 2017, Julian Straub <jstraub@csail.mit.edu>
 * Licensed under the MIT license. See the license file LICENSE.
 */
#pragma once
#include <cmath>
#include <iostream>
#include <Eigen/Dense>

#include "vmf.hpp"

///
/// vMF prior for 3D where a=1 and 0<b<1 for the hyperparameters
/// This allows the marginal data distribution under the prior to be
/// computed in closed form.
///
/// See J Straub, "Nonparametric Driectional Perception", 2017
///
/// Note that the posterior can have a != 0.
///
template<typename T>
class vMFprior : public Distribution<T>
{
 public:
  vMFprior(const Eigen::Matrix<T,Eigen::Dynamic,1>& m0, T a, T b, boost::mt19937
    *pRndGen)
    : Distribution<T>(pRndGen), m0_(m0), b_(b), a_(a), pRndGen_(pRndGen)
  {}

  void resetSufficientStatistics();
  void getSufficientStatistics(const Matrix<T,Dynamic,Dynamic> &x,
    const VectorXu& z, uint32_t k);

  /// Sample from prior
  vMF<T> sample() {
//    std::cout << "sample from vMF prior " << a_ << " " << b_
//      << " " << m0_.transpose() << std::endl;
    Eigen::Matrix<T,Eigen::Dynamic,1> mu;
    T tau = 1.;
    vMF<T> vmf(m0_, tau*b_, pRndGen_);
//    std::cout << "sampling from base" << std::endl;
    for (size_t it=0; it<10; ++it) {
      vmf.tau_ = tau*b_;
      mu = vmf.sample();
//      std::cout << "mu " << mu.transpose() << std::endl;
      const T dot = mu.dot(m0_);
      tau = sampleConcentration(dot, 3, tau);
      std::cout <<"@" << it << "tau " << tau << " mu " << mu.transpose() << std::endl;
    }
    return vMF<T>(mu, tau, pRndGen_);
  }

  /// Sample from the posterior
  vMF<T> sampleFromPosterior() {
    return posterior().sample();
//    Eigen::Matrix<T, Eigen::Dynamic, 1> vartheta = b_*m0_ + xSum_;
//    T bN = vartheta.norm() ;
//    Eigen::Matrix<T, Eigen::Dynamic, 1> mN = vartheta.normalized();
//    return sample(a_+count_, bN , mN);
  }

  vMFprior<T> posterior(const Eigen::Matrix<T,3,1>& xSum, const T count) const {
    T aN = a_+count;
    Eigen::Matrix<T,Eigen::Dynamic,1> muN = xSum + b_*m0_;
    T bN = muN.norm();
    muN /= bN;
    return vMFprior<T>(muN, aN, bN, pRndGen_);
  }
  vMFprior<T> posterior() const {
    return posterior(xSum_, count_);
  }
  vMFprior<T> posterior(const Matrix<T,Dynamic,Dynamic>& x, const
      VectorXu& z, uint32_t k) {
    getSufficientStatistics(x, z, k);
    return posterior();
  }

//  T logPdf(const vMF<T>& vmf) const;

  T logMarginal(const Eigen::Matrix<T,3,1>& x) const {
    const T bTilde = (x + b_*m0_).norm();
    const T bOverTan = b_ < 1e-9 ? 2./M_PI : b_/tan(M_PI*0.5*b_);
    const T sinc = bTilde < 1e-9 ? 1. : sin(bTilde*M_PI)/(bTilde*M_PI);
    const T sinus = sin(bTilde*0.5*M_PI);
    return log(bOverTan*0.125) + log(1.-sinc) - 2*log(sinus);
  }
  T logPdfMarginalized() const {
    return logMarginal(xSum_);
  }
//  T logPdfUnderPriorMarginalizedMerged(const NIW<T>& other) const;

  vMF<T> MAP() {
    Eigen::Matrix<T,3,1> xSum=b_*m0_;
    float tau = MLEstimateTau<float,3>(xSum, m0_, a_);
    return vMF<T>(m0_, tau);
  }

  Eigen::Matrix<T,3,1> m0_;
  T b_;
  T a_;
  Matrix<T,3,1> xSum_;
  T count_;

  boost::mt19937 *pRndGen_;
private:
  boost::uniform_01<> unif_;

  T propToConcentrationLogPdf(const T tau, const T dot) const
  {
    if (tau < 1e-16) {
      return 0.;
    } else {
      return a_*(log(tau) + LOG_2 - log(1.-exp(-2.*tau))) + tau*(b_*dot-a_);
    }
  };

  T propToConcentrationLogPdfDeriv(const T tau, const T dot) const
  {
    // this is only for 3D case
    if (tau < 1e-16) {
      return b_*dot;
    } else {
      return a_/tau - (2.*a_*exp(-2.*tau)/(1.-exp(-2.*tau))) + b_*dot -a_;
    }
  };
  T propToConcentrationLogPdfDerivDeriv(const T tau, const T dot) const
  {
    // this is only for 3D case
    if (tau < 1e-16) {
      return -a_/3.;
    } else {
//      return -a_/(tau*tau) + (4.*a_*exp(2.*tau)/(1.-2.*exp(2.*tau)+exp(4.*tau)));
      return -a_/(tau*tau) + (4.*a_*exp(-2.*tau)/(1.-2.*exp(-2.*tau)+exp(-4.*tau)));
    }
  };

  T maximum(const T dot) {
    if (dot*b_ <= 0)
      return 0.;
    T tau = 1.;
    for (size_t it=0; it<100; ++it) {
      T f = propToConcentrationLogPdfDeriv(tau, dot);
      T df = propToConcentrationLogPdfDerivDeriv(tau, dot);
      tau -= f/df;
      if (fabs(f/df) < 1e-6)
        break;
    }
    return tau;
  };

  T intersect(const T c, const T dot, const T tau0) {
    T tau = tau0;
    for (size_t it=0; it<100; ++it) {
      T f = propToConcentrationLogPdf(tau, dot) - c;
      T df = propToConcentrationLogPdfDeriv(tau, dot);
      tau = std::max((T)0., tau-f/df);
//      std::cout << "   __ " <<it << ": " << f << " " << df
//        << "\t f/df " << fabs(f/df)
//        << "\t step to " << tau-f/df << ": "<< tau << std::endl;
      if (fabs(f/df) < 1e-6 || tau == 0.)
        break;
    }
    return tau;
  };

  /// slice sampler for concentration paramter tau
  T sampleConcentration(const T dot, size_t maxIt, T tau0 = 0.3)
  {
    T tauMax = maximum(dot);
    T tau = tau0;
//    std::cout << " ----- max " << tauMax << " " << tau0
//      << " dot " << dot << std::endl;
    T tauL = 0.;
    T tauR = tauMax;
    for(size_t t=0; t<maxIt; ++t)
    {
      const T f = propToConcentrationLogPdf(tau,dot);
      const T u = log(unif_(*pRndGen_)) + f;

      if (tauMax > 0.) {
        tauL = intersect(u, dot, tauMax*0.001);
        tauR = intersect(u, dot, tauMax*1.5);
      } else {
        tauL = 0.;
        tauR = intersect(u, dot, 0.5);
      }
      tau = unif_(*pRndGen_)*(tauR-tauL)+tauL;
//      std::cout << tauL << " - " << tauMax << " - " << tauR
//        << " tau= " << tau
//        << " : u " << u << " f(tau) " << f
//        << " f(tau^star) " << propToConcentrationLogPdf(tauMax,dot)
//        << std::endl;
    }
    return tau;
  };

};

template<typename T>
void vMFprior<T>::resetSufficientStatistics()
{
  xSum_.setZero();
  count_ = 0;
};

template<typename T>
void vMFprior<T>::getSufficientStatistics(const
    Matrix<T,Dynamic,Dynamic> &x, const VectorXu& z, uint32_t k)
{
  this->resetSufficientStatistics();
  // TODO: be carefull here when parallelizing since all are writing to the same
  // location in memory
#pragma omp parallel for
  for (int32_t i=0; i<z.size(); ++i)
  {
    if(z(i) == k)
    {
#pragma omp critical
      {
        xSum_ += x.col(i);
        count_++;
      }
    }
  }
#ifndef NDEBUG
  cout<<" -- updating ss "<<count_<<endl;
  cout<<"xSum="<<xSum_.transpose()<<endl;
  posterior().print();
#endif
//  cout<<"SS: "<<count_<<" "<<xSum_.transpose()<<endl;
};


#ifndef __solver_eHQP_solver_h__
#define __solver_eHQP_solver_h__

#include <Eigen/Dense>
#include <vector>
#include <iostream>
#include "weighted_hqp/HCod.hpp"
#include "weighted_hqp/ehqp_primal.hpp"

namespace hcod{
    class eHQP_solver{
        public:
            eHQP_solver(const std::vector<Eigen::MatrixXd> &A, const std::vector<Eigen::MatrixXd> &b, const std::vector<Eigen::VectorXi> &btype, const std::vector<Eigen::VectorXi> &aset_init, const std::vector<Eigen::VectorXi> &aset_bound);
            eHQP_solver(const std::vector<Eigen::MatrixXd> &A, const std::vector<Eigen::MatrixXd> &b, const std::vector<Eigen::VectorXi> &btype, const std::vector<Eigen::VectorXi> &aset_init, const std::vector<Eigen::VectorXi> &aset_bound, const std::vector<Eigen::MatrixXd> &W);
            ~eHQP_solver(){};

        private:
            void set_problem();

        public:
            Eigen::VectorXd solve();

        private:
            std::vector<Eigen::MatrixXd> A_;
            std::vector<Eigen::MatrixXd> b_;
            std::vector<Eigen::VectorXi> btype_;
            std::vector<Eigen::MatrixXd> W_;
            std::vector<Eigen::VectorXi> aset_init_, aset_bound_;

            HCod* hcod_;
            Ehqp_primal* ehpq_primal_;

            Eigen::VectorXd x_opt_;
            bool _isweighted;
    };
}

#endif


#include <vector>
#include <iostream>
#include <boost/format.hpp>
#include "test_case.hpp"
#include "metro/SampleRange.hpp"
#include "metro/union_ranges.hpp"
#include "metro/regression/Design.hpp"

#define DEBUG 1

BOOST_AUTO_TEST_SUITE( test_regression_design )

AUTO_TEST_CASE( test_construction ) {
	using metro::SampleRange ;
	using metro::regression::Design ;
	typedef Design::Matrix Matrix ;
	typedef Design::Vector Vector ;

	for( int nSamples = 0; nSamples < 100; ++nSamples ) {
		Vector const outcome_nonmissingness = Vector::Constant( nSamples, 1.0 ) ;
		Vector outcome = Vector::Zero( nSamples ) ;
		for( int i = 1; i < nSamples; i += 2 ) {
			outcome(i) = 1 ;
		}

		std::vector< metro::SampleRange > all_samples ;
		all_samples.push_back( metro::SampleRange( 0, nSamples )) ;

		for( int nCovariates = 0; nCovariates < 10; ++nCovariates ) {
			Matrix const covariates = Matrix::Constant( nSamples, nCovariates, 1001.0 ) ;
			Matrix const covariate_nonmissingness = Matrix::Constant( nSamples, nCovariates, 1.0 ) ;
			for( int nPredictors = 0; nPredictors < 10; ++nPredictors ) {
				Matrix predictor_levels = Matrix::Constant( 1, nPredictors, 5.0 ) ;
				Matrix predictor_probabilities = Matrix::Constant( nSamples, 1, 1.0 ) ;

				Design design(
					outcome, all_samples, std::vector< std::string >( 1, "outcome" ),
					covariates, all_samples, std::vector< std::string >( nCovariates, "cov" ),
					std::vector< std::string >( nPredictors, "predictor" )
				) ;

				design.set_predictors( predictor_levels, predictor_probabilities, all_samples ) ;

				// check the outcome
				BOOST_CHECK_EQUAL( design.outcome().rows(), nSamples ) ;
				for( int i = 0; i < nSamples; ++i ) {
					BOOST_CHECK_EQUAL( design.outcome()(i,0), ( i % 2 ) ) ;
				}

				Matrix const& matrix = design.set_predictor_level( 0 ).matrix() ;
				BOOST_CHECK_EQUAL( matrix.rows(), nSamples ) ;
				BOOST_CHECK_EQUAL( matrix.cols(), nCovariates + nPredictors + 1 ) ;

				if( nSamples > 0 ) {
					// check the baseline column
					BOOST_CHECK_EQUAL( matrix.col(0).maxCoeff(), 1.0 ) ;
					BOOST_CHECK_EQUAL( matrix.col(0).minCoeff(), 1.0 ) ;

					// check the predictor columns
					for( int i = 0; i < nPredictors; ++i ) {
						BOOST_CHECK_EQUAL( matrix.col(i+1).maxCoeff(), 5.0 ) ;
						BOOST_CHECK_EQUAL( matrix.col(i+1).minCoeff(), 5.0 ) ;
					}

					// check the covariate columns
					for( int i = 0; i < nCovariates; ++i ) {
						BOOST_CHECK_EQUAL( matrix.col(i+1+nPredictors).maxCoeff(), 1001.0 ) ;
						BOOST_CHECK_EQUAL( matrix.col(i+1+nPredictors).minCoeff(), 1001.0 ) ;
					}
				}
			}
		}
	}
}

AUTO_TEST_CASE( test_predictor_levels ) {
	using metro::SampleRange ;
	using metro::regression::Design ;
	typedef Design::Matrix Matrix ;
	typedef Design::Vector Vector ;

	int nSamples = 10;
	int nCovariates = 0 ;

	typedef std::vector< metro::SampleRange > SampleRanges ;
	SampleRanges const all_samples( 1, metro::SampleRange( 0, nSamples )) ;

	Vector outcome = Vector::Zero( nSamples ) ;
	for( int i = 0; i < nSamples; i += 2 ) {
		outcome(i) = 1 ;
	}

	Matrix const covariates = Matrix::Constant( nSamples, nCovariates, 1001.0 ) ;

	for( int nPredictors = 0; nPredictors < 10; ++nPredictors ) {
		Design design(
			outcome, all_samples, std::vector< std::string >( 1, "outcome" ),
			covariates, all_samples, std::vector< std::string >( nCovariates, "cov" ),
			std::vector< std::string >( nPredictors, "predictor" )
		) ;

		for( int nPredictorLevels = 0; nPredictorLevels < 10; ++nPredictorLevels ) {
			Matrix predictor_levels = Matrix::Zero( nPredictorLevels, nPredictors ) ;
			for( int i = 0; i < nPredictorLevels; ++i ) {
				for( int j = 0; j < nPredictors; ++j ) {
					predictor_levels( i, j ) = double( (i+1)*(j+1) ) ;
				}
			}

			Matrix predictor_probabilities = Matrix::Constant( nSamples, nPredictorLevels, 1 / double( nPredictorLevels ) ) ;
			design.set_predictors( predictor_levels, predictor_probabilities, all_samples ) ;

			for( int iLevel = 0; iLevel < nPredictorLevels; ++iLevel ) {
				Matrix const& matrix = design.set_predictor_level( iLevel ).matrix() ;

				if( nSamples > 0 ) {
					for( int j = 0; j < nPredictors; ++j ) {
							BOOST_CHECK_EQUAL( matrix.col(j+1).maxCoeff(), double((iLevel+1)*(j+1)) ) ;
							BOOST_CHECK_EQUAL( matrix.col(j+1).minCoeff(), double((iLevel+1)*(j+1)) ) ;
					}
				}
			}
		}
	}
}

AUTO_TEST_CASE( test_names ) {
	using metro::SampleRange ;
	using metro::regression::Design ;

	typedef std::vector< metro::SampleRange > SampleRanges ;
	int const nSamples = 5 ;
	SampleRanges const all_samples( 1, metro::SampleRange( 0, nSamples )) ;

	for( int numberOfPredictors = 0; numberOfPredictors < 10; ++numberOfPredictors ) {
		std::vector< std::string > predictor_names( numberOfPredictors ) ;
		for( int i = 0; i < numberOfPredictors; ++i ) {
			predictor_names[i] = ( boost::format( "predictor%d" ) % (i+1) ).str() ;
		}

		for( int numberOfCovariates = 0; numberOfCovariates < 10; ++numberOfCovariates ) {
			Eigen::VectorXd outcome = Eigen::VectorXd::Zero( 5 ) ;
			Eigen::MatrixXd cov = Eigen::MatrixXd::Constant( 5, numberOfCovariates, 2 ) ;

			std::vector< std::string > cov_names( numberOfCovariates ) ;
			for( int i = 0; i < numberOfCovariates; ++i ) {
				cov_names[i] = ( boost::format( "cov%d" ) % (i+1)).str() ;
				cov.col(i).setConstant( i ) ;
			}

			Design design(
				outcome, all_samples, std::vector< std::string >( 1, "outcome" ),
				cov, all_samples, cov_names,
				predictor_names
			) ;
			std::vector< std::string > names = design.design_matrix_column_names() ;
			TEST_ASSERT( names.size() == 1 + numberOfPredictors + numberOfCovariates ) ;
			TEST_ASSERT( names[0] == "baseline" ) ;
			for( int i = 0; i < numberOfPredictors; ++i ) {
				TEST_ASSERT( names[1+i] == predictor_names[i] ) ;
			}
			for( int i = 0; i < numberOfCovariates; ++i ) {
				TEST_ASSERT( names[1+numberOfPredictors+i] == cov_names[i] ) ;
			}

#if DEBUG
			std::cerr << design.get_summary() << "\n" ;
#endif

		}
	}
}

AUTO_TEST_CASE( test_names_interaction ) {
	using metro::SampleRange ;
	using metro::regression::Design ;

	typedef std::vector< metro::SampleRange > SampleRanges ;
	int const nSamples = 5 ;
	SampleRanges const all_samples( 1, metro::SampleRange( 0, nSamples )) ;

	for( int numberOfPredictors = 0; numberOfPredictors < 10; ++numberOfPredictors ) {
		std::vector< std::string > predictor_names( numberOfPredictors ) ;
		for( int i = 0; i < numberOfPredictors; ++i ) {
			predictor_names[i] = ( boost::format( "predictor%d" ) % (i+1) ).str() ;
		}

		for( int numberOfCovariates = 0; numberOfCovariates < 10; ++numberOfCovariates ) {
			Eigen::VectorXd outcome = Eigen::VectorXd::Zero( 5 ) ;
			Eigen::MatrixXd cov = Eigen::MatrixXd::Constant( 5, numberOfCovariates, 2 ) ;

			std::vector< std::string > cov_names( numberOfCovariates ) ;
			for( int i = 0; i < numberOfCovariates; ++i ) {
				cov_names[i] = ( boost::format( "cov%d" ) % (i+1)).str() ;
				cov.col(i).setConstant( i ) ;
			}

			std::vector< int > interactions ;
			for( int interaction = 0; interaction < numberOfCovariates; interaction +=2 ) {
				interactions.push_back( interaction ) ;
			}

			Design design(
				outcome, all_samples, std::vector< std::string >( 1, "outcome" ),
				cov, all_samples, cov_names,
				predictor_names,
				Design::eIdentity,
				interactions
			) ;

#if DEBUG
			std::cerr << design.get_summary() << "\n" ;
#endif

			std::vector< std::string > names = design.design_matrix_column_names() ;
			BOOST_CHECK_EQUAL( names.size(), 1 + numberOfPredictors * ( 1 + interactions.size() ) + numberOfCovariates ) ;
			BOOST_CHECK_EQUAL( names[0], "baseline" ) ;
			for( int i = 0; i < numberOfPredictors; ++i ) {
				BOOST_CHECK_EQUAL( names[1+i], predictor_names[i] ) ;
			}
			for( int j = 0; j < interactions.size(); ++j ) {
				for( int i = 0; i < numberOfPredictors; ++i ) {
					BOOST_CHECK_EQUAL( names[1+((j+1)*numberOfPredictors)+i], predictor_names[i] + "x" + cov_names[interactions[j]] ) ;
				}
			}
			for( int i = 0; i < numberOfCovariates; ++i ) {
				BOOST_CHECK_EQUAL( names[1+(numberOfPredictors*(1+interactions.size()))+i], cov_names[i] ) ;
			}

		}
	}
}

BOOST_AUTO_TEST_SUITE_END()