/******************************************************************************
 ** $Id: Oberflaeche.java 1228 2017-05-21 22:32:26Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ** Lauffähig ab Java 7.
 ******************************************************************************
 ** 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 jafuffy.Eigenschaften;

import java.awt.Image;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;

/** Spezialitäten, Anpassungen und Einstellungen der JaFuffy Benutzeroberfläche. */
@SuppressWarnings("serial")
public class Oberflaeche extends UIManager implements PropertyChangeListener {

    /** XML-Datei, in der alle L&F stehen. */
    private static final String LAFS = "/LaF/lafs.xml";
    static {
        wendeAnpassungenAn();
        installiereLookAndFeel(parseEinstellungsDatei());
    }
    /** Standard Look & Feel. */
    private static final String NIMBUS = "Nimbus";
    /** Breite des Rahmens um den Würfel, verwendet für Nimbus. */
    private static final int WUERFELRAHMENBREITE = 6;
    /** Logo in Fensterleiste in unterschiedlichen Größen. */
    static final ArrayList<Image> LOGOS = new ArrayList<>();
    static {
        LOGOS.add(Grafik.bild("Fensterlogo16.png"));
        LOGOS.add(Grafik.bild("Fensterlogo32.png"));
        LOGOS.add(Grafik.bild("Fensterlogo48.png"));
    }

    /**
     * Ermittelt den Look & Feel, der als Standard benutzt werden soll.
     *
     * @return Klassenname des Standard-Look-And-Feel.
     */
    public static String standardLookAndFeel() {
        for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if (info.getName().equals(NIMBUS)) {
                return info.getClassName();
            }
        }
        return UIManager.getCrossPlatformLookAndFeelClassName();
    }

    /**
     * Sofortiges Starten der Applikation.
     *
     * @param runnable
     *            Objekt, das der Ausführung der Applikation dient.
     */
    public static void starteJetzt(Runnable runnable) {
        try {
            SwingUtilities.invokeAndWait(runnable);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }

    /**
     * Späteres Starten der Applikation.
     *
     * @param runnable
     *            Objekt, das der Ausführung der Applikation dient.
     */
    public static void starteSpaeter(Runnable runnable) {
        SwingUtilities.invokeLater(runnable);
    }

    /**
     * Installiert mitgelieferte Look & Feels.
     *
     * @param lafs
     *            Mitgelieferte Look & Feels
     */
    private static void installiereLookAndFeel(TreeMap<String, String> lafs) {
        UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
        for (Map.Entry<String, String> laf : lafs.entrySet()) {
            boolean vorhanden = false;
            for (UIManager.LookAndFeelInfo info : infos) {
                if (laf.getKey().equals(info.getClassName())) {
                    vorhanden = true;
                }
            }
            if (!vorhanden) {
                UIManager.installLookAndFeel(laf.getValue(), laf.getKey());
            }
        }
    }

    /**
     * Wertet die Einstellungsdatei zu den mitgelieferten Look & Feels aus.
     *
     * @return Alle mitgelieferten Look & Feels als Abbildung Klasse (Implementierung) -> Name
     */
    private static TreeMap<String, String> parseEinstellungsDatei() {
        TreeMap<String, String> lafs = new TreeMap<>();
        try {
            InputStream stream = Oberflaeche.class.getResourceAsStream(LAFS);
            if (stream != null) {
                parseEinstellungsDatei(lafs, stream);
            } else {
                System.out.println("Keine Verzeichnisdatei mit Look & Feel gefunden.");
            }
        } catch (XMLStreamException exception) {
            exception.printStackTrace();
        }
        return lafs;
    }

    /**
     * Wertet die Einstellungsdatei zu den mitgelieferten Look & Feels aus.
     * 
     * @param lafs
     *            Wird mit den verfügbaren Look & Feel befüllt.
     * @param stream
     *            Der Datenstrom der Verzeichnisdatei.
     * @throws FactoryConfigurationError
     *             Problem zur Vorbereitung des XML Event Leser.
     * @throws XMLStreamException
     *             Meldet Problem beim Parsen der Verzeichnisdatei.
     */
    private static void parseEinstellungsDatei(TreeMap<String, String> lafs, InputStream stream)
            throws FactoryConfigurationError, XMLStreamException {
        XMLInputFactory fabrik = XMLInputFactory.newInstance();
        XMLEventReader leser = fabrik.createXMLEventReader(stream);
        XMLEvent event;
        do {
            event = leser.nextEvent();
        } while (!event.isStartElement() && leser.hasNext());
        if (event.isStartElement() && event.asStartElement().getName().getLocalPart() == "lafs") {
            boolean geoeffnet = false;
            String implementierung = null;
            String name = null;
            while (leser.hasNext()) {
                event = leser.nextEvent();
                if (!geoeffnet) {
                    if (event.isStartElement() && event.asStartElement().getName().getLocalPart() == "laf") {
                        geoeffnet = true;
                    }
                } else {
                    if (event.isStartElement() && event.asStartElement().getName().getLocalPart() == "name") {
                        event = leser.nextEvent();
                        name = event.asCharacters().getData();
                    }
                    if (event.isStartElement() && event.asStartElement().getName().getLocalPart() == "class") {
                        event = leser.nextEvent();
                        implementierung = event.asCharacters().getData();
                    }
                    if (event.isEndElement() && event.asEndElement().getName().getLocalPart() == "laf") {
                        lafs.put(implementierung, name);
                        geoeffnet = false;
                    }
                }
            }
        }
    }

    /**
     * Gewünschtes Look & Feel setzen, mit Rückfallmöglichkeit auf Standardoberfläche, welche auf allen Plattformen zur
     * Verfügung steht.
     *
     * @param laf
     *            Klassenname der Oberfläche
     * @return Gesetzte Oberfläche
     */
    private static String setzeLookAndFeel(String laf) {
        try {
            setLookAndFeel(laf);
        } catch (Exception erstversuchausnahme) {
            System.out.println(erstversuchausnahme.getStackTrace());
            laf = getCrossPlatformLookAndFeelClassName();
            try {
                setLookAndFeel(laf);
            } catch (Exception rueckfallausnahme) {
                rueckfallausnahme.printStackTrace();
                System.exit(-1);
            }
        }
        return laf;
    }

    /** Wendet Anpassungen oder Work-Arounds an. */
    private static void wendeAnpassungenAn() {
        JFrame.setDefaultLookAndFeelDecorated(false);
        JDialog.setDefaultLookAndFeelDecorated(false);

        put("Table.showGrid", true);
        put("FileChooser.cancelButtonText", "Abbruch");
        put("FileChooser.cancelButtonToolTipText", "Abbruch der Auswahl");
        put("OptionPane.yesButtonText", "Ja");
        put("OptionPane.noButtonText", "Nein");
        put("OptionPane.cancelButtonText", "Abbruch");
        put("OptionPane.okButtonText", "OK");
        put("ToggleButton.contentMargins",
                new Insets(WUERFELRAHMENBREITE, WUERFELRAHMENBREITE, WUERFELRAHMENBREITE, WUERFELRAHMENBREITE));
    }

    /** Die Eigenschaften haben Einfluss auf die Oberfläche. */
    private final Eigenschaften eigenschaften;

    /**
     * Oberflächenkonstruktor.
     *
     * @param eigenschaften
     *            Wertet die Eigenschaften aus, welche Einfluss auf die Oberfläche haben.
     */
    public Oberflaeche(Eigenschaften eigenschaften) {
        eigenschaften.setProperty("LookAndFeel", setzeLookAndFeel(eigenschaften.getProperty("LookAndFeel")));
        this.eigenschaften = eigenschaften;
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        eigenschaften.setProperty("LookAndFeel", setzeLookAndFeel((String) event.getNewValue()));
    }

}
