/******************************************************************************
 ** $Id: Spieler.java 1646 2019-01-12 05:33:11Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ******************************************************************************
 ** 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 java.io.Serializable;
import java.util.HashMap;

/** Enthält Punktzahlen und weitere Daten, die den Stand beschreiben. */
public class Spieler implements Comparable<Spieler>, Cloneable, Serializable {

    /** Objekt ist serialisierbar. */
    private static final long serialVersionUID = -5170511106942700515L;

    /** Maximale Anzahl der Spieler. */
    public static final int SPIELER = 4;
    /** Grenzpunktzahl für Bonus. */
    public static final int GRENZE = 63;
    /** Bonus. */
    public static final int BONUS = 35;

    /** Spielername, wie er bei der Absprache gewählt wurde und auf dem Zettel angezeigt wird. */
    private final String name;
    /** Index dieses Spielers, eindeutig für den Spieler für ein Turnier. */
    private final int index;
    /** Punktstände inklusive Zwischensummen und Endsumme. */
    private final HashMap<Kategorie, Integer> punktzahlen = new HashMap<>(Kategorie.ANZAHL);
    /** Karte zur Ermittlung der freien, setzbaren Einträge. */
    private final HashMap<Kategorie, Boolean> setzbarkeiten = new HashMap<>(Kategorie.ANZAHL);

    /** Gibt an, ob der Spieler schon gewürfelt hat. Nur der aktive Spieler kann würfeln! */
    private boolean gewuerfelt;
    /** Spieler hat genau einmal gewürfelt. */
    private boolean einmalig;
    /** Anzahl der dem Spieler zur Verfügung stehenden Restwürfe. */
    private int rest;

    /**
     * Legt einen Spieler an, welcher an einem Turnier teilnehmen kann.
     *
     * @param name
     *            Name des Spielers.
     * @param index
     *            Index des Spielers.
     */
    public Spieler(String name, int index) {
        this.name = name;
        this.index = index;
        initialisiere();
        beende();
    }

    @Override
    public Spieler clone() {
        try {
            return (Spieler) super.clone();
        } catch (CloneNotSupportedException exception) {
            exception.printStackTrace();
            return null;
        }
    }

    @Override
    public int compareTo(Spieler spieler) {
        return Integer.compare(endsumme(), spieler.endsumme());
    }

    /** Teilt mit, ob der Spieler genau einmal gewürfelt hat. */
    public boolean einmalig() {
        return einmalig;
    }

    /** Gibt die Endsumme über den oberen und unteren Tabellenteil zurück. */
    public int endsumme() {
        return punktzahlen.get(Kategorie.ESUMME);
    }

    /** Gibt an, ob der Spieler alle zur Verfügung stehenden Würfe in Anspruch genommen hat. */
    public boolean fertig() {
        return 0 == rest;
    }

    /** Liefert Summe der oberen Einträge des oberen Tabellenteils zurück, aber ohne Bonus. */
    public int gesamt() {
        return punktzahlen.get(Kategorie.GESAMT);
    }

    /** Gibt an, ob der Spieler schon gewürfelt hat. */
    public boolean gewuerfelt() {
        return gewuerfelt;
    }

    /** Liefert Identität des Spielers, eindeutig auch bei gleichlautenden Namen. */
    public String identitaet() {
        return name + " (Spieler " + (index + 1) + ")";
    }

    /** Liefert den Index des Spielers zurück. */
    public int index() {
        return index;
    }

    /** Gibt an, ob dieses Objekt interaktiv durch einen Menschen gesteuert wird. */
    public boolean interaktiv() {
        return true;
    }

    /** Gibt an, ob dem Spieler noch alle Versuche zum Würfeln zur Verfügung stehen. */
    public boolean neu() {
        return Turnier.RUNDEN == rest;
    }

    /**
     * Liefert gewünschte Unsicherheit der Prognose zur Steuerung der Spielstärke des Bots.
     * 
     * @return Ein Wert zwischen 0 und 1; 0 ist ganz sicher, 1 ist völlig unsicher.
     */
    public double prognoseunsicherheit() {
        return 0;
    }

    /**
     * Gibt die erzielte Punktzahl für einen bestimmten Eintrag zurück.
     * 
     * @param kategorie
     *            Der Eintrag in der Tabelle auf dem Zettel.
     * @return Schon erwürfelte Punktzahl.
     */
    public int punkte(Kategorie kategorie) {
        return punktzahlen.get(kategorie);
    }

    /** Gibt an, wie oft der Spieler noch würfeln darf. */
    public int rest() {
        return rest;
    }

    /**
     * Ermittelt, ob ein Eintrag noch gesetzt werden kann.
     *
     * @param kategorie
     *            Der Eintrag in der Tabelle auf dem Zettel.
     * @return Ist der Eintrag noch setzbar?
     */
    public boolean setzbar(Kategorie kategorie) {
        return setzbarkeiten.get(kategorie);
    }

    @Override
    public String toString() {
        return name;
    }

    /**
     * Bringt die Punktzahlen auf den Stand, der durch den Eintrag entsteht.
     *
     * @param kategorie
     *            Der Eintrag in der Tabelle auf dem Zettel.
     * @param wert
     *            Einzutragender Wert.
     */
    private void tabelliere(Kategorie kategorie, int wert) {
        punktzahlen.put(kategorie, wert);
        int oben = 0;
        for (Kategorie z : Kategorie.OBEN) {
            oben += punktzahlen.get(z);
        }
        punktzahlen.put(Kategorie.GESAMT, oben);
        int bonus = 0;
        if (punktzahlen.get(Kategorie.GESAMT) >= GRENZE) {
            bonus = BONUS;
        }
        punktzahlen.put(Kategorie.BONUS, bonus);
        punktzahlen.put(Kategorie.OSUMME, oben + bonus);
        int unten = 0;
        for (Kategorie z : Kategorie.UNTEN) {
            unten += punktzahlen.get(z);
        }
        punktzahlen.put(Kategorie.USUMME, unten);
        punktzahlen.put(Kategorie.ESUMME, oben + bonus + unten);
    }

    /** Dieser Spieler wird aktiviert, um für diese Runde einen Spielvorgang durchzuführen. */
    void aktiviere() {
        gewuerfelt = false;
        einmalig = false;
        rest = Turnier.RUNDEN;
    }

    /**
     * Nimmt den letzten Setzvorgang zurück.
     * 
     * @param ruecknahmespieler
     *            Der Spieler, dessen Zug zurückgenommen wird.
     * @param ruecknahmemerker
     *            Der Eintrag in der Tabelle, der zuletzt gesetzt wurde.
     */
    void annulliere(Spieler ruecknahmespieler, Kategorie ruecknahmemerker) {
        tabelliere(ruecknahmemerker, 0);
        setzbarkeiten.put(ruecknahmemerker, true);
        gewuerfelt = ruecknahmespieler.gewuerfelt;
        einmalig = ruecknahmespieler.einmalig;
        rest = ruecknahmespieler.rest;
    }

    /** Bereitet durch Deaktivierung diesen Spieler auf einen neuen Spielvorgang vor. */
    void beende() {
        gewuerfelt = false;
        einmalig = false;
        rest = 0;
    }

    /**
     * Belegt einen Eintrag in der Tabelle mit einem gegebenen Wert.
     * 
     * @param kategorie
     *            Der Eintrag in der Tabelle auf dem Zettel.
     * @param wert
     *            Der einzutragende Wert.
     */
    void belege(Kategorie kategorie, int wert) {
        tabelliere(kategorie, wert);
        setzbarkeiten.put(kategorie, false);
    }

    /** Setzt auf Anfangszustand zurück, um neue Spielrunde beginnen. */
    void initialisiere() {
        for (Kategorie kategorie : Kategorie.values()) {
            punktzahlen.put(kategorie, 0);
            switch (kategorie) {
            case GESAMT:
            case BONUS:
            case OSUMME:
            case USUMME:
            case ESUMME:
                setzbarkeiten.put(kategorie, false);
                break;
            default:
                setzbarkeiten.put(kategorie, true);
            }
        }
    }

    /** Reagiert auf einen Wurf des Spielers. */
    void reagiere() {
        gewuerfelt = true;
        einmalig = neu();
        rest--;
    }

}
