 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Class ZEICHENFENSTER - Eine Klasse, die einfache grafische Zeichnungen 
 * in einem Programmfenster ermoeglicht.
 * 
 * @author Michael Kolling (mik)
 * @author Bruce Quig
 * @author Christian Heidrich
 * @author Daniel Keitel
 *
 * @version 2015.12.18
 */

public class ZEICHENFENSTER{
    private JFrame frame;
    private CanvasPane canvas;
    private JPanel steuerungOst,steuerungSued;
    private Color hintergrundFarbe;
    private Color vordergrundFarbe;
    private Font schriftArt;
    private boolean autoMalen;
    
    private static ZEICHENFENSTER singleton;

    /**
     * Erzeugt eine Zeichenfenster mit Standardmassen 600*500 und Hintergrundfarbe weiss 
     * @param titel  Titel des Fensters     
     */
    public ZEICHENFENSTER(String titel){
        this(titel, 600, 500, Color.white,true);        
    }

    /**
     * Erzeugt ein Zeichenfenster mit weissem Hintergrund.
     * @param titel  Fensterueberschrift
     * @param breite  Breite der Zeichenflaeche
     * @param hoehe  Hoehe der Zeichenflaeche
     */
    public ZEICHENFENSTER(String titel, int breite, int hoehe){
        this(titel, breite, hoehe, Color.white,true);
    }
    
    /**
     * Erzeugt ein Zeichenfenster mit weissem Hintergrund.
     * @param titel  Fensterueberschrift
     * @param breite  Breite der Zeichenflaeche
     * @param hoehe  Hoehe der Zeichenflaeche
     * @param autoMalen Parameter, ob das gezeichnete automatisch sichtbar wird,
     * zum manuellen Zeichnen male(); aufrufen
     */
    public ZEICHENFENSTER(String titel, int breite, int hoehe,boolean autoMalen){
        this(titel, breite, hoehe, Color.white,autoMalen);
    }

    /**
     * Erzeugt ein Zeichenfenster.
     * @param titel  Fensterueberschrift
     * @param breite  Breite der Zeichenflaeche
     * @param hoehe  Hoehe der Zeichenflaeche
     * @param hintergrundFarbe  Hintergrundfarbe des Zeichenfensters
     * @param autoMalen Parameter, ob das gezeichnete automatisch sichtbar wird,
     * zum manuellen Zeichnen male(); aufrufen
     */
    private ZEICHENFENSTER(String titel, int breite, int hoehe, Color hintergrundFarbe,boolean autoMalen){
        frame = new JFrame();
        canvas = new CanvasPane();
        canvas.setPreferredSize(new Dimension(breite, hoehe));
        frame.getContentPane().add(canvas,BorderLayout.CENTER);
        JPanel p1=new JPanel();
        p1.setLayout(new BorderLayout());
        steuerungOst = new JPanel();
        steuerungSued = new JPanel();
        steuerungOst.setLayout(new BoxLayout(steuerungOst,BoxLayout.Y_AXIS));
        steuerungSued.setLayout(new BoxLayout(steuerungSued,BoxLayout.X_AXIS));
        p1.add(steuerungOst,BorderLayout.NORTH);
        frame.getContentPane().add(p1,BorderLayout.EAST);
        frame.getContentPane().add(steuerungSued,BorderLayout.SOUTH);
        frame.setTitle(titel);
        vordergrundFarbe = Color.BLACK;
        schriftArt = new Font(Font.SERIF, Font.PLAIN, 15);
        this.hintergrundFarbe = hintergrundFarbe;
        this.autoMalen = autoMalen;
        frame.pack();
        canvas.addClear(hintergrundFarbe);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        male();
        zeige();
    }
    
    /**
     * Gibt ein Standart Zeichenfenster mit dem Titel "Das Zeichenfenster" zurueck
     * @return instanz des Fensters
     */
    public static ZEICHENFENSTER gibFenster(){
        if (singleton==null){singleton=new ZEICHENFENSTER("Das Zeichenfenster");}
        singleton.zeige();
        return singleton;
    }

    /**
     * Macht das Zeichenfenster sichtbar bzw. setzt es in den Vordergrund,
     * falls es bereits sichtbar ist.
     */
    public void zeige(){
        frame.setVisible(true);
    }
    
    /**
     * Zeigt die aktuell gepufferten Zeichen befehle an
     * wird bei deaktivirten AutoMalen benoetigt
     */
    public void male(){
    	canvas.draw();
	}

    /**
     * Gibt Information ueber die Sichtbarkeit.
     * @return  true falls das Fenster sichtbar ist.
     */
    public boolean istSichtbar(){
        return frame.isVisible();
    }
    
    /**
     * Setz den Parameter Automalen
     * @param autoMalen Parameter, ob das gezeichnete automatisch sichtbar wird,
     * zum manuellen Zeichnen male(); aufrufen
     */
    public void setzeAutomalen(boolean autoMalen){
    	this.autoMalen = autoMalen;
    }
    
    /**
     * Zeichnet einen Elipsenbogen (Siehe Graphics.drawArc)
     * @param x x-Koordinate des Elipsenmittelpunkts
     * @param y y-Koordinate des Elipsenmittelpunkts
     * @param halbachseX Halbachse der Elipse in x-Richtung
     * @param halbachseY Halbachse der Elipse in y-Richtung
     * @param startWinkel Polarwinkel, an dem der Bogen anfaengt
     * @param winkel Polarwinkel, welchen der Bogen durchlaeuft
     */
    public void zeichneBogen(int x, int y, int halbachseX, int halbachseY, int startWinkel, int winkel){
    	Shape s = new Arc2D.Float(x-halbachseX,y-halbachseY,2*halbachseX,2*halbachseY,startWinkel,winkel, Arc2D.OPEN);
    	canvas.addShape(s, 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Zeichnet einen Kreis (Siehe Graphics.drawOval)
     * @param x x-Koordinate des Mittelpunkts
     * @param y y-Koordinate des Mittelpunkts
     * @param radius Kreisradius
     */
    public void zeichneKreis(int x, int y, int radius){
    	canvas.addShape(new Ellipse2D.Float(x-radius,y-radius,2*radius,2*radius), 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere eines Kreises mit der angegebenen Farbe. 
     * @param x x-Koordinate des Mittelpunkts
     * @param y y-Koordinate des Mittelpunkts
     * @param radius Kreisradius
     * @param  farbe  Fuellfarbe fuer den Kreis, erlaubt sind "weiss" "schwarz" "rot"
     * "gruen" "blau" "gelb" "magenta" "cyan" "grau"
     */
    public void fuelleKreis(int x, int y, int radius, String farbe){
    	canvas.addShape(new Ellipse2D.Float(x-radius,y-radius,2*radius,2*radius), 0f, true, farbeZuColor(farbe));
        if(autoMalen)male();
    }
    
    /**
     * Fuellt das Innere eines Kreises mit der angegebenen Farbe. 
     * @param x x-Koordinate des Mittelpunkts
     * @param y y-Koordinate des Mittelpunkts
     * @param radius Kreisradius
     * @param  farbnr  Fuellfarbnummer fuer den Kreis (0 bis 8)
     */
    public void fuelleKreis(int x, int y, int radius, int farbnr){
    	canvas.addShape(new Ellipse2D.Float(x-radius,y-radius,2*radius,2*radius), 0f, true, farbeZuColor(farbnr));
        if(autoMalen)male();
    }

	/**
     * Loescht das Innere eines Kreises 
     * @param x x-Koordinate des Mittelpunkts
     * @param y y-Koordinate des Mittelpunkts
     * @param radius Kreisradius
     */
    public void loescheKreis(int x, int y, int radius){
        Ellipse2D.Double circle = new Ellipse2D.Double(x-radius, y-radius, 2*radius, 2*radius);
        loesche(circle);
    }

    /**
     * Zeichnet den Umriss eines Shape-Objekts.
     * @param  shape  das Shape-Object, welches gezeichnet werden soll
     */
    public void zeichne(Shape shape){
    	canvas.addShape(shape, 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }
 
    /**
     * Fuellt das Innere eines Shape-Objekts mit der angegebenen Farbe. 
     * @param  shape  das Shape-Objekt, welches gefuellt werden soll 
     * @param  farbe  Fuellfarbe fuer das Shape-Objekt, erlaubt sind "weiss" "schwarz" "rot"
     * "gruen" "blau" "gelb" "magenta" "cyan" "grau"
     */
    public void fuelle(Shape shape, String farbe){
    	canvas.addShape(shape, 0f, true, farbeZuColor(farbe));
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere eines Shape-Objekts mit der angegebenen Farbe. 
     * @param  shape  das Shape-Objekt, welches gefuellt werden soll 
     * @param  farbnr  Fuellfarbnummer fuer das Shape-Objekt (0 bis 8)
     */
    public void fuelle(Shape shape, int farbnr){
    	canvas.addShape(shape, 0f, true, farbeZuColor(farbnr));
        if(autoMalen)male();
    }

    /**
     * Loescht das Innere eines Shape-Objekts.
     * @param  shape  das Shape-Object, welches geloescht werden soll 
     */
    public void loesche(Shape shape){
    	canvas.addShape(shape, 0f, true, hintergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Zeichnet den Rand des Rechtecks mit der aktuellen Farbe.
     * @param xPos,yPos Koordinaten der linken oberen Ecke 
     * @param breite, hoehe Breite und Hoehe des Rechtecks
     */
    public void zeichneRechteck(int xPos, int yPos, int breite, int hoehe){
    	canvas.addShape(new Rectangle(xPos, yPos, breite, hoehe), 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere des Rechtecks mit der angegebenen Farbe.
     * @param xPos,yPos Koordinaten der linken oberen Ecke 
     * @param breite, hoehe Breite und Hoehe des Rechtecks
     * @param  farbe  Fuellfarbe fuer das Rechteck, erlaubt sind "weiss" "schwarz" "rot"
     * "gruen" "blau" "gelb" "magenta" "cyan" "grau"
     */
    public void fuelleRechteck(int xPos, int yPos, int breite, int hoehe, String farbe){
    	canvas.addShape(new Rectangle(xPos, yPos, breite, hoehe), 0f, true, farbeZuColor(farbe));
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere des Rechtecks mit der angegebenen Farbe.
     * @param xPos,yPos Koordinaten der linken oberen Ecke 
     * @param breite, hoehe Breite und Hoehe des Rechtecks
     * @param  farbnr  Fuellfarbnummer fuer das Rechteck (0 bis 8)
     */
    public void fuelleRechteck(int xPos, int yPos, int breite, int hoehe, int farbnr){
    	canvas.addShape(new Rectangle(xPos, yPos, breite, hoehe), 0f, true, farbeZuColor(farbnr));
        if(autoMalen)male();
    }

    /**
     * Loescht das Innere eines Rechtecks.
     * @param xPos,yPos Koordinaten der linken oberen Ecke 
     * @param breite, hoehe Breite und Hoehe des Rechtecks
     */
    public void loescheRechteck(int xPos, int yPos, int breite, int hoehe){
        loesche(new Rectangle(xPos, yPos, breite, hoehe));
    }
    
    private Polygon gibDreieck(int x1, int y1, int x2, int y2, int x3, int y3){
        Polygon p=new Polygon();
        p.addPoint(x1,y1);
        p.addPoint(x2,y3);
        p.addPoint(x3,y3);
        return p;
    }
    
    /**
     * Zeichnet den Rand eines Dreiecks mit der aktuellen Farbe.
     * @param x1,y1 Koordinaten des ersten Eckpunkts 
     * @param x2,y2 Koordinaten des zweiten Eckpunkts 
     * @param x3,y3 Koordinaten des dritten Eckpunkts 
     */
    public void zeichneDreieck(int x1, int y1, int x2, int y2, int x3, int y3){
    	canvas.addShape(gibDreieck(x1, y1, x2, y2, x3, y3), 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere eines Dreiecks mit der angegebenen Farbe.
     * @param x1,y1 Koordinaten des ersten Eckpunkts 
     * @param x2,y2 Koordinaten des zweiten Eckpunkts 
     * @param x3,y3 Koordinaten des dritten Eckpunkts 
     * @param  farbe  Fuellfarbe fuer das Dreieck, erlaubt sind "weiss" "schwarz" "rot"
     * "gruen" "blau" "gelb" "magenta" "cyan" "grau"
     */
    public void fuelleDreieck(int x1, int y1, int x2, int y2, int x3, int y3, String farbe){
        canvas.addShape(gibDreieck(x1, y1, x2, y2, x3, y3), 0f, true, farbeZuColor(farbe));
        if(autoMalen)male();
    }

    /**
     * Fuellt das Innere eines Dreiecks mit der angegebenen Farbe.
     * @param x1,y1 Koordinaten des ersten Eckpunkts 
     * @param x2,y2 Koordinaten des zweiten Eckpunkts 
     * @param x3,y3 Koordinaten des dritten Eckpunkts 
     * @param  farbnr  Fuellfarbnummer fuer das Dreieck (0 bis 8)
     */
    public void fuelleDreieck(int x1, int y1, int x2, int y2, int x3, int y3, int farbnr){
        canvas.addShape(gibDreieck(x1, y1, x2, y2, x3, y3), 0f, true, farbeZuColor(farbnr));
        if(autoMalen)male();
    }

    /**
     * Loescht das Innere eines Dreicks
     * @param x1,y1 Koordinaten des ersten Eckpunkts 
     * @param x2,y2 Koordinaten des zweiten Eckpunkts 
     * @param x3,y3 Koordinaten des dritten Eckpunkts 
     */
    public void loescheDreieck(int x1, int y1, int x2, int y2, int x3, int y3){
        loesche(gibDreieck(x1, y1, x2, y2, x3, y3));
    }

    /**
     * Loescht den Inhalt des Zeichenfensters.
     */
    public void loescheAlles(){
        canvas.addClear(hintergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Loescht den Umriss eines Shape-Objekts.
     * @param  shape  das Shape-Object, dessen Umriss geloescht werden soll 
     */
    public void loescheRand(Shape shape){
       canvas.addShape(shape, 0f, false, hintergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Zeichnet ein Bild in das Zeichnenfenster .
     * @param  bild    das anzuzeigende Bild 
     * @param  x       x-Koordinate des linken Bildrands 
     * @param  y       y-Koordinate des oberen Bildrands 
     * @return  gibt eines booleschen Wert zurueck, der angibt, ob das Bild vollstaendig geladen 
     *          werden konnte 
     */
    public boolean zeichneBild(Image bild, int x, int y){
    	if(bild.getWidth(null) == 0 || bild.getHeight(null) == 0)return false;
    	canvas.addImage(bild, 0, new Rectangle(x, y, bild.getWidth(null), bild.getHeight(null)));
        if(autoMalen)male();
        return true;
    }
    
    /**
     * Zeichnet ein Bild in das Zeichnenfenster .
     * @param  bild    das anzuzeigende Bild 
     * @param  x       x-Koordinate der oberen linken Ecke
     * @param  y       y-Koordinate der oberen linken Ecke 
     * @param  breite  breite des Bildes
     * @param  hoehe   hoehe des Bildes
     * @param  winkel  rotations winkel im Urzeigersinn um die oberen linken Ecke
     * @return  gibt eines booleschen Wert zurueck, der angibt, ob das Bild vollstaendig geladen 
     *          werden konnte 
     */
    public boolean zeichneBild(BufferedImage bild, int x, int y,int breite,int hoehe,int winkel){
    	if(bild.getWidth(null) == 0 || bild.getHeight(null) == 0)return false;
    	canvas.addImage(bild, winkel, new Rectangle(x, y, bild.getWidth(null), bild.getHeight(null)));
        if(autoMalen)male();
        return true;
    }

    /**
     * Zeichnet einen Text.
     * @param  text    die anzuzeigende Zeichenkette 
     * @param  x       x-Koordinate des linken Rands 
     * @param  y       y-Koordinate des oberen Rands 
     */
    public void zeichneText(String text, int x, int y){
    	canvas.addString(text, 0f, schriftArt, vordergrundFarbe, x, y);
        if(autoMalen)male();
    }

    /**
     * Loescht einen Text vom Zeichenfenster.
     * @param  text    die zu loeschende Zeichenkette 
     * @param  x       x-Koordinate des linken Rands 
     * @param  y       y-Koordinate des oberen Rands 
     */
    public void loescheText(String text, int x, int y){
    	canvas.addString(text, 0f, schriftArt, hintergrundFarbe, x, y);
        if(autoMalen)male();
    }

    /**
     * Zeichnet eine Strecke ins Zeichenfenster.
     * @param  x1   x-Koordinate des Anfangspunkts der Strecke 
     * @param  y1   y-Koordinate des Anfangspunkts der Strecke
     * @param  x2   x-Koordinate des Endpunkts der Strecke 
     * @param  y2   y-Koordinate des Endpunkts der Strecke  
     */
    public void zeichneStrecke(int x1, int y1, int x2, int y2){
        canvas.addShape(new Line2D.Float(x1, y1, x2, y2), 0f, false, vordergrundFarbe);
        if(autoMalen)male();
    }

    /**
     * Setzt die Vordergrundfarbe des Zeichenfensters.
     * @param  neueFarbe   neue Vordergrundfarbe 
     */
    public void setzeVordergrundFarbe(String neueFarbe){
        vordergrundFarbe = farbeZuColor(neueFarbe);
    }
    
    /**
     * Setzt die Vordergrundfarbe des Zeichenfensters.
     * @param  neueFarbe   neue Vordergrundfarbe 
     */
    public void setzeVordergrundFarbe(int neueFarbe){
        vordergrundFarbe = farbeZuColor(neueFarbe);
    }
    
    private Color farbeZuColor(int farbnr){
        switch (farbnr)
        {
            case 0: return Color.black;
            case 1: return Color.blue;
            case 2: return Color.green;
            case 3: return Color.cyan;
            case 4: return Color.red;
            case 5: return Color.magenta;
            case 6: return Color.yellow;
            case 7: return Color.gray;
            case 8: return Color.white;
            default: return vordergrundFarbe;
        }
        
    }
    
    private Color farbeZuColor(String farbe){
        if (farbe=="weiss") return Color.white;
        if (farbe=="schwarz") return Color.black;
        if (farbe=="rot") return Color.red;
        if (farbe=="gruen") return Color.green;
        if (farbe=="blau") return Color.blue;
        if (farbe=="gelb") return Color.yellow;
        if (farbe=="magenta") return Color.magenta;
        if (farbe=="cyan") return Color.cyan;
        if (farbe=="grau") return Color.gray;
        return vordergrundFarbe;
    }
    
    private String colorZuFarbe(Color color){
        if (color==Color.white) return "weiss";
        if (color==Color.black) return "schwarz";
        if (color==Color.red) return "rot";
        if (color==Color.green) return "gruen";
        if (color==Color.blue) return "blau";
        if (color==Color.yellow) return "gelb";
        if (color==Color.magenta) return "magenta";
        if (color==Color.cyan) return "cyan";
        if (color==Color.gray) return "grau";
        return color.toString();
    }

    /**
     * Gibt die aktuelle Vordergrundfarbe des Zeichenfensters zurueck.
     * @return   die aktuelle Vordergrundfarbe 
     */
    public String gibVordergrundFarbe(){
        return colorZuFarbe(vordergrundFarbe);
    }

    /**
     * Setzt die Hintergrundfarbe des Zeichenfensters.
     * @param  neueFarbe   neue Hintergrundfarbe 
     */
    public void setzeHintergrundFarbe(String neueFarbe){
        hintergrundFarbe = farbeZuColor(neueFarbe);   
    }
    
    /**
     * Setzt die Hintergrundfarbe des Zeichenfensters.
     * @param  neueFarbe   neue Hintergrundfarbe 
     */
    public void setzeHintergrundFarbe(int neueFarbe){
        hintergrundFarbe = farbeZuColor(neueFarbe);   
    }

    /**
     * Gibt die aktuelle Hintergrundfarbe des Zeichenfensters zurueck.
     * @return   die aktuelle Hintergrundfarbe 
     */
    public String gibHintergrundFarbe(){
        return colorZuFarbe(hintergrundFarbe);
    }

    /**
     * Aendert den aktuellen Zeichensatz des Zeichenfensters.
     * @param  neuerZeichensatz   Zeichensatz, der kuenftig fuer Zeichenkettenausgaben verwendet wird
     */
    public void setzeZeichensatz(Font neuerZeichensatz){
        schriftArt = neuerZeichensatz;
    }

    /**
     * Gibt den aktuellen Zeichensatz des Zeichenfensters zurueck.
     * @return     den aktuellen Zeichensatz
     **/
    public Font gibZeichensatz(){
        return schriftArt;
    }

    /**
     * Aendert die Abmessungen des Zeichenfensters.
     * @param  breite    neue Breite 
     * @param  hoehe     neue Hoehe 
     */
    public void setzeMasse(int breite, int hoehe){
        canvas.setPreferredSize(new Dimension(breite, hoehe));
        frame.pack();
    }

    /**
     * Gibt die Abmessungen des Zeichenfensters zurueck.
     * @return  die aktuellen Abmessungen des Zeichenfensters
     */
    public Dimension gibMasse(){
        return canvas.getSize();
    }

    /**
     * Wartet eine bestimmte Zeit.
     * Eine kurze Verzoegerung kann z. B. fuer Animationen verwendet werden.
     * @param  zeit  Wartezeit in Millisekunden 
     */
    public void warte(int zeit){
        try{
            Thread.sleep(zeit);
        }
        catch (InterruptedException e){
            // sehr unwarschenliche Ausnahme
        }
    }

    /**
     * Fuegt ein weiteres Steuerungselement in die rechte Steuerungsleiste ein.
     * @param  element  Das einzufuegende Steuerungselement muss aus JComponent abgeleitet
     * sein. z. B. JButton, JComboBox. 
     */
    public void komponenteHinzufuegen(JComponent element, String position){
        if (position.equals("rechts")) steuerungOst.add(element);
        else if (position.equals("unten")) steuerungSued.add(element);
        frame.pack();
    }
    
    /**
     * Beschriftet den Titel des Zeichenfensters neu.
     * @param  titelNeu  Text der neuen Fensterueberschrift
     */
    public void setzeTitel(String titelNeu){
        frame.setTitle(titelNeu);
    }

     
    /************************************************************************
     * Verschachtelte Klasse CanvasPane - 
     * Element (JPanel) auf dem gezeichnet werden kann
     * @author Daniel Keitel
     */
    @SuppressWarnings("unused")
    private class CanvasPane extends JPanel{
		private static final long serialVersionUID = -7848726259938668008L;
		private List<Shape2D> commands = new ArrayList<Shape2D>();
		private List<Shape2D> buffer = new ArrayList<Shape2D>();
		private Graphics2D graphics;
		private BufferedImage image;
		private int w,h;
		private Color backgroundColor = Color.DARK_GRAY;
		private Lock lock = new ReentrantLock();
		
		public CanvasPane(){
			w = Toolkit.getDefaultToolkit().getScreenSize().width;
			h = Toolkit.getDefaultToolkit().getScreenSize().height;
		}
		
		public CanvasPane(int maxW , int maxH,Color backgrundColor){
			this.backgroundColor = backgrundColor;
			w = Math.min(maxW, Toolkit.getDefaultToolkit().getScreenSize().width);
			h = Math.min(maxH, Toolkit.getDefaultToolkit().getScreenSize().height);
		}
		
		public void setMax(int maxW , int maxH,Color backgrundColor){
			this.backgroundColor = backgrundColor;
			w = Math.min(maxW, Toolkit.getDefaultToolkit().getScreenSize().width);
			h = Math.min(maxH, Toolkit.getDefaultToolkit().getScreenSize().height);
		}
		
		public void setBackgroundColor(Color color){
			backgroundColor = color;
		}
		
		public void addShape(Shape shape,float angle,boolean fill,Color color){
			buffer.add(new Shape2D(shape, angle, fill, color));
		}
		
		public void addClear(Color backgroundColor){
			buffer.add(new Shape2D(new Rectangle(0, 0, w, h), 0f,true,backgroundColor));
		}
		
		public void addImage(Image image, float angle,Rectangle bounds){
			if(image.getWidth(null) < 1 || image.getHeight(null) < 1)return;
			buffer.add(new Shape2D(image, angle, bounds));
		}
		
		public void addString(String text,float angle,Font font,Color color,int x,int y){
			buffer.add(new Shape2D(text, angle, color, font, x, y));
		}

		public Dimension getSize(){
			return new Dimension(w, h);
		}
		
		public void draw(){
			lock.lock();
			commands.addAll(buffer);
			buffer.clear();
			lock.unlock();
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			repaint();
		}
		
		@Override
		public void paint(Graphics g) {
			if(image == null){
				image = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
			}
			if(image.getWidth(null) != w || image.getHeight(null) != h){
				Image oldImage = image;
				image = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
		        graphics = (Graphics2D)image.getGraphics();
		        graphics.setColor(backgroundColor);
		        graphics.fillRect(0, 0, w, h);
		        graphics.drawImage(oldImage, 0, 0, null);
			}
			graphics = (Graphics2D) image.getGraphics();
			lock.lock();
			while(commands.size() > 0){
				Shape2D s = commands.remove(0);
				Graphics2D temp;
				switch (s.o) {
				case 0:
					temp = (Graphics2D) graphics.create();
					temp.rotate(Math.toRadians(s.angle), s.shape.getBounds().getX(), s.shape.getBounds().getY());
					temp.setColor(s.color);
					temp.draw(s.shape);
					break;
				case 1:
					temp = (Graphics2D) graphics.create();
					temp.rotate(Math.toRadians(s.angle), s.shape.getBounds().getX(), s.shape.getBounds().getY());
					temp.setColor(s.color);
					temp.fill(s.shape);
					break;
				case 2:
					temp = (Graphics2D) graphics.create();
					temp.rotate(Math.toRadians(s.angle), s.bounds.x, s.bounds.y);
					temp.drawImage(s.image, s.bounds.x, s.bounds.y, s.bounds.width, s.bounds.height, null);
					break;
				case 3:
					temp = (Graphics2D) graphics.create();
					temp.rotate(Math.toRadians(s.angle), s.bounds.x, s.bounds.y);
					temp.setColor(s.color);
					temp.setFont(s.font);
					temp.drawString(s.text, s.bounds.x, s.bounds.y);
					break;
				}
			}
			lock.unlock();
			g.drawImage(image, 0, 0, null);
			graphics.dispose();
		}
		
		private class Shape2D{
			public Shape shape;
			public Rectangle bounds;
			public Image image;
			public String text;
			public Font font;
			public int o;//Operation: 0:draw 1:fill 2:image 3:Text
			public float angle;
			public Color color;
			public Shape2D(Shape shape,float angle, boolean fill,Color color){
				this.shape = shape;
				this.color = color;
				this.angle = angle;
				o = fill ? 1 : 0;
			}
			public Shape2D(Image image,float angle,Rectangle bounds){
				this.image = image;
				this.bounds = bounds;
				this.angle = angle;
				o = 2;
			}
			public Shape2D(String text,float angle, Color color,Font font,int x,int y){
				this.text = text;
				this.font = font;
				this.color = color;
				this.angle = angle;
				bounds = new Rectangle(x, y, 0, 0);
				o = 3;
			}
		}
	}
}