// This program is copyright VUW.
// You are granted permission to use it to construct your answer to a COMP103 assignment.
// You may not distribute it in any other way without permission.

/* Code for COMP103 - 2024T2, Assignment 6
 * Name:
 * Username:
 * ID:
 */

import ecs100.*;
import java.io.*;
import java.util.*;
import java.nio.file.*;

public class BusNetworks {

    /** Map of towns, indexed by their names */
    private Map<String,Town> busNetwork = new HashMap<String,Town>();

    /** CORE
     * Loads a network of towns from a file.
     * Constructs a Map of Town objects in the busNetwork field
     * Each town has a name and a set of neighbouring towns
     * First line of file contains the names of all the towns.
     * Remaining lines have pairs of names of towns that are connected.
     */
    public void loadNetwork(String filename) {
        try {
            busNetwork.clear();
            UI.clearText();
            List<String> lines = Files.readAllLines(Path.of(filename));
            String firstLine = lines.remove(0);

            String[] townNames = firstLine.split(" ");
            for (String name : townNames) {
                busNetwork.put(name, new Town(name));
            }

            for (String line : lines) {
                String[] connection = line.split(" ");
                if (connection.length == 2) {
                    String town1 = connection[0];
                    String town2 = connection[1];

                    Town townObj1 = busNetwork.get(town1);
                    Town townObj2 = busNetwork.get(town2);

                    // Add each town as a neighbor to the other
                    townObj1.addNeighbour(townObj2);
                    townObj2.addNeighbour(townObj1);
                }
            }

            UI.println("Loaded " + busNetwork.size() + " towns:");

        } catch (IOException e) {
            throw new RuntimeException("Loading data.txt failed" + e);
        }
    }

    /**  CORE
     * Print all the towns and their neighbours:
     * Each line starts with the name of the town, followed by
     *  the names of all its immediate neighbours,
     */
    public void printNetwork() {
        UI.println("The current network: \n====================");
        List<Town> sortedTowns = new ArrayList<>(busNetwork.values());
        sortedTowns.sort(Comparator.comparing(Town::getName));

        for (Town town : sortedTowns) {
            StringBuilder sb = new StringBuilder(town.getName() + "-> ");
            List<Town> sortedNeighbors = new ArrayList<>(town.getNeighbours());

            for (Town neighbor : sortedNeighbors) {
                sb.append(neighbor.getName()).append(" ");
            }
            UI.println(sb.toString().trim());
        }
    }


    /**
     * COMPLETION
     * Return a set of all the nodes that are connected to the given node.
     * Traverse the network from this node in the standard way, using a
     * visited set, and then return the visited set
     */
    public Set<Town> findAllConnected(Town town) {

        Set<Town> v = new HashSet<>();
        recurseTraverse(v, town);
        return v;

    }


    public void recurseTraverse(Set<Town> tv, Town t) {
        if (tv.contains(t)) {return;}
        tv.add(t);
        t.getNeighbours().forEach(n -> recurseTraverse(tv, n));

    }


    /**  COMPLETION
     * Print all the towns that are reachable through the network from
     * the town with the given name.
     * Note, do not include the town itself in the list.
     */
    public void printReachable(String name){
        Town town = busNetwork.get(name);
        if (town==null){
            UI.println(name+" is not a recognised town");
        }
        else {
            UI.println("\nFrom "+town.getName()+" you can get to:");
        }
        Set<Town> reachable = findAllConnected(town);
        reachable.forEach(UI::println);

    }

    /**  COMPLETION
     * Print all the connected sets of towns in the busNetwork
     * Each line of the output should be the names of the towns in a connected set
     * Works through busNetwork, using findAllConnected on each town that hasn't
     * yet been printed out.
     */
    public void printConnectedGroups() {
        UI.println("Groups of Connected Towns: \n================");
        int groupNum = 1;
        Set<Town> visitedTowns = new HashSet<>();

        for (Town town : busNetwork.values()) {
            if (!visitedTowns.contains(town)) {
                Set<Town> connectedTowns = findAllConnected(town);
                UI.print(" \n Group" + groupNum + ": "  );
                connectedTowns.forEach(n -> {
                    UI.print(n.getName() + " ");
                });
                visitedTowns.addAll(connectedTowns);
                groupNum++;
            }
        }
    }


    /**
     * Set up the GUI (buttons and mouse)
     */
    public void setupGUI() {
        UI.addButton("Load", ()->{loadNetwork(UIFileChooser.open());});
        UI.addButton("Print Network", this::printNetwork);
        UI.addTextField("Reachable from", this::printReachable);
        UI.addButton("All Connected Groups", this::printConnectedGroups);
        UI.addButton("Clear", UI::clearText);
        UI.addButton("Quit", UI::quit);
        UI.setWindowSize(1100, 500);
        UI.setDivider(1.0);
        loadNetwork("data-small.txt");
    }

    // Main
    public static void main(String[] arguments) {
        BusNetworks bnw = new BusNetworks();
        bnw.setupGUI();
    }

}



Display the network of towns graphically.
(The file data-with-lat-long.txt contains the same network data as the data-big.txt, but in a different format (eg, it starts with the number of towns, followed by a line for each town and location), and with latitude/longitude information as well. (You will find this useful.)
Ideally, it should let the user click on a town and highlight the part of the network (towns and links) that can be reached from that town.