package graph;
import imp.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * Dies ist das Herz vom "GraphTester" - der Graph selber, gepeichert als Adjazenzliste.
 * Die Klasse erlaubt durch geeignete Methoden:
 * - die Speicherung als Adjazenzmatrix,
 * - das Hinzufuegen und Loeschen von knoten und Kanten,
 * - das Markieren von Knoten und Kanten,
 * - eine Aussage darueber, ob Knoten oder Kanten enthalten sind und
 * - eine Ausgabe des Graphen in textueller Form sowie als csv-Datei.
 * 
 * 
 * @author Dirk Zechnall, Thomas Schaller
 * @version 31.01.2021 (v6.5)
 */

public class Graph
{

    private ArrayList<Knoten> kList; // Liste aller Knoten
    private ArrayList<Kante> kaList;  // Liste aller Kanten
    private ArrayList<ArrayList<Kante>> adList; //Adjazenzliste
    private boolean gerichtet;
    private boolean gewichtet;

    /**
     * Der Konstruktor erstellt einen neuen Graphen (genauer eine neue Adjazenzliste)
     * @param isGerichtet gibt an, ob es sich um einen gerichteten Graphen handelt
     * @param isGewichtet gibt an, ob die Kanten gewichtet sind.
     */
    public Graph (boolean isGerichtet, boolean isGewichtet) {
        loescheGraph();
        gerichtet = isGerichtet;
        gewichtet = isGewichtet;
    }

    /**
     * Der Konstruktor erstellt einen neuen ungerichteten, ungewichteten Graphen (genauer eine neue Adjazenzliste)
     */
    public Graph() {
        loescheGraph();
    }

    /**
     * Löscht alle Knoten und Kanten eines Graphen und stellt auf ungerichtet und ungewichtet zurück.
     */
    public void loescheGraph() {
        gerichtet = false;
        gewichtet = false;
        kList = new ArrayList<Knoten>();
        kaList = new ArrayList<Kante>();
        adList = new ArrayList<ArrayList<Kante>>();
    }

    /**
     * Ein Graph wird aus einer csv-Datei erstellt, die entweder
     * eine Matrix oder eine Adjazenzliste enthält, die den Graph beschreibt.
     *
     * @param  csvParser   Eine CSV-Datei als Table-Objekt
     * @return gibt zurück, ob das Laden erfolgreich war
     */
    public boolean ladeGraph(Table csvParser) {
        boolean geschafft = false;
        try {
            int knotenAnzahl = 0;
            boolean hasInfotext = false;
            boolean hasStatus = false;
            geschafft = true;
            int start;
            int posx = 0;

            for(start = 0; start < csvParser.getRowCount(); start++) {
                TableRow row = csvParser.getRow(start);
                String m = row.getString(0);
                if(row.getString(0).equals("directed")) {
                    setGerichtet(row.getInt(1) == 1);
                }
                if(row.getString(0).equals("weighted")) {
                    setGewichtet(row.getInt(1) == 1);
                }
                if(row.getString(0).equals("matrix") || row.getString(0).equals("list")) {
                    String n = row.getString(1);
                    if(n.equals("infotext")) {
                        hasInfotext = true;
                        posx++;

                    }
                    break;
                }
            }

            // Knoten erstellen
            for(int i=start+1; i < csvParser.getRowCount(); i++) {
                TableRow row = csvParser.getRow(i);
                if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') {
                    Knoten k = new Knoten(row.getInt(posx), row.getInt(posx+1));
                    if(hasInfotext) k.setInfotext(row.getString(0));

                    neuerKnoten(k);
                    knotenAnzahl++;
                }
            }

            // Kanten erstellen
            int k=0;
            if(csvParser.getString(start,0).equals("matrix")){

                for(int i = start+1; i < csvParser.getRowCount(); i++) {
                    TableRow row = csvParser.getRow(i);
                    if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') { //Ueberpruefung auf Kommentare #
                        for(int j = posx+2; j < row.getColumnCount(); j+=1) {
                            if(!(row.getString(j).equals("-"))) {
                                neueKante(getKnoten(k), getKnoten(j - 2 -posx), row.getDouble(j));
                            }
                        }
                        k++;
                    }
                }

            } else {

                for(int i = start+1; i < csvParser.getRowCount(); i++) {

                    TableRow row = csvParser.getRow(i);
                    if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') { // Ueberpruefung auf Kommentare #
                        int z = row.getColumnCount();
                        z++;

                        for(int j = posx+2; j < row.getColumnCount(); j+= 1) {
                            if(isGewichtet()){
                                neueKante(getKnoten(k), getKnoten(row.getInt(j)), row.getDouble(j + 1));
                                j++;
                            } else {
                                neueKante(getKnoten(k), getKnoten(row.getInt(j)), 0);
                            }

                        }
                        k++;
                    }
                }
            }

        } catch(Exception e) {
            geschafft=false;
        }
        return geschafft;
    }

    /**
     * Legt fest, ob der Graph gewichtet oder ungewichtet ist.
     *
     * @param isGewichtet neuer Wert
     */
    public void setGewichtet(boolean isGewichtet) {
        gewichtet = isGewichtet;
    }

    /**
     * Gibt zurueck, ob der Graph gewichtet oder ungewichtet ist
     *
     * @return gewichtet? (true/false)
     */
    public boolean isGewichtet() {
        return gewichtet;
    }

    /**
     * Legt fest, ob der Graph gerichtet oder ungerichtet ist.
     *
     * @param  isGerichtet neuer Wert
     */
    public void setGerichtet(boolean isGerichtet) {
        gerichtet = isGerichtet;
        if(gerichtet) {
            for(Kante k: kaList) {
                adList.get(kList.indexOf(k.getZiel())).remove(k);
            }
        }

    }

    /**
     * Gibt zurueck, ob der Graph gerichtet oder ungerichtet ist.
     *
     * @return  gerichtet? (true/false)
     */
    public boolean isGerichtet() {
        return gerichtet;
    }

    /** Gibt die Nummer eines Knotens im Graphen zurück
     * @param  k  gesuchter Knoten
     * @return Nummer des Knotens (mit 0 beginnend)
     */
    public int getNummer(Knoten k) {
        return kList.indexOf(k);
    }

    /** Gibt die Nummer einer Kante zurück
     * @param  k gesuchte Kante
     * @return Nummer der Kante (mit 0 beginnend)
     */
    public int getNummer(Kante k) {
        return kaList.indexOf(k);
    }    

    /**
     * Die Methode getAdjazenzMatrix() gibt die Adjazenzmatrix zurueck.
     * 
     * @return  double[][] Die AdjazenzMatrix als zweidimensionales Array
     */
    public double[][] getAdjazenzMatrix() {
        double[][] matrix = new double[getAnzahlKnoten()][getAnzahlKnoten()];
        for (int i=0; i<matrix.length; i++){
            for (int j=0; j<matrix[i].length; j++){
                matrix[i][j] = Double.NaN;
            }
        }
        // speichere Liste In Matrix
        for(Kante k : getAlleKanten()) {
            int startKnotenNummer = getNummer(k.getStart());
            int zielKnotenNummer = getNummer(k.getZiel());
            // Wenn keine Kanten existieren, gibt es auch kein Kantengewicht - daher wird hier das Kantengewicht auf NaN ueberprueft
            // man koennte hier auch mit Double-Variablen arbeiten und diese null setzen --> hat jedoch einen deutlich groeßeren Speicheraufwand!
            if(gewichtet) matrix[startKnotenNummer][zielKnotenNummer] = k.getGewicht();
            else matrix[startKnotenNummer][zielKnotenNummer] = 0;

        }
        return matrix;
    }

    /**
     * Gibt eine Liste aller Kanten des Graphen zurück.
     * @param   filter         optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return Liste aller Kanten
     */
    public List<Kante> getAlleKanten(Predicate<Kante>... filter) {
        List<Kante> kanten = new ArrayList<Kante>(kaList);
        if(filter.length>0) {
            kanten = kanten.stream()                // convert list to stream
            .filter(filter[0]) 
            .collect(Collectors.toCollection(ArrayList::new));  
        }          
        return kanten;
    }

    /**
     * Entfernt die Markierung bei allen Knoten des Graphen.
     */
    public void entferneMarkierungBeiAllenKnoten() {
        for (Knoten k : kList) {
            k.setMarkiert(false);
        }
    }

    /**
     * Initialisiert alle Knoten des Graphen.
     */
    public void initialisiereAlleKnoten() {
        for (Knoten k : kList) {
            k.init();
        }
    }

    /**
     * Initialisiert alle Kanten des Graphen.
     */
    public void initialisiereAlleKanten() {
        for (Kante k: kaList) {            
            k.init();
        }
    }

    /**
     * Ueberprueft, ob ein Knoten in der Knotenliste enthalten ist.
     * Sobald in der Knotenliste der Knoten k gefunden wird, wird true ausgegeben.
     * 
     * @param  k  Der gesuchte Knoten
     * @return  true= ist enthalten, false = ist nicht enthalten
     */
    public boolean isKnotenEnthalten (Knoten k) {
        return kList.contains(k);
    }

    /**
     * Gibt die Anzahl der Knoten im Graph zurueck
     * 
     * @return  Anzahl der Knoten
     */
    public int getAnzahlKnoten() {
        return kList.size();
    }

    /**
     * Gibt eine Liste aller Knoten des Graphen zurueck. 
     * @param   filter         optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Die Knotenliste. Falls leer wird eine leere Liste  zurueckgegeben
     */
    public List<Knoten> getAlleKnoten(Predicate<Knoten>... filter) {
        List<Knoten> knoten = new ArrayList<Knoten>();
        knoten.addAll(kList);
        if(filter.length>0) {
            knoten = knoten.stream()                // convert list to stream
            .filter(filter[0]) 
            .collect(Collectors.toCollection(ArrayList::new));  
        }          
        return knoten;
    }

    /**
     * Gibt die Liste aller Nachbarknoten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. 
     *
     * @param   k    Der Knoten, zu dem die Adjazenzliste gesucht wird
     * @param   filter         optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Liste der Nachbarknoten
     */
    public List<Knoten> getNachbarknoten (Knoten k, Predicate<Knoten>... filter) {
        if(kList.contains(k)) {
            List<Knoten> nachbarn = new ArrayList<Knoten>();
            for (Kante e : adList.get(kList.indexOf(k))) {
                nachbarn.add(e.getAnderesEnde(k));
            } // end of for
            if(filter.length>0) {
                nachbarn = nachbarn.stream()                // convert list to stream
                .filter(filter[0]) 
                .collect(Collectors.toCollection(ArrayList::new));  
            }
            return nachbarn;
        } else {
            return null;
        }
    }

    /**
     * Gibt eine Liste der ausgehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. 
     * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten 
     * unterschieden.
     *
     * @param   k    Der Knoten, zu dem die Kanten gesucht werden
     * @param   filter         optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Liste der ausgehenden Kanten.
     */
    public List<Kante> getAusgehendeKanten (Knoten k, Predicate<Kante>... filter) {
        if(kList.contains(k)) {
            List<Kante> kanten = new ArrayList<Kante>();
            kanten.addAll(adList.get(kList.indexOf(k)));
            if(filter.length>0) {
                kanten = kanten.stream()                // convert list to stream
                .filter(filter[0]) 
                .collect(Collectors.toCollection(ArrayList::new));  
            }          
            return kanten;
        } else {
            return null;
        }
    }

    /**
     * Gibt eine Liste der ausgehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. 
     * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten 
     * unterschieden.
     *
     * @param   knotennr    Nummer des Knoten, zu dem die Kanten gesucht werden
     * @param   filter         optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Liste der ausgehenden Kanten.
     */
    public List<Kante> getAusgehendeKanten (int knotennr, Predicate<Kante>... filter) {
        if(knotennr < getAnzahlKnoten()) {
            List<Kante> kanten = new ArrayList<Kante>();
            kanten.addAll(adList.get(knotennr));
            if(filter.length>0) {
                kanten = kanten.stream()                // convert list to stream
                .filter(filter[0]) 
                .collect(Collectors.toCollection(ArrayList::new));  
            }          
            return kanten;
        } else {
            return null;
        }
    }

    /**
     * Gibt eine Liste der eingehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. 
     * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten 
     * unterschieden.
     *
     * @param   knotennr    Die Nummer des Knotens, zu dem die Kanten gesucht werden
     * @param   filter      optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Liste der eingehenden Kanten.
     */
    public List<Kante> getEingehendeKanten (int knotennr, Predicate<Kante>... filter) {
        if(knotennr >= getAnzahlKnoten()) {
            return null;
        }
        return getEingehendeKanten(this.getKnoten(knotennr),filter);
    }

    /**
     * Gibt eine Liste der eingehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. 
     * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten 
     * unterschieden.
     *
     * @param   k        Knoten, zu dem die Kanten gesucht werden
     * @param   filter   optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()}
     * @return  Liste der eingehenden Kanten.
     */

    public List<Kante> getEingehendeKanten (Knoten k, Predicate<Kante>... filter) {
        if(!kList.contains(k)) {
            return null;
        }
        List<Kante> kanten = new ArrayList<Kante>();
        if(this.isGerichtet()) {
            for (Kante e: kaList) {
                if(e.getZiel()== k) {
                    kanten.add(e);
                }
            } // end of for
        } else {
            kanten =  getAusgehendeKanten(k);
        } // end of if-else

        if(filter.length>0) {
            kanten = kanten.stream()                // convert list to stream
            .filter(filter[0]) 
            .collect(Collectors.toCollection(ArrayList::new));  
        }

        return kanten;
    }    

    /**
     * Liefert einen Knoten des Graphen
     * @param knotennr Nummer der Knoten (beginnend mit 0)
     * @return liefert den Knoten mit dieser Nummer
     */
    public Knoten getKnoten(int knotennr) {
        if(knotennr < getAnzahlKnoten()) {
            return kList.get(knotennr);
        } else {
            return null;
        }
    }    

    /**
     * Fügt einen Knoten dem Graph hinzu.
     * @param  k    Der Knoten, der hinzugefuegt werden soll
     */
    public void neuerKnoten (Knoten k) {
        if (!isKnotenEnthalten(k)){
            kList.add(k);
            adList.add(new ArrayList<Kante>());
            k.setGraph(this);
        }
    }

    /**
     * Entfernt einen Knoten aus dem Graphen
     *
     * @param   knotennr    Nummer des Knotens, der geloescht werden soll
     */
    public void entferneKnoten (int knotennr) {
        entferneKnoten(getKnoten(knotennr));
    }

    /**
     * Entfernt einen Knoten aus dem Graphen
     *
     * @param   k    Knoten, der geloescht werden soll
     * @return  gibt zurück, ob der Knoten erfolgreich entfernt wurde.
     */

    public boolean entferneKnoten (Knoten k) {
        if (!kList.contains(k)) return false;

        int index = kList.indexOf(k);
        for (List<Kante> a : adList) {
            int index2 = 0;
            while(index2 < a.size()){
                Kante ka = a.get(index2);
                if(ka.getStart()==k || ka.getZiel()==k){
                    if(kaList.contains(ka)) kaList.remove(ka);
                    a.remove(index2);
                } else {
                    index2++;
                }
            }
        }
        adList.remove(index);
        kList.remove(k);
        k.setGraph(null);

        return true;
    }

    /**
     * Ueberprueft, ob eine Kante im Graphen enthalten ist.
     *
     * @param   e     Die zu suchende Kante
     * @return  Kante enthalten (true) oder nicht (false)
     */
    public boolean isKanteEnthalten (Kante e) {
        return kaList.contains(e);
    }

    /**
     * Ueberprueft, ob eine Kante im Graphen enthalten ist. 
     * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden
     *
     * @param   startNr    Nummer des Startknotens
     * @param   zielNr     Nummer des Zielknotens
     * @return  boolean     Kante enthalten (true) oder nicht (false)
     */
    public boolean isKanteEnthalten (int startNr, int zielNr) {
        return getKante(startNr, zielNr)!=null;
    }

    /**
     * Ueberprueft, ob eine Kante im Graphen enthalten ist. 
     * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden
     *
     * @param   start    Startknoten
     * @param   ziel     Zielknoten
     * @return  boolean     Kante enthalten (true) oder nicht (false)
     */

    public boolean isKanteEnthalten (Knoten start, Knoten ziel) {
        return getKante(start, ziel)!=null;
    }

    /**
     * Gibt eine gesuchte Kante aus dem Graphen zurueck.
     * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden
     *
     * @param   start    Der StartKnoten
     * @param   ziel     Der StartKnoten
     * @return    Die gesuchte Kante
     */
    public Kante getKante (Knoten start, Knoten ziel) {

        for(Kante k: adList.get(kList.indexOf(start))) {
            if(k.getStart()==start && k.getZiel() == ziel) {
                return k;
            }
            if(!gerichtet && k.getStart()==ziel && k.getZiel() == start) {
                return k;
            }
        }
        return null;
    }

    /**
     * Gibt eine gesuchte Kante aus dem Graphen zurueck.
     * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden
     *
     * @param   startnr    Der Nummer des StartKnoten
     * @param   zielnr     Die Nummer des Zielknoten
     * @return  Die gesuchte Kante
     */
    public Kante getKante (int startnr, int zielnr) {
        return getKante(getKnoten(startnr), getKnoten(zielnr));
    }

    /**
     * Fuegt eine Kante dem Graphen hinzu.
     * Dabei wird ueberprueft, ob die Kante schon im Graphen enthalten ist.
     * 
     * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" erstellt.
     *
     * @param   e     Die Kante, die hinzugefuegt werden soll
     */
    public void neueKante(Kante e) {
        adList.get(kList.indexOf(e.getStart())).add(e);
        if(!gerichtet) adList.get(kList.indexOf(e.getZiel())).add(e);
        kaList.add(e);
        e.setGraph(this);
    }

    /**
     * Fuegt eine Kante dem Graphen hinzu.
     * Dabei wird ueberprueft, ob die Kante schon im Graphen enthalten ist.
     * 
     * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" erstellt.
     *
     * @param   start     Der StartKnoten der Kante, die hinzugefuegt werden soll
     * @param   ziel     Der ZielKnoten der Kante, die hinzugefuegt werden soll
     * @param   gewicht     Das Gewicht der Kante, die hinzugefuegt werden soll
     */
    public void neueKante(Knoten start, Knoten ziel, double gewicht) {
        if(!kList.contains(start) || !kList.contains(ziel) || getKante(start,ziel) != null) return;
        neueKante(new Kante(start,ziel, gewicht));
    }

    /**
     * Entfernt eine Kante aus dem Graphen.
     * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt.
     *
     * @param   e Die zu entfernende Kante
     */
    public void entferneKante (Kante e) {
        entferneKante(e.getStart(), e.getZiel());

    }

    /**
     * Entfernt eine Kante aus dem Graphen.
     * Dabei wird ueberprueft, ob die Kante ueberhaupt im Graphen enthalten ist.
     * 
     * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt.
     *
     * @param   start    StartKnotens
     * @param   ziel     ZielKnotens
     */
    public void entferneKante (Knoten start, Knoten ziel) {
        Kante e1 = getKante(start, ziel);
        Kante e2 = getKante(ziel, start);

        for(List<Kante> a: adList) {
            a.remove(e1);
            if(!gerichtet) {
                a.remove(e2);
            }
        }
        kaList.remove(e1);
            e1.setGraph(null);
        if(!gerichtet) {
            kaList.remove(e2);
            e2.setGraph(null);
        }
    }

    /**
     * Entfernt eine Kante aus dem Graphen.
     * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt.
     *
     * @param   startnr    Nummer des StartKnotens
     * @param   zielnr     Nummer des ZielKnotens
     */
    public void entferneKante (int startnr, int zielnr) {
        entferneKante(getKnoten(startnr), getKnoten(zielnr));
    }

    /**
     * Ueberprueft, ob die Adjazenzliste leer ist, d.h. keine Knoten im Graphen enthalten sind.
     * 
     * @return   true, wenn die Liste leer ist, sonst false
     */
    public boolean isLeer() {
        return adList.isEmpty();
    }

    /**
     * Loescht den gesamten Graphen
     */
    public void loescheAlles() {
        for(Kante k : kaList) k.setGraph(null);
        for(Knoten k : kList) k.setGraph(null);
        adList.clear();
        kList.clear();
        kaList.clear();
    }

    /**
     * Die Methode erstellt eine CSV-Ausgabe des Graphen entweder als Adjazenzliste oder als Adjazenzmatrix.
     *
     * @param   asMatrix    true, falls die CSV-Ausgabe eine AdjazenzMatrix sein soll, sonst false
     * @return  CSV-Ausgabe
     */

    public String toCSVString(boolean asMatrix) {
        String s="";
        //if(adList.isLeer()) return s;
        s += "# gewichtet 1, ungewichtet 0\n";
        s += "weighted,"+(isGewichtet() ? "1" : "0")+"\n";
        s += "# gerichtet 1, ungerichtet 0\n";
        s += "directed,"+(isGerichtet() ? "1" : "0")+"\n";

        // Kontrollieren, ob irgendwo ein Infotext vorhanden ist.
        boolean hasInfotext = false;
        for(Knoten k: kList) {
            if(!k.getInfotext().equals("")) hasInfotext = true;
        }

        if(asMatrix){
            s += "# Der Graph liegt hier in Form einer Adjazenzmatrix vor. \n";
            s += "# Jede Zeile steht fuer einen Knoten, durch Kommas getrennt stehen die Kantengewicht zu jedem anderen Knoten.\n";

            s += "matrix";
            if(hasInfotext) s+=",infotext";
            double[][] matrix = getAdjazenzMatrix();
            // hier muss ueberprueft werden, ob matrixinhalt ist NaN --> '-' speichern
            for (int i=0; i<matrix.length; i++){
                s+="\n";
                Knoten k = kList.get(i);
                if(hasInfotext) s+=k.getInfotext()+",";
                s+=(k.getX())+","+k.getY()+",";
                for (int j=0; j<matrix[i].length; j++){
                    if(Double.isNaN(matrix[i][j]))s+="-";
                    else s+=matrix[i][j];
                    if (j!=matrix.length-1) s+=",";
                }
            }
        }
        else {
            s += "# Der Graph liegt hier in Form einer Adjazenzliste vor.\n";
            s += "# Jede Zeile steht fuer einen Knoten, durch Komma getrennt steht der adjazente Knoten mit dem zugehoerigen Kantengewicht.\n";
            s += "list";
            if(hasInfotext) s+=",infotext";

            for(int knr = 0; knr < kList.size(); knr++) {
                s+="\n";
                Knoten k = kList.get(knr);
                List<Kante> a = adList.get(knr);
                if(hasInfotext) s+=k.getInfotext()+",";
                s+=(k.getX())+","+(k.getY());
                if (a.size()>0) s+=",";
                for (Kante ka : a) {
                    s+=""+(getNummer(ka.getAnderesEnde(k)));
                    if (isGewichtet()) s+=","+ka.getGewicht();
                    if (a.get(a.size()-1)!=ka) s+=",";
                }
            }
        }
        return s;
    }

    /**
     * Textuelle Repraesentation des Graphen.
     * 
     * @return  Der Graph als Stringrepraesentation
     */
    @Override
    public String toString() {
        String s="";
        s+="Adjazenzliste:\n";
        if(adList.isEmpty()) return "Liste ist leer!\n";
        for(int i=0; i< kList.size(); i++) {
            s+="K"+i+kList.get(i).toString()+": ";
            List<Kante> a =adList.get(i);
            for (Kante k : a) {
                s+=k.toString()+"K"+(kList.indexOf(k.getAnderesEnde(kList.get(i))))+" | ";
            }
            s=s.substring(0,s.length()-3)+"\n";
        }
        return s;
    }

    /**
     * Konsolenausgabe der textuellen Repraesentation des Graphen.
     */
    public void ausgabe() {
        System.out.println(toString());
    }

    /**
     * Info über einen Knoten zurückgeben 
     * @param k Knoten, des Info ermittelt werden soll
     * @param zeigeWert Soll der Wert des Knoten in der Info enthalten sein
     * @return Infotext oder Nummer des Knoten und ggf. sein Wert
     */
    public String getKnoteninfo(Knoten k, boolean zeigeWert) {
        String info = "";
        if (!k.getInfotext().equals("")) {
            info += k.getInfotext();
        } else {
            info += "Knoten Nr. "+getNummer(k);
        }
        if( zeigeWert) {
            info += " (Wert "+k.getDoubleWert()+")";
        }
        return info;
    }

    /** Speichert den Zustand des Graphen als String-List
     * @return List mit Zustand
     */
    public List<String> getStatus() {
        List<String> status = new ArrayList<String>();
        for(Knoten k : getAlleKnoten()) {
            status.add(k.getStatus());
        }
        for(Kante k : getAlleKanten()) {
            status.add(k.getStatus());
        }
        return status;
    }

    /** Stellt den Zustand des Graphen aus String-List wieder her
     * @param status List mit Zustand
     */
    public void setStatus(List<String> status) {
        int i=0;
        for(Knoten k : getAlleKnoten()) {
            k.setStatus(status.get(i++));
        }
        for(Kante k : getAlleKanten()) {
            k.setStatus(status.get(i++));
        }

    }
}
