/**
   * Creates a new AdvancedGameState by taking he previous AdvancedGameState and updating is using a
   * new GameState
   *
   * @param oldGameState
   * @param updatedState
   */
  public AdvancedGameState(AdvancedGameState oldGameState, GameState updatedState) {

    // Copy the stuff we can just re-use
    this.boardGraph = oldGameState.getBoardGraph();
    this.pubs = oldGameState.getPubs();
    this.viewUrl = oldGameState.getViewUrl();
    // Re-build the hero maps
    this.heroesByPosition = new HashMap<>();
    this.heroesById = new HashMap<>();
    for (GameState.Hero currentHero : updatedState.getGame().getHeroes()) {
      this.heroesByPosition.put(currentHero.getPos(), currentHero);
      this.heroesById.put(currentHero.getId(), currentHero);
    }
    this.me = updatedState.getHero();

    // Update the mines
    this.mines = oldGameState.getMines();
    for (Mine currentMine : this.mines.values()) {
      // Vindinium does the x and y coordinates backwards
      int tileStart =
          currentMine.getPosition().getX() * updatedState.getGame().getBoard().getSize() * 2
              + (currentMine.getPosition().getY() * 2);
      // We don't want the whole tile; we want the second char
      String owner =
          updatedState.getGame().getBoard().getTiles().substring(tileStart + 1, tileStart + 1 + 1);
      Mine mine;
      if (owner.equals("-")) {
        mine = new Mine(currentMine.getPosition(), null);
      } else {
        int ownerId = Integer.parseInt(owner);
        mine = new Mine(currentMine.getPosition(), this.heroesById.get(ownerId));
      }

      this.mines.put(mine.getPosition(), mine);
    }
  }
  @Override
  public GameState call() throws Exception {
    HttpContent content;
    HttpRequest request;
    HttpResponse response;
    GameState gameState = null;
    AdvancedGameState advancedGameState;

    try {
      // Initial request
      logger.info("Sending initial request...");
      content = new UrlEncodedContent(apiKey);
      request = REQUEST_FACTORY.buildPostRequest(gameUrl, content);
      request.setReadTimeout(0); // Wait forever to be assigned to a game
      response = request.execute();
      gameState = response.parseAs(GameState.class);
      logger.info("Game URL: {}", gameState.getViewUrl());

      advancedGameState = new AdvancedGameState(gameState);

      // Game loop
      while (!gameState.getGame().isFinished() && !gameState.getHero().isCrashed()) {
        logger.info("Taking turn " + gameState.getGame().getTurn());
        BotMove direction = bot.move(advancedGameState);
        Move move = new Move(apiKey.getKey(), direction.toString());

        HttpContent turn = new UrlEncodedContent(move);
        HttpRequest turnRequest =
            REQUEST_FACTORY.buildPostRequest(new GenericUrl(gameState.getPlayUrl()), turn);
        HttpResponse turnResponse = turnRequest.execute();

        gameState = turnResponse.parseAs(GameState.class);
        advancedGameState = new AdvancedGameState(advancedGameState, gameState);
      }

    } catch (Exception e) {
      logger.error("Error during game play", e);
    }

    logger.info("Game over");
    return gameState;
  }
  /**
   * Creates an AdvancedGameState from a GameState
   *
   * @param gameState
   */
  public AdvancedGameState(GameState gameState) {
    boardGraph = new HashMap<>();
    mines = new HashMap<>();
    pubs = new HashMap<>();
    heroesById = new HashMap<>();
    heroesByPosition = new HashMap<>();
    viewUrl = gameState.getViewUrl();

    // Hero stuffs
    for (GameState.Hero currentHero : gameState.getGame().getHeroes()) {
      this.heroesByPosition.put(currentHero.getPos(), currentHero);
      this.heroesById.put(currentHero.getId(), currentHero);
    }

    this.me = gameState.getHero();

    // Build the graph sans edges
    GameState.Board board = gameState.getGame().getBoard();
    for (int row = 0; row < board.getSize(); row++) {
      for (int col = 0; col < board.getSize(); col++) {
        // Yeah, Vindinium does the x and y coordinates backwards
        GameState.Position pos = new GameState.Position(row, col);
        int tileStart = row * board.getSize() * 2 + (col * 2);
        String tileValue = board.getTiles().substring(tileStart, tileStart + 1 + 1);

        // We do nothing with tiles that are barriers
        if (tileValue.equals("##")) {
          continue;
        }

        Vertex v = new Vertex(pos, new LinkedList<Vertex>());

        this.boardGraph.put(pos, v);

        // If its a mine or tavern, we treat it differently
        // We don't care if its a hero because a separate index for those already exists
        if (tileValue.startsWith("$")) {
          String owner = tileValue.substring(1);
          Mine mine;
          if (owner.equals("-")) {
            mine = new Mine(pos, null);
          } else {
            int ownerId = Integer.parseInt(owner);
            mine = new Mine(pos, this.heroesById.get(ownerId));
          }

          this.mines.put(pos, mine);
        } else if (tileValue.equals("[]")) {
          Pub pub = new Pub(pos);
          this.pubs.put(pos, pub);
        }
      }
    }

    // Add in the edges
    // This graph doesn't take into account players because they move.  That is done elsewhere.
    for (Vertex currentVertex : this.boardGraph.values()) {
      GameState.Position currentVertexPosition = currentVertex.getPosition();

      // Pubs and mines cannot be passed through
      if (this.mines.containsKey(currentVertexPosition)
          || this.pubs.containsKey(currentVertexPosition)) {
        continue;
      }

      // Other players cannot be passed through.  However, they move, and mines/pubs don't, so its
      // easier to make
      // the bot and path-finding deal with that.  We don't take other players into account here.
      // We can only move NSEW, so no need for a fancy set of nested loops...
      for (int xDelta = -1; xDelta <= 1; xDelta += 2) {
        int currentX = currentVertex.getPosition().getX();
        int newX = currentX + xDelta;
        if (newX >= 0 && newX < board.getSize()) {
          GameState.Position adjacentPos =
              new GameState.Position(newX, currentVertex.getPosition().getY());
          Vertex adjacentVertex = this.boardGraph.get(adjacentPos);
          if (adjacentVertex != null) {
            currentVertex.getAdjacentVertices().add(adjacentVertex);
          }
        }
      }
      for (int yDelta = -1; yDelta <= 1; yDelta += 2) {
        int currentY = currentVertex.getPosition().getY();
        int newY = currentY + yDelta;
        if (newY >= 0 && newY < board.getSize()) {
          GameState.Position adjacentPos =
              new GameState.Position(currentVertex.getPosition().getX(), newY);
          Vertex adjacentVertex = this.boardGraph.get(adjacentPos);
          if (adjacentVertex != null) {
            currentVertex.getAdjacentVertices().add(adjacentVertex);
          }
        }
      }
    }
  }