/**
   * Returns a movement result of the agent. If the agent tries to pass through a semi-wall, then it
   * is randomly selected whether the agent succeeds or not. If the agent tries to move through a
   * solid wall or to a location where there is another agent who is not moving, then no change
   * occurs.
   *
   * @param s the state containing the agent
   * @param p0 the initial position of the agent
   * @param delta the desired change of position.
   * @param agentNoOpLocs the locations occupied by agents who are not moving.
   * @return The resulting location of this agents movement.
   */
  protected Location2 sampleBasicMovement(
      State s, Location2 p0, Location2 delta, List<Location2> agentNoOpLocs) {

    Location2 p1 = p0.add(delta);

    boolean reset = false;

    for (Location2 anl : agentNoOpLocs) {
      if (p1.equals(anl)) {
        reset = true;
        break;
      }
    }

    if (delta.x != 0 && !reset) {
      reset =
          this.sampleWallCollision(p0, delta, s.getObjectsOfClass(GridGame.CLASSDIMVWALL), true);
    }

    if (delta.y != 0 && !reset) {
      reset =
          this.sampleWallCollision(p0, delta, s.getObjectsOfClass(GridGame.CLASSDIMHWALL), false);
    }

    if (reset) {
      p1 = p1.subtract(delta);
    }

    return p1;
  }
  /**
   * Returns with whom each agent is in a collision competition for a cell.
   *
   * @param candidatePositions a list of the positions to which each agent would like to go; the
   *     Location2 in index 1 indicates the location the 1th index agent wants to go to.
   * @return A map from the index of each agent to a list of the index of agents with whom there is
   *     competition for a cell
   */
  protected Map<Integer, List<Integer>> getColissionSets(List<Location2> candidatePositions) {

    Map<Integer, List<Integer>> collisionSets = new HashMap<Integer, List<Integer>>();

    for (int i = 0; i < candidatePositions.size(); i++) {

      Location2 candLoc = candidatePositions.get(i);
      List<Integer> collisions = new ArrayList<Integer>();
      collisions.add(i);
      for (int j = i + 1; j < candidatePositions.size(); j++) {
        if (collisionSets.containsKey(j)) {
          continue; // already found collisions with this agent
        }
        Location2 cLoc = candidatePositions.get(j);
        if (candLoc.equals(cLoc)) {
          collisions.add(j);
        }
      }

      if (collisions.size()
          > 1) { // greater than one because an agent always "collides" with itself
        // set the collision set for each agent involved
        for (Integer aid : collisions) {
          collisionSets.put(aid, collisions);
        }
      }
    }

    return collisionSets;
  }
  /**
   * Returns the position of each agent after accounting for collisions that are a result of agents
   * trying to move into each others previous locations.
   *
   * @param originalPositions the original position of the agents before their actions were taken.
   * @param desiredPositions the new position the agents are trying to go into
   * @return the positions of the agents accounting for collisions.
   */
  protected List<Location2> resolvePositionSwaps(
      List<Location2> originalPositions, List<Location2> desiredPositions) {

    List<Location2> resolvedPositions =
        new ArrayList<GridGameStandardMechanics.Location2>(desiredPositions);
    List<Location2> newNoopPositions = new ArrayList<GridGameStandardMechanics.Location2>();

    for (int i = 0; i < originalPositions.size(); i++) {
      Location2 a1op = originalPositions.get(i);
      Location2 a1dp = resolvedPositions.get(i);
      for (int j = i + 1; j < resolvedPositions.size(); j++) {
        Location2 a2op = originalPositions.get(j);
        Location2 a2dp = resolvedPositions.get(j);
        if (a1op.equals(a2dp) && a1dp.equals(a2op)) {
          // swap collision!
          resolvedPositions.set(i, new Location2(a1op));
          resolvedPositions.set(j, new Location2(a2op));
          newNoopPositions.add(a1op);
          newNoopPositions.add(a2op);

          break;
        }
      }
    }

    if (newNoopPositions.size() > 0) {
      return this.backupNoOps(originalPositions, resolvedPositions, newNoopPositions);
    }

    return resolvedPositions;
  }
  /**
   * Backups position changes by agents which can no longer move to their desired location. That is,
   * if agent a wanted to move to cell z, but failed for some reason (e.g., direct collision with
   * another agent), then z would be added to the noops list and this method would back up the
   * effect of a's ability to change position to any agents that wanted to move into a's position.
   *
   * @param originalPositions the original position of agents in the previous state
   * @param desiredPositions the desired position where the agents want to go
   * @param noops the locations in which agents have been forced to stay from other events
   * @return the new resolved locations of all agents
   */
  List<Location2> backupNoOps(
      List<Location2> originalPositions, List<Location2> desiredPositions, List<Location2> noops) {

    List<Location2> resolved = new ArrayList<GridGameStandardMechanics.Location2>(desiredPositions);

    boolean needsUpdating = true;
    while (needsUpdating) {
      needsUpdating = false;
      for (int i = 0; i < resolved.size(); i++) {
        Location2 dl = resolved.get(i);
        Location2 ol = originalPositions.get(i);
        if (!dl.equals(ol) && noops.contains(dl)) {
          resolved.set(i, ol);
          noops.add(ol);
          needsUpdating = true;
        }
      }
    }

    return resolved;
  }
  /**
   * Returns the list of possible outcome locations for a given start point and desired position
   * change.
   *
   * @param s the state in which the changes would occur
   * @param p0 the initial location
   * @param delta the desired change in position
   * @param agentNoOpLocs the locations occupied by agents who are not moving
   * @return the list of possible outcome locations in which the agent could wind up
   */
  protected List<Location2Prob> getPossibleLocationsFromWallCollisions(
      State s, Location2 p0, Location2 delta, List<Location2> agentNoOpLocs) {

    List<Location2Prob> locs = new ArrayList<GridGameStandardMechanics.Location2Prob>(2);

    Location2 p1 = p0.add(delta);
    for (Location2 anl : agentNoOpLocs) {
      if (p1.equals(anl)) {
        // definitely cannot move to desired position
        p1 = p1.subtract(delta);
        locs.add(new Location2Prob(p1, 1.));
        return locs;
      }
    }

    if (delta.x != 0) {
      int wc = this.wallCollision(p0, delta, s.getObjectsOfClass(GridGame.CLASSDIMVWALL), true);
      if (wc == 0) {
        locs.add(new Location2Prob(p1, 1.)); // agent freely moves
      } else if (wc == 1) {
        p1 = p1.subtract(delta);
        locs.add(new Location2Prob(p1, 1.)); // agent certainly cannot move
      } else {
        // agent moves with some probability
        locs.add(new Location2Prob(p1, this.pMoveThroughSWall));
        locs.add(new Location2Prob(p1.subtract(delta), 1. - this.pMoveThroughSWall));
      }
    } else if (delta.y != 0) {
      int wc = this.wallCollision(p0, delta, s.getObjectsOfClass(GridGame.CLASSDIMHWALL), false);
      if (wc == 0) {
        locs.add(new Location2Prob(p1, 1.)); // agent freely moves
      } else if (wc == 1) {
        p1 = p1.subtract(delta);
        locs.add(new Location2Prob(p1, 1.)); // agent certainly cannot move
      } else {
        // agent moves with some probability
        locs.add(new Location2Prob(p1, this.pMoveThroughSWall));
        locs.add(new Location2Prob(p1.subtract(delta), 1. - this.pMoveThroughSWall));
      }
    } else {
      // agent is not trying to change location
      locs.add(new Location2Prob(p1, 1.));
    }

    return locs;
  }