#pragma once
#include <string>
#include <boost/shared_ptr.hpp>
#include "ConcurrentQueue.h"
#include "Task.h"
#include "../../Utils/CriticalSection.h"

#ifdef NON_FINAL_VERSION
#include "ITaskListener.h"
#endif

namespace MP
{
  class MainThreadTaskHandler;
  class TaskScope;
#ifdef NON_FINAL_VERSION
  class SchedulerTaskScope;
#endif
 /**
  * @brief A scheduler that assigns tasks to a pool of worker threads.
  */
  class MPScheduler : boost::noncopyable
  {
    friend class TaskScope;
    friend class ThreadInfo;

    typedef ConcurrentQueue<Task *> TaskQueue;

#ifdef NON_FINAL_VERSION
    friend class SchedulerTaskScope;
    typedef boost::shared_ptr<ITaskListener> ListenerPtr;
    std::set<ListenerPtr> m_listeners;

    void onStartEvent(const TaskEvent &ev);

    void onEndEvent(const TaskEvent &ev);

    void addListener(ITaskListener * list)
    {
      m_listeners.insert(ListenerPtr(list));
    }
#endif
#ifdef USE_MULTITHREADED_SCHEDULER
    MainThreadTaskHandler* const m_mainThreadTasks;
    boost::shared_ptr<volatile bool> m_isClosed;
    ::Scheduler* const m_scheduler;  
    boost::thread_group m_workerThreads;
    mutable CriticalSection m_mutex;
    size_t m_nbActiveThreads;

    void runWorker(boost::shared_ptr<volatile bool> isClosed);

    void scheduleOnMainThread(TaskPtr task);

    template<class ReturnValue, class Callback>
    class CallbackWrapper
    {
    public:
      CallbackWrapper(MPScheduler* scheduler, Callback callback) : m_scheduler(scheduler), m_callback(callback) {}

      void operator()(const ReturnValue &value) const
      {
        TaskPtr task(new CallbackTask<ReturnValue, Callback>(m_callback, value));
        m_scheduler->scheduleOnMainThread(task);
      }
    private:
      MPScheduler* const m_scheduler;
      Callback m_callback;
      CallbackWrapper& operator=(const CallbackWrapper&); 
    };
#endif
    TaskQueue m_workerTasks;
    void schedule(Task * task);
public:
    MPScheduler(::Scheduler * scheduler);
    ~MPScheduler();

#ifdef USE_MULTITHREADED_SCHEDULER
    bool hasWorkerThreadsRunning() const
    {
      return m_nbActiveThreads > 0;
    }
#endif

    /**
	   * @brief Asynchronously execute a task on a worker thread with the specified name and priority, and invoke a callback function with the result when done.
	   * @param operation The task to execute.
     * @param callback The operation's result will be passed to this function.
     * @param priority The task's priority. Lower values have higher priority.
     * @param name A description for the task. Used for debbuging.
	   */
    template<class ReturnValue, class Operation, class Callback>
    void schedule(Operation operation, Callback callback, const std::string &name = std::string(), unsigned int priority = 0)
    {
#ifdef USE_MULTITHREADED_SCHEDULER
      typedef CallbackWrapper<ReturnValue, Callback> CallbackWrapper;
      Task * task = new TaskImpl<ReturnValue, Operation, CallbackWrapper>(operation, CallbackWrapper(this, callback), priority, name);
#else
      Task * task = new TaskImpl<ReturnValue, Operation, Callback>(operation, callback, priority, name);
#endif
      schedule(task);
    }

    /**
	   * @brief Executes a task on the main thread, blocking until it is complete. Immediately executes the task if invoked on the main thread.
	   * @param operation The task to execute.
	   */
    template<class ReturnValue, class Operation>
    ReturnValue runOnMainThread(Operation operation)
    {
#ifdef USE_MULTITHREADED_SCHEDULER
      if(isOnMainThread())
        return operation();

      TaskImpl<ReturnValue, Operation> * task = new TaskImpl<ReturnValue, Operation>(operation);
      TaskPtr guard(task);
      scheduleOnMainThread(guard);
      return task->waitForCompletion();
#else
      return operation();
#endif
    }
  };
}