/******************************************************************************
 ** $Id: JaFuffy.java 719 2015-03-02 00:41:34Z 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.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

import javax.swing.JApplet;
import javax.swing.JOptionPane;
import javax.swing.border.EmptyBorder;

/** JaFuffy (Hauptteil, Applet oder Applikation). */
@SuppressWarnings("serial")
public class JaFuffy extends JApplet implements ActionListener {

    /**
     * Einstieg für die Applikation.
     *
     * @param args
     *            Kommandozeilenargumente.
     */
    public static void main(String[] args) {
        werteArgumenteAus(args);
        try {
            if (laeuftSchon()) {
                JaFuffyDialoge.meldeMehrfachstart();
                System.exit(0);
            }
        } catch (IOException exception) {
            // Probleme mit Mehrfachstartdetektion melden und trotzdem weitermachen
            exception.printStackTrace();
        }
        if (Eigenschaften.GLOBAL.erststart()) {
            new Willkommen();
        }
        new JaFuffy(true);
    }

    /**
     * Prüft auf eine Lock-Datei und damit ob JaFuffy schon als Anwendung gestartet wurde.
     *
     * @return Ist JaFuffy schon gestartet worden?
     *
     * @throws IOException
     *             Probleme mit der Lock-Datei sind aufgetreten.
     */
    private static boolean laeuftSchon() throws IOException {
        final File datei = new File(System.getProperty("java.io.tmpdir"), VERRIEGELUNGSDATEI);
        final RandomAccessFile stream = new RandomAccessFile(datei, "rw");
        final FileChannel kanal = stream.getChannel();
        final FileLock riegel = kanal.tryLock();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    stream.close();
                } catch (IOException exception) {
                    exception.printStackTrace();
                }
                datei.delete();
            }
        });
        return riegel == null;
    }

    /**
     * Wertet die Kommandozeilenargumente aus.
     *
     * @param args
     *            Kommandozeilenargumente.
     */
    private static void werteArgumenteAus(String[] args) {
        if (args.length > 1) {
            System.err.println("Maximal ein Argument erlaubt!");
        }
        if (args.length == 1) {
            if (args[0].equals("nosound")) {
                Ressource.schalteStumm();
                System.out.println("Sound ist abgeschaltet");
            } else {
                System.err.println("Option nicht bekannt!");
            }
        }
    }

    /** Mindestversion Java. */
    private static final String MINDEST_LANG_VERSION = "1.7";
    /** Mindestversion Swing. */
    private static final String MINDEST_SWING_VERSION = "1.7";
    /** Lock-Datei zur Vermeidung eines Mehrfachstarts. */
    private static final String VERRIEGELUNGSDATEI = "JaFuffy.lock";
    /** Abstand vom Fensterrahmen zum Inhalt. */
    private static final int RAHMENABSTAND = 4;

    /** Hilfsobjekt für Benutzeroberfläche. */
    private Oberflaeche oberflaeche;
    /** Fenster, in dem JaFuffy abläuft. */
    private Fenster fenster;
    /** Dialog zur Absprache der Spielbedingungen. */
    private Absprache absprache;
    /** Dialog zur Anzeige des Spielreports. */
    private Report report;
    /** Dialog zur Anzeige der Bestenliste - je Spielregel. */
    private Bestenliste bestenliste;
    /** Nachfrage nach der Fortsetzung. */
    private Fortsetzung fortsetzung;
    /** Sicherheitsfrage zur Bestätigung der Programmbeendigung. */
    private Ende ende;
    /** Hinweise auf neuere Programmversion. */
    private Update update;
    /** Nachführung der Statistik. */
    private Statistik statistik = null;
    /** Spielplatz mit Punktezettel und Würfelfeld. */
    private Spielplatz spielplatz;
    /** Menü im Fenster. */
    private Menue menue;
    /** Eingabemaske für Eintrag in Bestenliste. */
    private Kommentierung kommentierung;
    /** Durchführung des Turniers. */
    private Turnier turnier;

    /**
     * JaFuffy ist als Applet gestartet worden.
     */
    public JaFuffy() {
    }

    /**
     * JaFuffy ist als Applikation gestartet worden.
     *
     * @param dummy
     *            Dummy-Wert, um die Überladung des Konstruktors für den Applikationsfall
     *            ausnutzen zu können.
     */
    public JaFuffy(boolean dummy) {
        registriereAufraeumer();
        beschaffeStatistik();
        Oberflaeche.starteSpaeter(new Runnable() {
            @Override
            public void run() {
                ende = new Ende(JaFuffy.this.statistik);
                fenster = new Fenster("JaFuffy");
                fenster.addWindowListener(ende);
                fortsetzung = new Fortsetzung(fenster);
                fortsetzung.addActionListener(JaFuffy.this);
                update = new Update(fenster);
                initialisiere();
                // Fensterinhalt aufbauen und anzeigen
                fenster.zeige(Eigenschaften.GLOBAL, menue, spielplatz);
                pruefeAufUpdate();
                bieteFortsetzungAn();
                Eigenschaften.GLOBAL.quittiereNeustartAnforderung();
            }
        });
    }

    /**
     * Abarbeitung eines Turnierereignisses: Turnier wird neu gestartet, abgebrochen, beendet
     * oder fortgesetzt.
     *
     * @param event
     *            Ein Turnierereignis.
     */
    @Override
    public final void actionPerformed(ActionEvent event) {
        String ac = event.getActionCommand();
        if (ac.equals("Start")) {
            if (turnier != null) {
                turnier.beende();
                turnier.removeChangeListener(fenster);
                turnier.removeChangeListener(spielplatz);
                turnier.removeChangeListener(menue);
                turnier.removeChangeListener(ende);
                turnier.auswertung().removeChangeListener(spielplatz);
                for (Wuerfel w : turnier.wuerfel()) {
                    w.entferneChangeListener(spielplatz);
                }
                if (fortsetzung != null) {
                    turnier.removeChangeListener(fortsetzung);
                }
            }
            turnier = ((Starter) event.getSource()).turnier();
            turnier.setzeFort(statistik);
            turnier.addChangeListener(fenster);
            turnier.addChangeListener(spielplatz);
            turnier.addChangeListener(menue);
            turnier.addChangeListener(ende);
            turnier.auswertung().addChangeListener(spielplatz);
            for (Wuerfel w : turnier.wuerfel()) {
                w.installiereChangeListener(spielplatz);
            }
            if (fortsetzung != null) {
                turnier.addChangeListener(fortsetzung);
            }
            turnier.starte();
        }
    }

    /** JaFuffy ist als Applet gestartet worden, hier wird es initialisiert. */
    @Override
    public final void init() {
        fortsetzung = null;
        ende = null;
        update = null;
        statistik = new Statistik();
        initialisiere();
        Oberflaeche.starteJetzt(new Runnable() {
            @Override
            public void run() {
                setJMenuBar(menue);
                spielplatz.setBorder(new EmptyBorder(RAHMENABSTAND, RAHMENABSTAND,
                        RAHMENABSTAND, RAHMENABSTAND));
                setContentPane(spielplatz);
            }
        });
    }

    /** Liest die Statistik-Daten für die Applikation ein oder aber erzeugt diese neu. */
    private void beschaffeStatistik() {
        File datei = Pfad.datei(Statistik.STAT);
        if (datei.exists()) {
            try {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(datei));
                statistik = (Statistik) ois.readObject();
                ois.close();
                return;
            } catch (Exception e) {
                e.printStackTrace();
                Signal.ALARM.play();
                JaFuffyDialoge.meldeFehlendeStatistikdaten(e);
            }
        }
        statistik = new Statistik();
    }

    /**
     * Auf Nachfrage eventuell altes Turnier fortsetzen aus automatischer Sicherung.
     */
    private void bieteFortsetzungAn() {
        File datei = Pfad.datei(Turnier.BASIS + "0." + Turnier.ENDUNG);
        boolean kalt = Eigenschaften.GLOBAL.kaltstart();
        boolean warm = Eigenschaften.GLOBAL.warmstart();
        boolean erinnerung = Eigenschaften.GLOBAL.aktiv("Erinnerung");
        if ((kalt || warm || erinnerung) && datei.exists()) {
            if (erinnerung) {
                Signal.WARNUNG.play();
                if (JaFuffyDialoge.frageNachFortsetzung(fenster, datei)) {
                    fortsetzung.versucheAutomatik();
                }
            } else if (warm) {
                fortsetzung.versucheAutomatik();
            }
        }
    }

    /**
     * Initialisierung für Applet und Applikation. Erzeugt insbesondere Spielplatz und
     * garantiert Statistik.
     */
    private void initialisiere() {
        pruefeKompatibilitaet();
        oberflaeche = new Oberflaeche(Eigenschaften.GLOBAL);
        absprache = new Absprache();
        absprache.addActionListener(this);
        int variante;
        try {
            variante = Variante.valueOf(Eigenschaften.GLOBAL.getProperty("Variante")).ordinal();
        } catch (IllegalArgumentException e) {
            variante = Variante.KLASSISCH.ordinal();
            System.out.println(e);
        }
        bestenliste = new Bestenliste(statistik, variante);
        // Reporterstellung
        report = new Report(statistik);
        // Spielplatz erzeugen
        spielplatz = new Spielplatz();
        spielplatz.addPropertyChangeListener("Beginnen", absprache);
        // Menü erzeugen
        menue = new Menue(report, bestenliste, fortsetzung, update);
        menue.addPropertyChangeListener("LookAndFeel", oberflaeche);
        menue.addPropertyChangeListener("Beginnen", absprache);
        menue.addPropertyChangeListener("Karte", spielplatz);
        // Kommentierung bei Eintrag in Bestenliste
        kommentierung = new Kommentierung(spielplatz);
        // Statistik an Oberfläche anbinden
        statistik.addChangeListener(report);
        statistik.addChangeListener(bestenliste);
        statistik.addChangeListener(kommentierung);
    }

    /** Nach neuer Version schauen, falls bei Programmstart erwünscht. */
    private void pruefeAufUpdate() {
        if (Eigenschaften.GLOBAL.aktiv("Nachschauen")) {
            update.ueberpruefe();
        }
        update.aktiviereMitteilung();
    }

    /**
     * Prüfe auf die Kompatibilität mit Java-Version und Swing-Version und zeige gegebenenfalls
     * einen Warnhinweis.
     */
    private void pruefeKompatibilitaet() {
        Package lang = Package.getPackage("java.lang");
        Package swing = Package.getPackage("javax.swing");
        if (!lang.isCompatibleWith(MINDEST_LANG_VERSION)
                || !swing.isCompatibleWith(MINDEST_SWING_VERSION)) {
            JaFuffyDialoge.meldeVersionsproblem(lang, MINDEST_LANG_VERSION, swing,
                    MINDEST_SWING_VERSION);
        }
    }

    /**
     * Registriere Aufräumthread, der bei Programmende aufgerufen wird und gegebenenfalls einen
     * Neustart durchführt.
     */
    private void registriereAufraeumer() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                if (Eigenschaften.GLOBAL.neustart()) {
                    try {
                        File jar =
                                new File(JaFuffy.class.getProtectionDomain().getCodeSource()
                                        .getLocation().toURI());
                        try {
                            String java = System.getProperty("java.home") + "/bin/java";
                            StringBuffer cmd = new StringBuffer("\"" + java + "\"");
                            cmd.append(" -jar \"").append(jar.getPath()).append("\"");
                            Runtime.getRuntime().exec(cmd.toString());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } catch (URISyntaxException exception) {
                        exception.printStackTrace();
                    }
                }
            }
        });
    }

}

/** Von der Klasse JaFuffy benötigte Dialoge. */
@SuppressWarnings("serial")
class JaFuffyDialoge extends JOptionPane {

    /**
     * Fragt nach, ob automatisch gesichertes Turnier fortgesetzt werden soll.
     *
     * @param umgebung
     *            Oberflächenumgebung.
     * @param datei
     *            Datei mit automatischen Sicherungsdaten.
     * @return Gibt an, ob fortgesetzt werden soll.
     */
    static boolean frageNachFortsetzung(Component umgebung, File datei) {
        int wahl =
                showConfirmDialog(umgebung,
                        "Automatisch gesicherten Turnierstand (" + Fortsetzung.datum(datei)
                                + ") gefunden:\n" + Fortsetzung.beschreibung(datei) + "\n"
                                + "Soll dieses Turnier fortgesetzt werden?", "JaFuffy"
                                + " (Menü \"Einstellungen - Meldungen - Erinnerung...\")",
                        YES_NO_OPTION);
        return wahl == YES_OPTION;
    }

    /**
     * Fehlerhinweis falls Statistikdaten nicht geladen werden können.
     *
     * @param exception
     *            Exception, die beim Ladeversuch aufgetreten ist.
     */
    static void meldeFehlendeStatistikdaten(Exception exception) {
        showMessageDialog(null, "Statistikdaten können nicht geladen werden!\n"
                + "Datenverlust bei nächster Sicherung möglich.\n" + "Java-Exception:\n"
                + exception, "JaFuffy (Hinweis)", INFORMATION_MESSAGE);
    }

    /** Fehlerhinweis falls JaFuffy mehrfach gestartet wurde. */
    static void meldeMehrfachstart() {
        showMessageDialog(null, "JaFuffy wurde offenbar schon gestartet!\n"
                + "Diese Instanz beendet sich jetzt.", "JaFuffy (Hinweis)", INFORMATION_MESSAGE);
    }

    /**
     * Hinweis auf Versionsproblem.
     *
     * @param lang
     *            Gefundene Javasprachversion.
     * @param minLang
     *            Minimale Javasprachversion.
     * @param swing
     *            Gefundene Swingversion.
     * @param minSwing
     *            Minimale Swingversion.
     */
    static void meldeVersionsproblem(Package lang, String minLang, Package swing,
            String minSwing) {
        JOptionPane.showMessageDialog(null,
                "Versionsforderungen an Java-Pakete nicht erfüllt!\n"
                        + "Möglicherweise treten Probleme auf.\n\n" + "Paket \"" + lang
                        + "\" gefunden,\n" + "es wird mindestens Version " + minLang
                        + " benötigt.\n" + "Paket \"" + swing + "\" gefunden\n"
                        + "es wird mindestens Version " + minSwing + " benötigt.", "JaFuffy"
                        + " (Warnung)", WARNING_MESSAGE);
    }

}
