/****************************************************************/
/* Prof. Dr. Carsten Vogt                                       */
/* FH Koeln, Fak. 07 / Nachrichtentechnik                       */
/* http://www.nt.fh-koeln.de/vogt                               */
/*                                                              */
/* Das Programm definiert Methoden zum Umgang mit Suchbaeumen   */
/* und ein Hauptprogramm zum Test dieser Methoden.              */
/****************************************************************/

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;


/****************************************************************

   Die Klasse 'Binaerknoten' definiert Knoten in Binaerbaeumen
   (siehe auch BinaerknotenTest.java).

*****************************************************************/


class Binaerknoten {

 // Attribute

 Object wert;   // Eintrag des Knotens: kann jedes beliebige Objekt sein

 String wertDarstellung;  // Stringdarstellung des Eintrags,
                          // wird bei einer Bildschirmanzeige des Knotens ausgegeben

 Binaerknoten linkerSohn,   // Verweis auf den linken Sohn
              rechterSohn;  // Verweis auf den rechten Sohn

 // Konstruktor: besetzt die vier Attribute mit den übergebenen Werten vor

 Binaerknoten(Object wert, String wertDarstellung, Binaerknoten linkerSohn, Binaerknoten rechterSohn) {
  this.wert = wert;
  this.wertDarstellung = wertDarstellung;
  this.linkerSohn = linkerSohn;
  this.rechterSohn = rechterSohn;
 }

}


/**************************************************************************

   Die Klasse 'Suchbaumknoten' definiert Knoten in Suchbaeumen.
   Sie ist eine Erweiterung der Klasse 'Binaerknoten' und ergaenzt
   den Knoteneintrag 'wert' durch ein Comparable-Attribut 'schluessel',
   über den dann Knoteneintraege per 'compareTo()' verglichen werden
   koennen.
   Zudem hat die Klasse ein Attribut, das auf den Vaterknoten verweist,
   und eine Hilfsmethode zum konsistenten Setzen eines Sohnattributs.

**************************************************************************/


class Suchbaumknoten extends Binaerknoten {

 // Attribute

 Comparable schluessel;  // Schlüsseleintrag des Knotens, anhand dessen
                         // Knoten verglichen werden koennen

 Suchbaumknoten vater;  // Verweis auf den Vaterknoten:
                        // ist zwar von der Suchbaumdefinition her nicht erforderlich,
                        // erleichtert aber die Programmierung der Methode zum Ausfügen von Knoten.

 // Konstruktor: besetzt die sechs Attribute mit den übergebenen Werten vor

 Suchbaumknoten(Object wert, Comparable schluessel, String wertDarstellung,
                 Suchbaumknoten linkerSohn, Suchbaumknoten rechterSohn, Suchbaumknoten vater) {
  super(wert,wertDarstellung,linkerSohn,rechterSohn);
  this.schluessel = (Comparable)schluessel;
  this.vater = vater;
 }

 // Hilfsmethode zum Setzen des Attributs 'linkerSohn' oder 'rechterSohn'.
 // Sie setzt insbesondere im Sohn das 'vater'-Attribut korrekt.

 void setSohn(Suchbaumknoten neuerSohn, char welcherSohn) {
  if (welcherSohn=='l')
    this.linkerSohn = neuerSohn;
   else
    this.rechterSohn = neuerSohn;
  if (neuerSohn!=null)
   neuerSohn.vater=this;
 }

}


/********************************************************************************

   Die Klasse 'SuchbaumMethoden' definiert Methoden zur Arbeit mit Suchbaeumen:
   einfuegen(): Einfügen eines neuen Knotens in einen Suchbaum
   entfernen(): Entfernen eines Knotens aus einem Suchbaum
   suchen():    Suchen nach einem Knoten in einem Suchbaum
   zeichnen():  grafische Ausgabe eines Suchbaums

*********************************************************************************/


class SuchbaumMethoden {


 /* Methode zum Einfügen eines neuen Knotens 'neuKnoten'
    in einen Suchbaum mit dem Wurzelknoten 'wurzel'.
    Sie arbeitet nur auf Baeumen, die schon mindestens einen Knoten
    besitzen.
    Die Methode gibt -1 zurück, wenn das Einfügen nicht erfolgreich war
    (insbesondere, wenn der Baum leer ist oder ein Knoten mit dem
    entsprechenden Text schon im Baum enthalten ist), und 0 sonst.
 */

 static int einfuegen(Suchbaumknoten wurzel, Suchbaumknoten neuKnoten) {

  if (wurzel==null || wurzel.wertDarstellung.equals(neuKnoten.wertDarstellung))
   // Einfügen fehlgeschlagen, da keine Wurzel vorhanden oder die Wurzel
   // den einzufügenden Wert bereits enthaelt
   return -1;

  if (wurzel.schluessel.compareTo(neuKnoten.schluessel)>0)
   // Einfügen in den linken Unterbaum, da Wurzelwert groesser ist als der neue Wert
   if (wurzel.linkerSohn==null) {
     // wenn Wurzel keinen linken Sohn hat: neuKnoten ist der neue linke Sohn
     wurzel.setSohn(neuKnoten,'l');
     return 0; }
    else
     // sonst Einfügen des neuen Knotens in den linken Unterbaum der wurzel
     return einfuegen((Suchbaumknoten)wurzel.linkerSohn,neuKnoten);

  // Einfügen in den rechten Unterbaum,
  // da aufgrund der vorherigen Tests der neue Wert groesser sein muss als der Wurzelwert
  if (wurzel.rechterSohn==null) {
     wurzel.setSohn(neuKnoten,'r');
    return 0; }
   else
    return einfuegen((Suchbaumknoten)wurzel.rechterSohn,neuKnoten);

 }

 /* Methode zum Suchen des Knotens mit dem minimalen Schlüsseleintrag,
    die als Hilfsmethode beim Ausfügen eines Knotens gebraucht wird.
    Rückgabewert: Verweis auf den Knoten mit dem minimalen Schlüssel. */

 static Suchbaumknoten sucheMinimum(Suchbaumknoten wurzel) {

  // Der Knoten mit dem minimalen Eintrag
  // ist der am weitesten links stehende Knoten im Baum
  Suchbaumknoten minKnoten = wurzel;
  while (minKnoten.linkerSohn!=null)
   minKnoten = (Suchbaumknoten) minKnoten.linkerSohn;
  return minKnoten;

 }

 /* Methode zum Entfernen eines Knotens mit dem Schlüssel 'auszufuegenderSchluessel'
    aus einem Suchbaum mit dem Wurzelknoten 'wurzel'. Enthaelt der Baum keinen
    passenden Knoten, so geschieht nichts.
    Rückgabewert: Verweis auf die Wurzel des Baums nach dem Ausfügen. */

 static Suchbaumknoten entfernen(Suchbaumknoten wurzel, Comparable auszufuegenderSchluessel) {

  Suchbaumknoten auszufuegen; // auszufügender Knoten

  // Suche nach dem auszufügenden Knoten

  auszufuegen = suchen(wurzel,auszufuegenderSchluessel);

  // Fall 1: kein entsprechender Knoten im Baum vorhanden

  if (auszufuegen==null)
   return wurzel;

  // Fall 2: auszufügender Knoten ist die Wurzel

  if (auszufuegen==wurzel) {
   if (wurzel.linkerSohn==null)
    // Wurzel hat nur einen rechten Unterbaum
    //  -> Wurzel durch den Wurzelknoten dieses Unterbaums ersetzen
    wurzel=(Suchbaumknoten)wurzel.rechterSohn;
   else if (wurzel.rechterSohn==null)
    // Wurzel hat nur einen linken Unterbaum
    //  -> Wurzel durch den Wurzelknoten dieses Unterbaums ersetzen
    wurzel=(Suchbaumknoten)wurzel.linkerSohn;
   else { // Wurzel hat linken und rechten Unterbaum
    // minimalen Knoten im rechten Unterbaum der Wurzel suchen
    Suchbaumknoten minKnoten = sucheMinimum((Suchbaumknoten)wurzel.rechterSohn);
    // minimalen Knoten von seiner bisherigen Stelle entfernen
    Suchbaumknoten unterbaumRechtsNeu = entfernen((Suchbaumknoten)wurzel.rechterSohn,minKnoten.schluessel);
    wurzel.setSohn(unterbaumRechtsNeu,'r');
    // minimalen Knoten anstelle der Wurzel einsetzen
    minKnoten.setSohn((Suchbaumknoten)wurzel.linkerSohn,'l');
    minKnoten.setSohn((Suchbaumknoten)wurzel.rechterSohn,'r');
    wurzel = minKnoten;
   }
   if (wurzel!=null)
    // Baumwurzel hat keinen Vater
    wurzel.vater=null;
   return wurzel;
  }

  // Fall 3: auszufügender Knoten ist nicht die Wurzel

  Suchbaumknoten vater = auszufuegen.vater; // Vater des auszufügenden Knotens

  if (auszufuegen==vater.linkerSohn) {
    // 'auszufuegen' aus dem Unterbaum ausfügen, dessen Wurzel er ist,
    Suchbaumknoten linkerUnterbaumNeu = entfernen(auszufuegen,auszufuegenderSchluessel);
    // und die neue Wurzel dieses Unterbaums als linken Sohn des Vaters einsetzen
    vater.setSohn(linkerUnterbaumNeu,'l');
   }
   else {
    // 'auszufuegen' aus dem Unterbaum ausfügen, dessen Wurzel er ist,
    Suchbaumknoten rechterUnterbaumNeu = entfernen(auszufuegen,auszufuegenderSchluessel);
    // und die neue Wurzel dieses Unterbaums als rechten Sohn des Vaters einsetzen
    vater.setSohn(rechterUnterbaumNeu,'r');
   }

  return wurzel;

 }


 /* Methode zur Suche eines Knotens mit dem Schlüssel 'gesuchterSchluessel'
    in einem Suchbaum mit dem Wurzelknoten 'wurzel'.
    Rückgabewert: Verweis auf das gefundene Knotenobjekt
     oder null, wenn kein entsprechender Knoten vorhanden. */

 static Suchbaumknoten suchen(Suchbaumknoten wurzel, Comparable gesuchterSchluessel) {

  if (wurzel==null)
   // gesuchter Wert ist im Baum nicht vorhanden
   return null;

  if (wurzel.schluessel.equals(gesuchterSchluessel))
   // Wurzelknoten enthaelt gesuchten Schlüssel
   return wurzel;

  if (wurzel.schluessel.compareTo(gesuchterSchluessel)>0)
   // Gesuchter Schlüssel ist, wenn überhaupt, im linken Unterbaum:
   // Daher wird dort weitergesucht und das von dort kommende Suchergebnis
   // per return nach oben weitergegeben.
   return suchen((Suchbaumknoten)wurzel.linkerSohn,gesuchterSchluessel);

  // Da alle anderen Faelle ausgeschlossen sind, kann der gesuchte Schlüssel
  // hoechstens im rechten Unterbaum sein
  return suchen((Suchbaumknoten)wurzel.rechterSohn,gesuchterSchluessel);

 }


 /* Methode zur graphischen Darstellung eines Binaerbaums */

 static void zeichnen(Suchbaumknoten wurzel, Graphics g, int spalteLinks, int spalteRechts, int zeileOben, int zeileUnten) {
  // Die vier int-Parameter definieren ein Rechteck innerhalb des Grafik-Kontexts g,
  // innerhalb dessen gezeichnet werden soll.

  if (wurzel!=null) {

   int spalteWurzel = (spalteLinks+spalteRechts)/2; // Spaltenposition der Wurzel

   // Wert des Knotens darstellen (obere Zeile, mittlere Spalte)
   g.setFont(new StandardFont());
   g.drawString(wurzel.wertDarstellung,spalteWurzel,zeileOben);

   // linken Unterbaum in einem kleineren Rechteck links darunter zeichnen
   zeichnen((Suchbaumknoten)wurzel.linkerSohn,g,spalteLinks,spalteWurzel,zeileOben+80,zeileUnten);

   // rechten Unterbaum in einem kleineren Rechteck rechts darunter zeichnen
   zeichnen((Suchbaumknoten)wurzel.rechterSohn,g,spalteWurzel,spalteRechts,zeileOben+80,zeileUnten);

   // Kanten von der Wurzel zu ihren beiden Soehnen zeichnen
   if (wurzel.linkerSohn!=null)
    g.drawLine(spalteWurzel-10,zeileOben+10,(spalteLinks+spalteWurzel)/2+10,zeileOben+60);
   if (wurzel.rechterSohn!=null)
    g.drawLine(spalteWurzel+10,zeileOben+10,(spalteRechts+spalteWurzel)/2-10,zeileOben+60);

  }

 }

}


/*****************************************************************************

   Die folgenden Klassen definieren die grafische Oberflaeche des Programms.

******************************************************************************/

/* standardmaessig verwendeter Font */

class StandardFont extends Font {
 StandardFont() {
  super("Arial",Font.BOLD,20);
 }
}

/* Label zur Anzeige von Textkonstanten */

class StandardLabel extends JLabel {
 StandardLabel(String str) {
  super(str);
  setFont(new StandardFont());
  setForeground(Color.black);
 }
}

/* Textfeld zur Eingabe von Strings */

class StandardTextfeld extends JTextField {
 StandardTextfeld() {
  super();
  setFont(new StandardFont());
  setForeground(Color.black);
 }
}

/* Button */

class StandardButton extends JButton {
 StandardButton(String text) {
  super(text);
  setFont(new StandardFont());
  setForeground(Color.black);
 }
}

/* Hauptfenster, das beim Programmstart erscheint */

class Hauptfenster extends JFrame {

 Suchbaumknoten hauptWurzel;  // Wurzelknoten des zugrundeliegenden Suchbaums

 BaumgrafikFrame baumgrafik;  // Rahmen für die zugehoerige grafische Darstellung

 // Textfelder zur Eingabe von Strings,
 // mit denen dann Aktionen auf dem Suchbaum ausgeführt werden

 JTextField einfuegeTextFeld = new StandardTextfeld(),  // zum Einfügen eines neuen Knotens
            entferneTextFeld = new StandardTextfeld(),  // zum Entfernen eines Knotens
            sucheTextFeld    = new StandardTextfeld();  // zur Suche nach einem Knoten

 // Button zur Beendigung des Programms

 JButton endeButton = new StandardButton("Ende");

 // Konstruktor

 Hauptfenster(Suchbaumknoten hauptWurzel) {

  super();

  // Setzen des Wurzelknotens
  this.hauptWurzel = hauptWurzel;

  // Definition von Ort und Layout der Darstellung
  setLocation(20,50);
  getContentPane().setLayout(new GridLayout(7,1));


  // Hinzufügen der Eingabefelder, des Ende-Buttons und zugehoeriger Beschriftungen
  getContentPane().add(new StandardLabel("Einfügen:"));
  getContentPane().add(einfuegeTextFeld);
  getContentPane().add(new StandardLabel("Entfernen:"));
  getContentPane().add(entferneTextFeld);
  getContentPane().add(new StandardLabel("Suchen:"));
  getContentPane().add(sucheTextFeld);
  getContentPane().add(endeButton);


  // Anbinden von Listenern an die Textfelder und den Button
  einfuegeTextFeld.addActionListener(new EinfuegeListener());
  entferneTextFeld.addActionListener(new EntferneListener());
  sucheTextFeld.addActionListener(new SucheListener());
  endeButton.addActionListener(new EndeListener());

  // Erzeugung eines zugehoerigen Rahmens zur grafischen Darstellung
  baumgrafik = new BaumgrafikFrame(hauptWurzel);

  pack();
  setVisible(true);

 }

 // Listener zum Start des Einfuegens eines neuen Knotens

 class EinfuegeListener implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   Suchbaumknoten neuKnoten; // neuerKnoten
   String eingabetext;  // Text zum Einsetzen in den neuen Knoten

   // Erzeugung des neuen Knotens
   eingabetext = ((JTextField)e.getSource()).getText();
   neuKnoten = new Suchbaumknoten(eingabetext,eingabetext,eingabetext,null,null,null);

   // Einfuegen des neuen Knotens in den Suchbaum
   if (hauptWurzel==null)
     hauptWurzel = neuKnoten;
    else {
     int erg;
     erg = SuchbaumMethoden.einfuegen(hauptWurzel,neuKnoten);
     if (erg==-1)
      JOptionPane.showMessageDialog(null,"Einfuegen fehlgeschlagen","",JOptionPane.INFORMATION_MESSAGE);
    }

   // Neuanzeige der Baumgrafik
   baumgrafik.dispose();
   baumgrafik = new BaumgrafikFrame(hauptWurzel);

  }

 }


 // Listener zum Start des Entfernens eines Knotens

 class EntferneListener implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   String auszufuegText;  // Schluesseltext des auszufuegenden Knotens

   // Entfernen des gewuenschten Knotens
   auszufuegText = ((JTextField)e.getSource()).getText();
   hauptWurzel = SuchbaumMethoden.entfernen(hauptWurzel,auszufuegText);

   // Neuanzeige der Baumgrafik
   baumgrafik.dispose();
   baumgrafik = new BaumgrafikFrame(hauptWurzel);

  }

 }

 // Listener zum Start der Suche nach einem Knoten

 class SucheListener implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   String gesuchterText = ((JTextField)e.getSource()).getText();;
   if (SuchbaumMethoden.suchen(hauptWurzel,gesuchterText)!=null)
     JOptionPane.showMessageDialog(null,"Knoten gefunden","Suchergebnis",JOptionPane.INFORMATION_MESSAGE);
    else
     JOptionPane.showMessageDialog(null,"Knoten nicht gefunden","Suchergebnis",JOptionPane.INFORMATION_MESSAGE);
   }

 }

 class EndeListener implements ActionListener {

  public void actionPerformed(ActionEvent e) {

   // Ende des Programms
   System.exit(0);

  }

 }

}


/* Rahmen zur Aufnahme einer Baumgrafik */

class BaumgrafikFrame extends JFrame {

 Suchbaumknoten wurzel;  // Wurzel des zu zeichnenden Baums
 
 // Panel zum Zeichnen der Grafik
 
 class BaumgrafikPanel extends JPanel {

   // paint: zeichnet mit Hilfe der Methode zeichne() die Baumgrafik

   public void paint(Graphics g) {
    SuchbaumMethoden.zeichnen(wurzel,g,10,864,50,708);
   }

 }

 // Konstruktor fuer umschliessenden Rahmen

 public BaumgrafikFrame(Suchbaumknoten wurzel) {

  super("Suchbaum:");
  this.wurzel = wurzel;
  setForeground(Color.black);
  setBackground(Color.white);
  setFont(new StandardFont());
  getContentPane().add(new BaumgrafikPanel());
  setSize(874,718);
  setLocation(150,50);
  setVisible(true);

 }

}


/**************************************************************************

   Die Klasse 'SuchbaumTest' definiert ein Hauptprogramm, mit dem ein
   Suchbaum aufgebaut, veraendert und ausgegeben werden kann.

***************************************************************************/


public class SuchbaumTest {

 public static void main(String args[]) throws IOException {

  // Erzeugung eines leeren Baums

  Suchbaumknoten hauptWurzel = null;

  // Erzeugung eines Bedienfensters
  // und einer grafischen Oberflaeche zur Darstellung des Baums

  new Hauptfenster(hauptWurzel);

 }

}


