#pragma once
#include "../../EventHandler.h"
#include "../../Logger.h"
#include "Condition.h"

// Macro to create transitions?

/**
 * @brief This class exists for documentation purposes. A TransitionAction does something when a
 *        transition is activated.
 */
class TransitionAction
{
};

/**
 * @brief Trivial class indicating the absence of any action on a transition. 
 */
class NoTransitionAction : public TransitionAction
{
public:
  void execute() const {}
  template <class Arguments>
  void execute(const Arguments&) const {}
};

/**
 * @brief A transition represents an activable link between two states.
 *
 * @tparam OriginState Type of the state the transition originates from.
 * @tparam Condition Type of the condition which must be satisfied for the transition to be activated.
 * @tparam TransitionAction Type of the action executed upon activation of this transition.
 * @tparam DestinationState Type of the state the transition leads to.
 * @tparam EventHandlerType Type of event handler the transition can use to subscribe to events.
 *                        If a transition needs more than one type of event handler, create a sub-class and
 *                        store the new event handler as a member of this sub-class.
 */
template <class OriginState, class Condition, class TransitionAction, class DestinationState, class EventHandlerType = EventHandler>
class Transition : public EventHandlerType
{
public:
  typedef typename EventHandlerType::arguments Arguments;
  /**
   * @brief Constructs a new transition by setting up the relations between the various elements.
   *        Note that you must explicitely register the new transition to some event.
   *
   * @param originState The state this transition originates from.
   * @param condition The condition that must be satisfied for the transition to occur.
   * @param action The action to execute when this transition is activated.
   * @param destinationState The state this transition leads to.
   */
  Transition(OriginState& originState, Condition condition, TransitionAction action, 
             DestinationState& destinationState)
    : EventHandlerType(originState)
    , m_originState(originState)
    , m_condition(condition)
    , m_action(action)
    , m_destinationState(destinationState)
  {
  }

  virtual ~Transition() {}

  /**
   * @brief This method is called when a notification is received from the event this transition 
   *        has subscribed to. The transition is activated if the transition's condition evaluates to true and
   *        if no other transitions associated to this state have been activated within the same frame.
   * @param args The arguments attached to the event
   */
  virtual void onEvent(void*, Arguments& args)
  {
    testTransition(args);
  }

  /**
   * @brief This method is used to manually activate the transition if its condition evaluates to true and
   *        if no other transitions associated to this state have been activated within the same frame.
   * @param args The arguments attached to the event
   */
  bool testTransition(const Arguments& args)
  {
    const size_t frameNumber = BWAPI::Broodwar->getFrameCount();
    bool activation = false;
    // Check whether another transition has already been activated in this frame
    if (m_originState.getEarliestNextTransitionFrame() != frameNumber + 1)
    {
      if (ExpandArguments<Arguments>::evaluate(m_condition, args))
      {
        activate(args);
        // If this assignation is moved before "activate(args)", states could not transition immediately to another one
        m_originState.setEarliestNextTransitionFrame(frameNumber + 1);
        activation = true;
      }
    }
    return activation;
  }

protected:
  virtual void _output(std::ostream& out) const
  {
    out << "Transition " << m_originState.getName() << "-" << m_destinationState.getName();
  }

  template <class Arguments>
  struct ExpandArguments {};

  template <>
  struct ExpandArguments<boost::tuple<>> {
    static bool evaluate(Condition& condition, const boost::tuple<>&) {
      return condition.evaluate();
    }
    static void execute(TransitionAction& action, const boost::tuple<>&) {
      action.execute();
    }
  };
  template <class T1>
  struct ExpandArguments<boost::tuple<T1>> {
    static bool evaluate(Condition& condition, const boost::tuple<T1>& args) {
      return condition.evaluate(args.get<0>());
    }
    static void execute(TransitionAction& action, const boost::tuple<T1>& args) {
      action.execute(args.get<0>());
    }
  };
  template <class T1,class T2>
  struct ExpandArguments<boost::tuple<T1,T2>> {
    static bool evaluate(Condition& condition, const boost::tuple<T1,T2>& args) {
      return condition.evaluate(args.get<0>(), args.get<1>());
    }
    static void execute(TransitionAction& action, const boost::tuple<T1,T2>& args) {
      action.execute(args.get<0>(), args.get<1>());
    }
  };
  template <class T1,class T2,class T3>
  struct ExpandArguments<boost::tuple<T1,T2,T3>> {
    static bool evaluate(Condition& condition, const boost::tuple<T1,T2,T3>& args) {
      return condition.evaluate(args.get<0>(), args.get<1>(), args.get<2>());
    }
    static void execute(TransitionAction& action, const boost::tuple<T1,T2,T3>& args) {
      action.execute(args.get<0>(), args.get<1>(), args.get<2>());
    }
  };
  template <class T1,class T2,class T3,class T4>
  struct ExpandArguments<boost::tuple<T1,T2,T3,T4>> {
    static bool evaluate(Condition& condition, const boost::tuple<T1,T2,T3,T4>& args) {
      return condition.evaluate(args.get<0>(), args.get<1>(), args.get<2>(), args.get<3>());
    }
    static void execute(TransitionAction& action, const boost::tuple<T1,T2,T3,T4>& args) {
      action.execute(args.get<0>(), args.get<1>(), args.get<2>(), args.get<3>());
    }
  };
  template <class T1,class T2,class T3,class T4,class T5>
  struct ExpandArguments<boost::tuple<T1,T2,T3,T4,T5>> {
    static bool evaluate(Condition& condition, const boost::tuple<T1,T2,T3,T4,T5>& args) {
      return condition.evaluate(args.get<0>(), args.get<1>(), args.get<2>(), args.get<3>(), args.get<4>());
    }
    static void execute(TransitionAction& action, const boost::tuple<T1,T2,T3,T4,T5>& args) {
      action.execute(args.get<0>(), args.get<1>(), args.get<2>(), args.get<3>(), args.get<4>());
    }
  };

  /**
   * @brief This method is called when the transition is activated.
   *
   * First all resources reserved by the current state (like registered events and sub-behaviors) 
   * are released, including other events which might have been processed on the same frame.
   * The the transition's action is activated, and the destination state is entered.
   *
   * @param args The arguments which were sent over to the transition
   */
  void activate(const Arguments& args)
  {
    assert(&m_originState.getBehavior() == &m_destinationState.getBehavior());
    const Behavior& behavior = m_originState.getBehavior();

    SPAR_LOG(LogTypes.LAYER_1_DECISION, "Behavior \"%s\": transition from state \"%s\" to state \"%s\"", 
      behavior.getName().c_str(), 
      m_originState.getName().c_str(), 
      m_destinationState.getName().c_str());

    m_originState.exit();
    if (!behavior.isTerminated())
    {
      ExpandArguments<Arguments>::execute(m_action, args);
      if (!behavior.isTerminated())
        m_destinationState.enter();
    }
  }

  /**
   * @brief Reference to the origin state of this transition.
   */
  OriginState& m_originState;
  /**
   * @brief Condition (guard) on this transition.
   */
  Condition m_condition;
  /**
   * @brief Action to execute when this transition is activated.
   */
  TransitionAction m_action;
  /**
   * @brief Reference to the destination state of this transition.
   */
  DestinationState& m_destinationState;
};
