package Klausur;

import java.util.*;

//Klasse für den Graphen mit Generics
class Graph<T> {

 // Wir verwenden eine HashMap, um die Kanten im Graphen zu speichern
 private Map<T, List<T>> map = new HashMap<>();

 // Diese Methode fügt einen neuen Knoten (Vertex) zum Graphen hinzu
 public void knotenHinzufuegen(T s) {
     map.put(s, new LinkedList<T>());
 }

 // Diese Methode fügt eine Kante zwischen Quelle und Ziel hinzu
 public void kanteHinzufuegen(T quelle, T ziel, boolean bidirektional) {

     if (!map.containsKey(quelle)) {
         knotenHinzufuegen(quelle);
     }

     if (!map.containsKey(ziel)) {
         knotenHinzufuegen(ziel);
     }

     map.get(quelle).add(ziel);
     if (bidirektional) {
         map.get(ziel).add(quelle);
     }
 }

 // Diese Methode gibt die Anzahl der Knoten (Vertices) im Graphen zurück
 public void anzahlKnoten() {
     System.out.println("Der Graph hat " + map.keySet().size() + " Knoten.");
 }

 // Diese Methode gibt die Anzahl der Kanten im Graphen zurück
 public void anzahlKanten(boolean bidirektional) {
     int anzahl = 0;
     for (T v : map.keySet()) {
         anzahl += map.get(v).size();
     }
     if (bidirektional) {
         anzahl = anzahl / 2;
     }
     System.out.println("Der Graph hat " + anzahl + " Kanten.");
 }

 // Diese Methode überprüft, ob ein bestimmter Knoten vorhanden ist
 public void hatKnoten(T s) {
     if (map.containsKey(s)) {
         System.out.println("Der Graph enthält " + s + " als Knoten.");
     } else {
         System.out.println("Der Graph enthält " + s + " nicht als Knoten.");
     }
 }

 // Diese Methode überprüft, ob eine Kante zwischen zwei Knoten vorhanden ist
 public void hatKante(T s, T d) {
     if (map.get(s).contains(d)) {
         System.out.println("Der Graph hat eine Kante zwischen " + s + " und " + d + ".");
     } else {
         System.out.println("Der Graph hat keine Kante zwischen " + s + " und " + d + ".");
     }
 }

 // Diese Methode gibt die Nachbarn eines Knotens zurück
 public void nachbarn(T s) {
     if (!map.containsKey(s)) {
         return;
     }
     System.out.println("Die Nachbarn von " + s + " sind:");
     for (T w : map.get(s)) {
         System.out.print(w + ", ");
     }
     System.out.println();
 }

 // Gibt die Adjazenzliste des Graphen aus
 @Override
 public String toString() {
     StringBuilder builder = new StringBuilder();

     for (T v : map.keySet()) {
         builder.append(v.toString()).append(": ");
         for (T w : map.get(v)) {
             builder.append(w.toString()).append(" ");
         }
         builder.append("\n");
     }

     return builder.toString();
 }
}

//Hauptprogramm (Driver Code)
class Main {

 public static void main(String args[]) {

     // Objekt des Graphen wird erstellt
     Graph<Integer> g = new Graph<>();

     // Kanten werden hinzugefügt
     // Da der Graph bidirektional ist, wird true übergeben
     g.kanteHinzufuegen(0, 1, true);
     g.kanteHinzufuegen(0, 4, true);
     g.kanteHinzufuegen(1, 2, true);
     g.kanteHinzufuegen(1, 3, true);
     g.kanteHinzufuegen(1, 4, true);
     g.kanteHinzufuegen(2, 3, true);
     g.kanteHinzufuegen(3, 4, true);

     // Den Graphen ausgeben
     System.out.println("Graph:\n" + g.toString());

     // Gibt die Anzahl der Knoten im Graphen aus
     g.anzahlKnoten();

     // Gibt die Anzahl der Kanten im Graphen aus
     g.anzahlKanten(true);

     // Überprüft, ob eine Kante zwischen zwei Knoten existiert
     g.hatKante(3, 4);

     // Überprüft, ob ein Knoten im Graphen existiert
     g.hatKnoten(5);

     // Gibt die Nachbarn eines Knotens aus
     g.nachbarn(1);
 }
}