//----------------------------------------------------------------------------
/// @file parallel_stable_sort.hpp
/// @brief This file contains the class parallel_stable_sort
///
/// @author Copyright (c) 2016 Francisco Jose Tapia (fjtapia@gmail.com )\n
///         Distributed under the Boost Software License, Version 1.0.\n
///         ( See accompanying file LICENSE_1_0.txt or copy at
///           http://www.boost.org/LICENSE_1_0.txt  )
/// @version 0.1
///
/// @remarks
//-----------------------------------------------------------------------------
#ifndef __BOOST_SORT_PARALLEL_DETAIL_PARALLEL_STABLE_SORT_HPP
#define __BOOST_SORT_PARALLEL_DETAIL_PARALLEL_STABLE_SORT_HPP

#include <boost/sort/sample_sort/sample_sort.hpp>
#include <boost/sort/common/util/traits.hpp>
#include <functional>
#include <future>
#include <iterator>
#include <memory>
#include <type_traits>
#include <vector>

namespace boost
{
namespace sort
{
namespace stable_detail
{

//---------------------------------------------------------------------------
//                    USING SENTENCES
//---------------------------------------------------------------------------
namespace bsc = boost::sort::common;
namespace bss = boost::sort::spin_detail;
using bsc::range;
using bsc::merge_half;
using boost::sort::sample_detail::sample_sort;
//
///---------------------------------------------------------------------------
/// @struct parallel_stable_sort
/// @brief This a structure for to implement a parallel stable sort, exception
///        safe
//----------------------------------------------------------------------------
template <class Iter_t, class Compare = compare_iter <Iter_t> >
struct parallel_stable_sort
{
    //-------------------------------------------------------------------------
    //                      DEFINITIONS
    //-------------------------------------------------------------------------
    typedef value_iter<Iter_t> value_t;

    //-------------------------------------------------------------------------
    //                     VARIABLES
    //-------------------------------------------------------------------------
    // Number of elements to sort
    size_t nelem;
    // Pointer to the auxiliary memory needed for the algorithm
    value_t *ptr;
    // Minimal number of elements for to be sorted in parallel mode
    const size_t nelem_min = 1 << 16;

    //------------------------------------------------------------------------
    //                F U N C T I O N S
    //------------------------------------------------------------------------
    parallel_stable_sort (Iter_t first, Iter_t last)
    : parallel_stable_sort (first, last, Compare(),
                            std::thread::hardware_concurrency()) { };

    parallel_stable_sort (Iter_t first, Iter_t last, Compare cmp)
    : parallel_stable_sort (first, last, cmp,
                            std::thread::hardware_concurrency()) { };

    parallel_stable_sort (Iter_t first, Iter_t last, uint32_t num_thread)
    : parallel_stable_sort (first, last, Compare(), num_thread) { };

    parallel_stable_sort (Iter_t first, Iter_t last, Compare cmp,
                          uint32_t num_thread);

    //
    //-----------------------------------------------------------------------------
    //  function : destroy_all
    /// @brief The utility is to destroy the temporary buffer used in the
    ///        sorting process
    //-----------------------------------------------------------------------------
    void destroy_all()
    {
        if (ptr != nullptr) std::return_temporary_buffer(ptr);
    };
    //
    //-----------------------------------------------------------------------------
    //  function :~parallel_stable_sort
    /// @brief destructor of the class. The utility is to destroy the temporary
    ///        buffer used in the sorting process
    //-----------------------------------------------------------------------------
    ~parallel_stable_sort() {destroy_all(); } ;
};
// end struct parallel_stable_sort

//
//############################################################################
//                                                                          ##
//                                                                          ##
//            N O N     I N L I N E      F U N C T I O N S                  ##
//                                                                          ##
//                                                                          ##
//############################################################################
//
//-----------------------------------------------------------------------------
//  function : parallel_stable_sort
/// @brief constructor of the class
///
/// @param first : iterator to the first element of the range to sort
/// @param last : iterator after the last element to the range to sort
/// @param comp : object for to compare two elements pointed by Iter_t
///                    iterators
/// @param nthread : Number of threads to use in the process. When this value
///                  is lower than 2, the sorting is done with 1 thread
//-----------------------------------------------------------------------------
template <class Iter_t, class Compare>
parallel_stable_sort <Iter_t, Compare>
::parallel_stable_sort (Iter_t first, Iter_t last, Compare comp,
                        uint32_t nthread) : nelem(0), ptr(nullptr)
{
    range<Iter_t> range_initial(first, last);
    assert(range_initial.valid());

    nelem = range_initial.size();
    size_t nptr = (nelem + 1) >> 1;

    if (nelem < nelem_min or nthread < 2)
    {
        bss::spinsort<Iter_t, Compare>
            (range_initial.first, range_initial.last, comp);
        return;
    };

    //------------------- check if sort --------------------------------------
    bool sw = true;
    for (Iter_t it1 = first, it2 = first + 1;
         it2 != last and (sw = not comp(*it2, *it1)); it1 = it2++);
    if (sw) return;

    //------------------- check if reverse sort ---------------------------
    sw = true;
    for (Iter_t it1 = first, it2 = first + 1;
         it2 != last and (sw = comp(*it2, *it1)); it1 = it2++);
    if (sw)
    {
        size_t nelem2 = nelem >> 1;
        Iter_t it1 = first, it2 = last - 1;
        for (size_t i = 0; i < nelem2; ++i)
            std::swap(*(it1++), *(it2--));
        return;
    };

    ptr = std::get_temporary_buffer<value_t>(nptr).first;
    if (ptr == nullptr) throw std::bad_alloc();

    //---------------------------------------------------------------------
    //     Parallel Process
    //---------------------------------------------------------------------
    range<Iter_t> range_first(range_initial.first, range_initial.first + nptr);

    range<Iter_t> range_second(range_initial.first + nptr, range_initial.last);

    range<value_t *> range_buffer(ptr, ptr + nptr);

    try
    {
        sample_sort<Iter_t, Compare>
            (range_initial.first, range_initial.first + nptr,
             comp, nthread, range_buffer);
    } catch (std::bad_alloc &)
    {
        destroy_all();
        throw std::bad_alloc();
    };

    try
    {
        sample_sort<Iter_t, Compare>
            (range_initial.first + nptr,
             range_initial.last, comp, nthread, range_buffer);
    } catch (std::bad_alloc &)
    {
        destroy_all();
        throw std::bad_alloc();
    };

    range_buffer = move_forward(range_buffer, range_first);
    range_initial = merge_half(range_initial, range_buffer, range_second, comp);
}; // end of constructor

//
//****************************************************************************
};//    End namespace stable_detail
//****************************************************************************
//

//---------------------------------------------------------------------------
//                    USING SENTENCES
//---------------------------------------------------------------------------
namespace bsc = boost::sort::common;
namespace bscu = bsc::util;
namespace bss = boost::sort::spin_detail;
using bsc::range;
using bsc::merge_half;
//
//############################################################################
//                                                                          ##
//                                                                          ##
//            P A R A L L E L _ S T A B L E _ S O R T                       ##
//                                                                          ##
//                                                                          ##
//############################################################################
//
//-----------------------------------------------------------------------------
//  function : parallel_stable_sort
/// @brief : parallel stable sort with 2 parameters
///
/// @param first : iterator to the first element of the range to sort
/// @param last : iterator after the last element to the range to sort
//-----------------------------------------------------------------------------
template<class Iter_t>
void parallel_stable_sort(Iter_t first, Iter_t last)
{
    typedef bscu::compare_iter<Iter_t> Compare;
    stable_detail::parallel_stable_sort<Iter_t, Compare>(first, last);
};
//
//-----------------------------------------------------------------------------
//  function : parallel_stable_sort
/// @brief parallel stable sort with 3 parameters. The third is the number 
///        of threads
///
/// @param first : iterator to the first element of the range to sort
/// @param last : iterator after the last element to the range to sort
/// @param nthread : Number of threads to use in the process. When this value
///                  is lower than 2, the sorting is done with 1 thread
//-----------------------------------------------------------------------------
template<class Iter_t>
void parallel_stable_sort(Iter_t first, Iter_t last, uint32_t nthread)
{
    typedef bscu::compare_iter<Iter_t> Compare;
    stable_detail::parallel_stable_sort<Iter_t, Compare>(first, last, nthread);
};
//
//-----------------------------------------------------------------------------
//  function : parallel_stable_sort
/// @brief : parallel stable sort with 3 parameters. The thisrd is the 
///          comparison object
///
/// @param first : iterator to the first element of the range to sort
/// @param last : iterator after the last element to the range to sort
/// @param comp : object for to compare two elements pointed by Iter_t
///               iterators
//-----------------------------------------------------------------------------
template <class Iter_t, class Compare,
          bscu::enable_if_not_integral<Compare> * = nullptr>
void parallel_stable_sort(Iter_t first, Iter_t last, Compare comp)
{
    stable_detail::parallel_stable_sort<Iter_t, Compare>(first, last, comp);
};

//
//-----------------------------------------------------------------------------
//  function : parallel_stable_sort
/// @brief : parallel stable sort with 3 parameters. 
///
/// @param first : iterator to the first element of the range to sort
/// @param last : iterator after the last element to the range to sort
/// @param comp : object for to compare two elements pointed by Iter_t
///               iterators
/// @param nthread : Number of threads to use in the process. When this value
///                  is lower than 2, the sorting is done with 1 thread
//-----------------------------------------------------------------------------
template<class Iter_t, class Compare>
void parallel_stable_sort (Iter_t first, Iter_t last, Compare comp,
                          uint32_t nthread)
{
    stable_detail::parallel_stable_sort<Iter_t, Compare>
                                                (first, last, comp, nthread);
}
//
//****************************************************************************
};//    End namespace sort
};//    End namespace boost
//****************************************************************************
//
#endif
