/******************************************************************************
 ** $Id$
 ** 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.bedienung;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import jafuffy.logik.Aufsicht;
import jafuffy.logik.CEAblauf;
import jafuffy.logik.CEJaFuffy;
import jafuffy.logik.Spieler;
import jafuffy.logik.Turnier;

/** Hier lassen sich die Aktionen eines Spielers verfolgen, einschließlich eines Bots. */
@SuppressWarnings("serial")
public class Verfolgung extends JPanel implements ChangeListener, PropertyChangeListener {

    /** Hintergrundfarbe für Schrittbeschreibung. */
    private static final Color SCHRITTBESCHREIBUNGSFARBE = new Color(240, 255, 255);
    /** Umrandungsfarbe für Schrittbeschreibung. */
    private static final Color LINIENFARBE = new Color(48, 128, 192);
    /** Innenabstände im Verfolgungsfeld. */
    private static final int ABSTAND = 4;
    /** Gewicht zur Verteilung von den Bot-Feldern und des Bot-Icons. */
    private static final float BOTFELDGEWICHT = 0.4f;
    /** Schrittbeschreibungsfeld und Bestätigungsfeld sollen gleiche Breite haben. */
    private static final int BOTFELDBREITE = 125;
    /** Karte für Darstellung bei Mensch als Mitspieler. */
    private static final String SPIELER_MENSCH = "Mensch";
    /** Karte für Darstellung bei Bot als Mitspieler. */
    private static final String SPIELER_BOT = "Card with JTextField";
    /** Animiertes Becher-Icon, welches zum Würfeln auffordert. */
    private static final ImageIcon WUERFELN = Grafik.icon("Wuerfeln.gif");
    /** Becher-Icon, welches zur Würfelauswahl auffordert. */
    private static final ImageIcon WUERFELN0 = Grafik.icon("Wuerfeln0.gif");
    /** Animiertes Schreibblock-Icon, welches zum Eintragen auffordert. */
    private static final ImageIcon EINTRAG = Grafik.icon("Eintrag.gif");
    /** Schreibblock-Icon, welches das Turnierende anzeigt. */
    private static final ImageIcon FERTIG = Grafik.icon("Fertig.gif");
    /** Icon für den Bot, angezeigt wenn ein Boot an der Reihe ist. */
    private static final ImageIcon BOT = Grafik.icon("Bot.png");

    /** Symbol für Mensch. */
    private final JLabel mensch = new JLabel(WUERFELN0);
    /** Symbol für Bot. */
    private final JLabel bot = new JLabel(BOT);
    /** Beschreibung des letzten vollendeten Schritts eines Bots. */
    private final JTextArea botschritt = new JTextArea(2, 5) {

    };
    /** Erlaubt den nächsten Schritt eines Bot-Spielers, inklusive der Weitergabe zum nächsten Spieler. */
    private final JButton naechsteAktion = weiter(KeyEvent.VK_PAGE_DOWN);
    /** Überspringt alle Schritte des Bot-Spielers, so dass die Würfel direkt dem nächsten Spieler übergeben werden. */
    private final JButton alleAktionen = weiter(KeyEvent.VK_END);

    /** Konstruktor. */
    public Verfolgung() {
        super(new CardLayout());
        add(menschenkarte(), SPIELER_MENSCH);
        add(botkarte(), SPIELER_BOT);
    }

    @Override
    public void addMouseListener(MouseListener listener) {
        mensch.addMouseListener(listener);
    }

    /**
     * Erledigt die nächste anstehende Aktion des Bots und zeigt dies für eine gewisse Dauer an.
     *
     * @param dauer
     *            Anzeigedauer für die Aktion.
     */
    public void erledigeNaechsteAktion(int dauer) {
        naechsteAktion.doClick(dauer);
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        String name = event.getPropertyName();
        Object wert = event.getNewValue();
        if (name.equals(Action.LONG_DESCRIPTION)) {
            botschritt.setText((String) wert);
        }
    }

    @Override
    public void stateChanged(ChangeEvent ce) {
        if (CEJaFuffy.adressiert(ce, CEAblauf.class)) {
            CEJaFuffy<CEAblauf> cej = CEJaFuffy.ereignisbehaelter(ce);
            switch (cej.ereignis()) {
            case START:
                starte();
            case SPIEL:
            case GESETZT:
            case GEWUERFELT:
            case RUECKGAENGIG:
                aktualisiere((Turnier) ce.getSource());
                break;
            case RESULTAT:
                verstecke();
                break;
            case ABBRUCH:
            case ENDE:
                beende();
                break;
            default:
                break;
            }
        }
    }

    /**
     * Verknüpft eine Aktion mit einem Bedienelement der Oberfläche.
     *
     * @param aktion
     *            Die zu verknüpfende Aktion.
     * @param anzeige
     *            Der Name, welcher in dem Bedienelement angezeigt werden soll.
     * @param hilfe
     *            Die Hilfe, welche als "Tool Tip Text" angezeigt werden soll.
     */
    public void verknuepfe(Action aktion, String anzeige, String hilfe) {
        aktion.addPropertyChangeListener(this);
        aktion.putValue(Action.NAME, anzeige);
        aktion.putValue(Action.SHORT_DESCRIPTION, hilfe);
        if (anzeige.equals(Aufsicht.ANSTEHENDE_AKTION)) {
            naechsteAktion.setAction(aktion);
        }
        if (anzeige.equals(Aufsicht.ALLE_AKTIONEN)) {
            alleAktionen.setAction(aktion);
        }
    }

    /**
     * Aktualisiert die Anzeige der Infos bezüglich aktuellem Spieler; (De)Aktivierung diverser Elemente.
     *
     * @param turnier
     *            Das Turnier, für welches der Status aktualisiert wird.
     */
    private void aktualisiere(Turnier turnier) {
        Spieler aktiver = turnier.aktiver();
        CardLayout layout = (CardLayout) getLayout();
        boolean neu = aktiver.neu();
        boolean fertig = aktiver.fertig();
        if (aktiver.menschlich()) {
            layout.show(this, SPIELER_MENSCH);
        } else {
            layout.show(this, SPIELER_BOT);
        }
        mensch.setEnabled(!neu);
        mensch.setIcon(fertig ? EINTRAG : WUERFELN0);
        bot.setEnabled(true);
    }

    /** Beendet das Turnier und zeigt dies über den Status an. */
    private void beende() {
        mensch.setDisabledIcon(FERTIG);
        mensch.setEnabled(false);
        bot.setEnabled(false);
    }

    /**
     * @param titel
     *            Der Titel des Bot-Feldes.
     * @param hilfe
     *            Die Kontexthilfe.
     * @return Die Felder für Schrittbeschreibung und Bestätigung, jeweils für Bot-Spieler.
     */
    private JPanel botfeld(String titel, String hilfe) {
        JPanel feld = new JPanel() {
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(BOTFELDBREITE, super.getPreferredSize().height);
            }
        };
        feld.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder(null, titel, TitledBorder.CENTER, TitledBorder.TOP, null, Color.BLUE),
                BorderFactory.createEmptyBorder(ABSTAND, ABSTAND, ABSTAND, ABSTAND)));
        feld.setToolTipText(hilfe);
        return feld;
    }

    /** @return Die Karte, welche Informationen bezüglich eines Bot-Spielers anzeigt. */
    private JPanel botkarte() {
        botschritt.setLineWrap(true);
        botschritt.setWrapStyleWord(true);
        botschritt.setEditable(false);
        botschritt.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(LINIENFARBE),
                BorderFactory.createEmptyBorder(ABSTAND, ABSTAND, ABSTAND, ABSTAND)));
        botschritt.setBackground(SCHRITTBESCHREIBUNGSFARBE);
        bot.setBorder(BorderFactory.createEtchedBorder());
        bot.setEnabled(false);
        GridBagConstraints c = new GridBagConstraints();
        JPanel aktion = botfeld("Anstehende Aktion",
                "Beschreibung der Aktion, welche der Bot als nächstes ausführen möchte.");
        aktion.setLayout(new GridBagLayout());
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridx = 0;
        c.weightx = 1;
        aktion.add(botschritt, c);
        JPanel bestaetigung = botfeld("Bestätigung",
                "Anstehende Bot-Aktion zur Kenntnis genommen, Bestätigung zum Weitermachen");
        bestaetigung.setLayout(new GridLayout(2, 1, 0, ABSTAND));
        bestaetigung.add(alleAktionen);
        bestaetigung.add(naechsteAktion);
        JPanel feld = new JPanel();
        feld.setLayout(new GridBagLayout());
        c.fill = GridBagConstraints.BOTH;
        c.gridx = 0;
        c.weightx = BOTFELDGEWICHT;
        c.weighty = 1;
        feld.add(aktion, c);
        c.fill = GridBagConstraints.NONE;
        c.gridx = 1;
        c.weightx = 1 - 2 * BOTFELDGEWICHT;
        feld.add(bot, c);
        c.fill = GridBagConstraints.BOTH;
        c.gridx = 2;
        c.weightx = BOTFELDGEWICHT;
        feld.add(bestaetigung, c);
        return feld;
    }

    /** @return Die Karte, welche Informationen bezüglich eines menschlichen Spielers anzeigt. */
    private JPanel menschenkarte() {
        mensch.setToolTipText("Sortieren durch Doppelklick, Kontextmenü");
        mensch.setBorder(BorderFactory.createEtchedBorder());
        mensch.setAlignmentX(CENTER_ALIGNMENT);
        mensch.setEnabled(false);
        JPanel feld = new JPanel();
        feld.setLayout(new BoxLayout(feld, BoxLayout.PAGE_AXIS));
        feld.add(Box.createVerticalGlue());
        feld.add(mensch);
        feld.add(Box.createVerticalGlue());
        return feld;
    }

    /** Startet das Turnier. */
    private void starte() {
        mensch.setDisabledIcon(WUERFELN);
    }

    /** Zeigt das Standardverfolgungsfeld an. */
    private void verstecke() {
        CardLayout layout = (CardLayout) getLayout();
        layout.show(this, SPIELER_MENSCH);
    }

    /**
     * @param taste
     *            Tastencode.
     * @return Der gewünschte Button.
     */
    private JButton weiter(int taste) {
        JButton weiter = new JButton();
        weiter.setMargin(new Insets(ABSTAND / 2, 0, ABSTAND / 2, 0));
        weiter.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(taste, 0), weiter);
        weiter.getActionMap().put(weiter, new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent event) {
                JButton weiter = (JButton) event.getSource();
                weiter.doClick();
            }
        });
        return weiter;
    }

}
