/******************************************************************************
 ** $Id: Fortsetzung.java 700 2015-02-08 21:00:42Z 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;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Dialog zum Fortsetzen und Abspeichern von Turnieren. Unterscheidung automatische/manuelle
 * Sicherung. Anzeige von Beschreibungen.
 */
@SuppressWarnings("serial")
class Fortsetzung extends JDialog implements PropertyChangeListener, ActionListener,
        ChangeListener {

    /**
     * Sucht Kurzbeschreibung des Turniers aus Datei heraus. String muss an erster Stelle des
     * serialisierten Objekts stehen.
     *
     * @param stand
     *            Datei mit Turnierstand
     * @return Kurzbeschreibung
     */
    static String beschreibung(File stand) {
        String beschreibung = "\n";
        if (stand.exists()) {
            try {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(stand));
                beschreibung = (String) ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return beschreibung;
    }

    /**
     * Erstellungsdatum.
     *
     * @param stand
     *            Datei
     * @return Datum & Uhrzeit der Datei stand (mit " vom " davor)
     */
    static String datum(File stand) {
        return stand.exists() ? DateFormat.getDateTimeInstance(DateFormat.SHORT,
                DateFormat.SHORT, Locale.GERMANY).format(new Date(stand.lastModified()))
                : "---";
    }

    /** Timer. */
    private static final int DELAY = 2500;
    /** Anzahl der Speicherschächte. */
    private static final int SICHERUNGEN = 4;
    /** Namen der Speicherschächte. */
    private static final String[] SCHACHTNAMEN = { "Automatische Sicherung",
            "Manuelle Sicherung A", "Manuelle Sicherung B", "Manuelle Sicherung C",
            "Manuelle Sicherung D" };
    /** Panel, falls keine Fortsetzungen vorhanden sind. */
    private static final String LEER = "Leer";

    /** Panel, falls Fortsetzungen vorhanden sind. */
    private static final String FORTSETZUNG = "Fortsetzung";
    /** Abstand des OK-Buttons zum Rahmen. */
    private static final int ABSTAND_OK = 32;
    /** Abstand vom Dialograhmen zum Inhalt. */
    private static final int DIALOGRAHMENABSTAND = 4;
    /** Abstand vom Feldrahmen zum Inhalt. */
    private static final int FELDRAHMENABSTAND = 4;
    /** Abstand Feldrahmen zu Buttons. */
    private static final int BUTTONABSTAND = 2 * FELDRAHMENABSTAND;

    /** Starter für ein Turnier. */
    private final Starter starter = new Starter();
    /** Umgebungsfenster. */
    private final Fenster fenster;
    /** Aufbau des Dialogs. */
    private final JPanel dialog = new JPanel(new CardLayout());
    /** Radiobuttons. */
    private final JRadioButton[] rButtons = new JRadioButton[SICHERUNGEN + 1];
    /** Gruppe der Radiobuttons. */
    private final ButtonGroup gruppe = new ButtonGroup();

    /** Beschreibungsfelder der Sicherungen (0. Eintrag: automatisch). */
    private final JTextArea[] textfelder = new JTextArea[SICHERUNGEN + 1];
    /** Speicherungsbestätigungsmeldung. */
    private final JDialog bestaetigung = new JDialog(this);
    /** alter Titel des selektierten Speicherschachtes. */
    private String altesDatum;
    /** alter Beschreibungtext des selektierten Speicherschachtes. */
    private String alterText;
    /** Gibt an, ob gespeichert oder geladen werden soll. */
    private boolean speichern;
    /** Ausgewähltes Turnier. */
    private int gewaehlt = -1;
    /** Vor Abbruch ausgewähltes Turnier (zur Wiederherstellung). */
    private int gewaehltVorAbbruch;
    /** Bestätigungsbutton (Stände vorhanden), mit längstem Text für Layout-Vorgabe. */
    private final JButton fortsetzen = new JButton("Turnier abspeichern");
    /** Bestätigungsbutton (keine Stände vorhanden). */
    private final JButton ok = new JButton("Keine Sicherungen vorhanden!");

    /** Menüpunkt, welcher speichern anstößt. */
    private JMenuItem menuepunkt;

    /** Gerade laufendes Turnier. */
    private Turnier turnier;

    /**
     * Konstruktor.
     *
     * @param fenster
     *            Umgebungsfenster
     */
    Fortsetzung(Fenster fenster) {
        this.fenster = fenster;
        UIManager.addPropertyChangeListener(this); // Look&Feel-Wechsel
        dialog.add(baueLeerfeld(), LEER);
        dialog.add(baueFortsetzungsfeld(), FORTSETZUNG);
        JScrollPane pane = new JScrollPane(dialog);
        pane.setBorder(new EmptyBorder(DIALOGRAHMENABSTAND, DIALOGRAHMENABSTAND,
                DIALOGRAHMENABSTAND, DIALOGRAHMENABSTAND));
        setContentPane(pane);
        setIconImages(Oberflaeche.LOGOS);
        setModal(true);
    }

    /**
     * Auswahl getroffen, Turnier fortsetzen/speichern.
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        String ac = event.getActionCommand();
        if (ac.equals("Speichern")) {
            sichere(gewaehlt, turnier, textfelder[gewaehlt].getText());
            menuepunkt.setEnabled(true);
        } else if (ac.equals("Fortsetzen")) {
            setVisible(false);
            starter.los(lade(gewaehlt));
            menuepunkt.setEnabled(gewaehlt != 0);
        } else if (ac.equals("OK")) {
            setVisible(false);
        } else if (ac.length() == 1) {
            int alt = gewaehlt;
            int neu = Integer.parseInt(ac);
            gewaehlt = neu;
            if (speichern) {
                fortsetzen.setEnabled(true);
                rButtons[alt].setText(altesDatum);
                textfelder[alt].setEditable(false);
                textfelder[alt].setEnabled(false);
                textfelder[alt].setText(alterText);
                altesDatum = rButtons[neu].getText();
                rButtons[neu].setText(SCHACHTNAMEN[neu] + " von jetzt");
                textfelder[neu].setEditable(true);
                textfelder[neu].setEnabled(true);
                alterText = textfelder[neu].getText();
                textfelder[neu].setText(turnier.beschreibung());
            }
        }
    }

    /**
     * Wer möchte über Speicher/Ladevorgänge auf dem Laufenden gehalten werden?
     *
     * @param listener
     *            Interessent and Startvorgängen.
     */
    public void addActionListener(ActionListener listener) {
        starter.addActionListener(listener);
    }

    /**
     * Realisiert Veränderungen des Look&Feel.
     *
     * @param evt
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals("lookAndFeel")) {
            SwingUtilities.updateComponentTreeUI(this);
            pack();
        }
    }

    /**
     * Modelldaten haben sich verändert. Neues Turnier begonnen (altes Turnier wird gesichert)
     * oder altes Turnier beendet.
     *
     * @param ce
     */
    @Override
    public void stateChanged(ChangeEvent ce) {
        if (CEJaFuffy.adressiert(ce, CEAblauf.class)) {
            Turnier turnier = (Turnier) ce.getSource();
            switch (CEJaFuffy.<CEAblauf> ereignis(ce)) {
            case START:
                this.turnier = turnier;
                break;
            case ABBRUCH:
                sichere(0, turnier, null);
                textfelder[0].setText(turnier.beschreibung());
                break;
            case ENDE:
                entferneAutomatischeSicherung();
                textfelder[0].setText(null);
                this.turnier = null;
                break;
            default:
                break;
            }
        }
    }

    /**
     * Baut Ständefeld zusammen; benötigt, wenn Stände vorhanden sind.
     *
     * @return Fortsetzungsfeld mit Schachtfeldern.
     */
    private JPanel baueFortsetzungsfeld() {
        JPanel staendefeld = baueStaendefeld();
        staendefeld.setBorder(new EmptyBorder(0, 0, FELDRAHMENABSTAND, 0));
        JPanel leiste = baueLeiste();
        leiste.setBorder(new EmptyBorder(FELDRAHMENABSTAND, 0, FELDRAHMENABSTAND / 2, 0));
        JPanel fortsetzung = new JPanel(new BorderLayout());
        fortsetzung.setBorder(new EmptyBorder(0, FELDRAHMENABSTAND, 0, FELDRAHMENABSTAND));
        fortsetzung.add(staendefeld, BorderLayout.CENTER);
        fortsetzung.add(leiste, BorderLayout.SOUTH);
        return fortsetzung;
    }

    /**
     * Baut Leerfeld zusammen; benötigt, wenn keine Stände vorhanden sind.
     *
     * @return Leeres Feld mit OK-Button.
     */
    private JPanel baueLeerfeld() {
        baueOk();
        JPanel leer = new JPanel();
        leer.setLayout(new BoxLayout(leer, BoxLayout.Y_AXIS));
        leer.add(Box.createVerticalGlue());
        leer.add(ok, BorderLayout.CENTER);
        leer.add(Box.createVerticalGlue());
        return leer;
    }

    /**
     * Leiste für "Fortsetzen" und "Abbruch".
     *
     * @return Leistenfeld.
     */
    private JPanel baueLeiste() {
        fortsetzen.addActionListener(this);
        fortsetzen.setActionCommand("Fortsetzen");
        JButton abbruch = new JButton("Abbruch");
        abbruch.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (speichern) {
                    textfelder[gewaehlt].setText(alterText);
                }
                gewaehlt = gewaehltVorAbbruch;
                setVisible(false);
            }
        });
        JPanel leiste = new JPanel(new GridLayout(1, 2, BUTTONABSTAND, 0));
        leiste.add(fortsetzen);
        leiste.add(abbruch);
        return leiste;
    }

    /** Baut OK-Button für Leerstand. */
    private void baueOk() {
        ok.addActionListener(this);
        ok.setActionCommand("OK");
        ok.setAlignmentX(CENTER_ALIGNMENT);
        ok.setMargin(new Insets(ABSTAND_OK, ABSTAND_OK, ABSTAND_OK, ABSTAND_OK));
    }

    /**
     * Baut das Ständefeld zusammen.
     *
     * @return Ständefeld.
     */
    private JPanel baueStaendefeld() {
        // Inhalte der Textfelder aus Sicherungsdatei auslesen
        for (int i = 0; i <= SICHERUNGEN; i++) {
            textfelder[i] = new JTextArea("\n", 2, 32);
            textfelder[i].setFont(new Font("Dialog", Font.PLAIN, 12));
            textfelder[i].setText(beschreibung(Pfad.datei(Turnier.BASIS + i + "."
                    + Turnier.ENDUNG)));
        }
        // Ständefeld mit den verschiedenen Ständen
        JPanel staendefeld = new JPanel();
        staendefeld.setLayout(new BoxLayout(staendefeld, BoxLayout.Y_AXIS));
        // automatische & manuelle Sicherungen
        for (int i = 0; i <= SICHERUNGEN; i++) {
            rButtons[i] = new JRadioButton();
            rButtons[i].addActionListener(this);
            rButtons[i].setActionCommand(String.valueOf(i));
            gruppe.add(rButtons[i]);
            textfelder[i].setToolTipText("<html><p>Beschreibt gespeicherten Turnierstand.</p>"
                    + "<p>Kann vor dem Speichern geändert werden.</p></html>");
            textfelder[i].setBorder(BorderFactory.createEtchedBorder());
            textfelder[i].setDisabledTextColor(Color.DARK_GRAY);
            JPanel schachtfeld = new JPanel(new BorderLayout());
            schachtfeld.add(rButtons[i], BorderLayout.NORTH);
            schachtfeld.add(textfelder[i], BorderLayout.CENTER);
            staendefeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND / 2));
            staendefeld.add(schachtfeld);
            staendefeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND));
        }
        textfelder[0].setToolTipText("<html>"
                + "<p>Beschreibt automatisch gespeicherten Turnierstand.</p>"
                + "<p>Der Text wird automatisch erzeugt.</p>" + "</html>");
        return staendefeld;
    }

    /**
     * Bereitet Auswahlanzeige vor mit aktuellen Daten.
     *
     * @param text
     *            Text des Weiter-Buttons
     * @param cmd
     *            ActionCommand
     */
    private void belegeAuswahlMitDaten(String text, String cmd) {
        Vector<Integer> auswahlgruppe = new Vector<Integer>(1 + SICHERUNGEN);
        // automatische & manuelle Sicherungen
        File stand = Pfad.datei(Turnier.BASIS + "0." + Turnier.ENDUNG);
        rButtons[0].setSelected(false);
        rButtons[0].setText(SCHACHTNAMEN[0] + " (" + datum(stand) + ")");
        rButtons[0].setToolTipText("Schacht entspricht gespeichertem Turnierstand");
        textfelder[0].setEditable(false);
        textfelder[0].setEnabled(false);
        if (!speichern && stand.exists()) {
            rButtons[0].setEnabled(true);
            auswahlgruppe.add(0);
        } else {
            rButtons[0].setEnabled(false);
        }
        for (int i = 1; i <= SICHERUNGEN; i++) {
            stand = Pfad.datei(Turnier.BASIS + i + "." + Turnier.ENDUNG);
            rButtons[i].setText(SCHACHTNAMEN[i] + " (" + datum(stand) + ")");
            rButtons[i].setToolTipText(speichern ? "Wähle Schacht aus, in den gespeichert wird"
                    : "Schacht entspricht gespeichertem Turnierstand");
            rButtons[i].setSelected(false);
            textfelder[i].setEnabled(false);
            if (speichern || stand.exists()) {
                rButtons[i].setEnabled(true);
                auswahlgruppe.add(i);
            } else {
                rButtons[i].setEnabled(false);
            }
        }
        // Karte auswählen, gegebenenfalls Radio-Button vorbelegen
        if (!auswahlgruppe.isEmpty()) {
            ((CardLayout) dialog.getLayout()).show(dialog, FORTSETZUNG);
            // Vorauswahl treffen
            if (gewaehlt < 0) {
                pack();
                if (speichern) {
                    gewaehlt = 0;
                } else {
                    gewaehlt = auswahlgruppe.get(0);
                }
            }
            // Auswahl vor Bearbeitung des Dialogs merken
            gewaehltVorAbbruch = gewaehlt;
            // für Speicherung selektierten Schacht anpassen editierbar machen
            if (speichern) {
                textfelder[gewaehlt].setEditable(gewaehlt > 0);
                textfelder[gewaehlt].setEnabled(gewaehlt > 0);
                alterText = textfelder[gewaehlt].getText();
                textfelder[gewaehlt].setText(turnier.beschreibung());
                altesDatum = rButtons[gewaehlt].getText();
                rButtons[gewaehlt].setText(SCHACHTNAMEN[gewaehlt] + " von jetzt");
            }
            rButtons[gewaehlt].setSelected(true); // die erste Möglichkeit
                                                  // vorauswählen
            // Weiter-Button mit richtigem Text und Aktionskommando belegen
            fortsetzen.setText(text);
            fortsetzen.setActionCommand(cmd);
            fortsetzen.setEnabled(!speichern || gewaehlt > 0);
            getRootPane().setDefaultButton(fortsetzen);
        } else {
            ((CardLayout) dialog.getLayout()).show(dialog, LEER);
            pack();
            getRootPane().setDefaultButton(ok);
        }
    }

    /** Entfernen der automatischen Sicherung, falls Turnier beendet wurde. */
    private void entferneAutomatischeSicherung() {
        File datei = Pfad.datei(Turnier.BASIS + "0." + Turnier.ENDUNG);
        datei.delete();
    }

    /**
     * Fortsetzung eines gespeicherten Turniers.
     *
     * @param index
     *            Index des gewählten Speicherschachtes.
     * @return Geladenes Turnier
     */
    private Turnier lade(int index) {
        Turnier turnier = null;

        // alten Turnierstand laden und Turnier fortsetzen
        try {
            ObjectInputStream in =
                    new ObjectInputStream(new FileInputStream(Pfad.datei(Turnier.BASIS
                            + String.valueOf(index) + "." + Turnier.ENDUNG)));
            in.readObject();
            turnier = (Turnier) in.readObject();
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
            Signal.ALARM.play();
            JOptionPane.showMessageDialog(this,
                    "Turnier kann nicht fortgesetzt werden!\nJava-Exception:\n" + e,
                    "JaFuffy (Fehler)", JOptionPane.ERROR_MESSAGE);
        }

        return turnier;
    }

    /**
     * Meldung über Speicherung.
     *
     * @param text
     *            Text der anzuzeigenden Meldung.
     */
    private void meldeSpeicherung(String text) {
        bestaetigung.setTitle("JaFuffy (Bestätigung)");
        bestaetigung.setIconImages(Oberflaeche.LOGOS);
        final JOptionPane meldung = new JOptionPane(text, JOptionPane.INFORMATION_MESSAGE);

        meldung.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent e) {
                String prop = e.getPropertyName();
                if (bestaetigung.isVisible()
                        && e.getSource() == meldung
                        && (prop.equals(JOptionPane.VALUE_PROPERTY) || prop
                                .equals(JOptionPane.INPUT_VALUE_PROPERTY))) {
                    bestaetigung.setVisible(false);
                    setVisible(false);
                }
            }
        });

        bestaetigung.setContentPane(meldung);
        bestaetigung.setModal(true);
        bestaetigung.pack();
        bestaetigung.setLocationRelativeTo(isVisible() ? this : fenster);

        javax.swing.Timer timer = new javax.swing.Timer(DELAY, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                bestaetigung.setVisible(false);
                setVisible(false);
            }
        });
        timer.setRepeats(false);
        timer.start();

        bestaetigung.setVisible(true);
    }

    /**
     * Speichern eines Turnierstands. Bei leerem String Standardbeschreibung, bei Nullstring
     * keine Bestätigung. Beendetes Turnier wird nicht gesichert.
     *
     * @param index
     *            Index des gewählten Speicherschachtes.
     * @param turnier
     *            Das zu sicherende Turnier.
     * @param beschreibung
     *            Turnierbeschreibung.
     */
    private void sichere(int index, Turnier turnier, String beschreibung) {

        if (!turnier.beendet()) {
            try {
                ObjectOutputStream out =
                        new ObjectOutputStream(new FileOutputStream(Pfad.datei(Turnier.BASIS
                                + String.valueOf(index) + "." + Turnier.ENDUNG)));
                out.writeObject(beschreibung == null || beschreibung.equals("") ? turnier
                        .beschreibung() : beschreibung);
                boolean[] status = new boolean[Turnier.WUERFEL];
                for (int i = 0; i < Turnier.WUERFEL; i++) {
                    status[i] = turnier.wuerfel(i).isSelected();
                    turnier.wuerfel(i).setSelected(false);
                }
                out.writeObject(turnier);
                for (int i = 0; i < Turnier.WUERFEL; i++) {
                    turnier.wuerfel(i).setSelected(status[i]);
                }
                out.close();
                if (beschreibung != null) {
                    meldeSpeicherung("Turnierstand erfolgreich gesichert!");
                }
            } catch (IOException e) {
                e.printStackTrace();
                Signal.ALARM.play();
                JOptionPane.showMessageDialog(this,
                        "Turnierstand konnte nicht gespeichert werden!\nJava-Exception:\n" + e,
                        "JaFuffy (Fehler)", JOptionPane.ERROR_MESSAGE);
            }
        }

    }

    /** Turnier fortsetzen. */
    void setzeFort() {
        speichern = false;
        setTitle("JaFuffy (Turnier fortsetzen...)");
        belegeAuswahlMitDaten("Turnier fortsetzen", "Fortsetzen");
    }

    /** Turnier speichern im gewählten Schacht (ohne Auswahl). */
    void speichere() {
        speichern = true;
        fortsetzen.setActionCommand("Speichern");
        fortsetzen.doClick();
    }

    /**
     * Speichern-Menü ("Speichern") mit Zustand der Fortsetzung verknüpfen.
     *
     * @param menuepunkt
     *            Der zuständige Menüpunkt, der sich um das schnelle Speichern kümmert.
     */
    void speichere(JMenuItem menuepunkt) {
        this.menuepunkt = menuepunkt;
    }

    /** Turnier speichern in neuem Schacht (mit Auswahl). */
    void speichereUnter() {
        speichern = true;
        setTitle("JaFuffy (Turnier speichern unter...)");
        belegeAuswahlMitDaten("Turnier abspeichern", "Speichern");
    }

    /**
     * Versucht automatisch gesichertes Turnier zu laden und zu starten.
     */
    void versucheAutomatik() {
        starter.los(lade(0));
    }

}
