/*
    Copyright 2005-2015 Intel Corporation.  All Rights Reserved.

    This file is part of Threading Building Blocks. Threading Building Blocks is free software;
    you can redistribute it and/or modify it under the terms of the GNU General Public License
    version 2  as  published  by  the  Free Software Foundation.  Threading Building Blocks is
    distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See  the GNU General Public License for more details.   You should have received a copy of
    the  GNU General Public License along with Threading Building Blocks; if not, write to the
    Free Software Foundation, Inc.,  51 Franklin St,  Fifth Floor,  Boston,  MA 02110-1301 USA

    As a special exception,  you may use this file  as part of a free software library without
    restriction.  Specifically,  if other files instantiate templates  or use macros or inline
    functions from this file, or you compile this file and link it with other files to produce
    an executable,  this file does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however invalidate any other
    reasons why the executable file might be covered by the GNU General Public License.
*/

#if _MSC_VER && !defined(__INTEL_COMPILER)
#pragma warning(disable: 4180) // "qualifier applied to function type has no meaning; ignored"
#endif

#ifndef TBB_PREVIEW_VARIADIC_PARALLEL_INVOKE
    #define TBB_PREVIEW_VARIADIC_PARALLEL_INVOKE __TBB_CPF_BUILD
#endif

#include "tbb/parallel_invoke.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/atomic.h"
#include "tbb/tbb_exception.h"
#include "harness.h"

#if !__INTEL_COMPILER && (_MSC_VER && _MSC_VER <= 1400 || __GNUC__==3 && __GNUC_MINOR__<=3 || __SUNPRO_CC)
    #define __TBB_FUNCTION_BY_CONSTREF_IN_TEMPLATE_BROKEN 1
#endif

tbb::atomic<size_t> function_counter;

// Some macros to make the test easier to read

// 10 functions test0 ... test9 are defined
// pointer to each function is also defined

#define TEST_FUNCTION(value) void test##value () \
{   \
    ASSERT(!(function_counter & (1 << value)), "Test function has already been called"); \
    function_counter += 1 << value; \
}   \
void (*test_pointer##value)(void) = test##value;

TEST_FUNCTION(0)
TEST_FUNCTION(1)
TEST_FUNCTION(2)
TEST_FUNCTION(3)
TEST_FUNCTION(4)
TEST_FUNCTION(5)
TEST_FUNCTION(6)
TEST_FUNCTION(7)
TEST_FUNCTION(8)
TEST_FUNCTION(9)

// The same with functors
#define TEST_FUNCTOR(value) class test_functor##value  \
{   \
public: \
    void operator() () const {  \
        function_counter += 1 << value;   \
    }   \
} functor##value;

TEST_FUNCTOR(0)
TEST_FUNCTOR(1)
TEST_FUNCTOR(2)
TEST_FUNCTOR(3)
TEST_FUNCTOR(4)
TEST_FUNCTOR(5)
TEST_FUNCTOR(6)
TEST_FUNCTOR(7)
TEST_FUNCTOR(8)
TEST_FUNCTOR(9)

#define INIT_TEST function_counter = 0;

#define VALIDATE_INVOKE_RUN(number_of_args, test_type) \
    ASSERT( (size_t)function_counter == (size_t)(1 << number_of_args) - 1, "parallel_invoke called with " #number_of_args " arguments didn't process all " #test_type);

// Calls parallel_invoke for different number of arguments
// It can be called with and without user context
template <typename F0, typename F1, typename F2, typename F3, typename F4, typename F5,
    typename F6, typename F7, typename F8, typename F9>
void call_parallel_invoke( size_t n, F0& f0, F1& f1, F2& f2, F3& f3, F4 &f4, F5 &f5,
                          F6& f6, F7 &f7, F8 &f8, F9 &f9, tbb::task_group_context* context) {
    switch(n) {
    default:
        ASSERT(false, "number of arguments must be between 2 and 10");
    case 2:
        if (context)
            tbb::parallel_invoke (f0, f1, *context);
        else
            tbb::parallel_invoke (f0, f1);
        break;
    case 3:
        if (context)
            tbb::parallel_invoke (f0, f1, f2, *context);
        else
            tbb::parallel_invoke (f0, f1, f2);
        break;
    case 4:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3);
        break;
    case 5:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4);
        break;
    case 6:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5);
        break;
    case 7:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6);
        break;
    case 8:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7);
        break;
    case 9:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7, f8, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7, f8);
        break;
    case 10:
        if(context)
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, *context);
        else
            tbb::parallel_invoke (f0, f1, f2, f3, f4, f5, f6, f7, f8, f9);
        break;
    }
}

#if !__TBB_FUNCTION_BY_CONSTREF_IN_TEMPLATE_BROKEN
template<typename function> void aux_invoke(const function& f) {
    f();
}

bool function_by_constref_in_template_codegen_broken() {
    function_counter = 0;
    aux_invoke(test1);
    return function_counter==0;
}
#endif /* !__TBB_FUNCTION_BY_CONSTREF_IN_TEMPLATE_BROKEN */

void test_parallel_invoke()
{
    REMARK (__FUNCTION__);
    // Testing with pointers to functions
    for (int n = 2; n <=10; n++)
    {
        INIT_TEST;
        call_parallel_invoke(n, test_pointer0, test_pointer1, test_pointer2, test_pointer3, test_pointer4,
            test_pointer5, test_pointer6, test_pointer7, test_pointer8, test_pointer9, NULL);
        VALIDATE_INVOKE_RUN(n, "pointers to function");
    }

    // Testing parallel_invoke with functors
    for (int n = 2; n <=10; n++)
    {
        INIT_TEST;
        call_parallel_invoke(n, functor0, functor1, functor2, functor3, functor4,
            functor5, functor6, functor7, functor8, functor9, NULL);
        VALIDATE_INVOKE_RUN(n, "functors");
    }

#if __TBB_FUNCTION_BY_CONSTREF_IN_TEMPLATE_BROKEN
    // some old compilers can't cope with passing function name into parallel_invoke
#else
    // and some compile but generate broken code that does not call the function
    if (function_by_constref_in_template_codegen_broken())
        return;

    // Testing parallel_invoke with functions
    for (int n = 2; n <=10; n++)
    {
        INIT_TEST;
        call_parallel_invoke(n, test0, test1, test2, test3, test4, test5, test6, test7, test8, test9, NULL);
        VALIDATE_INVOKE_RUN(n, "functions");
    }
#endif
}

// Exception handling support test

#if __TBB_TASK_GROUP_CONTEXT
#define HARNESS_EH_SIMPLE_MODE 1
#include "harness_eh.h"

#if TBB_USE_EXCEPTIONS
volatile size_t exception_mask; // each bit represents whether the function should throw exception or not

// throws exception if corresponding exception_mask bit is set
#define TEST_FUNCTOR_WITH_THROW(value) \
struct throwing_functor##value { \
    void operator() () const {  \
        if (exception_mask & (1 << value))   \
            ThrowTestException();    \
    }   \
} test_with_throw##value;

TEST_FUNCTOR_WITH_THROW(0)
TEST_FUNCTOR_WITH_THROW(1)
TEST_FUNCTOR_WITH_THROW(2)
TEST_FUNCTOR_WITH_THROW(3)
TEST_FUNCTOR_WITH_THROW(4)
TEST_FUNCTOR_WITH_THROW(5)
TEST_FUNCTOR_WITH_THROW(6)
TEST_FUNCTOR_WITH_THROW(7)
TEST_FUNCTOR_WITH_THROW(8)
TEST_FUNCTOR_WITH_THROW(9)

void TestExceptionHandling()
{
    REMARK (__FUNCTION__);
    for( size_t n = 2; n <= 10; ++n ) {
        for( exception_mask = 1; exception_mask < (size_t) (1 << n); ++exception_mask ) {
            ResetEhGlobals();
            TRY();
                REMARK("Calling parallel_invoke, number of functions = %d, exception_mask = %d\n", n, exception_mask);
                call_parallel_invoke(n, test_with_throw0, test_with_throw1, test_with_throw2, test_with_throw3,
                    test_with_throw4, test_with_throw5, test_with_throw6, test_with_throw7, test_with_throw8, test_with_throw9, NULL);
            CATCH_AND_ASSERT();
        }
    }
}
#endif /* TBB_USE_EXCEPTIONS */

// Cancelation support test
void function_to_cancel() {
    ++g_CurExecuted;
    CancellatorTask::WaitUntilReady();
}

// The function is used to test cancellation
void simple_test_nothrow (){
    ++g_CurExecuted;
}

size_t g_numFunctions,
       g_functionToCancel;

class ParInvokeLauncherTask : public tbb::task
{
    tbb::task_group_context &my_ctx;
    void(*func_array[10])(void);

    tbb::task* execute () {
        func_array[g_functionToCancel] = &function_to_cancel;
        call_parallel_invoke(g_numFunctions, func_array[0], func_array[1], func_array[2], func_array[3],
            func_array[4], func_array[5], func_array[6], func_array[7], func_array[8], func_array[9], &my_ctx);
        return NULL;
    }
public:
    ParInvokeLauncherTask ( tbb::task_group_context& ctx ) : my_ctx(ctx) {
        for (int i = 0; i <=9; ++i)
            func_array[i] = &simple_test_nothrow;
    }
};

void TestCancellation ()
{
    REMARK (__FUNCTION__);
    for ( int n = 2; n <= 10; ++n ) {
        for ( int m = 0; m <= n - 1; ++m ) {
            g_numFunctions = n;
            g_functionToCancel = m;
            ResetEhGlobals();
            RunCancellationTest<ParInvokeLauncherTask, CancellatorTask>();
        }
    }
}
#endif /* __TBB_TASK_GROUP_CONTEXT */

//------------------------------------------------------------------------
// Entry point
//------------------------------------------------------------------------

#include "harness_cpu.h"

int TestMain () {
    MinThread = min(MinThread, MaxThread);
    ASSERT (MinThread>=1, "Minimal number of threads must be 1 or more");
    for ( int p = MinThread; p <= MaxThread; ++p ) {
        tbb::task_scheduler_init init(p);
        test_parallel_invoke();
        if (p > 1) {
#if __TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN
            REPORT("Known issue: exception handling tests are skipped.\n");
#elif TBB_USE_EXCEPTIONS
            TestExceptionHandling();
#endif /* TBB_USE_EXCEPTIONS */
#if __TBB_TASK_GROUP_CONTEXT
            TestCancellation();
#endif /* __TBB_TASK_GROUP_CONTEXT */
        }
        TestCPUUserTime(p);
    }
    return Harness::Done;
}
