/*
Datei............: TurtleGraphics.java
Projekt..........: Einführung in die Java-Programmierung
Erstellt.........: 28.11.97, Guido Krüger
Geändert.........: --
Aufgabe..........: Turtlegrafik zu den Aufgaben des Abschnitts 5
Kommentare.......:
*/
import java.awt.*;
import java.util.*;

/**
 * Die Klasse TurtleGraphics dient als Ergänzung zur Klasse
 * Graphics und stellt Funktionen zur Implementierung einer
 * Turtle-Grafik zur Verfügung. Um die Klasse zu verwenden, muß 
 * ein TurtleGraphics-Objekt durch Übergabe eines Graphics-Objektes
 * und Übergabe des Ausgabefensters instanziert werden. 
 * <p>
 * Ein TurtleGraphics-Objekt hat drei bestimmende Merkmale:
 * <ul>
 * <li>Die aktuelle Position der Turtle.
 * <li>Ihre aktuelle Orientierung (Drehrichtung).
 * <li>Die Lage des Zeichenstifts (angehoben oder abgesenkt).
 * </ul>
 * Am Anfang zeigt die Turtle nach Norden, steht in der Mitte
 * des Bildschirms und ist vom Papier abgehoben.
 */
public class TurtleGraphics
{
  Graphics    g;
  Container   wnd;
  TurtleState state;
  Stack       stateStack;

  /**
   * Alle Ausgaben der Turlte gehen auf den in g übergebenen 
   * Grafik-Kontext. Der Container wnd sollte das Fenster
   * repräsentieren, auf dem die Ausgabe erfolgt. Er wird
   * verwendet, um den Mittelpunkt der Ausgabefläche zu
   * bestimmen.
   */
  public TurtleGraphics(Graphics g, Container wnd)
  {
    this.g     = g;
    this.wnd   = wnd;
    state      = new TurtleState();
    stateStack = new Stack();
    goHome();
    turnNorth();
    liftPen();
  }

  /**
   * Setzt die Turtle auf den Mittelpunkt der Ausgabefläche. Die 
   * Richtung wird nicht verändert.
   */
  public void goHome()
  {
    int sizex  = wnd.getSize().width - 
	             wnd.getInsets().left - wnd.getInsets().right;
    int sizey  = wnd.getSize().height - 
	             wnd.getInsets().top - wnd.getInsets().bottom;
    state.actx = wnd.getInsets().left + (sizex / 2);
    state.acty = wnd.getInsets().top + (sizey / 2);
  }

  /** 
   * Bewegt die Turtle um die angegebene Distanz nach vorne. Falls
   * der Zeichenstift abgesenkt ist, wird eine entsprechende Linie 
   * gezogen. Das Ergebnis hängt von der aktuellen Orientierung der
   * Turtle ab, die Bewegung erfolgt immer in Laufrichtung.
   */
  public void moveForward(int distance)
  {
    int dx, dy;

    dy = (int)(Math.sin(state.actangle) * distance);
    dx = (int)(Math.cos(state.actangle) * distance);
    dy = -dy;
    if (state.pendown) {
      g.drawLine(state.actx,state.acty,state.actx+dx,state.acty+dy);
    }
    state.actx += dx;
    state.acty += dy;
  }

  /**
   * Dreht die Turtle nach Norden, also mit Laufrichtung nach oben.
   */
  public void turnNorth()
  {
    state.actangle = Math.PI / 2.0;
  }

  /**
   * Dreht die Turtle um den angegebenen Winkel nach links. Der Winkel
   * wird in Grad angegeben. 
   */
  public void turnLeft(double angle)
  {
    angle = angle * 2 * Math.PI / 360.0;
    state.actangle += angle;
    if (state.actangle >= 360.0) {
      state.actangle -= 360.0;
	  //    } else if (state.actangle < 0) {
	  //	  state.actangle += 360.0;
	}
  }

  /**
   * Dreht die Turtle um den angegebenen Winkel nach rechts. Der Winkel
   * wird in Grad angegeben. 
   */
  public void turnRight(double angle)
  {
    turnLeft(-angle);
  }

  /**
   * Hebt den Zeichenstift an, d.h. nachfolgende Bewegungen der Turtle
   * hinterlassen keine Spur auf dem Bildschirm.
   */
  public void liftPen()
  {
    state.pendown = false;
  }

  /**
   * Senkt den Zeichenstift an, d.h. nachfolgende Bewegungen der Turtle
   * hinterlassen eine Spur auf dem Bildschirm.
   */
  public void dropPen()
  {
    state.pendown = true;
  }

  /**
   * Diese Hilfsfunktion speichert den aktuellen Zustand der 
   * Turtle auf einem Zustandsstack, von wo er später durch Aufruf 
   * von popState rekonstruiert werden kann. Diese beiden Methoden
   * sind nützlich, um etwa Rundungsfehler (durch die ganzzahlige
   * Arithmetik) bei komplexen Ausgabeoperationen zu vermeiden. 
   * <p>
   * Soll beispielsweise ein schiefwinkliger Streckenzug gezeichnet
   * und nach dem letzten Element wieder an den Ausgangspunkt
   * zurückgekehrt werden, so gelingt dies wegen besagter Fehler 
   * nicht immer. Durch Aufruf von pushState kann der Anfangszustand
   * gesichert und nach Ausgabe des Streckenszugs wieder hergestellt
   * werden. 
   * <p>
   * Diese Methode sichert alle Bestandteile des Turtlezustands, also
   * aktuelle Position, Orientierung und Lage des Stifts.
   */
  public void pushState()
  {
    TurtleState tmp = new TurtleState();
    tmp.pendown  = state.pendown;
    tmp.actx     = state.actx;
    tmp.acty     = state.acty;
    tmp.actangle = state.actangle;
    stateStack.push(tmp);
  }

  /**
   * Das Gegenstück zu pushState. Reaktiviert den zuoberst auf dem 
   * Zustandsstack abgelegten Turtlezustand und entfernt dann das
   * oberste Element vom Stack.
   */
  public void popState()
  {
    if (!stateStack.empty()) {
      state = (TurtleState)stateStack.pop();
    }
  }
}

/**
 * Die Klasse TurtleState dient dazu, den aktuellen Turtlezustand
 * festzuhalten. Sie wurde gewählt, um die Implementierung des
 * Zustandstacks zu vereinfachen.
 */
class TurtleState
{
  boolean   pendown;
  int       actx;
  int       acty;
  double    actangle;
}


