/*
    Copyright 2005-2012 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

#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

static const size_t MAX_NUMBER_OF_PINVOKE_ARGS = 10;
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;
}
