#pragma once
#include "../../../../PerceptualState/Locations/Dynamic/GroupRectangularLocation.h"
#include "../Behaviors/GroupBehavior.h"
#include "../Behaviors/General/EngageEnemies/EngageEnemiesBehavior.h"
#include "../Conditions/IsIdle.h"
#include "../Conditions/AtLeastOneUnit.h"
#include "BehaviorChoice.h"

/**
 * @brief The PatrolBaseBehavior instructs a group to patrol along a sequence of checkpoints.
 * @tparam NextCheckpoint the type of mecanism used to get the next checkpoint.
 */
template <class NextCheckpoint>
class PatrolBaseBehavior : public GroupBehaviorT<PatrolBaseBehavior<NextCheckpoint>>
{
protected:
  class Moving;
  class Engaging;
  class Retreating;
  friend class Moving;
  friend class Engaging;
  friend class Retreating;

  /**
   * @brief The Moving state is active when the group is moving from one checkpoint to another.
   * @tparam NextCheckpoint the type of mecanism used to get the next checkpoint.
   */
  class Moving : public BehaviorStateT<PatrolBaseBehavior, Moving>
  {
    //
    // Moving should incorporate choosing (and keeping) the optimal units formation,
    // depending on the map and expected enemies.
    //
  public:
    Moving(PatrolBaseBehavior& behavior);
    void prepare();
    void onEnter();
    void onExit() {}

    void onTimer()
    {
      m_context.m_enemyUnits = Spar->getPerceptualState()->getEnemyUnits(GroupRectangularLocation(m_context.getGroup()));
      m_transitionToEngagement.testTransition(boost::tuple<const std::set<BWAPI::Unit*>&>(m_context.m_enemyUnits));
      m_transitionNextWaypoint.testTransition(boost::tuple<>());
    }

    typedef SCHEDULER_EVENT_HANDLER(Moving, onTimer) TimerEvent;

    typedef boost::tuple<
        TimerEvent&
      > Events;
		const Events m_events;
    
    typedef boost::tuple<
        Transition<Moving, AtLeastOneUnit, NoTransitionAction, Engaging, EventHandler1<const std::set<BWAPI::Unit*>&>>&,
        Transition<Moving, IsGroupIdle<PersistentUnitGroup>, NextCheckpoint, Moving>&
      > Transitions;
		const Transitions m_transitions;

  protected:
    // Variables
    std::list<BWAPI::Position>::const_iterator m_currentCheckpoint;

    // Actions
    //MoveCommand m_moveCommand;

    // Transitions
    TimerEvent m_timerEvent;
    Transition<Moving, AtLeastOneUnit, NoTransitionAction, Engaging, EventHandler1<const std::set<BWAPI::Unit*>&>> m_transitionToEngagement;
    Transition<Moving, IsGroupIdle<PersistentUnitGroup>, NextCheckpoint, Moving> m_transitionNextWaypoint;
  };

  /**
   * @brief The Engaging state is active when the patrolling group is fighting off enemies on its
   *        patrol mission.
   * @tparam NextCheckpoint the type of mecanism used to get the next checkpoint.
   */
  class Engaging : public BehaviorStateT<PatrolBaseBehavior, Engaging>
  {
  public:
    Engaging(PatrolBaseBehavior& behavior);
    void onEnter();
    void onExit();

    class EndInSuccess
    {
    public:
      EndInSuccess(Engaging& state)
        : m_state(state) {}
      bool evaluate()
      {
        TerminationMode m = m_state.m_subBehavior->getTerminationMode();
        return m != EngageEnemiesBehavior::Retreat;
      }
    private:
      Engaging& m_state;
    };

    class EndInRetreat
    {
    public:
      EndInRetreat(Engaging& state)
        : m_state(state) {}
      bool evaluate()
      {
        TerminationMode m = m_state.m_subBehavior->getTerminationMode();
        return m == EngageEnemiesBehavior::Retreat;
      }
    private:
      Engaging& m_state;
    };

    typedef boost::tuple<
      > Events;
		const Events m_events;
    
    typedef boost::tuple<
        Transition<Engaging, EndInSuccess, NoTransitionAction, Moving>&,
        Transition<Engaging, EndInRetreat, NoTransitionAction, Retreating>&
      > Transitions;
		const Transitions m_transitions;

  protected:
    // Variables
    EngageEnemiesBehavior* m_subBehavior;
    BehaviorT::OnTerminate::SubscriberID m_retreatingSubscriptionID;
    BehaviorT::OnTerminate::SubscriberID m_movingSubscriptionID;

    // Transitions
    Transition<Engaging, EndInSuccess, NoTransitionAction, Moving> m_transitionToMoving;
    Transition<Engaging, EndInRetreat, NoTransitionAction, Retreating> m_transitionToRetreating;
  };

  class Retreating : public BehaviorStateT<PatrolBaseBehavior, Retreating>
  {
  public:
    Retreating(PatrolBaseBehavior& behavior);
    void onEnter();
    void onExit() {}

    void onTimer()
    {
      m_context.m_enemyUnits = Spar->getPerceptualState()->getEnemyUnits(GroupRectangularLocation(m_context.getGroup()));
      m_transitionToSelf.testTransition(boost::make_tuple());
    }

    typedef SCHEDULER_EVENT_HANDLER(Retreating, onTimer) TimerEvent;

    typedef boost::tuple<
        TimerEvent&
      > Events;
		const Events m_events;
    
    typedef boost::tuple<
        Transition<Retreating, NoCondition, NoTransitionAction, Retreating>&
      > Transitions;
		const Transitions m_transitions;

  protected:
    // Variables

    // Actions
    //MoveCommand m_moveCommand;

    // Transitions
    TimerEvent m_timerEvent;
    Transition<Retreating, NoCondition, NoTransitionAction, Retreating> m_transitionToSelf;
  };

public:
  PatrolBaseBehavior(const Process* parent, unsigned int initialPriority, const PersistentUnitGroup* patrollingGroup, std::list<BWAPI::Position>& checkPoints)
    : GroupBehaviorT(parent, initialPriority, patrollingGroup)
    , GroupBehavior(parent, initialPriority, patrollingGroup)
    , Behavior(parent, initialPriority)
    , FSM(parent)
    , Process(parent)
    , m_checkPoints(checkPoints)
    , m_movingState(*this)
    , m_engagingState(*this)
    , m_retreatingState(*this)
    , m_states(m_movingState, m_engagingState, m_retreatingState)
  {
    assert(checkPoints.size() > 0);
  }

  typedef boost::tuple<
      Moving&,
      Engaging&,
      Retreating&
    > States;
	const States m_states;

protected:
  virtual void onStart()
  {
    GroupBehaviorT::onStart();
    m_movingState.prepare();
    m_movingState.enter();
  }

  virtual void onUnitAddedToGroup(void* data, BWAPI::Unit* unit)
  {
  }

  virtual void onUnitRemovedFromGroup(void* data, BWAPI::Unit* unit)
  {
  }

  // Arguments
  std::list<BWAPI::Position> m_checkPoints;

  // Variables
  std::set<BWAPI::Unit*> m_enemyUnits;

  // States
  Moving m_movingState;
  Engaging m_engagingState;
  Retreating m_retreatingState;
};

///////////////////////////////////////////////////
// Moving state
///////////////////////////////////////////////////

template <class NextCheckpoint>
PatrolBaseBehavior<NextCheckpoint>::Moving::Moving(PatrolBaseBehavior& behavior)
  : BehaviorStateT(behavior)
  //, m_moveCommand(*this)
  , m_timerEvent(*this, Scheduler::FSM_SCHEDULING_PRIORITY, 5, 5)
  , m_transitionToEngagement(*this,
                             AtLeastOneUnit(), 
                             NoTransitionAction(),
                             behavior.m_engagingState)
  , m_transitionNextWaypoint(*this,
                             IsGroupIdle<PersistentUnitGroup>(m_context.getGroup()), 
                             NextCheckpoint(m_currentCheckpoint, behavior.m_checkPoints), 
                             behavior.m_movingState)
  , m_events(m_timerEvent)
  , m_transitions(m_transitionToEngagement, m_transitionNextWaypoint)
{
  m_currentCheckpoint = m_context.m_checkPoints.begin();
}

template <class NextCheckpoint>
void PatrolBaseBehavior<NextCheckpoint>::Moving::prepare()
{
  m_context.m_enemyUnits = Spar->getPerceptualState()->getEnemyUnits(GroupRectangularLocation(m_context.getGroup()));
}

template <class NextCheckpoint>
void PatrolBaseBehavior<NextCheckpoint>::Moving::onEnter()
{
  if (m_currentCheckpoint == m_context.m_checkPoints.end())
  {
    m_context.terminate(BehaviorT::Success);
  }
  else
  {
    // Attack-move is theoretically not necessary since Engaging state is supposed to take care of that
    Layer1FSM::getInstance().getArbitrator().
      submitCommand(m_context.getGroup(), MoveCommand(*this, *m_currentCheckpoint), Arbitration::ActionImportance(m_context));
  }
}

///////////////////////////////////////////////////
// Engaging state
///////////////////////////////////////////////////

template <class NextCheckpoint>
PatrolBaseBehavior<NextCheckpoint>::Engaging::Engaging(PatrolBaseBehavior& behavior)
  : BehaviorStateT(behavior)
  , m_transitionToMoving(*this,
                         EndInSuccess(*this), 
                         NoTransitionAction(),
                         behavior.m_movingState)
  , m_transitionToRetreating(*this,
                             EndInRetreat(*this), 
                             NoTransitionAction(),
                             behavior.m_retreatingState)
  , m_transitions(m_transitionToMoving, m_transitionToRetreating)
{
}

template <class NextCheckpoint>
void PatrolBaseBehavior<NextCheckpoint>::Engaging::onEnter()
{
  // Attack-move is theoretically not necessary since Engaging state is supposed to take care of that
  assert(m_context.m_enemyUnits.size() > 0);
	m_subBehavior = BehaviorChoice<EngageEnemiesBehavior>::
		instantiate(&m_context, m_context.getCurrentPriority(), m_context.getGroup(), m_context.m_enemyUnits);
  m_subBehavior->zombifyOnTerminate();
  
  assert(m_subBehavior != NULL);
	
  m_movingSubscriptionID = m_subBehavior->getTerminateEvent().subscribe(&m_transitionToMoving, NULL);
  m_retreatingSubscriptionID = m_subBehavior->getTerminateEvent().subscribe(&m_transitionToRetreating, NULL);
  m_subBehavior->start();
}

template <class NextCheckpoint>
void PatrolBaseBehavior<NextCheckpoint>::Engaging::onExit()
{
  assert(m_subBehavior != NULL);
  m_subBehavior->getTerminateEvent().unsubscribe(m_movingSubscriptionID);
  m_subBehavior->getTerminateEvent().unsubscribe(m_retreatingSubscriptionID);
}

///////////////////////////////////////////////////
// Retreating state
///////////////////////////////////////////////////

template <class NextCheckpoint>
PatrolBaseBehavior<NextCheckpoint>::Retreating::Retreating(PatrolBaseBehavior& behavior)
  : BehaviorStateT(behavior)
  , m_moveCommand(*this)
  , m_timerEvent(*this, Scheduler::FSM_SCHEDULING_PRIORITY, 5, 5)
  , m_transitionToSelf(
      *this, 
      NoCondition(), 
      NoTransitionAction(), 
      *this)
  , m_events(m_timerEvent)
  , m_transitions(m_transitionToSelf)
{
}

template <class NextCheckpoint>
void PatrolBaseBehavior<NextCheckpoint>::Retreating::onEnter()
{
  //
  // This behavior should give retreat points to different clusters of units, and
  // spawn individual retreat behaviors for each unit.
  // At the moment, a retreat point is estimated for the entire group, and one
  // action is submitted for the group.
  //
  
  BWAPI::Position destination = Spar->getPerceptualState()->getSafeRetreatPosition(m_context.getGroup(), m_context.m_enemyUnits);
  Layer1FSM::getInstance().getArbitrator().
    submitCommand(m_context.getGroup(), MoveCommand(*this, destination), Arbitration::ActionImportance(m_context));
}

