#include "thread_pool.h"
#include <chrono>
#include <algorithm>
using namespace std;
namespace my_threadpool{

    thread_pool::thread_pool(int nThreads)
        :m_n_threads(nThreads)
    {
        for (int i = 0; i< nThreads ;i++)
            m_vec_threads.push_back(shared_ptr<worker>(new worker()));
    }
    thread_pool::~thread_pool()
    {

    }

    //total threads;
    size_t thread_pool::total_threads()
    {
        return m_vec_threads.size();
    }
    //wait until all threads is terminated;
    void thread_pool::join()
    {
        for_each(m_vec_threads.begin(),m_vec_threads.end(),[this](shared_ptr<worker> & item)
        {
            item->terminate();
            item->join();
        });
    }
    //wait until this thread has no tasks pending.
    void thread_pool::wait_for_idle()
    {
        int n_tasks = 0;
        do
        {
            if (n_tasks)
                this_thread::sleep_for(chrono::milliseconds(200));
            n_tasks = 0;
            for_each(m_vec_threads.begin(),m_vec_threads.end(),[this,&n_tasks](shared_ptr<worker> & item)
            {
                n_tasks += item->current_load();
            });
        }while (n_tasks);

    }
    //set the mask to termminiate
    void thread_pool::terminate()
    {
        for_each(m_vec_threads.begin(),m_vec_threads.end(),[this](shared_ptr<worker> & item)
        {
            item->terminate();
        });
    }
    //return the current load of this thread
    size_t thread_pool::current_load(size_t n)
    {
        return (n>=m_vec_threads.size())?0:m_vec_threads[n]->current_load();
    }
    //Append a task to do
    void thread_pool::append(function< bool (void) > func)
    {
        int nIdx = -1;
        unsigned int nMinLoad = -1;
        for (unsigned int i=0;i<m_n_threads;i++)
        {
            if (nMinLoad> m_vec_threads[i]->current_load())
            {
                nMinLoad = m_vec_threads[i]->current_load();
                nIdx = i;
            }
        }
        m_vec_threads[nIdx]->append(func);
    }


    thread_pool::worker::worker()
        :m_b_is_finish(false)
        ,m_cond_locker(m_cond_mutex)
    {
        m_pthread = shared_ptr<thread>(new thread (bind(&thread_pool::worker::run,this)));
    }

    thread_pool::worker::~worker()
    {
        m_list_tasks.clear();
    }

    //wait until this thread is terminated;
    void thread_pool::worker::join()
    {
        terminate();
        m_pthread->join();
    }

    //wait until this thread has no tasks pending.
    void thread_pool::worker::wait_for_idle()
    {
        while(current_load())
            this_thread::sleep_for(chrono::milliseconds(200));
    }

    //set the mask to termminiate
    void thread_pool::worker::terminate()
    {
        m_b_is_finish = true;
        m_cond_incoming_task.notify_one();
    }

    //return the current load of this thread
    size_t thread_pool::worker::current_load()
    {
        size_t sz = 0;
        m_list_tasks_mutex.lock();
        sz = m_list_tasks.size();
        m_list_tasks_mutex.unlock();
        return sz;
    }

    //Append a task to do
    size_t thread_pool::worker::append(function< bool (void) > func)
    {
        size_t sz = 0;
        m_list_tasks_mutex.lock();
        m_list_tasks.push_back(func);
        sz = m_list_tasks.size();
        //if there were no tasks before, we should notidy the thread to do next job.
        if (sz==1)
            m_cond_incoming_task.notify_one();
        m_list_tasks_mutex.unlock();
        return sz;
    }

    void thread_pool::worker::run()
    {
        // loop wait
        while (!m_b_is_finish)
        {
            function< bool (void)> curr_task ;
            bool bHasTasks = false;
            m_list_tasks_mutex.lock();
            if (m_list_tasks.empty()==false)
            {
                bHasTasks = true;
                curr_task = *m_list_tasks.begin();
            }
            m_list_tasks_mutex.unlock();
            //doing task
            if (bHasTasks)
            {
                bool finished = curr_task();
                m_list_tasks_mutex.lock();
                m_list_tasks.pop_front();
                if (finished==false)
                    m_list_tasks.push_back(curr_task);
                m_list_tasks_mutex.unlock();
            }
            if (!current_load())
            {
                m_cond_incoming_task.wait_for(m_cond_locker,
                                              chrono::milliseconds(200)
                                              );
            }
        }
    }

}
