#pragma once
#include "../Utils/Utils.h"
#include "Config/Config.h"
#include "Utils/Logger.h"
#include "Utils/Process/Process.h"
#include <iostream>
#include <BWAPI.h>
#include <BWTA.h>

class SparAIContainer;
extern SparAIContainer* Spar;

class SparObserver;
class PerceptualState;
class UnitManager;
class Scheduler;
namespace MP
{
  class MPScheduler;
}
class Timer;

namespace detail
{
  //
  // Implementation of Process::onStart() different from AIModule::onStart()
  //

  /**
   * @brief SparAIContainer's base class handling the implementation of Process.
   *        Needed since the function names of Process and BWAPI::AIModule clash.
   */
  class ProcessHelper : public Process
  {
  public:
    ProcessHelper(const Process* parent)
      : Process(parent)
    {
      if (parent != NULL)
        parent->addUser();
    }
    ~ProcessHelper()
    {
      if (m_parent != NULL)
        m_parent->removeUser();
    }
    virtual void onStart()
    {
      onStartProcess();
    }
    virtual void onEnd()
    {
      onEndProcess();
    }
  protected:
    virtual void onStartProcess() = 0;
    virtual void onEndProcess() = 0;
  };

  /**
   * @brief SparAIContainer's base class handling the implementation of BWAPI::AIModule.
   *        Needed since the function names of Process and BWAPI::AIModule clash.
   */
  class AIModuleHelper : public BWAPI::AIModule
  {
  public:
    virtual void onStart()
    {
      onStartAIModule();
    }
    virtual void onEnd(bool isWinner)
    {
      onEndAIModule(isWinner);
    }
  protected:
    virtual void onStartAIModule() = 0;
    virtual void onEndAIModule(bool isWinner) = 0;
  };
}

/**
 * @brief Non-templated base class of SparAIModule. You can refer to the unique instance via the "Spar" global variable.
 */
class SparAIContainer : public detail::AIModuleHelper, public detail::ProcessHelper
{
public:
  SparAIContainer(const Process* parent,
                  Scheduler* scheduler,
                  MP::MPScheduler * mpScheduler,
                  PerceptualState* perceptualState,
                  Timer* timer);
  virtual ~SparAIContainer();

  PerceptualState* getPerceptualState()       { return m_perceptualStatePtr; }
  Scheduler* getScheduler()                   { return m_schedulerPtr; }
  MP::MPScheduler * getMultiThreadScheduler() { return m_mpSchedulerPtr; }

  Timer* getTimer() { return m_timerPtr; }

  bool isWinner() const
  {
    assert(isTerminated());
    return m_isWinner;
  }

  virtual void onFrame()
  {
    Component::deleteZombieComponents();
#ifdef _DEBUG_COMPONENT
    Component::_setComponentsDebugInfo();
#endif
  }

protected:
  friend class SparObserver;

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

  virtual void onStartAIModule();

  virtual void onEndAIModule(bool isWinner);

  virtual void onStartProcess();

  virtual void onEndProcess();

  /**
   * @brief Type of ID assigned to observers, actually a position in the list of observers.
   */
  typedef std::list<SparObserver*>::const_iterator ObserverID;

#ifdef USE_SPAR_GUI
  /**
   * @brief Adds a new observer that will be notified when something happens.
   * @param observer The new observer to add.
   * @return The ID of the new observer.
   */
  ObserverID addObserver(SparObserver* observer) const
  {
    return m_observers.insert(m_observers.end(), observer);
  }
  /**
   * @brief Removes the observer with the given ID from the list.
   * @param id The ID of the observer to remove.
   */
  void removeObserver(ObserverID id) const
  {
    m_observers.erase(id);
  }

  /**
   * @brief The list of observers.
   */
  mutable std::list<SparObserver*> m_observers;
#endif

private:
  // Keep m_scheduler BEFORE m_perceptualState (else deleting locations will fail on Component::_verifyPendingTasks())
  Scheduler* const m_schedulerPtr;
  MP::MPScheduler* const m_mpSchedulerPtr;
  PerceptualState* const m_perceptualStatePtr;
  Timer* const m_timerPtr;
  bool m_isWinner;
};
