/******************************************************************************
 ** $Id: Turnier.java 816 2015-11-14 15:48:27Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ** Lauffähig ab Java 6.
 ******************************************************************************
 ** Copyright (C) Wolfgang Hauck <wolfgang.hauck@3kelvin.de>
 ******************************************************************************
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************
 ** Die aktuellste Version von JaFuffy findet sich im Internet unter
 ** <http://jafuffy.3kelvin.de>.
 **
 ** Kommentare, Fehler oder Erweiterungswünsche bitte per E-Mail senden an
 ** <jafuffy@3kelvin.de>.
 ******************************************************************************/
package jafuffy.logik;

import jafuffy.bedienung.Auswahl;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Random;

/** Durchführung eines Turniers. */
public class Turnier extends Aenderungen<CEAblauf> implements ActionListener {

    /** Zur Serialisierung. */
    private static final long serialVersionUID = -8157448193770055452L;

    /** Anzahl der möglichen Einträge auf Gewinnkarte . */
    private static final int EINTRAEGE = 13;

    /** Die Anzahl der Würfel, die für das Spiel verwendet werden. */
    public static final int WUERFEL = 5;
    /** Die Anzahl der möglichen Runden pro Durchgang. */
    public static final int RUNDEN = 3;

    /** Würfel im Spiel. */
    private final Wuerfel[] wuerfel;
    /** Spielregelvariante, die für das Turnier benutzt wird. */
    private final Variante variante;
    /** Alle Mitspieler. */
    private final ArrayList<Spieler> spieler;
    /** Auswertungsregel für dieses Turnier. */
    private final Auswertung auswertung;
    /** Anzahl der Spiele im Turnier. */
    private final int anzahl;
    /** Hält fest, wer das nächste Spiel beginnt. */
    private final Beginner beginnmodus;

    /**
     * Spieler, der gerade dran ist. Der Wert "null" bedeutet, dass das Turnier beendet ist.
     */
    private Spieler aktiver;
    /** Anzahl der vollendeten Spiele. */
    private int gespielt = 0;
    /** Anzahl der gesetzten Einträge, wobei eine Runde beendet sein muss. */
    private int eintraege = 0;
    /**
     * Index des Index des Spielers pro Runde, der gerade an der Reihe. Zählung fängt bei Null mit Spieler an, welcher
     * die Runde eröffnet. Der Index ist nicht identisch mit dem Index des Containers, welcher die Spieler enthält.
     */
    private int teilnehmer = 0;
    /** Bestimmt, wer als Erster eine Spielrunde eröffnet. */
    private int startindex;
    /**
     * Der Turnierverlauf, alle Spieler pro Turnier werden gespeichert, was die Erstellung eines Reports ermöglicht.
     */
    private ArrayList<Integer>[] verlauf;
    /** Der aktuelle Turnierstand, für jeden Mitspieler eine Punktezahl. */
    private int[] turnierstand;

    /**
     * Statistik. Muss wiederhergestellt werden, da bei jedem Programmlauf aktualisiert.
     */
    private transient Statistik statistik;
    /**
     * Punktestand. Muss wiederhergestellt werden, da das Modell Listener enthält, die bei jedem Programmlauf neu
     * erstellt werden.
     */
    private transient Punkte punkte;

    /** Benötigt für die Rücknahme eines Zuges, wobei die Spielerdaten gemerkt werden müssen. */
    private transient Spieler kopieAktiver;
    /** Benötigt für die Rücknahme eines Zuges. */
    private transient Spieler merkerAktiver;
    /** Benötigt für die Rücknahme eines Zuges. */
    private transient int merkerGespielt;
    /** Benötigt für die Rücknahme eines Zuges. */
    private transient int merkerEintrag;
    /** Benötigt für die Rücknahme eines Zuges. */
    private transient int merkerTeilnehmer;

    /**
     * Konstruktor. Muss von Initialisierungsfunktion gefolgt werden.
     *
     * @param variante
     *            Spielregelvariante
     * @param spieler
     *            Liste aller Mitspieler
     * @param startindex
     *            Nummer des ersten Spielers
     * @param beginnmodus
     *            Wer beginnt das nächste Spiel?
     * @param anzahl
     *            Anzahl der Spiele im Turnier (0=unendlich)
     */
    public Turnier(Variante variante, ArrayList<Spieler> spieler, int startindex, Beginner beginnmodus, int anzahl) {
        this.variante = variante;
        this.spieler = spieler;
        this.startindex = startindex;
        this.beginnmodus = beginnmodus;
        this.anzahl = anzahl;

        // Würfel vorbereiten
        wuerfel = new Wuerfel[Turnier.WUERFEL];
        for (int i = 0; i < Turnier.WUERFEL; i++) {
            wuerfel[i] = new Wuerfel(i + 1);
        }

        // Auswertung je nach Spielregel erstellen
        auswertung = variante.auswertung(wuerfel);

        // Spieler vorbereiten, Beginner heraussuchen
        for (Spieler s : spieler) {
            s.initialisiere();
        }
        aktiviere(startindex);
    }

    /**
     * Spieler hat gewürfelt oder Setzen rückgängig gemacht.
     *
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        String ac = e.getActionCommand();
        if (ac.equals("Gewuerfelt")) {
            wuerfle();
        } else if (ac.equals("Rueckgaengig")) {
            nimmZurueck();
        } else if (ac.equals("Vorschlagen")) {
            auswertung.vorschlagen();
        } else if (ac.equals("Setzen")) {
            setze(auswertung.bester());
        } else if (ac.equals("Beenden")) {
            beende();
        }
    }

    /**
     * @return aktiver Spieler
     */
    public Spieler aktiver() {
        return aktiver;
    }

    /**
     * @return Anzahl der Spiele pro Turnier
     */
    public int anzahl() {
        return anzahl;
    }

    /**
     * @return Auswahl
     */
    public Auswahl auswahl() {
        return variante.auswahl(this);
    }

    /**
     * @return Auswertungsobjekt
     */
    public Auswertung auswertung() {
        return auswertung;
    }

    /**
     * Aktion bei Turnierabbruch: Sichern des Turnierstands unter Standardnamen.
     */
    public void beende() {
        if (aktiver != null) {
            auswertung.tipps(false);
            fireStateChanged(gespielt == anzahl && anzahl > 0 ? CEAblauf.ENDE : CEAblauf.ABBRUCH);
            aktiver.warte(); // keine Spaltenmarkierung in der Tabelle mehr
            aktiver = null;
        }
    }

    /** @return Turnier beendet? */
    public boolean beendet() {
        return aktiver == null;
    }

    /**
     * @return Turnierbeschreibung
     */
    public String beschreibung() {
        String s = new String("Mitspieler: ");
        ListIterator<Spieler> iterator = spieler.listIterator();
        while (iterator.hasNext()) {
            s += iterator.next().toString();
            if (iterator.hasNext()) {
                s += ", ";
            } else {
                s += ".";
            }
        }
        s += "\n" + auswertung.toString() + ". " + (spieler.size() > 1 ? beginnmodus + ". " : "");
        s += anzahl == 0 ? "Beliebig viele Spiele."
                : "Im " + (gespielt + 1) + ". von " + anzahl + (anzahl != 1 ? " Spielen" : " Spiel") + ".";
        return s;
    }

    /**
     * @return Nummer des laufenden Spiels
     */
    public int nummer() {
        return gespielt + 1;
    }

    /**
     * Pause einlegen (keine Aktionen mehr entgegennehmen).
     */
    public void pausiere() {
        fireStateChanged(CEAblauf.PAUSE);
    }

    /**
     * @return Punktemodell
     */
    public Punkte punkte() {
        return punkte;
    }

    /**
     * Spieler hat gesetzt. Weiter im Spielgeschehen.
     *
     * @param eintrag
     *            Eintrag
     */
    public void setze(Tabzeile eintrag) {
        setze(eintrag, auswertung.wert(eintrag));
    }

    /**
     * Wurf des aktiven Spielers setzen. Weiter im Spielgeschehen.
     *
     * @param eintrag
     *            Eintrag
     * @param wert
     *            Punktewert
     */
    public void setze(Tabzeile eintrag, int wert) {
        merke();
        aktiver.setze(eintrag, wert, true);
        beendeSetzen();
    }

    /**
     * Weiter mit dem Turnier nach Turnierpause. Erforderlich wegen Serialisierung.
     *
     * @param statistik
     *            Aktuelles Statistikobjekt, das vom Turnier verwendet und gepflegt werden soll.
     */
    public void setzeFort(Statistik statistik) {
        // aktuelle Statistik soll dieses Turnier verfolgen
        this.statistik = statistik;
        // werte Wurf des aktiven Spielers aus um auf aktuellem Stand zu sein
        auswertung.gewuerfelt(aktiver);
        // Punktemodell für Zettel
        punkte = new Punkte(spieler, auswertung);
        if (verlauf == null) {
            statistik.verfolgen(variante.ordinal(), anzahl, spieler, auswertung.mittelwert(), auswertung.abweichung());
            verlauf = statistik.verlauf();
            turnierstand = statistik.turnierstand();
        } else {
            statistik.verfolgen(variante.ordinal(), anzahl, spieler, auswertung.mittelwert(), auswertung.abweichung(),
                    verlauf, turnierstand);
        }
    }

    /**
     * @return Liste aller Spieler
     */
    public ArrayList<Spieler> spieler() {
        return spieler;
    }

    /** Turnier starten. */
    public void starte() {
        fireStateChanged(CEAblauf.START);
    }

    /**
     * @return Feld aller Würfel
     */
    public Wuerfel[] wuerfel() {
        return wuerfel;
    }

    /**
     * @param i
     *            Nummer des Würfels
     * @return Würfel
     */
    public Wuerfel wuerfel(int i) {
        return wuerfel[i];
    }

    /** Runde beenden. */
    private void beendeRunde() {
        eintraege++;
        teilnehmer = 0;
        statistik.beendeRunde();
    }

    /**
     * Spieler hat gesetzt, der nächste ist dran. Es werden keine for/while-Schleifen benutzt, da diese Methode bei
     * jedem Setz-Ereignis angesprungen wird.
     */
    private void beendeSetzen() {
        aktiviere((spieler.indexOf(aktiver) + 1) % spieler.size());
        teilnehmer++;
        if (teilnehmer >= spieler.size()) {
            beendeRunde();
            if (eintraege >= EINTRAEGE) {
                // in der Tabelle soll kein Spieler markiert sein
                aktiver.warte();
                auswertung.tipps(false);
                fireStateChanged(CEAblauf.RESULTAT);
                beendeSpiel();
            }
        }
        if (anzahl > 0 && gespielt == anzahl) {
            beende();
        } else {
            if (gespielt == merkerGespielt) {
                auswertung.tipps(false);
                fireStateChanged(CEAblauf.GESETZT);
            } else {
                fireStateChanged(CEAblauf.SPIEL);
            }
        }
    }

    /** Spiel beenden, nächsten Spieleröffner bestimmen. */
    private void beendeSpiel() {
        Spieler verlierer, gewinner;

        // suche Gewinner und Verlierer
        verlierer = spieler.get(0);
        gewinner = verlierer;
        for (Spieler s : spieler) {
            if (s.endsumme() < verlierer.endsumme()) {
                verlierer = s;
            }
            if (s.endsumme() > gewinner.endsumme()) {
                gewinner = s;
            }
        }

        // Suche nächsten Spielbeginner
        switch (beginnmodus) {
        case VERLIERER:
            startindex = spieler.indexOf(verlierer);
            break;
        case GEWINNER:
            startindex = spieler.indexOf(gewinner);
            break;
        case REIHUM:
            startindex++;
            startindex %= spieler.size();
            break;
        case ZUFALL:
            startindex = new Random().nextInt(spieler.size());
            break;
        default:
            break;
        }

        // Daten zurücksetzen
        statistik.beendeSpiel();
        for (Spieler s : spieler) {
            s.initialisiere();
        }

        // Weiterschaltung
        aktiviere(startindex);
        gespielt++;
        eintraege = 0;
    }

    /** Merken für Rücknahme. */
    private void merke() {
        merkerTeilnehmer = teilnehmer;
        merkerEintrag = eintraege;
        merkerGespielt = gespielt;
        merkerAktiver = aktiver;
        kopieAktiver = aktiver.kopie();
    }

    /** Zurücknehmen. */
    private void nimmZurueck() {
        if (eintraege != merkerEintrag) {
            statistik.rueckgaengig();
        }
        aktiver.warte();
        teilnehmer = merkerTeilnehmer;
        eintraege = merkerEintrag;
        gespielt = merkerGespielt;
        spieler.set(spieler.indexOf(merkerAktiver), kopieAktiver);
        aktiver = kopieAktiver;
        aktiver.aktualisiere();
        auswertung.gewuerfelt(aktiver);
        for (Wuerfel w : wuerfel) {
            w.setSelected(false);
        }
        auswertung.tipps(false);
        fireStateChanged(CEAblauf.RUECKGAENGIG);
    }

    /** Spieler würfelt. */
    private void wuerfle() {
        for (Wuerfel w : wuerfel) {
            if (w.isSelected()) {
                w.wirf();
            }
        }
        fireStateChanged(CEAblauf.WUERFELN);
        aktiver.reagiere();
        auswertung.gewuerfelt(aktiver);
        auswertung.tipps(false);
        fireStateChanged(CEAblauf.GEWUERFELT);
    }

    /**
     * Spieler aktivieren.
     *
     * @param index
     *            Spielerindex im Spielerfeld
     */
    void aktiviere(int index) {
        aktiver = spieler.get(index);
        aktiver.aktiviere();
    }

    /**
     * @param i
     *            Nummer
     * @return Spieler
     */
    Spieler spieler(int i) {
        return spieler.get(i);
    }

}
