import java.util.function.BiFunction;

/**
 * Führt verschiedene Algorithmen auf Binärbäumen aus
 * 
 * @author Rainer Helfrich
 * @version November 2020
 */
public class Baumalgorithmen
{
    /**
     * Die Wurzel des Baums, mit dem der Algorithmus durchgeführt werden soll
     */
    protected Binaerbaum wurzel;

    /**
     * Erzeugt ein neues Algorithmen-Objekt
     * @param wurzel Die Wurzel des Baums, der verarbeitet werden soll
     */
    public Baumalgorithmen(Binaerbaum wurzel)
    {
        this.wurzel = wurzel;
    }

    /**
     * Zeigt den Baum auf der Konsole an.
     */
    public void baumAnzeigen()
    {
        if (wurzel != null)
        {
            System.out.println(wurzel.daten);
            baumAnzeigen(wurzel.links, "", true);
            baumAnzeigen(wurzel.rechts, "", false);
        }
    }
    
    private void baumAnzeigen(Binaerbaum b, String indent, boolean left)
    {
        System.out.print(indent);
        System.out.print(left ? "\u251C " : "\u2514 ");
        if (b != null)
        {
            System.out.println(b.daten);
            if (b.links != null || b.rechts != null)
            {
                indent += (left ? "\u2502 " : "  ");
                baumAnzeigen(b.links, indent, true);
                baumAnzeigen(b.rechts, indent, false);
            }
        }
        else
        {
            System.out.println("");
        }
    }
    
    /**
     * Führt eine Baumtraversierung durch und wendet eine Funktion auf alle Elemente an
     * @param b Der Baum, dessen Kinder traversiert werden sollen
     * @param f Die Funktion, die auf die Werte angewendet werden soll
     * @param neutral Das Neutralelement bezüglich der Funktion f
     * @return Das Ergebnis der Traversierung
     */
    private <T> T traversierung(Binaerbaum b, BiFunction<T, T, T> f, T neutral)
    {
        if (b == null)
        {
            return neutral;
        }
        return f.apply(traversierung(b.links, f, neutral), traversierung(b.rechts, f, neutral));
    }

    /**
     * Gibt die Tiefe des Baums zurück
     * @return Die Tiefe des Baums, gezählt ab der Wurzel
     */
    public int tiefe()
    {
        return traversierung(wurzel, (x, y) -> 1 + Math.max(x,y), 0);
    }

    /**
     * Gibt die Anzahl der Knoten im Baums zurück
     * @return Die Anzahl aller Knoten im Baum
     */
    public int anzahl()
    {
        return traversierung(wurzel, (x, y) -> 1 + x + y, 0);
    }

    /**
     * Prüft, ob sich ein Wert im Baum befindet
     * @param wert Der zu suchende Wert
     * @return true, wenn ein Knoten den Wert enthält; false sonst
     */
    public <T> boolean enthaelt(T wert)
    {
        return enthaelt(wurzel, wert);
    }

    /**
     * Prüft, ob sich ein Wert in b oder darunter befindet
     * @param b Der Baum, in dem der Wert gesucht werden soll
     * @return true, wenn b oder einer seiner Kindknoten den Wert enthält; false sonst
     */
    private <T> boolean enthaelt(Binaerbaum b, T wert)
    {
        if (b == null)
        {
            return false;
        }
        if (b.daten.equals(wert))
        {
            return true;
        }
        return enthaelt(b.links, wert) || enthaelt(b.rechts, wert);
    }
}
