/*
    Copyright 2005-2013 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/tbb_thread.h"
#include "tbb/atomic.h"

#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;
        const double SHORT_TOLERANCE = 1e-8;
#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 t0 = tbb::tick_count::now();
        tbb::this_tbb_thread::sleep( tbb::tick_count::interval_t(WAIT) );
        tbb::tick_count t1 = tbb::tick_count::now();
        double delta = (t1-t0).seconds() - WAIT;
        if(delta+SHORT_TOLERANCE <= 0.0)
            REPORT("ERROR: Sleep interval too short (%g outside short tolerance(%g))\n", (t1-t0).seconds(), WAIT - SHORT_TOLERANCE);
        if(delta > LONG_TOLERANCE)
            REPORT("Warning: Sleep interval too long (%g outside long tolerance(%g))\n", (t1-t0).seconds(), WAIT + LONG_TOLERANCE);

        init_barrier.wait();

        sum.fetch_and_add(num);
        sum.fetch_and_add(dx.value);
    }
    void operator()(Data<0> d) {
        tbb::this_tbb_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 );
                }
            }
// Intel Compiler sometimes fails to destroy all implicitly generated copies 
// of an object when a copy constructor throws an exception.
// Problem was reported as Quad issue 482935.
// This #if should be removed or tightened when the bug is fixed.
#if !((_WIN32 || _WIN64) && defined(__INTEL_COMPILER))
            ASSERT( BaseCount==original_count, "object leak detected" );
#endif
        }
    }
}
#endif /* TBB_USE_EXCEPTIONS */

#include <cstdio>

void RunTests() {

    ThreadFunc t;
    Data<0> d100(100), d1(1), d0(0);
    THREAD::id id;
    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, NULL );
    id0 = thr0.get_id();
    tbb::move(thrs[0], thr0);
    ASSERT( thr0.get_id() == id, 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 );
    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, NULL );

    tbb::move(thrs[2], thr2);
    ASSERT( thr2.get_id() == id, 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, 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, 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(); 
        thr_to = thr_from;
        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];
}
