import theater.*;

import java.util.List;


/**
 * Solist im Frosch-Simulator ist ein Frosch, der in dieser Klasse implementiert wird.
 *
 * @author Dietrich Boles (Universitaet Oldenburg)
 * @version 1.0 (24.09.2009)
 *
 */
public class Frosch extends Actor {
    // der Frosch wird durch unterschiedliche Bilder repräsentiert,
    // je nachdem, ob er sich im Gras oder im Wasser befindet
    private TheaterImage froschImGras;
    private TheaterImage froschImWasser;

    // der Frosch besitzt den Namen kermit
    protected Solist kermit;

    // Default-Konstruktor (muss implementiert werden!)
    public Frosch() {
        super(); // nicht loeschen!
        froschImGras = new TheaterImage("frosch-gras.gif");
        froschImWasser = new TheaterImage("frosch-wasser.gif");

        // Frosch sitzt anfangs im Gras
        setImage(froschImGras);

        // Frosch bekommt die höchste Z-Koordinate, damit er immer im Vordergrund ist
        setZCoordinate(2);

        // hiermit wir dem Frosch der Name kermit zugeteilt
        kermit = (Solist) this;
    }

    // Copy-Konstruktor (muss implementiert werden!)
    public Frosch(Frosch actor) {
        super(actor); // nicht loeschen!
        froschImGras = new TheaterImage("frosch-gras.gif");
        froschImWasser = new TheaterImage("frosch-wasser.gif");

        setImage(actor.getImage());

        // hiermit kann dem Solist ein "Name" gegeben werden
        kermit = (Solist) this;
    }

    @Description("<html>Der Frosch hüpft ein Feld in seiner aktuellen Blickrichtung " +
    "nach vorne.<br>Fehler, wenn er sich im Wasser befindet.</html>")
    public void huepfen() throws FroschException {
        Performance.getPerformance().lock();

        try {
            if (!imGras()) {
                throw new FroschException(
                    "Der Frosch kann im Wasser nicht hüpfen.");
            }

            Position vorne = getVorne();
            setLocation(vorne.col, vorne.row);
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    @Description("<html>Der Frosch schwimmt ein Feld in seiner aktuellen Blickrichtung " +
    "nach vorne.<br>Fehler, wenn er im Gras sitzt.</html>")
    public void schwimmen() throws FroschException {
        Performance.getPerformance().lock();

        try {
            if (imGras()) {
                throw new FroschException(
                    "Der Frosch kann im Gras nicht schwimmen.");
            }

            Position vorne = getVorne();
            setLocation(vorne.col, vorne.row);
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    @Description("<html>Der Frosch dreht sich um 90 Grad nach rechts.</html>")
    public void rechtsUm() {
        Performance.getPerformance().lock();

        try {
            setRotation((getRotation() + 90) % 360);
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    @Description("<html>Der Frosch dreht sich um 90 Grad nach links.</html>")
    public void linksUm() {
        Performance.getPerformance().lock();

        // bewirkt, dass Zustandsänderungen auf der Bühne erst beim nächsten unfreeze
        // sichtbar werden
        Performance.getPerformance().freeze();

        try {
            rechtsUm();
            rechtsUm();
            rechtsUm();
        } finally {
            Performance.getPerformance().unfreeze();
            Performance.getPerformance().unlock();
        }
    }

    @Description("Liefert genau dann true, wenn sich der Frosch auf einem Grasfeld befindet.")
    public boolean imGras() {
        Performance.getPerformance().lock();

        try {
            return getStage()
                       .getComponentsAt(getColumn(), getRow(), Wasser.class)
                       .size() == 0;
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    @Description("Liefert genau dann true, wenn sich vor dem Frosch ein Grasfeld befindet.")
    public boolean vorneGras() {
        Performance.getPerformance().lock();

        try {
            return grasDa(getVorne());
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    @Description("Liefert genau dann true, wenn sich der Frosch auf einem Feld mit einer Mücke befindet.")
    public boolean mueckeDa() {
        Performance.getPerformance().lock();

        try {
            return getStage()
                       .getComponentsAt(getColumn(), getRow(), Muecke.class)
                       .size() > 0;
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    // überschreibt die geerbte Methode, die zum Setzen der Position dient
    @Invisible // soll nicht im Befehlsfenster sichtbar sein

    public void setLocation(int col, int row) {
        Performance.getPerformance().lock();

        // bewirkt, dass Zustandsänderungen auf der Bühne erst beim nächsten unfreeze
        // sichtbar werden
        Performance.getPerformance().freeze();

        try {
            // berechnet die neue Position in BLickrichtung nach vorne
            col = calcColumn(col);
            row = calcRow(row);

            // der Frosch sitzt bereits auf der gewünschten Kachel
            if ((this.getColumn() == col) && (this.getRow() == row)) {
                return;
            }

            List<Component> comps = getStage()
                                        .getComponentsAt(col, row, Wasser.class);

            if (comps.size() > 0) {
                // Frosch sitzt  im Wasser
                if (getImage() != froschImWasser) {
                    this.setImage(froschImWasser);
                }
            } else {
                // Frosch sitzt im Gras
                if (getImage() != froschImGras) {
                    this.setImage(froschImGras);
                }
            }

            super.setLocation(col, row);
        } finally {
            Performance.getPerformance().unfreeze();
            Performance.getPerformance().unlock();
        }
    }

    // berechnet die Spalte im torusförmigen Territorium
    @Invisible // soll nicht im Befehlsfenster sichtbar sein

    private int calcColumn(int col) {
        col = col % getStage().getNumberOfColumns();

        if (col < 0) {
            col = getStage().getNumberOfColumns() + col;
        }

        return col;
    }

    // berechnet die Reihe im torusförmigen Territorium
    @Invisible // soll nicht im Befehlsfenster sichtbar sein

    private int calcRow(int row) {
        row = row % getStage().getNumberOfRows();

        if (row < 0) {
            row = getStage().getNumberOfRows() + row;
        }

        return row;
    }

    // überprüft, ob sich an der angegebenen Kachel Gras oder Wasser befindet
    @Invisible // soll nicht im Befehlsfenster sichtbar sein

    private boolean grasDa(Position pos) {
        return getStage().getComponentsAt(pos.col, pos.row, Wasser.class).size() == 0;
    }

    // berechnet die Kachel in Blickrichtung nach vorne (Achtung: das
    // Territorium ist torusförmig)
    @Invisible // soll nicht im Befehlsfenster sichtbar sein

    private Position getVorne() {
        int col = getColumn();
        int row = getRow();

        switch (getRotation()) {
        case 0: // Ost
            return new Position(calcColumn(col + 1), row);

        case 270: // Nord
            return new Position(col, calcRow(row - 1));

        case 180: // West
            return new Position(calcColumn(col - 1), row);

        default: //90 = Sued

            return new Position(col, calcRow(row + 1));
        }
    }

    void changeImage(boolean imGras) {
        if (imGras) {
            setImage(froschImGras);
        } else {
            setImage(froschImWasser);
        }
    }
}


// Hilfsklasse für Kachelpositionen
class Position {
    int col;
    int row;

    Position(int c, int r) {
        col = c;
        row = r;
    }
}
