import theater.*;

import java.io.*;


/**
 * Repraesentation vom Hamster im Java-Hamster-Modell
 *
 * @author Dietrich Boles (Universitaet Oldenburg)
 * @version 1.0 (17.07.2009)
 *
 */
public class Hamster extends Actor {
    /**
     * Blickrichtung Nord
     */
    public final static int NORD = 0;

    /**
     * Blickrichtung Ost
     */
    public final static int OST = 1;

    /**
     * Blickrichtung Sued
     */
    public final static int SUED = 2;

    /**
     * Blickrichtung West
     */
    public final static int WEST = 3;

    // Blickrichtung des Hamsters
    private int blickrichtung;

    // Anzahl an Körnern im Maul des Hamsters
    private int koernerImMaul;

    // Name des Hamsters (der Solist-Hamster kann damit auch via
    // willi.vor(); etc. gesteuert werden)
    protected Solist willi;

    /**
     * Konstruktor zum Erzeugen eines neuen Hamsters mit der angegebenen
     * Blickrichtung und Anzahl an Koernern im Maul
     *
     * @param blickrichtung
     *            Blickrichtung des Hamsters
     * @param anzahlKoerner
     *            Anzahl an Koernern im Maul
     */
    public Hamster(int blickrichtung, int anzahlKoerner) {
        this.setImage("hamster.png");
        this.setBlickrichtung(blickrichtung);
        this.koernerImMaul = anzahlKoerner;
        willi = (Solist) this; // der Hamster bekommt den Namen willi
    }

    /**
     * Konstruktor zum Erzeugen eines neuen Hamsters mit Blickrichtung OST und
     * keinem Korn im Maul
     */
    public Hamster() {
        super();
        this.setImage("hamster.png");
        this.blickrichtung = Hamster.OST;
        this.koernerImMaul = 0;
        willi = (Solist) this; // der Hamster bekommt den Namen willi
    }

    // Copy-Konstruktor (zum Klonen des Hamsters)
    public Hamster(Hamster h) {
        super(h);
        this.blickrichtung = h.blickrichtung;
        this.koernerImMaul = h.koernerImMaul;
        willi = (Solist) this;
    }

    /**
     * liefert genau dann true, wenn sich in Blickrichtung vor dem aufgerufenen
     * Hamster keine Mauer befindet (wenn sich der Hamster in Blickrichtung am
     * Rand des Territoriums befindet, wird false geliefert)
     *
     * @return true, wenn sich in Blickrichtung vor dem aufgerufenen Hamster
     *         keine Mauer befindet; sonst false
     */
    @Description("Liefert genau dann true, wenn sich vor dem Hamster keine Mauer und " +
    "auch nicht der Rand des Territoriums befindet.")
    public boolean vornFrei() {
        Performance.getPerformance().lock();

        try {
            int col = this.getColumn();
            int row = this.getRow();

            switch (this.blickrichtung) {
            case SUED:
                row++;

                break;

            case OST:
                col++;

                break;

            case NORD:
                row--;

                break;

            case WEST:
                col--;

                break;
            }

            if ((col >= this.getStage().getNumberOfColumns()) ||
                    (row >= this.getStage().getNumberOfRows()) || (col < 0) ||
                    (row < 0)) {
                // Hamster befindet sich am Rand
                return false;
            }

            // Mauer vor dem Hamster?
            return this.getStage().getComponentsAt(col, row, Mauer.class).size() == 0;
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    /**
     * liefert genau dann true, wenn auf der Kachel, auf der sich der
     * aufgerufene Hamster gerade befindet, mindestens ein Korn liegt
     *
     * @return true, wenn auf der Kachel, auf der sich der aufgerufene Hamster
     *         gerade befindet, mindestens ein Korn liegt; sonst false
     */
    @Description("Liefert genau dann true, wenn sich auf der Kachel des Hamsters " +
    "mindestens ein Korn befindet.")
    public boolean kornDa() {
        Performance.getPerformance().lock();

        try {
            // Korn auf der Kachel des Hamsters?
            return this.getStage()
                       .getComponentsAt(this.getColumn(), this.getRow(),
                Korn.class).size() > 0;
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    /**
     * liefert genau dann true, wenn der aufgerufene Hamster keine Koerner im
     * Maul hat
     *
     * @return true, wenn der aufgerufene Hamster keine Koerner im Maul hat;
     *         sonst false
     */
    @Description("Liefert genau dann true, wenn sich aktuell keine Körner im Maul " +
    "des Hamsters befinden.")
    public boolean maulLeer() {
        return this.koernerImMaul == 0;
    }

    /**
     * Der aufgerufene Hamster springt auf die in Blickrichtung vor ihm liegende
     * Kachel.
     *
     * @throws MauerDaException
     *             wird geworfen, wenn die Kachel in Blickrichtung vor dem
     *             Hamster durch eine Mauer blockiert ist oder der Hamster in
     *             Blickrichtung am Rand des Territoriums steht
     */
    @Description("<html>Der Hamster hüpft eine Kachel in seiner aktuellen Blickrichtung nach " +
    "vorn.<br>Fehler, wenn sich dort eine Mauer oder der Rand des Territoriums befindet.</html>")
    public void vor() throws MauerDaException {
        // Hinweis: Die Nutzung der lock- und unlock-Methoden ist wichtig,
        // damit nicht zwischen dem Ändern der internen Attribute (blickrichtung,
        // koernerImMaul, Position, ...) und dem Darstellen des geänderten Zustandes 
        // auf der Bühne bspw. eine Pausierung einer Aufführung 
        // durch den Benutzer erfolgen kann. Ansonsten befände sich die Aufführung
        // in einem inkonsistenten Zustand!
        Performance.getPerformance().lock();

        try {
            if (!this.vornFrei()) {
                Performance.getPerformance().playSound("death.wav");
                throw new MauerDaException(this, this.getRow(), this.getColumn());
            }

            switch (this.blickrichtung) {
            case SUED:
                this.setLocation(this.getColumn(), this.getRow() + 1);

                break;

            case OST:
                this.setLocation(this.getColumn() + 1, this.getRow());

                break;

            case NORD:
                this.setLocation(this.getColumn(), this.getRow() - 1);

                break;

            case WEST:
                this.setLocation(this.getColumn() - 1, this.getRow());

                break;
            }
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    /**
     * Der aufgerufene Hamster dreht sich linksum.
     */
    @Description("Der Hamster dreht sich um 90 Grad nach links um.")
    public void linksUm() {
        Performance.getPerformance().lock();

        try {
            switch (this.blickrichtung) {
            case SUED: 
                this.setBlickrichtung(Hamster.OST);

                break;

            case OST:
                this.setBlickrichtung(Hamster.NORD);

                break;

            case NORD:
                this.setBlickrichtung(Hamster.WEST);

                break;

            case WEST:
                this.setBlickrichtung(Hamster.SUED);

                break;
            }
        } finally {
            Performance.getPerformance().unlock();
        }
    }
    
    /**
     * Der aufgerufene Hamster frisst ein Korn auf der Kachel, auf der er sich
     * gerade befindet.
     *
     * @throws KachelLeerException
     *             wird geworfen, wenn auf der Kachel, auf der sich der Hamster
     *             gerade befindet, kein Korn liegt
     */
    @Description("<html>Der Hamster nimmt von seiner Kachel ein Korn ins Maul.<br>" +
    "Fehler, wenn sich auf seiner Kachel gar kein Korn befindet.</html>")
    public void nimm() throws KachelLeerException {
        Performance.getPerformance().lock();

        try {
            if (!this.kornDa()) {
                Performance.getPerformance().playSound("death.wav");
                throw new KachelLeerException(this, this.getRow(),
                    this.getColumn());
            }

            // ein Korn mehr im Maul
            this.koernerImMaul++;

            Korn korn = (Korn) this.getStage()
                                   .getComponentsAt(this.getColumn(),
                    this.getRow(), Korn.class).get(0);

            // ein Korn weniger auf der Kachel
            korn.inkAnzahl(-1);
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    /**
     * Der aufgerufene Hamster legt ein Korn auf der Kachel ab, auf der er sich
     * gerade befindet.
     *
     * @throws MaulLeerException
     *             wird geworfen, wenn der Hamster keine Koerner im Maul hat
     */
    @Description("<html>Der Hamster legt aus seinem Maul ein Korn auf seine Kachel ab.<br>" +
    "Fehler, wenn der Hamster gar kein Korn im Maul hat.</html>")
    public void gib() throws MaulLeerException {
        Performance.getPerformance().lock();

        try {
            if (this.maulLeer()) {
                Performance.getPerformance().playSound("death.wav");
                throw new MaulLeerException(this);
            }

            // ein Korn weniger im Maul
            this.koernerImMaul--;

            // ein Korn mehr auf der Kachel
            this.getStage().add(new Korn(), this.getColumn(), this.getRow());
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    /**
     * liefert die Reihe der Kachel des Territoriums, auf der sich der
     * aufgerufene Hamster gerade befindet
     *
     * @return die Reihe der Kachel des Territoriums, auf der sich der
     *         aufgerufene Hamster gerade befindet
     */
    @Description("Liefert die Reihe, in der sich der Hamster gerade befindet.")
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    public int getReihe() {
        return this.getRow();
    }

    /**
     * liefert die Spalte der Kachel des Territoriums, auf der sich der
     * aufgerufene Hamster gerade befindet
     *
     * @return die Spalte der Kachel des Territoriums, auf der sich der
     *         aufgerufene Hamster gerade befindet
     */
    @Description("Liefert die Spalte, in der sich der Hamster gerade befindet.")
    @Invisible
    public int getSpalte() {
        return this.getColumn();
    }

    /**
     * liefert die Blickrichtung, in die der aufgerufene Hamster gerade schaut
     * (die gelieferten Werte entsprechen den obigen Konstanten)
     *
     * @return die Blickrichtung, in die der aufgerufene Hamster gerade schaut
     */
    @Description("Liefert die Blickrichtung, in die der Hamster gerade schaut.\n" +
    "(0 = Nord, 1 = Ost, 2 = Süd, 3 = West)")
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    public int getBlickrichtung() {
        return this.blickrichtung;
    }

    /**
     * liefert die Anzahl der Koerner, die der aufgerufene Hamster gerade im
     * Maul hat
     *
     * @return die Anzahl der Koerner, die der aufgerufene Hamster gerade im
     *         Maul hat
     */
    @Description("Liefert die Anzahl an Körnern im Maul des Hamsters.")
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    public int getAnzahlKoerner() {
        return this.koernerImMaul;
    }

    // Blickrichtung setzen
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    private final void setBlickrichtung(int richtung) {
        Performance.getPerformance().lock();

        try {
            this.blickrichtung = richtung;

            switch (this.blickrichtung) {
            case SUED:
                this.setRotation(90);

                break;

            case OST:
                this.setRotation(0);

                break;

            case NORD:
                this.setRotation(270);

                break;

            case WEST:
                this.setRotation(180);

                break;

            default:
                break;
            }
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    // setzt die Anzahl an Körnern im Maul;
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    final void setKoernerImMaul(int anzahl) {
        this.koernerImMaul = anzahl;
    }

    // wird aufgerufen, wenn der Hamster in das Territorium platziert wird
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

    public final void addedToStage(Stage stage) {
        Performance.getPerformance().lock();

        try {
            // Hamster kann nicht auf Mauer platziert werden
            if (this.getStage()
                        .getComponentsAt(this.getColumn(), this.getRow(),
                        Mauer.class).size() > 0) {
                this.getStage().remove(this);

                return;
            }

            this.setZCoordinate(1); // Hamster ist immer im Vordergrund
        } finally {
            Performance.getPerformance().unlock();
        }
    }

    // Platziert einen Hamster auf der Bühne um. Übergeben werden die neue Spalte und Reihe.
    // Die Methode wird auch aufgerufen,
    // wenn ein Hamster per Mausklick auf der Bühne verschoben wird.
    // Überschreibt die entsprechende Component-Methode.
    @Invisible // soll nicht im Popup-Menü und Befehlsfenster erscheinen

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

        try {
            if ((col >= 0) && (row >= 0) &&
                    (col < this.getStage().getNumberOfColumns()) &&
                    (row < this.getStage().getNumberOfRows()) &&
                    (this.getStage().getComponentsAt(col, row, Mauer.class)
                             .size() == 0)) {
                // nur innerhalb der sichtbaren Bühne und nur, falls sich auf der
                // angegebenen Kachel keine Mauer befindet
                super.setLocation(col, row);
            }
        } finally {
            Performance.getPerformance().unlock();
        }
    }
}
