package graph;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;

/**
 * Die Klasse GraphElement ist eine Oberklasse von Knoten und Kanten.
 * Sie ist nur für die interne Verarbeitung wichtig.
 *
 * @author Thomas Schaller
 * @version 28.02.2023 (v7.0)
 * v7.0: Die am Element gespeicherten Informationen werden in einer Hashmap gespeichert. Daher können beliebige weitere Informationen abgelegt werden.
 *       Es wird auch gespeichert, als welcher Typ die Information übergeben wurde. 
 */
public abstract class GraphElement implements Comparable<GraphElement>
{
    protected HashMap<String,String> daten;
    protected HashMap<String,String> typen;
    protected Graph g;
    protected String sortierKriterium;

    public GraphElement() {
        daten = new HashMap<String,String>();
        typen = new HashMap<String,String>();
    }

     /**
     * Setzt den Status einer Kante, der in einem String gespeichert ist.
     * Form: markiert,geloescht,farbe 
     * Dabei sind markiert und geloescht boolsche Werte (0 = false, 1 = true) und
     * die farbe eine Zahl
     * @param status  Statusstring
     */
    public void setStatus(String status) {
        List<String> items = Arrays.asList(status.split("\\s*,\\s*"));
        for(int i=0; i< items.size(); i++) {
            String[] pair = items.get(i).split(":");
            daten.put(pair[0], pair[1]); 
        }
    }    

    /**
     * Liefert den Status einer Kante als String.
     * Form: markiert,geloescht,farbe 
     * Dabei sind markiert und geloescht boolsche Werte (0 = false, 1 = true) und
     * die farbe eine Zahl
     * @return Statusstring
     */
    public String getStatus() {
        String s = "";
        for (String i : daten.keySet()) {
            s = s + ","+i+":"+daten.get(i);
        }
        s = s.substring(1);
        return s;
    }    


    /** Setzt das Sortierkriterium des Knoten/der Kante. 
     * @param name Bezeichnung des Wertes nach dem sortiert werden soll
     */
    public void setSortierkriterium(String name) {
        sortierKriterium = name;
    }

    /** Gibt die Beschreibung des Knoten / der Kante als Kurztext für die Anzeige im 
     * Kreis bzw. Kasten zurück. Dabei wird jeder Eintrag der Liste als eigene Zeile 
     * dargestellt. Es werden nur die Werte angezeigt. Es sind max. 2 Zeilen zulässig.
     * @param namen Namen der Werte, die im Kurztext angezeigt werden sollen.
     */
    public abstract List<String> getKurztext(String[] namen);

    /** Gibt die Beschreibung des Knoten / der Kante als Langtext für die Anzeige im 
     * Tooltip-Fenster zurück. Dabei wird jeder Eintrag der Liste als eigene Zeile 
     * dargestellt. Es wird jeweils die Bezeichnung und der Wert ausgegeben.
     * @param namen Namen der Werte, die im Tooltip angezeigt werden sollen.
     */
    public abstract List<String> getLangtext(String[] namen);

    /** Speichert den Graphen, in den Knoten/Kante eingefügt wurde. Damit kann er selbst seine Nummer
     *  ermitteln.
     *  @param g Graph
     */
    public void setGraph(Graph g) {
        this.g = g;
    }

    /** Speichert zusätzliche Daten am Knoten oder der Kante
     * @param name Bezeichnung der Art der Daten
     * @param wert Wert der zu speichernden Daten
     */
    public void set(String name, String wert) {
        wert = wert.replaceAll(":", "");
        wert = wert.replaceAll(",", "");
        daten.put(name.toLowerCase(), wert);
        typen.put(name.toLowerCase(), "String");
    }

    /** Speichert zusätzliche Daten am Knoten oder der Kante
     * Double.POSITIVE_INFINITY bzw. NEGATIVE_INFINITY wird als +/- unendlich dargestellt
     * @param name Bezeichnung der Art der Daten
     * @param wert Wert der zu speichernden Daten
     */
    public void set(String name, double wert) {
        if(wert == Double.POSITIVE_INFINITY) {
            daten.put(name.toLowerCase(), "\u221e");

        } else {
            if(wert == Double.NEGATIVE_INFINITY) {
                daten.put(name.toLowerCase(), "-\u221e");

            } else {
                if((int) wert == wert) {
                    daten.put(name.toLowerCase(), ""+(int) wert);
                } else {
                    daten.put(name.toLowerCase(), ""+wert);
                }
            }
        }
        typen.put(name.toLowerCase(), "Number");
    }

    /** Speichert zusätzliche Daten am Knoten oder der Kante
     * Integer.MAX_VALUE bzw. MIN_VALUE werden als +/- unendlich dargestellt.
     * @param name Bezeichnung der Art der Daten
     * @param wert Wert der zu speichernden Daten
     */
    public void set(String name, int wert) {
        typen.put(name.toLowerCase(), "Number");
        
        if(wert == Integer.MAX_VALUE) {
            daten.put(name.toLowerCase(), "\u221e");
            return;
        }
        if(wert == Integer.MIN_VALUE) {
            daten.put(name.toLowerCase(), "-\u221e");
            return;
        }
        daten.put(name.toLowerCase(), ""+wert);
    }

    /** Speichert zusätzliche Daten am Knoten oder der Kante
     * @param name Bezeichnung der Art der Daten
     * @param wert Wert der zu speichernden Daten
     */
    public void set(String name, boolean wert) {
        typen.put(name.toLowerCase(), "Boolean");
        daten.put(name.toLowerCase(), ""+wert);
    }

    /** Gibt zusätzliche Daten als String zurück
     * @param name Bezeichnung der zusätzlichen Daten
     * @return Wert von "name" oder "", wenn name nicht gespeichert ist
     */
    public String getString(String name) {
        if(daten.containsKey(name.toLowerCase())) {
            return daten.get(name.toLowerCase());
        } else {
            return "";
        }
    }

    /** Gibt zusätzliche Daten als int zurück
     * @param name Bezeichnung der zusätzlichen Daten
     * @return Wert von "name" oder 0, wenn name nicht gespeichert ist oder keine Zahl ist
     */
    public int getInt(String name) {
        try{
            if(daten.get(name.toLowerCase()).equals("\u221e")) return Integer.MAX_VALUE;
            if(daten.get(name.toLowerCase()).equals("-\u221e")) return Integer.MIN_VALUE;
            return Integer.parseInt(daten.get(name.toLowerCase()));
        } catch(Exception e) {
            return 0;
        }
    }

    /** Gibt zusätzliche Daten als int zurück
     * @param name Bezeichnung der zusätzlichen Daten
     * @return Wert von "name" oder 0, wenn name nicht gespeichert ist oder keine Zahl ist
     */
    public double getDouble(String name) {
        try{
            if(daten.get(name.toLowerCase()).equals("\u221e")) return Double.POSITIVE_INFINITY;
            if(daten.get(name.toLowerCase()).equals("-\u221e")) return Double.NEGATIVE_INFINITY;
            return Double.parseDouble(daten.get(name.toLowerCase()));
        } catch(Exception e) {
            return 0.0;
        }
    }

    /** Gibt zusätzliche Daten als int zurück
     * @param name Bezeichnung der zusätzlichen Daten
     * @return Wert von "name" oder false, wenn name nicht gespeichert ist oder kein Boolean ist
     */
    public boolean getBoolean(String name) {
        try{
            return Boolean.parseBoolean(daten.get(name.toLowerCase()));
        } catch(Exception e) {
            return false;
        }
    }
    
    /** Vergleicht den Knoten/Kante mit einem anderen Knoten/Kante bezüglich seines Sortierkriteriums
     * Das Sortierkriterium ist normalerweise der "Wert", kann aber mit setSortierkriterium gesetzt werden.
     * @param e anderer Knoten
     * @return kleiner 0 der andere Knoten hat einen größeren Wert, größer 0 der andere Knoten hat einen kleineren Wert, gleich 0 beide sind gleich
     */
    public int compareTo(GraphElement e) {
        double w1, w2;
        String s1 = sortierKriterium.toLowerCase();
        String s2 = e.sortierKriterium.toLowerCase();
        if(!typen.get(s1).equals(e.typen.get(s2))) return 0;
        
        if(typen.get(s1).equals("String")){
            return getString(s1).compareTo(e.getString(s2));
        }
        if(typen.get(s1).equals("Number")){
            return (int) (getDouble(s1) - e.getDouble(s2));
        }
        if(typen.get(s1).equals("Boolean")){
            if(getBoolean(s1) == e.getBoolean(s2)) return 0;
            if(getBoolean(s1)) return 1; else return -1;
        }

        return 0;
    }

    
}
