#pragma once
#include "../Utils/Component.h"
#include "../Utils/Logger.h"
#include "../Utils/SafeMultiset.h"
#include "../Utils/SafeMultisetAdaptor.h"
#include "../Utils/Task.h"
#include <cassert>
#include <string>
#include <vector>
#include "timer.h"

class SchedulerTask;

/*
#define TASK_LOG_SCOPE(t) MP::SchedulerTaskScope task_log_scope_ ## t(t)
#else
#define TASK_LOG_SCOPE(t)
#endif*/
#define TASK_LOG_SCOPE(t)

typedef unsigned int id_type;
extern volatile id_type s_currentId;

/**
* @brief The scheduler is responsible for executing tasks.
*/
class Scheduler : public Component
{
protected:
  /**
   * @brief Compares two tasks, ordering by execution time then by priority.
   */
  struct TaskComparator
  {
    bool operator() (const SchedulerTask* task1, const SchedulerTask* task2) const;
  };
  typedef SafeMultiset<SchedulerTask*, TaskComparator> TaskList;

  /**
   * @brief The subscribers associates an ID to each activated task.
   */
  struct TaskID
  {
    TaskID()
      : m_isActive(false)
#ifdef NON_FINAl_VERSION
      , m_debugId(0)
#endif
    {
    }
    /**
     * @brief Position of the task in the tasks multiset.
     */
    TaskList::iterator m_posInMultiset;
    /**
     * @brief Whether the task is activated (m_posInMultiset is valid iff the task is active).
     */
    bool m_isActive;

#ifdef NON_FINAL_VERSION
    id_type m_debugId;
#endif
  };

public:
  Scheduler()
    : m_tasks(TaskComparator())
  {
  }

	/**
	 * @brief Executes the tasks that should be executed at or before the frame number, until time runs out.
	 */
	void onFrame(Timer::value_type endTime);

	/**
	 * @brief Subscribes a new task to the controller.
   *
	 * @param task The new task to subscribe.
   * @param priority The priority of the new task.
   * @param period The period of the new task, 0 if not periodic.
   * @param waitingTimeBeforeExecution The number of frames to wait before executing the task.
	 */
	void activate(SchedulerTask* task, unsigned int priority, size_t period, size_t waitingTimeBeforeExecution);

  /**
   * @brief Unregisters the task from the scheduler.
   *
   * @param task The task to remove from the scheduler.
   */
  void deactivate(SchedulerTask* task);

  static const unsigned int SCHEDULING_MEDIUM_PRIORITY = 1000;
  static const unsigned int FSM_SCHEDULING_PRIORITY = SCHEDULING_MEDIUM_PRIORITY;

  /////////
  // GUI //
  /////////
  typedef ConstSafeMultisetAdaptor<SchedulerTask*, TaskComparator>::const_iterator TaskIterator;
  std::pair<TaskIterator, TaskIterator> getCurrentTasks() const
  {
    ConstSafeMultisetAdaptor<SchedulerTask*, TaskComparator> adaptor(m_tasks);
    return std::make_pair(TaskIterator(adaptor.unsafe_begin()), TaskIterator(adaptor.unsafe_end()));
  }

protected:
  friend class SchedulerTask;

  virtual void _output(std::ostream& out) const
  {
    out << "Scheduler";
  }

  /**
   * @brief Set of tasks to be executed.
	 */
  TaskList m_tasks;

  /**
   * @brief Functor to execute tasks.
   */
  class ExecuteTask
  {
  public:
    ExecuteTask(Scheduler& scheduler, Timer::value_type endTime)
      : m_scheduler(scheduler), m_endTime(endTime)
    {
    }

    /**
     * @brief Executes the current task, copying it further down the list if the task is periodic.
     * @param wrapper The current task, including additional information like whether the task has been deleted.
     * @return Whether to break to iteration and whether to delete the current task.
     */
    TaskList::IterationStep operator()(TaskList::Wrapper& wrapper) const;
  protected:
    Scheduler& m_scheduler;
    const Timer::value_type m_endTime;
  private:
    ExecuteTask & operator=(const ExecuteTask &);
  };
  friend class ExecuteTask;
};

/**
 * @brief Task to be planned by the scheduler. This type is accessible to the outside. 
 *        Clients are expected to supply their own internal task 'memory' but should not modify
 *        the internal scheduler task's members.
 */
class SchedulerTask : public Task, public virtual Component
{
public:

#ifndef _DEBUG_COMPONENT

  SchedulerTask(const Component& owner)
  {
    unused(owner);
  }

  virtual ~SchedulerTask()
  {
  }

#else

  /**
   * @brief Internal scheduler task constructor.
   * @param owner The owner of the task.
   */
  SchedulerTask(const Component& owner)
    : m_owner(owner)
  {
    owner.onNewTask(this);
  }

  virtual ~SchedulerTask()
  {
    m_owner.onDestroyTask(this);
    printErrorIfStillActivated("ERROR SchedulerTask::~SchedulerTask(): Task \"%s\" has not been deactivated");
  }

  void printErrorIfStillActivated(const char* error) const
  {
    if (isActive())
    {
      SPAR_LOG(LogTypes.GENERAL_ERROR, error, m_debugName.c_str());
    }
  }

  const Component& m_owner;
#endif

  /**
   * @return Whether the task is active (registered in the scheduler).
   */
  bool isActive() const
  {
    return m_taskID.m_isActive;
  }

  unsigned int getPriority() const
  {
    return m_priority;
  }

  size_t getExecutionFrame() const
  {
    return m_executionFrame;
  }

  size_t getPeriod() const
  {
    return m_period;
  }

#ifdef NON_FINAL_VERSION
  unsigned int getDebugId() const
  {
    return m_taskID.m_debugId;
  }
#endif

private:
  friend class Scheduler;
  //
  // The following members are not visible to the outside; they are only stored as part of the task
  // to eliminate the need to dynamically create them (to reduce the 'new' operations).
  // I know it is a bad reason, I have no problem if someone wants to change the implementation.
  //

  /**
   * @brief The ID associated with this task. This field is not visible to the outside.
   */
  Scheduler::TaskID m_taskID;
  /**
   * @brief The priority of the task. This field is not visible to the outside.
   */
  unsigned int m_priority;
  /**
   * @brief For periodic tasks, the number of frames between two executions, or 0 if the task 
   *        is not periodic.  This field is not visible to the outside.
   */
  size_t m_period;
  /**
   * @brief Frame to execute the task at. This field is not visible to the outside.
   */
  size_t m_executionFrame;

  //const member prevent the use of the assigment operator
  SchedulerTask& operator=(const SchedulerTask &);
};

/**
 * @brief Subscribes a new task to the controller.
 * @param task The new task to subscribe.
 * @param priority The priority of the new task.
 * @param period The period of the new task, 0 if not periodic.
 * @param waitingTimeBeforeExecution The number of frames to wait before executing the task.
 */
inline void Scheduler::activate(SchedulerTask* task, unsigned int priority, size_t period, size_t waitingTimeBeforeExecution)
{
  task->m_priority = priority;
  task->m_period = period;
  task->m_executionFrame = BWAPI::Broodwar->getFrameCount() + waitingTimeBeforeExecution;
  TaskList::iterator it = m_tasks.insert(task);
  task->m_taskID.m_posInMultiset = it;
  task->m_taskID.m_isActive = true;
#ifdef NON_FINAL_VERSION
  task->m_taskID.m_debugId = s_currentId++;
#endif
}

/**
 * @brief Unregisters the task from the scheduler.
 * @param task The task to remove from the scheduler.
 */
inline void Scheduler::deactivate(SchedulerTask* task)
{
  if (task->m_taskID.m_isActive)
  {
    task->m_taskID.m_isActive = false;
    m_tasks.erase(task->m_taskID.m_posInMultiset);
  }
}

inline Scheduler::TaskList::IterationStep Scheduler::ExecuteTask::operator()(TaskList::Wrapper& wrapper) const
{
  if (Spar->getTimer()->currentTime() >= m_endTime)
  {
    // No time left
    return TaskList::IterationStep(false,true);
  }

  SchedulerTask* task = wrapper;

  if (static_cast<int>(task->m_executionFrame) > BWAPI::Broodwar->getFrameCount())
  {
    // The next tasks are to be executed at some point in the future.
    return TaskList::IterationStep(false,true);
  }
  
  const size_t period = task->m_period;

  TASK_LOG_SCOPE(task); // send log events for that task
  
  // When running we want to allow the user to reactivate a non-periodic task
  if (period == 0)
  {
    m_scheduler.deactivate(task);
  }
  
  task->run();
  
  // At this point we have to know whether the user has explicitely deactivated the task (if it is periodic)
  
  if (period > 0)
  {
    // We do not use "task->m_taskID.m_isActive" since the user may have deactivated and restarted the task
    if (!wrapper.isDeleted())
    {
      m_scheduler.deactivate(task);

      m_scheduler.activate(task, task->m_priority, task->m_period, task->m_period);
    }
  }
  return TaskList::IterationStep(false,false);
}

inline bool Scheduler::TaskComparator::operator() (const SchedulerTask* task1, const SchedulerTask* task2) const
{
  return task1->m_executionFrame < task2->m_executionFrame
     || (task1->m_executionFrame == task2->m_executionFrame && task1->m_priority < task2->m_priority);
}