import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot und MouseInfo)
import java.awt.image.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.BasicStroke;
import java.awt.Font;
import java.util.Vector;
import java.awt.Color;
import javax.imageio.*;
import java.io.*;

/**
 * Der PictureViewer ist ein Actor, der in der Lage ist ein
 * Objekt der Klasse Picture anzuzeigen. Zusätzlich können 
 * mehrere Verarbeitungsschritte gespeichert werden, um ein
 * "zurück"-Funktion zu ermöglichen.
 * 
 * @author Thomas Schaller
 * @version V1.0 vom 10.02.2019
 */
public class PictureViewer extends Actor
{
    public static final int FIT = -1;
    public static final int NORMAL = 1;

    private static final int ANZ_BACK = 0;

    private double zoom;
    protected Picture  picture = null;
    private Vector<BufferedImage> history;

    /**
     * Erzeugt ein ScrollPanel mit integriertem Bild der Größe 500x400
     */
    public PictureViewer() {
        this(500,400);
    }

    /**
     * Erzeugt ein ScrollPanel mit integriertem Bild der angegebenen Größe 
     * @param width Breite des Bildes
     * @param height Höhe des Bildes
     */
    public PictureViewer(int width, int height) {
        this(width,height, "D0D0D0");
    }

    /**
     * Erzeugt ein ScrollPanel mit integriertem Bild der angegebenen Größe 
     * @param width Breite des Bildes
     * @param height Höhe des Bildes
     * @param background Farbe des Hintergrunds als HEX-String (z.B. "FF3A45")
     */
    public PictureViewer(int width, int height, String background) {
        picture = new Picture(width,height, background);
        picture.setObserver(this);
        this.history = new Vector<BufferedImage>();
        this.zoom = NORMAL;
        repaint();
    }

    /**
     * Erzeugt ein ScrollPanel mit integriertem Bild aus einer Bilddatei
     * @param filename Name des Bildes
     */
    public PictureViewer(String filename) {
        picture = new Picture(filename);
        picture.setObserver(this);
        this.history = new Vector<BufferedImage>();
        this.zoom = NORMAL;
        repaint();
    }

    /**
     * Speichert das übergebene Bild in der History.
     * @param b zu speicherndes Bild
     */    
    public void pushImage() {
        if( this.ANZ_BACK > 0) {
            if(history.size() == this.ANZ_BACK) {
                history.removeElementAt(0);
            }

            BufferedImage b = new BufferedImage(picture.getWidth(), picture.getHeight(), picture.getImage().getType());
            Graphics g = b.getGraphics();
            g.drawImage(picture.getImage(), 0, 0, null);
            g.dispose();

            history.add(b);
        }   
    }

    /**
     * Ruft das letzte abgespeicherte Bild aus der History wieder auf.
     */
    private void popImage() {
        int anz = history.size();
        if(anz>0) {
            BufferedImage i = history.get(anz-1);
            history.removeElementAt(anz-1);
            picture.setImage(i);
            repaint();
        }
    }

    /**
     * Ruft das letzte abgespeicherte Bild aus der History wieder auf.
     */
    public void back() {
        popImage();
    }

    /**
     * Setzt den Zoom-Faktor für das Bild.
     * Als Zoomfaktor sind auch die Konstanten Bildanzeiger.FIT (auf Bildschirmgröße zoomen) und Bildanzeiger.NORMAL (100%) möglich.
     * @param factor Zoomfaktor (1.0 = 100%). 
     */
    public void setZoom(double zoom) {
        this.zoom = zoom;
        repaint();
    }

    /**
     * Setzt die automatische Neuanzeige des Bildes.
     * Mit dieser Methode kann man einstellen, ob nach jedem Zeichenbefehl
     * die Anzeige auf dem Bildschirm aktualisiert werden soll. Bei sehr 
     * vielen Zeichenbefehlen wird die Ausgabe dadurch sehr langsam. Es reicht
     * eine Anzeige am Ende der Zeichenbefehle. Rufen Sie dann für das Neuzeichnen
     * die Methode refresh() auf.
     * @param autoRefresh true, wenn nach jedem Zeichenbefehl die Anzeige aktualisiert werden soll.
     */
    public void setAutoRefresh(boolean autoRefresh) {
        picture.setAutoRefresh(autoRefresh);
    }

    /**
     * Sorgt für die Aktualisierung der Bildschrimanzeige. Das aktuelle Bild
     * wird dadurch angezeigt. Durch Einstellung von autoRefresh kann die 
     * Anzeige automatisiert werden.
     */
    public void refresh() {
        repaint();
    }

    /**
     * Setzt das angezeigt Bild neu und beachtet dabei den Zoomfaktor.
     */
    public void repaint() {
        double faktor = zoom;

        if (zoom == FIT) {
            double faktorw = (double) this.getWorld().getWidth() / picture.getWidth();
            double faktorh = (double) this.getWorld().getHeight() / picture.getHeight();
            faktor = Math.min(faktorw, faktorh);
        }
        int gfi_width = (int) (picture.getWidth()*faktor);
        int gfi_height = (int) (picture.getHeight()*faktor);

        setImage(new GreenfootImage(gfi_width, gfi_height));
        Graphics2D g = (Graphics2D) this.getImage().getAwtImage().getGraphics();
        g.drawImage(picture.getImage(),0,0,gfi_width-1, gfi_height-1, 0, 0, picture.getWidth(), picture.getHeight(), null);
        g.setColor(new java.awt.Color(0,0,0));
        g.setStroke(new BasicStroke((float) 1));
        g.drawRect(0,0,gfi_width-1, gfi_height-1); 

    }

    // Wrappermethoden
    /**
     * Definiert die Dimension der Breite und Höhe des Anzeigefensters in Pixeleinheiten. 
     * Die eingebauten Variablen Breite und Höhe werden durch die an diese Funktion übergebenen Parameter festgelegt. So weist beispielsweise 
     * der Befehl size(640, 480) der Variablen Breite 640 und der Variablen Höhe 480 zu. 
     * @param width Breite des Bildes
     * @param height Höhe des Bildes
     */
    public void size(int width, int height){
        picture.size(width, height);
    }

    /** 
     * Liefert die Breite des Bildes zurück.
     * @return Breite des Bildes
     */
    public int getImageWidth() {
        return picture.getWidth();
    }

    /** 
     * Liefert die Höhe des Bildes zurück.
     * @return Höhe des Bildes
     */
    public int getImageHeight() {
        return picture.getHeight();
    }

    /**
     * Die Funktion background() setzt die Farbe, die für den Hintergrund des Bildes verwendet wird. Der Standardhintergrund ist hellgrau. 
     * Es ist nicht möglich, den Alpha-Parameter Transparenz mit Hintergrundfarben auf der Hauptzeichnungsoberfläche zu verwenden. 
     * @param c Farbe für den Hintergrund (0-255: Graustufe zwischen 0 schwarz und 255 weiß, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public void background(int c) {
        picture.background(c);
    }

    /**
     * Die Funktion background() setzt die Farbe, die für den Hintergrund des Bildes verwendet wird. Der Standardhintergrund ist hellgrau. 
     * Es ist nicht möglich, den Alpha-Parameter Transparenz mit Hintergrundfarben auf der Hauptzeichnungsoberfläche zu verwenden. 
     * @param r Rotanteil (0-255) der Hintergrundfarbe
     * @param g Grünanteil (0-255) der Hintergrundfarbe
     * @param b Blauanteil (0-255) der Hintergrundfarbe
     */
    public void background(int r, int g, int b) {
        picture.background(r,g,b);
    }

    /**
     * Zeichnet eine Linie (einen direkten Weg zwischen zwei Punkten) auf den Bildschirm. 
     * Um eine Linie einzufärben, verwenden Sie die {@link #stroke(int, int, int) stroke()} Funktion. Eine Zeile kann nicht gefüllt werden, daher hat die Funktion fill() keinen 
     * Einfluss auf die Farbe einer Zeile. Linien werden standardmäßig mit einer Breite von einem Pixel gezeichnet, dies kann jedoch mit der Funktion 
     * {@link #strokeWeight(double) strokeWeight()} geändert werden.
     * @param x1 x-Koordinate des 1. Punktes
     * @param y1 y-Koordinate des 1. Punktes
     * @param x2 x-Koordinate des 2. Punktes
     * @param y2 y-Koordinate des 2. Punktes
     */
    public void line(int x1, int y1, int x2, int y2) {
        picture.line(x1,y1,x2,y2);
    }

    /**
     * Zeichnet ein Rechteck auf das Bild. 
     * Standardmäßig legen die ersten beiden Parameter die Position der linken oberen Ecke fest, der dritte die Breite und der vierte die Höhe. 
     * Die Art und Weise, wie diese Parameter interpretiert werden, kann jedoch mit der Funktion {@link #rectMode(int) rectMode()} geändert werden.
     * Durch den Befehl {@link #fill(int,int,int) fill()} /{@link #noFill() noFill()}  kann die Füllfarbe des Rechtecks gewählt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     * @param a meist die x-Koordinate der linken oberen Ecke (kann durch rectMode() geändert werden).
     * @param b meist die y-Koordinate der linken oberen Ecke (kann durch rectMode() geändert werden).
     * @param c meist die Breite des Rechtecks (kann durch rectMode() geändert werden).
     * @param d meist die Höhe des Rechtecks (kann durch rectMode() geändert werden).
     * 
     */
    public void rect(int a, int b, int c, int d) {
        picture.rect(a,b,c,d);
    }

    /**
     * Zeichnet eine Ellipse/Kreis auf das Bild. 
     * Standardmäßig legen die ersten beiden Parameter die Position des Mittelpunkts fest, der dritte die Breite und der vierte die Höhe. 
     * Die Art und Weise, wie diese Parameter interpretiert werden, kann jedoch mit der Funktion {@link #ellipseMode(int) ellipseMode()} geändert werden.
     * Durch den Befehl {@link #fill(int,int,int) fill()} /{@link #noFill() noFill()} kann die Füllfarbe des Rechtecks gewählt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     * @param a meist die x-Koordinate des Mittelpunkts (kann durch ellipseMode() geändert werden).
     * @param b meist die y-Koordinate des Mittelpunkts (kann durch ellipseMode() geändert werden).
     * @param c meist die Breite des Rechtecks (kann durch ellipseMode() geändert werden).
     * @param d meist die Höhe des Rechtecks (kann durch ellipseMode() geändert werden).
     * 
     */
    public void ellipse(int a, int b, int c, int d) {
        picture.ellipse(a,b,c,d);
    }

    /**
     * Zeichnet ein Dreieck auf das Bild. 
     * Ein Dreieck ist eine Ebene, die durch die Verbindung von drei Punkten entsteht. Die ersten beiden Argumente spezifizieren den 
     * ersten Punkt, die mittleren beiden Argumente spezifizieren den zweiten Punkt und die letzten beiden Argumente spezifizieren den dritten Punkt. 
     * Durch den Befehl {@link #fill(int,int,int) fill()} /{@link #noFill() noFill()} kann die Füllfarbe des Rechtecks gewählt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     * @param x1 meist die x-Koordinate des 1. Punkts.
     * @param y1 meist die y-Koordinate des 1. Punkts.
     * @param x2 meist die x-Koordinate des 2. Punkts.
     * @param y2 meist die y-Koordinate des 2. Punkts.
     * @param x3 meist die x-Koordinate des 3. Punkts.
     * @param y3 meist die y-Koordinate des 3. Punkts.
     */
    public void triangle(int x1, int y1, int x2, int y2, int x3, int y3) {
        picture.triangle(x1,y1,x2,y2,x3,y3);
    }

    /**
     * Zeichnet ein Viereck auf das Bild. 
     * Ein Viereck ist ein vierseitiges Polygon. Es ist ähnlich wie ein Rechteck, aber die Winkel zwischen seinen Kanten 
     * sind nicht auf neunzig Grad beschränkt. Das erste Paar von Parametern (x1,y1) setzt den ersten Scheitelpunkt und die nachfolgenden 
     * Paare sollten im Uhrzeigersinn oder gegen den Uhrzeigersinn um die definierte Form herum verlaufen. 
     * Durch den Befehl {@link #fill(int,int,int) fill()} /{@link #noFill() noFill()} kann die Füllfarbe des Rechtecks gewählt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     * @param x1 meist die x-Koordinate des 1. Punkts.
     * @param y1 meist die y-Koordinate des 1. Punkts.
     * @param x2 meist die x-Koordinate des 2. Punkts.
     * @param y2 meist die y-Koordinate des 2. Punkts.
     * @param x3 meist die x-Koordinate des 3. Punkts.
     * @param y3 meist die y-Koordinate des 3. Punkts.
     * @param x4 meist die x-Koordinate des 3. Punkts.
     * @param y4 meist die y-Koordinate des 3. Punkts.
     */
    public void quad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
        picture.quad(x1,y1,x2,y2,x3,y3,x4,y4);
    }

    /**
     * Zeichnet ein Polygon auf das Bild.
     * Gleich lange Listen von x und y-Koordinaten bestimmen die Eckpunkte des Polygons.
     * Durch den Befehl {@link #fill(int,int,int) fill()} /{@link #noFill() noFill()} kann die Füllfarbe des Rechtecks gewählt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()} die Rahmenfarbe.
     * @param x Liste der x-Koordinaten der Punkte.
     * @param y Liste der y-Koordinaten der Punkte.
     */
    public void polygon(int[] x, int[] y) {
        picture.polygon(x,y);
    }

    /**
     * Zeichnet einen Punkt, d.h. einen Kreis in der Dimension eines Pixels. 
     * Der erste Parameter ist der x-Wert für den Punkt, der zweite Wert ist der y-Wert für den Punkt. 
     * @param x x-Koordinate des Punktes
     * @param y y-Koordinate des Punktes
     */
    public void point(int x, int y) {
        picture.point(x,y);

    }

    /**
     * Ändert den Koordinaten-Modus beim Zeichnen von Rechtecken.
     * Ändert die Position, von der aus Rechtecke gezeichnet werden, indem es die Art und Weise ändert, wie Parameter, die an rect() übergeben werden, interpretiert werden.
     * Der Standardmodus ist rectMode(Bild.CORNER), der die ersten beiden Parameter von rect() als die linke obere Ecke der Form interpretiert, 
     * während der dritte und vierte Parameter seine Breite und Höhe sind.
     * rectMode(Bild.CORNERS) interpretiert die ersten beiden Parameter von rect() als die Position einer Ecke 
     * und die dritten und vierten Parameter als die Position der gegenüberliegenden Ecke.
     * rectMode(Bild.CENTER) interpretiert die ersten beiden Parameter von rect() als Mittelpunkt der Form, 
     * während der dritte und vierte Parameter seine Breite und Höhe sind.
     * rectMode(RADIUS) verwendet auch die ersten beiden Parameter von rect() als Mittelpunkt der Form, 
     * verwendet aber den dritten und vierten Parameter, um die Hälfte der Breite und Höhe der Formen festzulegen.
     * @param mode Modus der Koordinateninterpretation (CORNER, CORNERS, CENTER oder RADIUS)
     */
    public void rectMode(int mode) {
        picture.rectMode(mode);
    }

    /**
     * Ändert den Koordinaten-Modus beim Zeichnen von Kreisen/Ellipsen.
     * Ändert die Position, von der aus Kreise/Ellipsen gezeichnet werden, indem es die Art und Weise ändert, wie Parameter, die an ellipse() übergeben werden, interpretiert werden.
     * Der Standardmodus ist ellipseMode(Bild.CENTER), der die ersten beiden Parameter von ellipse() als Mittelpunkt der Form interpretiert, 
     * während der dritte und vierte Parameter seine Breite und Höhe sind.
     * ellipseMode(Bild.CORNER) interpretiert die ersten beiden Parameter von ellipse() als die Position einer Ecke 
     * und die dritten und vierten Parameter als Breite und Höhe der Form.
     * ellipseMode(Bild.CORNERS) interpretiert die ersten beiden Parameter von ellipse() als die Position einer Ecke 
     * und die dritten und vierten Parameter als die Position der gegenüberliegenden Ecke.
     * ellipseMode(RADIUS) verwendet auch die ersten beiden Parameter von ellipse() als Mittelpunkt der Form, 
     * verwendet aber den dritten und vierten Parameter, um die Hälfte der Breite und Höhe der Formen festzulegen.
     * @param mode Modus der Koordinateninterpretation (CORNER, CORNERS, CENTER oder RADIUS)
     */
    public void ellipseMode(int mode) {
        picture.ellipseMode(mode);
    }

    /**
     * Legt die Farbe fest, mit der Linien und Ränder um Formen gezeichnet werden. 
     * Diese Farbe wird hexadezimal in Form der RGB angegeben: z.B.  "CCFFAA" oder "004E23". Die Syntax verwendet sechs Ziffern - je zwei für die roten, grünen und blauen Komponenten,
     * um eine Farbe anzugeben (genau wie Farben typischerweise in HTML und CSS angegeben werden). 
     * @param pencolor Stiftfarbe in Hexadezimaldarstellung
     */
    public void stroke(String pencolor) {
        picture.stroke(pencolor);
    }

    /**
     * Legt die Farbe fest, mit der Linien und Ränder um Formen gezeichnet werden. 
     * Diese Farbe wird entweder als Graustufe (0-255) oder als 3-Byte RGB-Wert angegeben
     * @param pencolor Stiftfarbe (0-255: Graustufe zwischen 0 schwarz und 255 weiß, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public void stroke(int pencolor) {
        picture.stroke(pencolor);
    }

    /**
     * Legt die Farbe fest, mit der Linien und Ränder um Formen gezeichnet werden. 
     * Diese Farbe wird komponentenweise als RGB-Wert angegeben
     * @param r Rotanteil (0-255) der Stiftfarbe
     * @param g Grünanteil (0-255) der Stiftfarbe
     * @param b Blauanteil (0-255) der Stiftfarbe
     */
    public void stroke(int r, int g, int b) {
        picture.stroke(r,g,b);
    }

    /**
     * Legt fest, dass keine Linien oder Ränder um Formen gezeichnet werden soll. 
     */
    public void noStroke() {
        picture.noStroke();
    }

    /**
     * Legt die Breite des Strichs für Linien, Punkte und den Rand um Formen fest. 
     * Alle Breiten werden in Pixeleinheiten angegeben. 
     * @param width Breite in Pixel
     */
    public void strokeWeight(double width) {
        picture.strokeWeight(width);
    }

    /**
     * Legt die Farbe fest, mit der Formen gefüllt werden. 
     * Diese Farbe wird hexadezimal in Form der RGB angegeben: z.B.  "CCFFAA" oder "004E23". Die Syntax verwendet sechs Ziffern - je zwei für die roten, grünen und blauen Komponenten,
     * um eine Farbe anzugeben (genau wie Farben typischerweise in HTML und CSS angegeben werden). 
     * @param fillcolor Füllfarbe in Hexadezimaldarstellung
     */
    public void fill(String fillcolor) {
        picture.fill(fillcolor);
    }

    /**
     * Legt die Farbe fest, mit der Formen gefüllt werden.
     * Diese Farbe wird entweder als Graustufe (0-255) oder als 3-Byte RGB-Wert angegeben.
     * @param fillcolor Füllfarbe (0-255: Graustufe zwischen 0 schwarz und 255 weiß, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public void fill(int fillcolor) {
        picture.fill(fillcolor);
    }

    /**
     * Legt die Farbe fest, mit der Formen gefüllt werden.
     * Diese Farbe wird komponentenweise als RGB-Wert angegeben.
     * @param r Rotanteil (0-255) der Füllfarbe
     * @param g Grünanteil (0-255) der Füllfarbe
     * @param b Blauanteil (0-255) der Füllfarbe
     */
    public void fill(int r, int g, int b) {
        picture.fill(r,g,b);
    }

    /** Legt fest, dass die Formen nicht gefüllt werden sollen.
     */
    public void noFill() {
        picture.noFill();
    }

    /** 
     * Löscht den Inhalt des Bildes.
     * Der Hintergrund wird mit der Hintergrundfarbe neu gefüllt.
     */
    public void clear(){
        picture.clear();
    }

    /** 
     * Lädt ein Bild aus dem Dateisystem.
     * Lädt ein Bild von einem Datenträger und setzt Stiftfarbe und Füllfarbe auf Standardwerte zurück.
     * @param filename Dateiname des Bildes
     */
    public void load(String filename) {
        picture.load(filename);
    }

    /** 
     * Speichert ein Bild.
     * Speichert ein Bild auf einem Datenträger. Zulässig sind die Dateiformate PNG und GIF. Die Dateiendung legt den Typ fest.
     * Standardmäßig wird die Dateiendung .png ergänzt, wenn keine angegeben ist.
     * @param filename Dateiname des Bildes
     */
    public void save(String filename) {
        picture.save(filename);
    }

    /**
     * Gibt einen Text an den gegebenen Koordinaten aus
     * Zur Ausgabe des Textes wird der ausgewählte Font verwendet. Dieser muss vorher mit {@link #textFont(Font) textFont() } festgelegt.
     * @param t Text, der angezeigt werden soll
     * @param x x-Koordinate des Textanfangs
     * @param y y-Koordinate der Grundlinie des Textes.
     */
    public void text(String t, int x, int y) {
        picture.text(t,x,y);
    }

    /**
     * Legt die Schriftart für Textausgaben fest.
     * Jeder übliche Java-Font kann verwendet werden. Er kann mit z.B. Font f = new Font( "Arial", Font.PLAIN, 14 ); definiert werden. 
     * @param font ein Font-Objekt 
     */    
    public void textFont(Font font) {
        picture.textFont(font);
    }

    /**
     * Liefert das Bild als zweidimensionales Pixel-Array.
     * @return zweidimensionales Array von Color-Objekten, die den Pixeln des Bildes entsprechen.
     */  
    public Color[][] getPixelArray() {
        return picture.getPixelArray();
    }

    /**
     * Setzt das Bild neu auf Basis des Pixel-Arrays.
     * Die Größe des Bildes wird nicht automatisch an das Array angepasst.
     * @param pixel zweidimensionales Array von Color-Objekten
     */    
    public void setPixelArray(Color[][] pixel) {
        picture.setPixelArray(pixel);
    }


}

