/**
   * When saving a map, we are looping through the map that was built and adding the fixed roads to
   * a new map. To prevent adding the same bits of roads again, delete all the bits from the already
   * added road
   *
   * @param oldMap the map from which to delete some number of components
   * @param start the start coordinate from where to begin deleting components
   * @param end the end coordinate to which we must delete all components
   */
  private void deleteFromOldMap(Map oldMap, Coordinate start, Coordinate end) {
    int startX = start.getX();
    int startY = start.getY();
    int endX = end.getX();
    int endY = end.getY();

    if (startY == endY) { // horizontal
      for (int i = startX; i <= endX; i++) {
        oldMap.clearCell(new Coordinate(i, startY));
      }
    } else { // vertical
      for (int i = startY; i <= endY; i++) {
        oldMap.clearCell(new Coordinate(startX, i));
      }
    }
  }
  /**
   * Takes a map with disconnected Roads and Intersections and connects them. This is like
   * connecting nodes (intersections) to edges (roads) in a directed graph.
   *
   * @param fixed the map where components need to be connected
   */
  private void assignIntersectionsToRoads(Map fixed) throws Exception {
    ArrayList<Intersection> intersections = fixed.getIntersections();
    for (int i = 0; i < intersections.size(); i++) {
      Intersection current = intersections.get(i);
      Coordinate coord = current.getLocation();
      int x = coord.getX();
      int y = coord.getY();
      Coordinate north = (y - 1 >= 0) ? new Coordinate(x, y - 1) : null;
      Coordinate south = (y + 1 < height) ? new Coordinate(x, y + 1) : null;
      Coordinate east = (x + 1 < width) ? new Coordinate(x + 1, y) : null;
      Coordinate west = (x - 1 >= 0) ? new Coordinate(x - 1, y) : null;

      if (north != null) {
        Component component = fixed.getAtLocation(north);
        if (component instanceof Road) {
          current.setNorthRoad((Road) component);
        }
      }

      if (south != null) {
        Component component = fixed.getAtLocation(south);
        if (component instanceof Road) {
          current.setSouthRoad((Road) component);
        }
      }

      if (east != null) {
        Component component = fixed.getAtLocation(east);
        if (component instanceof Road) {
          current.setEastRoad((Road) component);
        }
      }

      if (west != null) {
        Component component = fixed.getAtLocation(west);
        if (component instanceof Road) {
          current.setWestRoad((Road) component);
        }
      }

      current.setDefaultTrafficLightsForRoads();
    }
  }
  /**
   * Build the map that the user drew into a complete and connected map
   *
   * @param map the map that the user built
   * @return a fixed map that has all roads and intersections connected
   * @throws Exception
   */
  private Map buildAndSaveMap(Map map) throws Exception {
    System.out.println("Building and saving map...");
    int width = map.getWidth();
    int height = map.getHeight();
    Map fixed = new Map(width, height);

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        Component current = map.getGrid().get(x, y);
        if (current instanceof Intersection) {
          Coordinate location = new Coordinate(x, y);
          Intersection i = new Intersection(location);
          fixed.addIntersection(i);
          deleteFromOldMap(map, location, location);
        } else if (current instanceof Road) {
          Road road = (Road) current;
          Coordinate lastKnownCoord = road.getEndLocation();

          if (road.runsVertically()) {
            if (lastKnownCoord.getY() != height - 1) {
              Component next = map.getGrid().get(x, y++);
              while (next != null && next instanceof Road) {
                lastKnownCoord = ((Road) next).getEndLocation();
                if (y == height) break;
                next = map.getGrid().get(x, y++);
              }
              y = road.getStartLocation().getY(); // go back to the row we started at
            }
            Coordinate start = road.getStartLocation();
            Coordinate end = lastKnownCoord;
            Road newRoad = new Road(start, end);
            newRoad.addLane(new Lane(end, start, MapDirection.NORTH));
            newRoad.addLane(new Lane(start, end, MapDirection.SOUTH));
            fixed.addRoad(newRoad);
            deleteFromOldMap(map, start, end);
          } else {
            if (lastKnownCoord.getX() != width - 1) {
              Component next = map.getGrid().get(x++, y);
              while (next != null && next instanceof Road) {
                lastKnownCoord = ((Road) next).getEndLocation();
                if (x == width) break;
                next = map.getGrid().get(x++, y);
              }
              x =
                  x
                      - 2; // we overshot by 1, so go back, and loop will increment, so go back
                           // another
            }
            Coordinate start = road.getStartLocation();
            Coordinate end = lastKnownCoord;
            Road newRoad = new Road(start, end);
            newRoad.addLane(new Lane(start, end, MapDirection.EAST));
            newRoad.addLane(new Lane(end, start, MapDirection.WEST));
            fixed.addRoad(newRoad);
            deleteFromOldMap(map, start, end);
          }
        }
      }
    }

    assignIntersectionsToRoads(fixed);
    return fixed;
  }