/*
    Copyright (c) 2005-2021 Intel Corporation

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

#include <atomic>

#include "common/parallel_reduce_common.h"
#include "common/cpu_usertime.h"
#include "common/exception_handling.h"
#include "common/concepts_common.h"

//! \file test_parallel_reduce.cpp
//! \brief Test for [algorithms.parallel_reduce algorithms.parallel_deterministic_reduce] specification

#if _MSC_VER
#pragma warning (push)
// Suppress conditional expression is constant
#pragma warning (disable: 4127)
#endif //#if _MSC_VER

using ValueType = uint64_t;

struct Sum {
    template<typename T>
    T operator() ( const T& v1, const T& v2 ) const {
        return v1 + v2;
    }
};

struct Accumulator {
    ValueType operator() ( const tbb::blocked_range<ValueType*>& r, ValueType value ) const {
        for ( ValueType* pv = r.begin(); pv != r.end(); ++pv )
            value += *pv;
        return value;
    }
};

class ParallelSumTester {
public:
    ParallelSumTester( const ParallelSumTester& ) = default;
    void operator=( const ParallelSumTester& ) = delete;

    ParallelSumTester() : m_range(nullptr, nullptr) {
        m_array = new ValueType[unsigned(count)];
        for ( ValueType i = 0; i < count; ++i )
            m_array[i] = i + 1;
        m_range = tbb::blocked_range<ValueType*>( m_array, m_array + count );
    }
    ~ParallelSumTester() { delete[] m_array; }

    template<typename Partitioner>
    void CheckParallelReduce() {
        Partitioner partitioner;
        ValueType result1 = reduce_invoker<ValueType>( m_range, Accumulator(), Sum(), partitioner );
        REQUIRE_MESSAGE( result1 == expected, "Wrong parallel summation result" );
        ValueType result2 = reduce_invoker<ValueType>( m_range,
            [](const tbb::blocked_range<ValueType*>& r, ValueType value) -> ValueType {
                for ( const ValueType* pv = r.begin(); pv != r.end(); ++pv )
                    value += *pv;
                return value;
            },
            Sum(),
            partitioner
        );
        REQUIRE_MESSAGE( result2 == expected, "Wrong parallel summation result" );
    }
private:
    ValueType* m_array;
    tbb::blocked_range<ValueType*> m_range;
    static const ValueType count, expected;
};

const ValueType ParallelSumTester::count = 1000000;
const ValueType ParallelSumTester::expected = count * (count + 1) / 2;

namespace test_cancellation {

struct ReduceToCancel {
    std::size_t operator()( const tbb::blocked_range<std::size_t>&, std::size_t ) const {
        ++g_CurExecuted;
        Cancellator::WaitUntilReady();
        return 1;
    }
}; // struct ReduceToCancel

struct JoinToCancel {
    std::size_t operator()( std::size_t, std::size_t ) const {
        ++g_CurExecuted;
        Cancellator::WaitUntilReady();
        return 1;
    }
}; // struct Join

struct ReduceFunctorToCancel {
    std::size_t result;

    ReduceFunctorToCancel() : result(0) {}
    ReduceFunctorToCancel( ReduceFunctorToCancel&, tbb::split ) : result(0) {}

    void operator()( const tbb::blocked_range<std::size_t>& br ) {
        result = ReduceToCancel{}(br, result);
    }

    void join( ReduceFunctorToCancel& rhs ) {
        result = JoinToCancel{}(result, rhs.result);
    }
}; // struct ReduceFunctorToCancel

static constexpr std::size_t buffer_test_size = 1024;
static constexpr std::size_t maxParallelReduceRunnerMode = 9;

template <std::size_t Mode>
class ParallelReduceRunner {
    tbb::task_group_context& my_ctx;

    static_assert(Mode >= 0 && Mode <= maxParallelReduceRunnerMode, "Incorrect mode for ParallelReduceTask");

    template <typename... Args>
    void run_parallel_reduce( Args&&... args ) const {
        switch(Mode % 5) {
            case 0 : {
                tbb::parallel_reduce(std::forward<Args>(args)..., my_ctx);
                break;
            }
            case 1 : {
                tbb::parallel_reduce(std::forward<Args>(args)..., tbb::simple_partitioner{}, my_ctx);
                break;
            }
            case 2 : {
                tbb::parallel_reduce(std::forward<Args>(args)..., tbb::auto_partitioner{}, my_ctx);
                break;
            }
            case 3 : {
                tbb::parallel_reduce(std::forward<Args>(args)..., tbb::static_partitioner{}, my_ctx);
                break;
            }
            case 4 : {
                tbb::affinity_partitioner aff;
                tbb::parallel_reduce(std::forward<Args>(args)..., aff, my_ctx);
                break;
            }
        }
    }

public:
    ParallelReduceRunner( tbb::task_group_context& ctx )
        : my_ctx(ctx) {}

    void operator()() const {
        tbb::blocked_range<std::size_t> br(0, buffer_test_size);
        if (Mode < 5) {
            ReduceFunctorToCancel functor;
            run_parallel_reduce(br, functor);
        } else {
            run_parallel_reduce(br, std::size_t(0), ReduceToCancel{}, JoinToCancel{});
        }
    }
}; // class ParallelReduceRunner

static constexpr std::size_t maxParallelDeterministicReduceRunnerMode = 5;

// TODO: unify with ParallelReduceRunner
template <std::size_t Mode>
class ParallelDeterministicReduceRunner {
    tbb::task_group_context& my_ctx;

    static_assert(Mode >= 0 && Mode <= maxParallelDeterministicReduceRunnerMode, "Incorrect Mode for deterministic_reduce task");

    template <typename... Args>
    void run_parallel_deterministic_reduce( Args&&... args ) const {
        switch(Mode % 3) {
            case 0 : {
                tbb::parallel_deterministic_reduce(std::forward<Args>(args)..., my_ctx);
                break;
            }
            case 1 : {
                tbb::parallel_deterministic_reduce(std::forward<Args>(args)..., tbb::simple_partitioner{}, my_ctx);
                break;
            }
            case 2 : {
                tbb::parallel_deterministic_reduce(std::forward<Args>(args)..., tbb::static_partitioner{}, my_ctx);
                break;
            }
        }
    }

public:
    ParallelDeterministicReduceRunner( tbb::task_group_context& ctx )
        : my_ctx(ctx) {}

    void operator()() const {
        tbb::blocked_range<std::size_t> br(0, buffer_test_size);
        if (Mode < 3) {
            ReduceFunctorToCancel functor;
            run_parallel_deterministic_reduce(br, functor);
        } else {
            run_parallel_deterministic_reduce(br, std::size_t(0), ReduceToCancel{}, JoinToCancel{});
        }
    }
}; // class ParallelDeterministicReduceRunner

template <std::size_t Mode>
void run_parallel_reduce_cancellation_test() {
    for ( auto concurrency_level : utils::concurrency_range() ) {
        if (concurrency_level < 2) continue;

        tbb::global_control gc(tbb::global_control::max_allowed_parallelism, concurrency_level);
        ResetEhGlobals();
        RunCancellationTest<ParallelReduceRunner<Mode>, Cancellator>();
    }
}

template <std::size_t Mode>
void run_parallel_deterministic_reduce_cancellation_test() {
    for ( auto concurrency_level : utils::concurrency_range() ) {
        if (concurrency_level < 2) continue;

        tbb::global_control gc(tbb::global_control::max_allowed_parallelism, concurrency_level);
        ResetEhGlobals();
        RunCancellationTest<ParallelDeterministicReduceRunner<Mode>, Cancellator>();
    }
}

template <std::size_t Mode>
struct ParallelReduceTestRunner {
    static void run() {
        run_parallel_reduce_cancellation_test<Mode>();
        ParallelReduceTestRunner<Mode + 1>::run();
    }
}; // struct ParallelReduceTestRunner

template <>
struct ParallelReduceTestRunner<maxParallelReduceRunnerMode> {
    static void run() {
        run_parallel_reduce_cancellation_test<maxParallelReduceRunnerMode>();
    }
}; // struct ParallelReduceTestRunner<maxParallelReduceRunnerMode>

template <std::size_t Mode>
struct ParallelDeterministicReduceTestRunner {
    static void run() {
        run_parallel_deterministic_reduce_cancellation_test<Mode>();
        ParallelDeterministicReduceTestRunner<Mode + 1>::run();
    }
}; // struct ParallelDeterministicReduceTestRunner

template <>
struct ParallelDeterministicReduceTestRunner<maxParallelDeterministicReduceRunnerMode> {
    static void run() {
        run_parallel_deterministic_reduce_cancellation_test<maxParallelDeterministicReduceRunnerMode>();
    }
}; // struct ParallelDeterministicReduceTestRunner<maxParallelDeterministicReduceRunnerMode>

} // namespace test_cancellation

#if __TBB_CPP20_CONCEPTS_PRESENT
template <typename... Args>
concept can_call_parallel_reduce_basic = requires( Args&&... args ) {
    tbb::parallel_reduce(std::forward<Args>(args)...);
};

template <typename... Args>
concept can_call_parallel_deterministic_reduce_basic = requires ( Args&&... args ) {
    tbb::parallel_deterministic_reduce(std::forward<Args>(args)...);
};

template <typename... Args>
concept can_call_preduce_helper = can_call_parallel_reduce_basic<Args...> &&
                                  can_call_parallel_reduce_basic<Args..., tbb::task_group_context&>;

template <typename... Args>
concept can_call_pdet_reduce_helper = can_call_parallel_deterministic_reduce_basic<Args...> &&
                                      can_call_parallel_deterministic_reduce_basic<Args..., tbb::task_group_context&>;

template <typename... Args>
concept can_call_preduce_with_partitioner = can_call_preduce_helper<Args...> &&
                                            can_call_preduce_helper<Args..., const tbb::simple_partitioner&> &&
                                            can_call_preduce_helper<Args..., const tbb::auto_partitioner&> &&
                                            can_call_preduce_helper<Args..., const tbb::static_partitioner&> &&
                                            can_call_preduce_helper<Args..., tbb::affinity_partitioner&>;

template <typename... Args>
concept can_call_pdet_reduce_with_partitioner = can_call_pdet_reduce_helper<Args...> &&
                                                can_call_pdet_reduce_helper<Args..., const tbb::simple_partitioner&> &&
                                                can_call_pdet_reduce_helper<Args..., const tbb::static_partitioner&>;

template <typename Range, typename Body>
concept can_call_imperative_preduce = can_call_preduce_with_partitioner<const Range&, Body&>;

template <typename Range, typename Body>
concept can_call_imperative_pdet_reduce = can_call_pdet_reduce_with_partitioner<const Range&, Body&>;

template <typename Range, typename Value, typename RealBody, typename Reduction>
concept can_call_functional_preduce = can_call_preduce_with_partitioner<const Range&, const Value&,
                                                                        const RealBody&, const Reduction&>;

template <typename Range, typename Value, typename RealBody, typename Reduction>
concept can_call_functional_pdet_reduce = can_call_pdet_reduce_with_partitioner<const Range&, const Value&,
                                                                                const RealBody&, const Reduction&>;

template <typename Range>
using CorrectBody = test_concepts::parallel_reduce_body::Correct<Range>;

template <typename Range>
using CorrectFunc = test_concepts::parallel_reduce_function::Correct<Range>;

using CorrectReduction = test_concepts::parallel_reduce_combine::Correct<int>;
using CorrectRange = test_concepts::range::Correct;

void test_preduce_range_constraints() {
    using namespace test_concepts::range;
    static_assert(can_call_imperative_preduce<Correct, CorrectBody<Correct>>);
    static_assert(!can_call_imperative_preduce<NonCopyable, CorrectBody<NonCopyable>>);
    static_assert(!can_call_imperative_preduce<NonDestructible, CorrectBody<NonDestructible>>);
    static_assert(!can_call_imperative_preduce<NonSplittable, CorrectBody<NonSplittable>>);
    static_assert(!can_call_imperative_preduce<NoEmpty, CorrectBody<NoEmpty>>);
    static_assert(!can_call_imperative_preduce<EmptyNonConst, CorrectBody<EmptyNonConst>>);
    static_assert(!can_call_imperative_preduce<WrongReturnEmpty, CorrectBody<WrongReturnEmpty>>);
    static_assert(!can_call_imperative_preduce<NoIsDivisible, CorrectBody<NoIsDivisible>>);
    static_assert(!can_call_imperative_preduce<IsDivisibleNonConst, CorrectBody<NoIsDivisible>>);
    static_assert(!can_call_imperative_preduce<WrongReturnIsDivisible, CorrectBody<WrongReturnIsDivisible>>);

    static_assert(can_call_functional_preduce<Correct, int, CorrectFunc<Correct>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<NonCopyable, int, CorrectFunc<NonCopyable>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<NonDestructible, int, CorrectFunc<NonDestructible>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<NonSplittable, int, CorrectFunc<NonSplittable>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<NoEmpty, int, CorrectFunc<NoEmpty>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<EmptyNonConst, int, CorrectFunc<EmptyNonConst>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<WrongReturnEmpty, int, CorrectFunc<WrongReturnEmpty>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<NoIsDivisible, int, CorrectFunc<NoIsDivisible>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<IsDivisibleNonConst, int, CorrectFunc<IsDivisibleNonConst>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<WrongReturnIsDivisible, int, CorrectFunc<WrongReturnIsDivisible>, CorrectReduction>);
}

void test_preduce_body_constraints() {
    using namespace test_concepts::parallel_reduce_body;
    static_assert(can_call_imperative_preduce<CorrectRange, Correct<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, NonSplittable<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, NonDestructible<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, NoOperatorRoundBrackets<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, WrongInputOperatorRoundBrackets<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, NoJoin<CorrectRange>>);
    static_assert(!can_call_imperative_preduce<CorrectRange, WrongInputJoin<CorrectRange>>);
}

void test_preduce_func_constraints() {
    using namespace test_concepts::parallel_reduce_function;
    static_assert(can_call_functional_preduce<CorrectRange, int, Correct<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, NoOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, OperatorRoundBracketsNonConst<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, WrongFirstInputOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, WrongSecondInputOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, WrongReturnOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
}

void test_preduce_combine_constraints() {
    using namespace test_concepts::parallel_reduce_combine;
    static_assert(can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, Correct<int>>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, NoOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, OperatorRoundBracketsNonConst<int>>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongFirstInputOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongSecondInputOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_preduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongReturnOperatorRoundBrackets<int>>);
}

void test_pdet_reduce_range_constraints() {
    using namespace test_concepts::range;
    static_assert(can_call_imperative_pdet_reduce<Correct, CorrectBody<Correct>>);
    static_assert(!can_call_imperative_pdet_reduce<NonCopyable, CorrectBody<NonCopyable>>);
    static_assert(!can_call_imperative_pdet_reduce<NonDestructible, CorrectBody<NonDestructible>>);
    static_assert(!can_call_imperative_pdet_reduce<NonSplittable, CorrectBody<NonSplittable>>);
    static_assert(!can_call_imperative_pdet_reduce<NoEmpty, CorrectBody<NoEmpty>>);
    static_assert(!can_call_imperative_pdet_reduce<EmptyNonConst, CorrectBody<EmptyNonConst>>);
    static_assert(!can_call_imperative_pdet_reduce<WrongReturnEmpty, CorrectBody<WrongReturnEmpty>>);
    static_assert(!can_call_imperative_pdet_reduce<NoIsDivisible, CorrectBody<NoIsDivisible>>);
    static_assert(!can_call_imperative_pdet_reduce<IsDivisibleNonConst, CorrectBody<NoIsDivisible>>);
    static_assert(!can_call_imperative_pdet_reduce<WrongReturnIsDivisible, CorrectBody<WrongReturnIsDivisible>>);

    static_assert(can_call_functional_pdet_reduce<Correct, int, CorrectFunc<Correct>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<NonCopyable, int, CorrectFunc<NonCopyable>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<NonDestructible, int, CorrectFunc<NonDestructible>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<NonSplittable, int, CorrectFunc<NonSplittable>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<NoEmpty, int, CorrectFunc<NoEmpty>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<EmptyNonConst, int, CorrectFunc<EmptyNonConst>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<WrongReturnEmpty, int, CorrectFunc<WrongReturnEmpty>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<NoIsDivisible, int, CorrectFunc<NoIsDivisible>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<IsDivisibleNonConst, int, CorrectFunc<IsDivisibleNonConst>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<WrongReturnIsDivisible, int, CorrectFunc<WrongReturnIsDivisible>, CorrectReduction>);
}

void test_pdet_reduce_body_constraints() {
    using namespace test_concepts::parallel_reduce_body;
    static_assert(can_call_imperative_pdet_reduce<CorrectRange, Correct<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, NonSplittable<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, NonDestructible<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, NoOperatorRoundBrackets<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, WrongInputOperatorRoundBrackets<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, NoJoin<CorrectRange>>);
    static_assert(!can_call_imperative_pdet_reduce<CorrectRange, WrongInputJoin<CorrectRange>>);
}

void test_pdet_reduce_func_constraints() {
    using namespace test_concepts::parallel_reduce_function;
    static_assert(can_call_functional_pdet_reduce<CorrectRange, int, Correct<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, NoOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, OperatorRoundBracketsNonConst<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, WrongFirstInputOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, WrongSecondInputOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, WrongReturnOperatorRoundBrackets<CorrectRange>, CorrectReduction>);
}

void test_pdet_reduce_combine_constraints() {
    using namespace test_concepts::parallel_reduce_combine;
    static_assert(can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, Correct<int>>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, NoOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, OperatorRoundBracketsNonConst<int>>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongFirstInputOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongSecondInputOperatorRoundBrackets<int>>);
    static_assert(!can_call_functional_pdet_reduce<CorrectRange, int, CorrectFunc<CorrectRange>, WrongReturnOperatorRoundBrackets<int>>);
}
#endif // __TBB_CPP20_CONCEPTS_PRESENT

//! Test parallel summation correctness
//! \brief \ref stress
TEST_CASE("Test parallel summation correctness") {
    ParallelSumTester pst;
    pst.CheckParallelReduce<utils_default_partitioner>();
    pst.CheckParallelReduce<tbb::simple_partitioner>();
    pst.CheckParallelReduce<tbb::auto_partitioner>();
    pst.CheckParallelReduce<tbb::affinity_partitioner>();
    pst.CheckParallelReduce<tbb::static_partitioner>();
}

static std::atomic<long> ForkCount;
static std::atomic<long> FooBodyCount;

//! Class with public interface that is exactly minimal requirements for Range concept
class MinimalRange {
    size_t begin, end;
    friend class FooBody;
    explicit MinimalRange( size_t i ) : begin(0), end(i) {}
    template <typename Partitioner_> friend void TestSplitting( std::size_t nthread );
public:
    MinimalRange( MinimalRange& r, tbb::split ) : end(r.end) {
        begin = r.end = (r.begin+r.end)/2;
    }
    bool is_divisible() const {return end-begin>=2;}
    bool empty() const {return begin==end;}
};

//! Class with public interface that is exactly minimal requirements for Body of a parallel_reduce
class FooBody {
private:
    FooBody( const FooBody& );          // Deny access
    void operator=( const FooBody& );   // Deny access
    template <typename Partitioner_> friend void TestSplitting( std::size_t nthread );
    //! Parent that created this body via split operation.  NULL if original body.
    FooBody* parent;
    //! Total number of index values processed by body and its children.
    size_t sum;
    //! Number of join operations done so far on this body and its children.
    long join_count;
    //! Range that has been processed so far by this body and its children.
    size_t begin, end;
    //! True if body has not yet been processed at least once by operator().
    bool is_new;
    //! 1 if body was created by split; 0 if original body.
    int forked;
    FooBody() {++FooBodyCount;}
public:
    ~FooBody() {
        forked = 0xDEADBEEF;
        sum=0xDEADBEEF;
        join_count=0xDEADBEEF;
        --FooBodyCount;
    }
    FooBody( FooBody& other, tbb::split ) {
        ++FooBodyCount;
        ++ForkCount;
        sum = 0;
        parent = &other;
        join_count = 0;
        is_new = true;
        forked = 1;
    }

    void init() {
        sum = 0;
        parent = nullptr;
        join_count = 0;
        is_new = true;
        forked = 0;
        begin = ~size_t(0);
        end = ~size_t(0);
    }

    void join( FooBody& s ) {
        REQUIRE( s.forked==1 );
        REQUIRE( this!=&s );
        REQUIRE( this==s.parent );
        REQUIRE( end==s.begin );
        end = s.end;
        sum += s.sum;
        join_count += s.join_count + 1;
        s.forked = 2;
    }
    void operator()( const MinimalRange& r ) {
        for( size_t k=r.begin; k<r.end; ++k )
            ++sum;
        if( is_new ) {
            is_new = false;
            begin = r.begin;
        } else
            REQUIRE( end==r.begin );
        end = r.end;
    }
};

template<typename Partitioner>
void TestSplitting( std::size_t nthread ) {
    ForkCount = 0;
    long join_count = 0;
    Partitioner partitioner;
    for( size_t i=0; i<=1000; ++i ) {
        FooBody f;
        f.init();
        REQUIRE_MESSAGE( FooBodyCount==1, "Wrong initial BodyCount value" );
        reduce_invoker(MinimalRange(i), f, partitioner);

        if (nthread == 1) REQUIRE_MESSAGE(ForkCount==0, "Body was split during 1 thread execution");

        join_count += f.join_count;
        REQUIRE_MESSAGE( FooBodyCount==1, "Some copies of FooBody was not removed after reduction");
        REQUIRE_MESSAGE( f.sum==i, "Incorrect reduction" );
        REQUIRE_MESSAGE( f.begin==(i==0 ? ~size_t(0) : 0), "Incorrect range borders" );
        REQUIRE_MESSAGE( f.end==(i==0 ? ~size_t(0) : i), "Incorrect range borders" );
    }
}

//! Test splitting range and body during reduction, test that all workers sleep when no work
//! \brief \ref resource_usage \ref error_guessing
TEST_CASE("Test splitting range and body during reduction, test that all workers sleep when no work") {
    for ( auto concurrency_level : utils::concurrency_range() ) {
        tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_level);

        TestSplitting<tbb::simple_partitioner>(concurrency_level);
        TestSplitting<tbb::static_partitioner>(concurrency_level);
        TestSplitting<tbb::auto_partitioner>(concurrency_level);
        TestSplitting<tbb::affinity_partitioner>(concurrency_level);
        TestSplitting<utils_default_partitioner>(concurrency_level);

        // Test that all workers sleep when no work
        TestCPUUserTime(concurrency_level);
    }
}

//! Define overloads of parallel_deterministic_reduce that accept "undesired" types of partitioners
namespace unsupported {
    template<typename Range, typename Body>
    void parallel_deterministic_reduce(const Range&, Body&, const tbb::auto_partitioner&) { }
    template<typename Range, typename Body>
    void parallel_deterministic_reduce(const Range&, Body&, tbb::affinity_partitioner&) { }

    template<typename Range, typename Value, typename RealBody, typename Reduction>
    Value parallel_deterministic_reduce(const Range& , const Value& identity, const RealBody& , const Reduction& , const tbb::auto_partitioner&) {
        return identity;
    }
    template<typename Range, typename Value, typename RealBody, typename Reduction>
    Value parallel_deterministic_reduce(const Range& , const Value& identity, const RealBody& , const Reduction& , tbb::affinity_partitioner&) {
        return identity;
    }
}

struct Body {
    float value;
    Body() : value(0) {}
    Body(Body&, tbb::split) { value = 0; }
    void operator()(const tbb::blocked_range<int>&) {}
    void join(Body&) {}
};

//! Check that other types of partitioners are not supported (auto, affinity)
//! In the case of "unsupported" API unexpectedly sneaking into namespace tbb,
//! this test should result in a compilation error due to overload resolution ambiguity
//! \brief \ref negative \ref error_guessing
TEST_CASE("Test Unsupported Partitioners") {
    using namespace tbb;
    using namespace unsupported;
    Body body;
    parallel_deterministic_reduce(blocked_range<int>(0, 10), body, tbb::auto_partitioner());

    tbb::affinity_partitioner ap;
    parallel_deterministic_reduce(blocked_range<int>(0, 10), body, ap);

    parallel_deterministic_reduce(
        blocked_range<int>(0, 10),
        0,
        [](const blocked_range<int>&, int init)->int {
            return init;
        },
        [](int x, int y)->int {
            return x + y;
        },
        tbb::auto_partitioner()
    );
    parallel_deterministic_reduce(
        blocked_range<int>(0, 10),
        0,
        [](const blocked_range<int>&, int init)->int {
            return init;
        },
        [](int x, int y)->int {
            return x + y;
        },
        ap
    );
}

//! Testing tbb::parallel_reduce with tbb::task_group_context
//! \brief \ref interface \ref error_guessing
TEST_CASE("cancellation test for tbb::parallel_reduce") {
    test_cancellation::ParallelReduceTestRunner</*First mode = */0>::run();
}

//! Testing tbb::parallel_deterministic_reduce with tbb::task_group_context
//! \brief \ref interface \ref error_guessing
TEST_CASE("cancellation test for tbb::parallel_deterministic_reduce") {
    test_cancellation::ParallelDeterministicReduceTestRunner</*First mode = */0>::run();
}

#if __TBB_CPP20_CONCEPTS_PRESENT
//! \brief \ref error_guessing
TEST_CASE("parallel_reduce constraints") {
    test_preduce_range_constraints();
    test_preduce_body_constraints();
    test_preduce_func_constraints();
    test_preduce_combine_constraints();
}

//! \brief \ref error_guessing
TEST_CASE("parallel_deterministic_reduce constraints") {
    test_pdet_reduce_range_constraints();
    test_pdet_reduce_body_constraints();
    test_pdet_reduce_func_constraints();
    test_pdet_reduce_combine_constraints();
}
#endif

#if _MSC_VER
#pragma warning (pop)
#endif
