import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Label;
import java.awt.Font;
import java.awt.Color;

/**
   Eine Klasse zu pädagogischen Zwecken (Einführung in die Informatik:
   Programmierung und Software-Entwicklung im WS 02/03, Institut für
   Informatik der Universität München). Erlaubt die Eingabe von Punktkoordinaten
   mittels Mausklicks sowie das Zeichnen von Linien.
   @version 2.0
*/

public class GraphicsWindow extends JFrame implements MouseListener {
  
    private static int fensterZahl;
    private static int fensterNr;
    private Label label;
    private GraphicsWindowPanel panel;
    private Point mousePos;
    /**
       Erzeugt ein Fenster mit Textausgabe, Mauseingabe und Grafikausgabe.
    */
    public GraphicsWindow() {
	super();
	setTitle("Grafikfenster " + ++fensterNr);
	fensterZahl++;
	setSize(610,650);
	
	addWindowListener(new WindowAdapter(){
		public void windowClosing(WindowEvent e) {
		    dispose(); // nicht gleich alle Fenster abschiessen
		    if (--fensterZahl<1) System.exit(0);
		}
	    });

	label = new Label("Statuszeile...");
	label.setFont(new Font("Helvetica", Font.PLAIN, 12));
	getContentPane().add(label,"North" );
	
	panel = new GraphicsWindowPanel();
	//panel.setBackground(Color.cyan);
	getContentPane().add(panel,"Center");
	
	mousePos = new Point();
	panel.addMouseListener(this);
	show();
    }
    /**
       Gibt eine Zeichenkette oben im Fenster aus.
       @param text diese Zeichenkette
    */
    public void setText(String text) {
	label.setText(text);
    }
    /**
       Liest den oben im Fenster angezeigten Text aus.
       @return den Text
    */ 
    public String getText() {
	return label.getText();
    }
    /**
       Wartet auf einen Mausklick.
       (Das Warten verbraucht im Gegensatz zur Version 1.0
       vom 30. Oktober 2002 keine CPU-Ressourcen.)
       @return die Koordinaten des angeklickten Punkts
    */
    synchronized public Point mouseClick() {
	try{
	    wait();
	}
	catch(InterruptedException e){
	    e.printStackTrace();
	}
	return mousePos;
    }
    /**
       Beendet das Warten auf den Mausklick und verwertet die Koordinaten.
       Diese Methode ist nicht für den Anwender bestimmt.
     */
    synchronized public void mouseClicked(MouseEvent e){
	mousePos = e.getPoint();
	notify();
    }

    /**
       leere Implementierung (nur aus formalen Gründen vorhanden)
    */
    public void mouseEntered(MouseEvent e){
    }
    /**
       leere Implementierung (nur aus formalen Gründen vorhanden)
    */
    public void mouseExited(MouseEvent e){
    }
    /**
       leere Implementierung (nur aus formalen Gründen vorhanden)
    */
    public void mousePressed(MouseEvent e){
    }
    /**
       leere Implementierung (nur aus formalen Gründen vorhanden)
    */
    public void mouseReleased(MouseEvent e){
    }


    /**
       Schaltet die Zeichenfarbe auf die Hintergrundfarbe um. Dies ist das
       Mittel, um gezeichnete Linien wieder zu löschen.
    */
    public void switchToBackgroundColour(){
	//	panel.kommandos[panel.kommandoZahl++] = -1;
	//panel.repaint();
	panel.addCommand(new Command(Command.SETBACKGROUND));
	panel.repaint();
    }
    /**
       Schaltet die Zeichenfarbe (wieder) auf Schwarz um.
    */
    public void switchToForegroundColour(){
	panel.addCommand(new Command(Command.SETFOREGROUND));
	panel.repaint();
    }
    /**
       Zeichnet eine Linie in der aktuellen Zeichenfarbe.
       Hinweis für Insider: Werden die Parameter (die Punkt-Objekte sind)
       nach dem Aufruf dieser Methode geändert, so ändert sich die bisherige
       Zeichnung im Grafikfenster NICHT.
       @param x Anfangspunkt
       @param y Endpunkt
    */
    public void drawLine(Point x, Point y){
	panel.addCommand(new Command(Command.DRAWLINE,x,y));
	panel.repaint();
    }

    /**
       Zeichnet ein gefuelltes Rechteck in der angegebenen Zeichenfarbe.
       @param p linke, obere Ecke
       @param width Breite
       @param height Hoehe
       @param color Farbe
    */
    public void drawBox(Point p, int width, int height, Color color)
    {
	panel.addCommand(new Command(Command.DRAWBOX,p,width,height,color));
	panel.repaint();
    }
}

/** Eine Hilfsklasse fuer das Panel. */
class GraphicsWindowPanel extends JPanel
{
    private CommandList cl = new CommandList();

    public void paintComponent(Graphics g)
    {
	super.paintComponent(g);
	Graphics2D g2D = (Graphics2D)g;
	cl.reset();
	while (cl.isNextPresent())
        {
            (cl.next()).execute(g2D);
        }
    }

    /** Fuegt einen Zeichenbefehl in die Liste der Befehle ein, die zu diesem Panel gehoeren. */
    public void addCommand(Command c)
    {
	cl.add(c);
    }
}


/** Eine Klasse fuer Zeichenbefehle */
class Command
{
    final public static int ERROR=-1;
    final public static int DRAWLINE = 0;
    final public static int SETFOREGROUND=1;
    final public static int SETBACKGROUND=2;
    final public static int DRAWBOX=3;

    private Point ersterPunkt=null,zweiterPunkt=null;
    private int breite=0,hoehe=0;
    private Color color=null;

    private int command=-1;

    /** Erzeugt einen Befehl zum Zeichnen einer Linie.
	@param c muss Command.DRAWLINE sein
	@param p1 erster Punkt
	@param p2 zweiter Punkt */
    Command(int c,Point p1,Point p2)
    {
	if (c == DRAWLINE)
	{
	    ersterPunkt = new Point(p1);
	    zweiterPunkt = new Point(p2);
	    command=c;
	}
    }

    /** Erzeugt einen Befehl zum Setzen der aktuellen Zeichenfarbe.
	@param c setzt die Farbe auf Vordergrund, falls c==Command.SETFOREGROUND
	und auf den Hintergrund, falls c==Command.SETBACKGROUND */
    Command(int c)
    {
	if (c == SETFOREGROUND || c == SETBACKGROUND)
	{
	    command = c;
	}
    }

    /** Erzeugt einen Befehl zum Zeichnen eines gefuellten Rechtecks.
	@param c muss Command.DRAWBOX sein
        @param p linke, obere Ecke
        @param b Breite
        @param h Hoehe
        @param d Farbe */
    Command(int c,Point p, int b, int h,Color d)
    {
	if (c == DRAWBOX)
	{
	    command = c;
	    ersterPunkt = new Point(p);
	    breite = b;
	    hoehe = h;
	    color = d;
	}
    }
	  
    /** liefert die Kennzahl des Befehl */
    public int command()
    {
	return command;
    }

    /** fuehrt den Befehl in einem Grafikfenster aus
	@param g2D das enstprechende Graphics2D-Objekt */
    public void execute(Graphics2D g2D)
    {
	if (command == DRAWLINE)
	{
	    g2D.drawLine(ersterPunkt.x,ersterPunkt.y,zweiterPunkt.x,zweiterPunkt.y);
	}
	else if (command == SETFOREGROUND)
	{
	    g2D.setColor(Color.black);
	}
	else if (command == SETBACKGROUND)
	{
	    g2D.setColor(g2D.getBackground());
	}
	else if (command == DRAWBOX) 
	{ 
	    Color currentColor=g2D.getColor();
	    g2D.setColor(color);
	    g2D.fillRect(ersterPunkt.x,ersterPunkt.y,breite,hoehe);
	    g2D.setColor(currentColor);
	}
	else
	{
	    System.out.println(command);
	    System.exit(-1); // waere ein Programmierfehler
	}
    }
}



/** Eine Klasse fuer unbeschraenkt grosse Listen von Zeichenbefehlen. */
class CommandList
{
    private int length=10;
    private int readIndex,writeIndex;
    private Command[] list;
    private int numberOfObjects;

    private int current = 0;

    /** Erzeugt eine leere Liste von Zeichenbefehlen. */
    public CommandList()
    {
	list = new Command[length];
	writeIndex=0;
	numberOfObjects = 0;
    }

    /** Fuegt einen Zeichenbefehl in die Liste ein. */
    public void add(Command x)
    {
	int i,j=0;
	int newLength;

	if (writeIndex == length)
	{
	    newLength = length*2;
	    Command[] auxList = new Command[newLength];

	    for (i=0; i<writeIndex; i++)
	    {
		auxList[i] = list[i];
	    }
	    list = auxList;
	    length = newLength;
	}
	list[writeIndex] = x;
	writeIndex++;
    }

    /** Setzt den Lesezeiger dieser Liste auf das erste Element. */
    public void reset()
    {
	current=0;
    }

    /** Liefert das naechste Element der Liste, d.h. das, auf den 
	der Lesezeiger zeigt. Dieser wird um eins erhoeht. */
    public Command next()
    {
	if (current < writeIndex)
	{
	    Command c = list[current];
	    current++;
	    return c;
	}
	else
	{
	    return null;
	}
    }

    /** Prueft, ob noch weitere Elemente aus der Liste gelesen werden koennen. */
    public boolean isNextPresent()
    {
	return (current < writeIndex);
    }
}
