/*
    Copyright 2005-2010 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/parallel_sort.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/concurrent_vector.h"
#include "harness.h"
#include <math.h>
#include <exception>

#if !TBB_USE_EXCEPTIONS && _MSC_VER
    // Suppress "C++ exception handler used, but unwind semantics are not enabled" warning in STL headers
    #pragma warning (push)
    #pragma warning (disable: 4530)
#endif

#include <algorithm>
#include <iterator>
#include <functional>
#include <string>
#include <cstring>

#if !TBB_USE_EXCEPTIONS && _MSC_VER
    #pragma warning (pop)
#endif

/** Has tightly controlled interface so that we can verify
    that parallel_sort uses only the required interface. */
class Minimal {
    int val;
public:
    Minimal() {}
    void set_val(int i) { val = i; }
    static bool CompareWith (const Minimal &a, const Minimal &b) { 
        return (a.val < b.val);
    }
    static bool AreEqual( Minimal &a,  Minimal &b) { 
       return a.val == b.val; 
    }
};

//! Defines a comparison function object for Minimal 
class MinimalCompare {
public:
    bool operator() (const Minimal &a, const Minimal &b) const { 
        return Minimal::CompareWith(a,b);
    }
};

//! The default validate; but it uses operator== which is not required
template<typename RandomAccessIterator>
bool Validate(RandomAccessIterator a, RandomAccessIterator b, size_t n) {
    for (size_t i = 0; i < n; i++) {
        ASSERT( a[i] == b[i], NULL );
    }
    return true;
} 

//! A Validate specialized to string for debugging-only 
template<>
bool Validate<std::string *>(std::string * a, std::string * b, size_t n) {
    for (size_t i = 0; i < n; i++) {
        if ( Verbose && a[i] != b[i]) {
          for (size_t j = 0; j < n; j++) {
              REPORT("a[%llu] == %s and b[%llu] == %s\n", static_cast<unsigned long long>(j), a[j].c_str(), static_cast<unsigned long long>(j), b[j].c_str());
          }
        }
        ASSERT( a[i] == b[i], NULL );
    }
    return true;
}

//! A Validate specialized to Minimal since it does not define an operator== 
template<>
bool Validate<Minimal *>(Minimal *a, Minimal *b, size_t n) {
    for (size_t i = 0; i < n; i++) {
        ASSERT( Minimal::AreEqual(a[i],b[i]), NULL );
    }
    return true;
} 

//! A Validate specialized to concurrent_vector<Minimal> since it does not define an operator== 
template<>
bool Validate<tbb::concurrent_vector<Minimal>::iterator>(tbb::concurrent_vector<Minimal>::iterator a, 
                                                         tbb::concurrent_vector<Minimal>::iterator b, size_t n) {
    for (size_t i = 0; i < n; i++) {
        ASSERT( Minimal::AreEqual(a[i],b[i]), NULL );
    }
    return true;
}

//! used in Verbose mode for identifying which data set is being used 
static std::string test_type; 

//! The default initialization routine.
/*! This routine assumes that you can assign to the elements from a float.
    It assumes that iter and sorted_list have already been allocated. It fills
    them according to the current data set (tracked by a local static variable).
    Returns true if a valid test has been setup, or false if there is no test to
    perform.
*/ 
   
template < typename RandomAccessIterator, typename Compare >
bool init_iter(RandomAccessIterator iter, RandomAccessIterator sorted_list, size_t n, const Compare &compare, bool reset) {
    static char test_case = 0; 
    const char num_cases = 3; 

    if (reset) test_case = 0;

    if (test_case < num_cases) {
        // switch on the current test case, filling the iter and sorted_list appropriately
        switch(test_case) {
            case 0:
                /* use sin to generate the values */
                test_type = "sin";
                for (size_t i = 0; i < n; i++) 
                    iter[i] = sorted_list[i] = static_cast<typename std::iterator_traits< RandomAccessIterator >::value_type>(sin(float(i)));
                break;
            case 1:
                /* presorted list */ 
                test_type = "pre-sorted";
                for (size_t i = 0; i < n; i++) 
                    iter[i] = sorted_list[i] = static_cast<typename std::iterator_traits< RandomAccessIterator >::value_type>(i);
                break;
            case 2:
                /* reverse-sorted list */ 
                test_type = "reverse-sorted";
                for (size_t i = 0; i < n; i++) 
                    iter[i] = sorted_list[i] = static_cast<typename std::iterator_traits< RandomAccessIterator >::value_type>(n - i);
                break;
        } 

        // pre-sort sorted_list for later validity testing 
        std::sort(sorted_list, sorted_list + n, compare);
        test_case++;
        return true;
    }
    return false;
}

template < typename T, typename Compare >
bool init_iter(T * iter, T * sorted_list, size_t n, const Compare &compare, bool reset) {
    static char test_case = 0;
    const char num_cases = 3;

    if (reset) test_case = 0;

    if (test_case < num_cases) {
        // switch on the current test case, filling the iter and sorted_list appropriately
        switch(test_case) {
            case 0:
                /* use sin to generate the values */
                test_type = "sin";
                for (size_t i = 0; i < n; i++)
                    iter[i] = sorted_list[i] = T(sin(float(i)));
                break;
            case 1:
                /* presorted list */
                test_type = "pre-sorted";
                for (size_t i = 0; i < n; i++)
                    iter[i] = sorted_list[i] = T(i);
                break;
            case 2:
                /* reverse-sorted list */
                test_type = "reverse-sorted";
                for (size_t i = 0; i < n; i++)
                    iter[i] = sorted_list[i] = T(n - i);
                break;
        }

        // pre-sort sorted_list for later validity testing 
        std::sort(sorted_list, sorted_list + n, compare);
        test_case++;
        return true;
    }
    return false;
}


//! The initialization routine specialized to the class Minimal 
/*! Minimal cannot have floats assigned to it.  This function uses the set_val method
*/

template < >
bool init_iter(Minimal* iter, Minimal * sorted_list, size_t n, const MinimalCompare &compare, bool reset) {
    static char test_case = 0;
    const char num_cases = 3; 

    if (reset) test_case = 0;

    if (test_case < num_cases) {
        switch(test_case) {
            case 0:
                /* use sin to generate the values */
                test_type = "sin";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int( sin( float(i) ) * 1000.f) );
                    sorted_list[i].set_val( int ( sin( float(i) ) * 1000.f) );
                }
                break;
            case 1:
                /* presorted list */ 
                test_type = "pre-sorted";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int(i) ); 
                    sorted_list[i].set_val( int(i) );
                }
                break;
            case 2:
                /* reverse-sorted list */ 
                test_type = "reverse-sorted";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int(n-i) ); 
                    sorted_list[i].set_val( int(n-i) );
                }
                break;
        } 
        std::sort(sorted_list, sorted_list + n, compare);
        test_case++;
        return true;
    }
    return false;
}

//! The initialization routine specialized to the class concurrent_vector<Minimal> 
/*! Minimal cannot have floats assigned to it.  This function uses the set_val method
*/

template < >
bool init_iter(tbb::concurrent_vector<Minimal>::iterator iter, tbb::concurrent_vector<Minimal>::iterator sorted_list, 
               size_t n, const MinimalCompare &compare, bool reset) {
    static char test_case = 0;
    const char num_cases = 3;

    if (reset) test_case = 0;

    if (test_case < num_cases) {
        switch(test_case) {
            case 0:
                /* use sin to generate the values */
                test_type = "sin";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int( sin( float(i) ) * 1000.f) );
                    sorted_list[i].set_val( int ( sin( float(i) ) * 1000.f) );
                }
                break;
            case 1:
                /* presorted list */
                test_type = "pre-sorted";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int(i) );
                    sorted_list[i].set_val( int(i) );
                }
                break;
            case 2:
                /* reverse-sorted list */
                test_type = "reverse-sorted";
                for (size_t i = 0; i < n; i++) {
                    iter[i].set_val( int(n-i) );
                    sorted_list[i].set_val( int(n-i) );
                }
                break;
        }
        std::sort(sorted_list, sorted_list + n, compare);
        test_case++;
        return true;
    }
    return false;
}

//! The initialization routine specialized to the class string 
/*! strings are created from floats. 
*/

template<>
bool init_iter(std::string *iter, std::string *sorted_list, size_t n, const std::less<std::string> &compare, bool reset) {
    static char test_case = 0;
    const char num_cases = 1; 
 
    if (reset) test_case = 0;

    if (test_case < num_cases) {
        switch(test_case) {
            case 0:
                /* use sin to generate the values */
                test_type = "sin";
                for (size_t i = 0; i < n; i++) {
                    char buffer[20];
#if __STDC_SECURE_LIB__>=200411 && !__MINGW64__
                    sprintf_s(buffer, sizeof(buffer), "%f", float(sin(float(i))));
#else
                    sprintf(buffer, "%f", float(sin(float(i))));
#endif /* _MSC_VER>=1400 */
                    sorted_list[i] = iter[i] = std::string(buffer);
                }
                break;
        } 
        std::sort(sorted_list, sorted_list + n, compare);
        test_case++;
        return true;
    }
    return false;
}

//! The current number of threads in use (for Verbose only) 
static size_t current_p;

//! The current data type being sorted (for Verbose only) 
static std::string current_type; 

//! The default test routine.
/*! Tests all data set sizes from 0 to N, all grainsizes from 0 to G=10, and selects from 
    all possible interfaces to parallel_sort depending on whether a scratch space and
    compare have been provided.
*/
template<typename RandomAccessIterator, typename Compare>
bool parallel_sortTest(size_t n, RandomAccessIterator iter, RandomAccessIterator sorted_list, const Compare *comp) {
    bool passed = true;

    Compare local_comp; 

    init_iter(iter, sorted_list, n, local_comp, true);
    do {
        REMARK("%s %s p=%llu n=%llu :",current_type.c_str(), test_type.c_str(), 
                   static_cast<unsigned long long>(current_p), static_cast<unsigned long long>(n));
        if (comp != NULL) {
            tbb::parallel_sort(iter, iter + n, local_comp );
         } else {
            tbb::parallel_sort(iter, iter + n );
         }
        if (!Validate(iter, sorted_list, n)) 
            passed = false;
        REMARK("passed\n");
    } while (init_iter(iter, sorted_list, n, local_comp, false));
    return passed;
}

//! The test routine specialize to Minimal, since it does not have a less defined for it
template<>
bool parallel_sortTest(size_t n, Minimal * iter, Minimal * sorted_list, const MinimalCompare *compare) {
    bool passed = true;

    if (compare == NULL) return passed;

    init_iter(iter, sorted_list, n, *compare, true);
    do {
        REMARK("%s %s p=%llu n=%llu :",current_type.c_str(), test_type.c_str(),
                    static_cast<unsigned long long>(current_p), static_cast<unsigned long long>(n));

        tbb::parallel_sort(iter, iter + n, *compare );

        if (!Validate(iter, sorted_list, n))
            passed = false;
        REMARK("passed\n");
    } while (init_iter(iter, sorted_list, n, *compare, false));
    return passed;
}

//! The test routine specialize to concurrent_vector of Minimal, since it does not have a less defined for it
template<>
bool parallel_sortTest(size_t n, tbb::concurrent_vector<Minimal>::iterator iter, 
                       tbb::concurrent_vector<Minimal>::iterator sorted_list, const MinimalCompare *compare) {
    bool passed = true;

    if (compare == NULL) return passed;
    
    init_iter(iter, sorted_list, n, *compare, true);
    do {
        REMARK("%s %s p=%llu n=%llu :",current_type.c_str(), test_type.c_str(),
                    static_cast<unsigned long long>(current_p), static_cast<unsigned long long>(n));
    
        tbb::parallel_sort(iter, iter + n, *compare );

        if (!Validate(iter, sorted_list, n))
            passed = false;
        REMARK("passed\n");
    } while (init_iter(iter, sorted_list, n, *compare, false));
    return passed;
}

//! The main driver for the tests.
/*! Minimal, float and string types are used.  All interfaces to parallel_sort that are usable
    by each type are tested.
*/
void Flog() {
    // For each type create: 
    // the list to be sorted by parallel_sort (array) 
    // the list to be sort by STL sort (array_2)
    // and a less function object

    const size_t N = 50000;
    
    Minimal *minimal_array = new Minimal[N];
    Minimal *minimal_array_2 = new Minimal[N];
    MinimalCompare minimal_less;

    float *float_array = new float[N];
    float *float_array_2 = new float[N];
    std::less<float> float_less;

    tbb::concurrent_vector<float> float_cv1;
    tbb::concurrent_vector<float> float_cv2;
    float_cv1.grow_to_at_least(N);
    float_cv2.grow_to_at_least(N);

    std::string *string_array = new std::string[N];
    std::string *string_array_2 = new std::string[N];
    std::less<std::string> string_less;

    tbb::concurrent_vector<Minimal> minimal_cv1;
    tbb::concurrent_vector<Minimal> minimal_cv2;
    minimal_cv1.grow_to_at_least(N);
    minimal_cv2.grow_to_at_least(N);
 
 
    // run the appropriate tests for each type

    current_type = "Minimal(less)";
    parallel_sortTest(0, minimal_array, minimal_array_2, &minimal_less);
    parallel_sortTest(1, minimal_array, minimal_array_2, &minimal_less);
    parallel_sortTest(10, minimal_array, minimal_array_2, &minimal_less);
    parallel_sortTest(9999, minimal_array, minimal_array_2, &minimal_less);
    parallel_sortTest(50000, minimal_array, minimal_array_2, &minimal_less);

    current_type = "float (no less)";
    parallel_sortTest(0, float_array, float_array_2, static_cast<std::less<float> *>(NULL)); 
    parallel_sortTest(1, float_array, float_array_2, static_cast<std::less<float> *>(NULL)); 
    parallel_sortTest(10, float_array, float_array_2, static_cast<std::less<float> *>(NULL)); 
    parallel_sortTest(9999, float_array, float_array_2, static_cast<std::less<float> *>(NULL)); 
    parallel_sortTest(50000, float_array, float_array_2, static_cast<std::less<float> *>(NULL)); 

    current_type = "float (less)";
    parallel_sortTest(0, float_array, float_array_2, &float_less); 
    parallel_sortTest(1, float_array, float_array_2, &float_less); 
    parallel_sortTest(10, float_array, float_array_2, &float_less); 
    parallel_sortTest(9999, float_array, float_array_2, &float_less); 
    parallel_sortTest(50000, float_array, float_array_2, &float_less); 

    current_type = "concurrent_vector<float> (no less)";
    parallel_sortTest(0, float_cv1.begin(), float_cv2.begin(), static_cast<std::less<float> *>(NULL));
    parallel_sortTest(1, float_cv1.begin(), float_cv2.begin(), static_cast<std::less<float> *>(NULL));
    parallel_sortTest(10, float_cv1.begin(), float_cv2.begin(), static_cast<std::less<float> *>(NULL));
    parallel_sortTest(9999, float_cv1.begin(), float_cv2.begin(), static_cast<std::less<float> *>(NULL));
    parallel_sortTest(50000, float_cv1.begin(), float_cv2.begin(), static_cast<std::less<float> *>(NULL));

    current_type = "concurrent_vector<float> (less)";
    parallel_sortTest(0, float_cv1.begin(), float_cv2.begin(), &float_less);
    parallel_sortTest(1, float_cv1.begin(), float_cv2.begin(), &float_less);
    parallel_sortTest(10, float_cv1.begin(), float_cv2.begin(), &float_less);
    parallel_sortTest(9999, float_cv1.begin(), float_cv2.begin(), &float_less);
    parallel_sortTest(50000, float_cv1.begin(), float_cv2.begin(), &float_less);

    current_type = "string (no less)";
    parallel_sortTest(0, string_array, string_array_2, static_cast<std::less<std::string> *>(NULL));
    parallel_sortTest(1, string_array, string_array_2, static_cast<std::less<std::string> *>(NULL));
    parallel_sortTest(10, string_array, string_array_2, static_cast<std::less<std::string> *>(NULL));
    parallel_sortTest(9999, string_array, string_array_2, static_cast<std::less<std::string> *>(NULL));
    parallel_sortTest(50000, string_array, string_array_2, static_cast<std::less<std::string> *>(NULL));

    current_type = "string (less)";
    parallel_sortTest(0, string_array, string_array_2, &string_less);
    parallel_sortTest(1, string_array, string_array_2, &string_less);
    parallel_sortTest(10, string_array, string_array_2, &string_less);
    parallel_sortTest(9999, string_array, string_array_2, &string_less);
    parallel_sortTest(50000, string_array, string_array_2, &string_less);

    current_type = "concurrent_vector<Minimal> (less)";
    parallel_sortTest(0, minimal_cv1.begin(), minimal_cv2.begin(), &minimal_less);
    parallel_sortTest(1, minimal_cv1.begin(), minimal_cv2.begin(), &minimal_less);
    parallel_sortTest(10, minimal_cv1.begin(), minimal_cv2.begin(), &minimal_less);
    parallel_sortTest(9999, minimal_cv1.begin(), minimal_cv2.begin(), &minimal_less);
    parallel_sortTest(50000, minimal_cv1.begin(), minimal_cv2.begin(), &minimal_less);

    delete [] minimal_array;
    delete [] minimal_array_2;

    delete [] float_array;
    delete [] float_array_2;

    delete [] string_array;
    delete [] string_array_2;
}

#include <cstdio>
#include "harness_cpu.h"

int TestMain () {
    if( MinThread<1 ) {
        REPORT("Usage: number of threads must be positive\n");
        exit(1);
    }
    for( int p=MinThread; p<=MaxThread; ++p ) {
        if( p>0 ) {
            tbb::task_scheduler_init init( p );
            current_p = p;
            Flog();

            // Test that all workers sleep when no work
            TestCPUUserTime(p);
        }
    } 
    return Harness::Done;
}

