import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;


/**
 * Backend class that loads graph data and performs queries related to locations and travel times.
 */
public class Backend implements BackendInterface {
  private GraphADT<String, Double> graph;

  // Constructor
  public Backend(GraphADT<String, Double> graph) {
    this.graph = graph;
  }
  /**
   * Loads graph data from a file into the graph.
   * Removes any previous graph data first.
   *
   * @param filename name of the file containing graph data
   * @throws IOException if there is a problem reading the file
   */
  @Override
  public void loadGraphData(String filename) throws IOException {
    // if the graph was previously loaded, remove all nodes and edges
    List<String> existingNodes = new ArrayList<>(graph.getAllNodes());
    for (String node : existingNodes) {
      graph.removeNode(node);
    }
    // parse the from/to/nodes and the weight
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
      String line;
      while ((line = reader.readLine()) != null) {
        line = line.trim();

        if (line.contains("->") && line.contains("[seconds=")) {
          String[] parts = line.split("->");
          // before "->" is from-node
          String from = parts[0].trim().replace("\"", "");

          String rightPart = parts[1].trim();
          String[] toAndSeconds = rightPart.split("\\[seconds=");
          // before "'seconds" is to-node
          String to = toAndSeconds[0].replace("\"", "").replace(";", "").trim();
          String weightString = toAndSeconds[1].replace("];", "").trim();

          double weight = Double.parseDouble(weightString);
          // create nodes
          graph.insertNode(from);
          graph.insertNode(to);
          // insert edge
          graph.insertEdge(from, to, weight);
        }
      }
    }catch (IOException | NumberFormatException | ArrayIndexOutOfBoundsException e) {
      System.err.println("Error while loading or parsing graph data: " + e.getMessage());
      throw e;
    }

  }

  /**
   * Returns a list of all location names (nodes) in the graph.
   *
   * @return a list of all locations
   */
  @Override
  public List<String> getListOfAllLocations() {
    List<String> existingNodes = new ArrayList<>(graph.getAllNodes());
    return existingNodes;

  }
  /**
   * Finds the locations along the shortest path from a start location to an end location.
   *
   * @param startLocation the starting node
   * @param endLocation   the ending node
   * @return a list of location names on the shortest path, or an empty list if none exists
   */
  @Override
  public List<String> findLocationsOnShortestPath(String startLocation, String endLocation) {
    // if either the start or end node cannot be found in the graph, or if there is no directed path
    // from the start node to the end node
    // shortestpathdata will throw nosuchelement exception and we return an empty list
    try {
      return graph.shortestPathData(startLocation, endLocation);
    } catch (NoSuchElementException e) {
      return new ArrayList<>();
    }
  }
  /**
   * Finds the times taken between each consecutive pair of locations along the shortest path.
   *
   * @param startLocation the starting node
   * @param endLocation   the ending node
   * @return a list of times between locations, or an empty list if no path exists
   */
  @Override
  public List<Double> findTimesOnShortestPath(String startLocation, String endLocation) {
    // create an empty list
    List<Double> walkingTime = new ArrayList<>();
    try {
      // first get the shortest path, if both nodes are found and there is path between two nodes
      List<String> path = graph.shortestPathData(startLocation, endLocation);
      // loop through all the nodes
      for (int i = 0; i < path.size() - 1; i++) {
        String from = path.get(i);
        String to = path.get(i + 1);
        // get the times between each two node
        Double weight = graph.getEdge(from, to);
        walkingTime.add(weight);
      }
    } catch (NoSuchElementException e) {
      // return empty list
    }
    return walkingTime;

  }
  /**
   * Finds all locations reachable from the given start location within the given travel time.
   *
   * @param startLocation the starting node
   * @param travelTime    the maximum travel time allowed
   * @return a list of reachable destination locations
   * @throws NoSuchElementException if the start location does not exist
   */
  @Override
  public List<String> getReachableFromWithin(String startLocation, double travelTime)
      throws NoSuchElementException {
    // if the graph does not contain this startlocation, throw an exception
    if (!graph.containsNode(startLocation)) {
      throw new NoSuchElementException();
    }
    List<String> allNodes = new ArrayList<>();
    // loop through every node in the graph
    for (String node : graph.getAllNodes()) {
      // as long as the node is not the start node
      if (!node.equals(startLocation)) {
        try {
          // get the time between the node and the start node
          double cost = graph.shortestPathCost(startLocation, node);
          // if the time is less than the trvael time add it to the answer list
          if (cost <= travelTime) {
            allNodes.add(node);
          }
        } catch (NoSuchElementException e) {
          // No path to this node; skip it
        }
      }
    }
    return allNodes;
  }

}
