// File: lzham_task_pool_win32.cpp
// See Copyright Notice and license at the end of include/lzham.h
#include "lzham_core.h"
#include "lzham_win32_threading.h"
#include "lzham_timer.h"
#include "lzham_lzcomp_internal.h"
#include <process.h>

#if LZHAM_USE_WIN32_API

namespace lzham
{
   task_pool::task_pool(lzham_malloc_context malloc_context) :
      m_malloc_context(malloc_context),
      m_task_stack(malloc_context),
      m_num_threads(0),
      m_tasks_available(0, 32767),
      m_num_outstanding_tasks(0),
      m_exit_flag(false)
   {
      utils::zero_object(m_threads);
   }

   task_pool::task_pool(lzham_malloc_context malloc_context, uint num_threads) :
      m_malloc_context(malloc_context),
      m_task_stack(malloc_context),
      m_num_threads(0),
      m_tasks_available(0, 32767),
      m_num_outstanding_tasks(0),
      m_exit_flag(false)
   {
      utils::zero_object(m_threads);

      bool status = init(num_threads);
      LZHAM_VERIFY(status);
   }

   task_pool::~task_pool()
   {
      deinit();
   }

   bool task_pool::init(uint num_threads)
   {
      LZHAM_ASSERT(num_threads <= cMaxThreads);
      num_threads = math::minimum<uint>(num_threads, cMaxThreads);

      deinit();

      bool succeeded = true;

      m_num_threads = 0;
      while (m_num_threads < num_threads)
      {
         m_threads[m_num_threads] = (HANDLE)_beginthreadex(NULL, 32768, thread_func, this, 0, NULL);
         LZHAM_ASSERT(m_threads[m_num_threads] != 0);

         if (!m_threads[m_num_threads])
         {
            succeeded = false;
            LZHAM_LOG_ERROR(10000);
            break;
         }

         m_num_threads++;
      }

      if (!succeeded)
      {
         deinit();
         return false;
      }

      return true;
   }

   void task_pool::deinit()
   {
      if (m_num_threads)
      {
         join();

         atomic_exchange32(&m_exit_flag, true);

         m_tasks_available.release(m_num_threads);

         for (uint i = 0; i < m_num_threads; i++)
         {
            if (m_threads[i])
            {
               for ( ; ; )
               {
                  DWORD result = WaitForSingleObject(m_threads[i], 30000);
                  if ((result == WAIT_OBJECT_0) || (result == WAIT_ABANDONED))
                     break;
               }

               CloseHandle(m_threads[i]);
               m_threads[i] = NULL;
            }
         }

         m_num_threads = 0;

         atomic_exchange32(&m_exit_flag, false);
      }

      m_task_stack.clear();
      m_num_outstanding_tasks = 0;
   }

   bool task_pool::queue_task(task_callback_func pFunc, uint64 data, void* pData_ptr)
   {
      LZHAM_ASSERT(m_num_threads);
      LZHAM_ASSERT(pFunc);

      task tsk;
      tsk.m_callback = pFunc;
      tsk.m_data = data;
      tsk.m_pData_ptr = pData_ptr;
      tsk.m_flags = 0;

      if (!m_task_stack.try_push(tsk))
      {
         LZHAM_LOG_ERROR(10001);
         return false;
      }

      atomic_increment32(&m_num_outstanding_tasks);

      m_tasks_available.release(1);

      return true;
   }

   // It's the object's responsibility to delete pObj within the execute_task() method, if needed!
   bool task_pool::queue_task(executable_task* pObj, uint64 data, void* pData_ptr)
   {
      LZHAM_ASSERT(m_num_threads);
      LZHAM_ASSERT(pObj);

      task tsk;
      tsk.m_pObj = pObj;
      tsk.m_data = data;
      tsk.m_pData_ptr = pData_ptr;
      tsk.m_flags = cTaskFlagObject;

      if (!m_task_stack.try_push(tsk))
      {
         LZHAM_LOG_ERROR(10002);
         return false;
      }

      atomic_increment32(&m_num_outstanding_tasks);

      m_tasks_available.release(1);

      return true;
   }

   void task_pool::process_task(task& tsk)
   {
      if (tsk.m_flags & cTaskFlagObject)
         tsk.m_pObj->execute_task(tsk.m_data, tsk.m_pData_ptr);
      else
         tsk.m_callback(tsk.m_data, tsk.m_pData_ptr);

      atomic_decrement32(&m_num_outstanding_tasks);
   }

   void task_pool::join()
   {
      while (atomic_add32(&m_num_outstanding_tasks, 0) > 0)
      {
         task tsk;
         if (m_task_stack.pop(tsk))
         {
            process_task(tsk);
         }
         else
         {
            lzham_sleep(1);
         }
      }
   }

   unsigned __stdcall task_pool::thread_func(void* pContext)
   {
      task_pool* pPool = static_cast<task_pool*>(pContext);

      for ( ; ; )
      {
         if (!pPool->m_tasks_available.wait())
            break;

         if (pPool->m_exit_flag)
            break;

         task tsk;
         if (pPool->m_task_stack.pop(tsk))
         {
            pPool->process_task(tsk);
         }
      }

      _endthreadex(0);
      return 0;
   }

   static uint g_num_processors;

   uint lzham_get_max_helper_threads()
   {
      if (!g_num_processors)
      {
         SYSTEM_INFO system_info;
         GetSystemInfo(&system_info);
         g_num_processors = system_info.dwNumberOfProcessors;
      }

      if (g_num_processors > 1)
      {
         // use all CPU's
         return LZHAM_MIN((uint)task_pool::cMaxThreads, g_num_processors - 1);
      }

      return 0;
   }

} // namespace lzham

#endif // LZHAM_USE_WIN32_API
