//----------------------------------------------------------------------------
/// @file   scheduler.hpp
/// @brief  This file contains the implementation of the scheduler for
///         dispatch the works stored
///
/// @author Copyright (c) 2010 2015 Francisco José Tapia (fjtapia@gmail.com )\n
///         Distributed under the Boost Software License, Version 1.0.\n
///         ( See accompanyingfile LICENSE_1_0.txt or copy at
///           http://www.boost.org/LICENSE_1_0.txt  )
/// @version 0.1
///
/// @remarks
//-----------------------------------------------------------------------------
#ifndef __BOOST_SORT_COMMON_SCHEDULER_HPP
#define __BOOST_SORT_COMMON_SCHEDULER_HPP

#include <ciso646>
#include <scoped_allocator>
#include <utility>
#include <vector>
#include <deque>
#include <iostream>
#include <unordered_map>
#include <boost/sort/common/spinlock.hpp>
#include <boost/sort/common/util/search.hpp>
#include <boost/sort/common/util/traits.hpp>

namespace boost
{
namespace sort
{
namespace common
{

//
//###########################################################################
//                                                                         ##
//    ################################################################     ##
//    #                                                              #     ##
//    #           C L A S S      S C H E D U L E R                   #     ##
//    #                                                              #     ##
//    ################################################################     ##
//                                                                         ##
//###########################################################################

//
//---------------------------------------------------------------------------
/// @class  scheduler
/// @brief This class is a concurrent stack controled by a spin_lock
/// @remarks
//---------------------------------------------------------------------------
template<typename Func_t, typename Allocator = std::allocator<Func_t> >
struct scheduler
{
    //-----------------------------------------------------------------------
    //                     D E F I N I T I O N S
    //-----------------------------------------------------------------------
    typedef std::scoped_allocator_adaptor <Allocator>   scoped_alloc;
    template <class T>
    using alloc_t = typename std::allocator_traits<Allocator>::
                    template rebind_alloc<T>;


    typedef std::deque <Func_t, scoped_alloc>           deque_t;
    typedef typename deque_t::iterator                  it_deque;
    typedef std::thread::id                             key_t;
    typedef std::hash <key_t>                           hash_t;
    typedef std::equal_to <key_t>                       equal_t;
    typedef std::unique_lock <spinlock_t>               lock_t;
    typedef std::pair<const key_t, deque_t>             pair_t;

    typedef std::unordered_map <key_t, deque_t, hash_t,
                                equal_t, alloc_t <pair_t> >  map_t;
    typedef typename map_t::iterator                    it_map;

    //-----------------------------------------------------------------------
    //                     V A R I A B L E S
    //-----------------------------------------------------------------------
    map_t mp;
    size_t nelem;
    mutable spinlock_t spl;

    //------------------------------------------------------------------------
    //  function : scheduler
    /// @brief  constructor
    //------------------------------------------------------------------------
    scheduler(void) : mp(), nelem(0)  { };
    //
    //-----------------------------------------------------------------------
    //  function : scheduler
    /// @brief  Copy & move constructor
    /// @param [in] VT : stack_cnc from where copy the data
    //-----------------------------------------------------------------------
    scheduler(scheduler && VT) = delete;
    scheduler(const scheduler & VT) = delete;
    //
    //------------------------------------------------------------------------
    //  function : ~scheduler
    /// @brief  Destructor
    //------------------------------------------------------------------------
    virtual ~scheduler(void) {mp.clear();};
    //
    //------------------------------------------------------------------------
    //  function : operator =
    /// @brief Asignation operator
    /// @param [in] VT : stack_cnc from where copy the data
    /// @return Reference to the stack_cnc after the copy
    //------------------------------------------------------------------------
    scheduler & operator=(const scheduler &VT) = delete;
    //
    //------------------------------------------------------------------------
    //  function : size
    /// @brief Asignation operator
    /// @param [in] VT : stack_cnc from where copy the data
    /// @return Reference to the stack_cnc after the copy
    //------------------------------------------------------------------------
    size_t size(void) const
    {
        lock_t s(spl);
        return nelem;
    };
    //
    //------------------------------------------------------------------------
    //  function : clear
    /// @brief Delete all the elements of the stack_cnc.
    //------------------------------------------------------------------------
    void clear_all(void)
    {
        lock_t s(spl);
        mp.clear();
        nelem = 0;
    };

    //
    //------------------------------------------------------------------------
    //  function : insert
    /// @brief Insert one element in the back of the container
    /// @param [in] D : value to insert. Can ve a value, a reference or an
    ///                 rvalue
    /// @return iterator to the element inserted
    /// @remarks This operation is O ( const )
    //------------------------------------------------------------------------
    void insert(Func_t & f)
    {
        lock_t s(spl);
        key_t th_id = std::this_thread::get_id();
        it_map itmp = mp.find(th_id);
        if (itmp == mp.end())
        {
            auto aux = mp.emplace(th_id, deque_t());
            if (aux.second == false) throw std::bad_alloc();
            itmp = aux.first;
        };
        itmp->second.emplace_back(std::move(f));
        nelem++;
    };

    //
    //------------------------------------------------------------------------
    //  function :emplace
    /// @brief Insert one element in the back of the container
    /// @param [in] args :group of arguments for to build the object to insert
    /// @return iterator to the element inserted
    /// @remarks This operation is O ( const )
    //------------------------------------------------------------------------
    template<class ... Args>
    void emplace(Args && ... args)
    {
        lock_t s(spl);
        key_t th_id = std::this_thread::get_id();
        it_map itmp = mp.find(th_id);
        if (itmp == mp.end())
        {
            auto aux = mp.emplace(th_id, deque_t());
            if (aux.second == false) throw std::bad_alloc();
            itmp = aux.first;
        };
        itmp->second.emplace_back(std::forward <Args>(args) ...);
        nelem++;
    };
    //
    //------------------------------------------------------------------------
    //  function : insert
    /// @brief Insert one element in the back of the container
    /// @param [in] D : value to insert. Can ve a value, a reference or an rvalue
    /// @return iterator to the element inserted
    /// @remarks This operation is O ( const )
    //------------------------------------------------------------------------
    template<class it_func>
    void insert_range(it_func first, it_func last)
    {
        //--------------------------------------------------------------------
        //                    Metaprogramming
        //--------------------------------------------------------------------
        typedef util::value_iter<it_func> value2_t;
        static_assert (std::is_same< Func_t, value2_t >::value,
                        "Incompatible iterators\n");

        //--------------------------------------------------------------------
        //                     Code
        //--------------------------------------------------------------------
        assert((last - first) > 0);

        lock_t s(spl);
        key_t th_id = std::this_thread::get_id();
        it_map itmp = mp.find(th_id);
        if (itmp == mp.end())
        {
            auto aux = mp.emplace(th_id, deque_t());
            if (aux.second == true) throw std::bad_alloc();
            itmp = aux.first;
        };
        while (first != last)
        {
            itmp->second.emplace_back(std::move(*(first++)));
            nelem++;
        };
    };
    //
    //------------------------------------------------------------------------
    //  function : extract
    /// @brief erase the last element of the tree and return a copy
    /// @param [out] V : reference to a variable where copy the element
    /// @return code of the operation
    ///         0- Element erased
    ///         1 - Empty tree
    /// @remarks This operation is O(1)
    //------------------------------------------------------------------------
    bool extract(Func_t & f)
    {
        lock_t s(spl);
        if (nelem == 0) return false;
        key_t th_id = std::this_thread::get_id();
        it_map itmp = mp.find(th_id);
        if (itmp != mp.end() and not itmp->second.empty())
        {
            f = std::move(itmp->second.back());
            itmp->second.pop_back();
            --nelem;
            return true;
        };
        for (itmp = mp.begin(); itmp != mp.end(); ++itmp)
        {
            if (itmp->second.empty()) continue;
            f = std::move(itmp->second.back());
            itmp->second.pop_back();
            --nelem;
            return true;
        }
        return false;
    };
};
// end class scheduler
//*************************************************************************
//               P R I N T      F U N C T I O N S
//************************************************************************
template<class ... Args>
std::ostream & operator <<(std::ostream &out, const std::deque<Args ...> & dq)
{
    for (uint32_t i = 0; i < dq.size(); ++i)
        out << dq[i] << " ";
    out << std::endl;
    return out;
}

template<typename Func_t, typename Allocator = std::allocator<Func_t> >
std::ostream & operator <<(std::ostream &out,
                           const scheduler<Func_t, Allocator> &sch)
{
    std::unique_lock < spinlock_t > s(sch.spl);
    out << "Nelem :" << sch.nelem << std::endl;
    for (auto it = sch.mp.begin(); it != sch.mp.end(); ++it)
    {
        out << it->first << "  :" << it->second << std::endl;
    }
    return out;
}

//***************************************************************************
};// end namespace common
};// end namespace sort
};// end namespace boost
//***************************************************************************
#endif