#ifndef __COMMON_HPP__
#define __COMMON_HPP__

#include <sstream>

namespace hfo {

// For descriptions of the different feature sets see
// https://github.com/mhauskn/HFO/blob/master/doc/manual.pdf
enum feature_set_t
{
  LOW_LEVEL_FEATURE_SET,
  HIGH_LEVEL_FEATURE_SET
};

// The actions available to the agent
enum action_t
{
  DASH,       // [Low-Level] Dash(power [0,100], direction [-180,180])
  TURN,       // [Low-Level] Turn(direction [-180,180])
  TACKLE,     // [Low-Level] Tackle(direction [-180,180])
  KICK,       // [Low-Level] Kick(power [0,100], direction [-180,180])
  KICK_TO,    // [Mid-Level] Kick_To(target_x [-1,1], target_y [-1,1], speed [0,3])
  MOVE_TO,    // [Mid-Level] Move(target_x [-1,1], target_y [-1,1])
  DRIBBLE_TO, // [Mid-Level] Dribble(target_x [-1,1], target_y [-1,1])
  INTERCEPT,  // [Mid-Level] Intercept(): Intercept the ball
  MOVE,       // [High-Level] Move(): Reposition player according to strategy
  SHOOT,      // [High-Level] Shoot(): Shoot the ball
  PASS,       // [High-Level] Pass(teammate_unum [0,11]): Pass to the most open teammate
  DRIBBLE,    // [High-Level] Dribble(): Offensive dribble
  CATCH,      // [High-Level] Catch(): Catch the ball (Goalie only!)
  NOOP,       // Do nothing
  QUIT        // Special action to quit the game
};

// Status of a HFO game
enum status_t
{
  IN_GAME,             // Game is currently active
  GOAL,                // A goal has been scored by the offense
  CAPTURED_BY_DEFENSE, // The defense has captured the ball
  OUT_OF_BOUNDS,       // Ball has gone out of bounds
  OUT_OF_TIME,         // Trial has ended due to time limit
  SERVER_DOWN          // Server is not alive
};

// Configuration of the HFO domain including the team names and player
// numbers for each team. This struct is populated by ParseConfig().
struct Config {
  std::string offense_team_name;
  std::string defense_team_name;
  int num_offense; // Number of offensive players
  int num_defense; // Number of defensive players
  std::vector<int> offense_nums; // Offensive player numbers
  std::vector<int> defense_nums; // Defensive player numbers
};

enum SideID {
  RIGHT = -1,
  NEUTRAL = 0,
  LEFT = 1
};

// A Player is described by its uniform number and side
struct Player {
  SideID side;
  int unum;
};

/**
 * Returns the number of parameters required for each action.
 */
inline int NumParams(const action_t action) {
 switch (action) {
   case DASH:
     return 2;
   case TURN:
     return 1;
   case TACKLE:
     return 1;
   case KICK:
     return 2;
   case KICK_TO:
     return 3;
   case MOVE_TO:
     return 2;
   case DRIBBLE_TO:
     return 2;
   case INTERCEPT:
     return 0;
   case MOVE:
     return 0;
   case SHOOT:
     return 0;
   case PASS:
     return 1;
   case DRIBBLE:
     return 0;
   case CATCH:
     return 0;
   case NOOP:
     return 0;
   case QUIT:
     return 0;
 }
 std::cerr << "Unrecognized Action: " << action << std::endl;
 return -1;
};

/**
 * Returns a string representation of an action.
 */
inline std::string ActionToString(action_t action) {
  switch (action) {
    case DASH:
      return "Dash";
    case TURN:
      return "Turn";
    case TACKLE:
      return "Tackle";
    case KICK:
      return "Kick";
    case KICK_TO:
      return "KickTo";
    case MOVE_TO:
      return "MoveTo";
    case DRIBBLE_TO:
      return "DribbleTo";
    case INTERCEPT:
      return "Intercept";
    case MOVE:
      return "Move";
    case SHOOT:
      return "Shoot";
    case PASS:
      return "Pass";
    case DRIBBLE:
      return "Dribble";
    case CATCH:
      return "Catch";
    case NOOP:
      return "No-op";
    case QUIT:
      return "Quit";
    default:
      return "Unknown";
  }
};

/**
 * Returns a string representation of a game_status.
 */
inline std::string StatusToString(status_t status) {
  switch (status) {
    case IN_GAME:
      return "InGame";
    case GOAL:
      return "Goal";
    case CAPTURED_BY_DEFENSE:
      return "CapturedByDefense";
    case OUT_OF_BOUNDS:
      return "OutOfBounds";
    case OUT_OF_TIME:
      return "OutOfTime";
    case SERVER_DOWN:
      return "ServerDown";
    default:
      return "Unknown";
  }
};

/**
 * Parse a Trainer message to populate config. Returns a bool
 * indicating if the struct was correctly parsed.
 */
inline bool ParseConfig(const std::string& message, Config& config) {
  config.num_offense = -1;
  config.num_defense = -1;
  std::istringstream iss(message);
  std::string header = "HFO_SETUP";
  std::string key, val;
  iss >> key;
  if (header.compare(key) != 0) {
    return false;
  }
  while (iss >> key) {
    if (key.compare("offense_name") == 0) {
      iss >> config.offense_team_name;
    } else if (key.compare("defense_name") == 0) {
      iss >> config.defense_team_name;
    } else if (key.compare("num_offense") == 0) {
      iss >> val;
      config.num_offense = strtol(val.c_str(), NULL, 0);
    } else if (key.compare("num_defense") == 0) {
      iss >> val;
      config.num_defense = strtol(val.c_str(), NULL, 0);
    } else if (key.compare("offense_nums") == 0) {
      assert(config.num_offense >= 0);
      for (int i=0; i<config.num_offense; ++i) {
        iss >> val;
        config.offense_nums.push_back(strtol(val.c_str(), NULL, 0));
      }
    } else if (key.compare("defense_nums") == 0) {
      assert(config.num_defense >= 0);
      for (int i=0; i<config.num_defense; ++i) {
        iss >> val;
        config.defense_nums.push_back(strtol(val.c_str(), NULL, 0));
      }
    } else {
      std::cerr << "Unrecognized key: " << key << std::endl;
      return false;
    }
  }
  assert(config.offense_nums.size() == config.num_offense);
  assert(config.defense_nums.size() == config.num_defense);
  return true;
};

/**
 * Parse a trainer message to extract the player on the ball
 */
inline bool ParsePlayerOnBall(const std::string& message, Player& player) {
  if (message.find("GOAL") != std::string::npos){
    player.unum = atoi((message.substr(message.find("-")+1)).c_str());
    player.side = LEFT;
  } else if (message.find("CAPTURED_BY_DEFENSE") != std::string::npos) {
    player.unum = atoi((message.substr(message.find("-")+1)).c_str());
    player.side = RIGHT;
  } else if (message.find("IN_GAME") != std::string::npos){
    switch (message.at(message.find("-")+1)){
      case 'L':
        player.side = LEFT;
        break;
      case 'R':
        player.side = RIGHT;
        break;
      case 'U':
        player.side = NEUTRAL;
        break;
    }
    player.unum = atoi((message.substr(message.find("-")+2)).c_str());
  } else {
    return false;
  }
  return true;
};

/**
 * Parse a trainer message to extract the game status
 */
inline bool ParseGameStatus(const std::string& message, status_t& status) {
  status = IN_GAME;
  if (message.find("GOAL") != std::string::npos){
    status = GOAL;
  } else if (message.find("CAPTURED_BY_DEFENSE") != std::string::npos) {
    status = CAPTURED_BY_DEFENSE;
  } else if (message.compare("OUT_OF_BOUNDS") == 0) {
    status = OUT_OF_BOUNDS;
  } else if (message.compare("OUT_OF_TIME") == 0) {
    status = OUT_OF_TIME;
  } else if (message.find("IN_GAME") != std::string::npos){
    status = IN_GAME;
  } else {
    return false;
  }
  return true;
};

}
#endif