package edu.whs.spin2018.baoapp.model.spiel;

import edu.whs.spin2018.baoapp.model.spiel.spieler.Spieler;
import edu.whs.spin2018.baoapp.model.spiel.spieler.SpielerIndex;
import edu.whs.spin2018.baoapp.presenter.BaoSpielPresenter;
import edu.whs.spin2018.baoapp.service.Konstanten;

/**
 * Stellt ein Bao-BaoSpiel dar und ermöglicht Spielzüge darauf auszuführen.
 */
public class BaoSpiel implements TimerListener {

    private static final int INNERE_REIHE_SPIELER_EINS = 2;
    private static final int INNERE_REIHE_SPIELER_ZWEI = 1;
    private static final int ZEILEN = 4;
    private static final int SPALTEN = 8;
    private static final int STARTANZAHL_STEINE = 2;
    private BaoSpielPresenter baoSpielPresenter;
    private Spieler spielerEins;
    private Spieler spielerZwei;
    private BaoTimer spielerEinsTimer;
    private BaoTimer spielerZweiTimer;
    private BaoTimer aktuellerTimer;
    private String zeitTyp;
    private String spielTyp;
    private Mulde[][] spielbrett;
    private Spieler aktuellerSpieler;
    private long spielzeit;


    /**
     * Repräsentiert ein BaoSpiel wie zu Beginn des Spiels, in jeder Mulde liegen 2 Steine
     *
     * @param baoSpielPresenter - der baoSpielPresenter, der für die Verarbeitung zuständig ist
     * @param spielerEins         - der erste Spieler
     * @param spielerZwei         - der zweite Spieler
     * @param spielTyp            - der Spielmodus
     * @param zeitTyp             - der Zeitmodus (Rundenzeit, Spielzeit, keine Zeit)
     * @param zeit                - die Startzeit der Timer
     * @param startenderSpieler   - der Index des Spielers, der das Spiel beginnt
     */
    public BaoSpiel(BaoSpielPresenter baoSpielPresenter, Spieler spielerEins, Spieler spielerZwei, String spielTyp, String zeitTyp, long zeit, SpielerIndex startenderSpieler) {
        spielbrett = new Mulde[ZEILEN][SPALTEN];
        for (int i = 0; i < ZEILEN; i++) {
            for (int j = 0; j < SPALTEN; j++) {
                spielbrett[i][j] = new Mulde(new Position(i, j), STARTANZAHL_STEINE);
            }
        }
        this.spielerEins = spielerEins;
        this.spielzeit = zeit;
        this.zeitTyp = zeitTyp;
        this.spielerEins.setzeBaoSpiel(this);
        this.spielerZwei = spielerZwei;
        this.spielerZwei.setzeBaoSpiel(this);
        this.baoSpielPresenter = baoSpielPresenter;
        this.spielTyp = spielTyp;
        this.aktuellerSpieler = (startenderSpieler == SpielerIndex.SPIELER_EINS)
                ? spielerEins
                : spielerZwei;
        this.aktuellerSpieler.setzeZug(true);
        this.spielerEinsTimer = null;
        this.spielerZweiTimer = null;
        this.aktuellerTimer = null;

        initialisereTimer();
    }

    /**
     * Ein BaoSpiel aus einer bestimmten Spielsituation wird erstellt. Die bestimmte Spielsituation
     * wird durch das übergebende MuldenArray dargestellt.
     *
     * @param aktuell repräsentiert das aktuelle MuldenArray
     */
    public BaoSpiel(Mulde[][] aktuell) {
        spielbrett = new Mulde[ZEILEN][SPALTEN];
        for (int i = 0; i < ZEILEN; i++) {
            for (int j = 0; j < SPALTEN; j++) {
                spielbrett[i][j] = new Mulde(new Position(i, j), aktuell[i][j].gibAnzahlSteine());
            }
        }
    }

    @Override
    public void onTick(long millis) {
        baoSpielPresenter.onTick(millis);
    }

    @Override
    public void onTimerStart() {
        baoSpielPresenter.initialisiereAktuelleTimeranzeige(aktuellerSpieler.gibSpielerIndex());
    }

    @Override
    public void onFinish() {
        baoSpielPresenter.onFinish();
    }

    /**
     * Erstellt und initialisert die Timer, wenn welche für das Spiel nötig sind. Zusätzlich wird
     * der Timer des aktuellen Spielers gestartet.
     */
    private void initialisereTimer() {
        if ((spielTyp.equals(BaoSpielPresenter.SPIEL_TYP_LOKALES_SPIEL)
                || spielTyp.equals(BaoSpielPresenter.SPIEL_TYP_MEHRSPIELER))
                && (!this.zeitTyp.equals(Konstanten.SPIELZEIT_OHNE_ZEIT))) {
            spielerEinsTimer = new BaoTimer(this.spielzeit, this);
            spielerZweiTimer = new BaoTimer(this.spielzeit, this);
            aktuellerTimer = (aktuellerSpieler == spielerEins) ? spielerEinsTimer : spielerZweiTimer;
            aktuellerTimer.start();
        }
    }

    /**
     * Führt einen Spielzug von der übergebenden Startposition aus. Der ausgeführte Spielzug wird in
     * einem Spielzug-Objekt festgehalten und an die Methode verarbeiteSpielzug() des
     * baoSpielPresenter weitergegeben. Während der Ausführung wird geprüft ob das Spiel beendet
     * ist.
     *
     * @param position - ist die Position der Mulde, bei der der Spielzug angefangen wird.
     */
    public void macheSpielzug(Position position) {


        int zeile = position.gibZeile();
        int spalte = position.gibSpalte();
        //Ist in der geklickten Mulde mehr als ein Stein, führe Spielzug aus.

        if (spielbrett[zeile][spalte].gibAnzahlSteine() > 1) {

            if (aktuellerTimer != null) {
                aktuellerTimer.stop();
            }
            // Erstellt einen neuen Spielzug, in dem die Veränderungen festgehalten werden.
            Spielzug spielzug = new Spielzug(aktuellerSpieler);
            int gegnerZeile;
            int steineInHand;
            steineInHand = spielbrett[zeile][spalte].gibAnzahlSteine();
            //Sind mehr als ein Stein in der Mulde, führe Spielzug aus
            if (steineInHand > 1) {
                spielbrett[zeile][spalte].entferneSteine();
                spielzug.fuegeVeraenderungHinzu(new MuldenVeraenderung(new Mulde(new Position(zeile, spalte),
                        spielbrett[zeile][spalte].gibAnzahlSteine()), MuldenAktion.ENTFERNE_STEINE));
                while (steineInHand > 0) {

                    if (zeile % 2 == 0) {
                        if (spalte < 7) {
                            spalte++;
                        } else {
                            zeile++;
                        }
                    } else {
                        if (spalte > 0) {
                            spalte--;
                        } else {
                            zeile--;
                        }
                    }
                    spielbrett[zeile][spalte].fuegeSteinHinzu();
                    spielzug.fuegeVeraenderungHinzu(new MuldenVeraenderung(new Mulde(new Position(zeile, spalte),
                            spielbrett[zeile][spalte].gibAnzahlSteine()), MuldenAktion.FUEGE_STEIN_HINZU));
                    steineInHand--;

                    gegnerZeile = (zeile == INNERE_REIHE_SPIELER_EINS) ? INNERE_REIHE_SPIELER_ZWEI : INNERE_REIHE_SPIELER_EINS;

                /*Ist kein Stein mehr in der Hand und mehr als ein Stein in der aktuellen Mulde, setze den Spielzug mit
                diesen fort.*/
                    if (steineInHand == 0 && spielbrett[zeile][spalte].gibAnzahlSteine() > 1) {
                        steineInHand = spielbrett[zeile][spalte].gibAnzahlSteine();
                        spielbrett[zeile][spalte].entferneSteine();

                        /* Ist es zusätzlich möglich Steine zu klauen, lege diese ebenfalls in deine Hand
                         */
                        if ((zeile == 1 || zeile == 2) && (spielbrett[gegnerZeile][spalte].gibAnzahlSteine() > 0)) {
                            steineInHand = steineInHand + spielbrett[gegnerZeile][spalte].gibAnzahlSteine();
                            spielbrett[gegnerZeile][spalte].entferneSteine();
                            spielzug.fuegeVeraenderungHinzu(new MuldenVeraenderung(new Mulde(new Position(gegnerZeile, spalte),
                                    spielbrett[gegnerZeile][spalte].gibAnzahlSteine()), MuldenAktion.KLAUE_STEINE));
                        }

                        spielzug.fuegeVeraenderungHinzu(new MuldenVeraenderung(new Mulde(new Position(zeile, spalte),
                                spielbrett[zeile][spalte].gibAnzahlSteine()), MuldenAktion.ENTFERNE_STEINE));
                    }
                }
            }

            pruefeObSpielVorbei(spielzug);
            if (baoSpielPresenter != null) {
                this.baoSpielPresenter.verarbeiteSpielzug(spielzug);
            }
        }
    }


    /**
     * Prüft ob das Spiel vorbei ist. Ist dies der Fall, so wird der Gewinnername in das übergebende
     * Spielzugobjekt geschrieben.
     *
     * @return ob das Spiel vorbei ist.
     */
    private boolean pruefeObSpielVorbei(Spielzug spielzug) {
        boolean spielerEinsVerloren = true;
        boolean spielerZweiVerloren = true;
        for (int i = 0; i < SPALTEN; i++) {
            if (spielbrett[1][i].gibAnzahlSteine() > 1) {
                spielerZweiVerloren = false;
            }
            if (spielbrett[2][i].gibAnzahlSteine() > 1) {
                spielerEinsVerloren = false;
            }
        }
        if (spielerZweiVerloren) {
            spielzug.setzeGewinnerNamen(spielerEins.gibName());
        } else if (spielerEinsVerloren) {
            spielzug.setzeGewinnerNamen(spielerZwei.gibName());
        }
        return spielerEinsVerloren || spielerZweiVerloren;
    }

    /**
     * Gibt das Muldenarray des Spielbretts.
     *
     * @return Muldenarray.
     */
    public Mulde[][] gibSpielbrett() {
        return this.spielbrett;
    }

    @Override
    public boolean equals(Object o) {
        boolean istGleich = false;
        if (o instanceof BaoSpiel) {
            istGleich = true;
            for (int i = 0; i < ZEILEN; i++) {
                for (int j = 0; j < SPALTEN; j++) {
                    istGleich = ((BaoSpiel) o).gibSpielbrett()[i][j].equals(this.spielbrett[i][j]);
                }
            }
        }
        return istGleich;
    }

    /**
     * Wird vom Presenter aufgerufen, wenn die Animation auf der View beendet wurde.
     */
    public void onAnimationEnde() {
        wechselDenSpieler();
    }

    /**
     * Gibt den Spieler, der gerade am Zug ist.
     *
     * @return den aktuellen Spieler
     */
    public Spieler gibAktuellenSpieler() {
        return this.aktuellerSpieler;
    }

    /**
     * Wechselt den Spieler.
     */
    private void wechselDenSpieler() {
        aktuellerSpieler.setzeZug(false);
        aktuellerSpieler = (aktuellerSpieler == spielerEins) ? spielerZwei : spielerEins;
        wechselDieTimer();
        baoSpielPresenter.verarbeiteSpielerwechsel(aktuellerSpieler.gibSpielerIndex());
        aktuellerSpieler.setzeZug(true);
    }

    /**
     * Wechselt die Timer und startet den aktuellen.
     */
    private void wechselDieTimer() {
        if (zeitTyp != null) {
            aktuellerTimer = (aktuellerTimer == spielerEinsTimer) ? spielerZweiTimer : spielerEinsTimer;
            switch (this.zeitTyp) {
                case Konstanten.SPIELZEIT_RUNDENDAUER:
                    aktuellerTimer.reset();
                    aktuellerTimer.start();
                    break;
                case Konstanten.SPIELZEIT_SPIELDAUER:
                    aktuellerTimer.start();
                    break;
            }
        }
    }

    /**
     * Beendet beide Timer. Wird beim Beenden der Activity aufgerufen.
     */
    public void beendeTimer() {
        if (spielerEinsTimer != null) {
            spielerEinsTimer.stop();
            spielerZweiTimer.stop();
        }
    }

}
