/*
    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.
*/

#include "tbb/atomic.h"
#if __TBB_CPP11_RVALUE_REF_PRESENT
#include <utility> // std::move
#endif

#define HARNESS_NO_PARSE_COMMAND_LINE 1
#include "harness_report.h"
#include "harness_assert.h"

static const int THRDS = 3;
static const int THRDS_DETACH = 2;
static tbb::atomic<int> sum;
static tbb::atomic<int> BaseCount;
static THREAD::id real_ids[THRDS+THRDS_DETACH];

class Base {
    mutable int copy_throws;
    friend void RunTests();
    friend void CheckExceptionSafety();
    void operator=( const Base& );   // Deny access
protected:
    Base() : copy_throws(100) {++BaseCount;}
    Base( const Base& c ) : copy_throws(c.copy_throws) {
        if( --copy_throws<=0 ) 
            __TBB_THROW(0);
        ++BaseCount; 
    }
    ~Base() {--BaseCount;}
};

template<int N>
class Data: Base {
    Data();                          // Deny access
    explicit Data(int v) : value(v) {}

    friend void RunTests();
    friend void CheckExceptionSafety();
public:
    int value;
};

#include "harness_barrier.h"

class ThreadFunc: Base {
    ThreadFunc() {}

    static Harness::SpinBarrier init_barrier;

    friend void RunTests();
public:
    void operator()(){
        real_ids[0] = THIS_THREAD::get_id();
        init_barrier.wait();
        
        sum.fetch_and_add(1);
    }
    void operator()(int num){
        real_ids[num] = THIS_THREAD::get_id();
        init_barrier.wait();

        sum.fetch_and_add(num);
    }
    void operator()(int num, Data<0> dx) { 
        real_ids[num] = THIS_THREAD::get_id();

        const double WAIT = .1;
#if _WIN32 || _WIN64
        const double LONG_TOLERANCE = 0.120;  // maximal scheduling quantum for Windows Server
#else
        const double LONG_TOLERANCE = 0.200;  // reasonable upper bound
#endif
        tbb::tick_count::interval_t test_interval(WAIT);
        tbb::tick_count t0 = tbb::tick_count::now();
        THIS_THREAD_SLEEP ( test_interval );
        tbb::tick_count t1 = tbb::tick_count::now();
        double delta = ((t1-t0)-test_interval).seconds();
        if(delta < 0.0)
            REPORT("ERROR: Sleep interval too short (%g < %g)\n",
                (t1-t0).seconds(), test_interval.seconds() );
        if(delta > LONG_TOLERANCE)
            REPORT("Warning: Sleep interval too long (%g outside long tolerance(%g))\n",
                (t1-t0).seconds(), test_interval.seconds() + LONG_TOLERANCE);
        init_barrier.wait();

        sum.fetch_and_add(num);
        sum.fetch_and_add(dx.value);
    }
    void operator()(Data<0> d) {
        THIS_THREAD_SLEEP ( tbb::tick_count::interval_t(d.value*1.) );
    }
};

Harness::SpinBarrier ThreadFunc::init_barrier(THRDS);

void CheckRelations( const THREAD::id ids[], int n, bool duplicates_allowed ) {
    for( int i=0; i<n; ++i ) {
        const THREAD::id x = ids[i];
        for( int j=0; j<n; ++j ) {
            const THREAD::id y = ids[j];
            ASSERT( (x==y)==!(x!=y), NULL );
            ASSERT( (x<y)==!(x>=y), NULL );
            ASSERT( (x>y)==!(x<=y), NULL );
            ASSERT( (x<y)+(x==y)+(x>y)==1, NULL );
            ASSERT( x!=y || i==j || duplicates_allowed, NULL );
            for( int k=0; k<n; ++k ) {
                const THREAD::id z = ids[j];
                ASSERT( !(x<y && y<z) || x<z, "< is not transitive" );
            }    
        }
    }
}

class AnotherThreadFunc: Base {
public:
    void operator()() {}
    void operator()(const Data<1>&) {}
    void operator()(const Data<1>&, const Data<2>&) {}
    friend void CheckExceptionSafety();
};

#if TBB_USE_EXCEPTIONS
void CheckExceptionSafety() { 
    int original_count = BaseCount;
    // d loops over number of copies before throw occurs 
    for( int d=1; d<=3; ++d ) {
        // Try all combinations of throw/nothrow for f, x, and y's copy constructor.
        for( int i=0; i<8; ++i ) {
            { 
                const AnotherThreadFunc f = AnotherThreadFunc();
                if( i&1 ) f.copy_throws = d;
                const Data<1> x(0);
                if( i&2 ) x.copy_throws = d;
                const Data<2> y(0);
                if( i&4 ) y.copy_throws = d;
                bool exception_caught = false;
                for( int j=0; j<3; ++j ) {
                    try { 
                        switch(j) {
                            case 0: {THREAD t(f); t.join();} break;
                            case 1: {THREAD t(f,x); t.join();} break;
                            case 2: {THREAD t(f,x,y); t.join();} break;
                        }
                    } catch(...) {
                        exception_caught = true;
                    } 
                    ASSERT( !exception_caught||(i&((1<<(j+1))-1))!=0, NULL );
                }
            }
            ASSERT( BaseCount==original_count, "object leak detected" );
        }
    }
}
#endif /* TBB_USE_EXCEPTIONS */

#include <cstdio>

#if __TBB_CPP11_RVALUE_REF_PRESENT

tbb::tbb_thread returnThread() {
    return tbb::tbb_thread();
}
#endif

void RunTests() {

    ThreadFunc t;
    Data<0> d100(100), d1(1), d0(0);
    const THREAD::id id_zero;
    THREAD::id id0, uniq_ids[THRDS];
    
    THREAD thrs[THRDS];
    THREAD thr;
    THREAD thr0(t);
    THREAD thr1(t, 2);
    THREAD thr2(t, 1, d100);
    
    ASSERT( thr0.get_id() != id_zero, NULL );
    id0 = thr0.get_id();
    tbb::move(thrs[0], thr0);
    ASSERT( thr0.get_id() == id_zero, NULL );
    ASSERT( thrs[0].get_id() == id0, NULL );

    THREAD::native_handle_type h1 = thr1.native_handle();
    THREAD::native_handle_type h2 = thr2.native_handle();
    THREAD::id id1 = thr1.get_id();
    THREAD::id id2 = thr2.get_id();
    tbb::swap(thr1, thr2);
    ASSERT( thr1.native_handle() == h2, NULL );
    ASSERT( thr2.native_handle() == h1, NULL );
    ASSERT( thr1.get_id() == id2, NULL );
    ASSERT( thr2.get_id() == id1, NULL );
#if __TBB_CPP11_RVALUE_REF_PRESENT
    {
        THREAD tmp_thr(std::move(thr1));
        ASSERT( tmp_thr.native_handle() == h2 && tmp_thr.get_id() == id2, NULL );
        thr1 = std::move(tmp_thr);
        ASSERT( thr1.native_handle() == h2 && thr1.get_id() == id2, NULL );
    }
#endif

    thr1.swap(thr2);
    ASSERT( thr1.native_handle() == h1, NULL );
    ASSERT( thr2.native_handle() == h2, NULL );
    ASSERT( thr1.get_id() == id1, NULL );
    ASSERT( thr2.get_id() == id2, NULL );
    thr1.swap(thr2);

    tbb::move(thrs[1], thr1);
    ASSERT( thr1.get_id() == id_zero, NULL );

#if __TBB_CPP11_RVALUE_REF_PRESENT
    thrs[2] = returnThread();
    ASSERT( thrs[2].get_id() == id_zero, NULL );
#endif
    tbb::move(thrs[2], thr2);
    ASSERT( thr2.get_id() == id_zero, NULL );

    for (int i=0; i<THRDS; i++)
        uniq_ids[i] = thrs[i].get_id();

    ASSERT( thrs[2].joinable(), NULL );

    for (int i=0; i<THRDS; i++)
        thrs[i].join();

#if !__TBB_WIN8UI_SUPPORT
    //  TODO: to find out the way to find thread_id without GetThreadId and other
    //  desktop functions.
    //  Now tbb_thread does have its own thread_id that stores std::thread object
    //  Test will fail in case it is run in desktop mode against New Windows*8 UI library
    for (int i=0; i<THRDS; i++)
        ASSERT(  real_ids[i] == uniq_ids[i], NULL );
#endif

    int current_sum = sum;
    ASSERT( current_sum == 104, NULL );
    ASSERT( ! thrs[2].joinable(), NULL );
    ASSERT( BaseCount==4, "object leak detected" );

#if TBB_USE_EXCEPTIONS
    CheckExceptionSafety(); 
#endif

    // Note: all tests involving BaseCount should be put before the tests
    // involing detached threads, because there is no way of knowing when 
    // a detached thread destroys its arguments.

    THREAD thr_detach_0(t, d0);
    real_ids[THRDS] = thr_detach_0.get_id();
    thr_detach_0.detach();
    ASSERT( thr_detach_0.get_id() == id_zero, NULL );

    THREAD thr_detach_1(t, d1);
    real_ids[THRDS+1] = thr_detach_1.get_id();
    thr_detach_1.detach();
    ASSERT( thr_detach_1.get_id() == id_zero, NULL );

    CheckRelations(real_ids, THRDS+THRDS_DETACH, true);

    CheckRelations(uniq_ids, THRDS, false);

    for (int i=0; i<2; i++) {
        AnotherThreadFunc empty_func;
        THREAD thr_to(empty_func), thr_from(empty_func);
        THREAD::id from_id = thr_from.get_id();
        if (i) thr_to.join();
#if __TBB_CPP11_RVALUE_REF_PRESENT
        thr_to = std::move(thr_from);
#else
        thr_to = thr_from;
#endif
        ASSERT( thr_from.get_id() == THREAD::id(), NULL );
        ASSERT( thr_to.get_id() == from_id, NULL );
    }

    ASSERT( THREAD::hardware_concurrency() > 0, NULL);
}

typedef bool (*id_relation)( THREAD::id, THREAD::id );

id_relation CheckSignatures() {
    id_relation r[6] = {&tbb::operator==,
                        &tbb::operator!=,
                        &tbb::operator<,
                        &tbb::operator>,
                        &tbb::operator<=,
                        &tbb::operator>=};
    return r[1];
}
