/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.util;

import java.util.HashMap;
import java.util.Map;
import java.util.prefs.Preferences;


/** Responsible for importing/exporting user preferences.
 * @since 2.0
 */
public final class JaxoPrefs {

    // Strings:

    /** The preferred HTML browser. */
    public static final String PREF_BROWSER = "prefBROWSER";

    /** The preferred text editor. */
    public static final String PREF_EDITOR = "prefEDITOR";

    /** The preferred postscript viewer. */
    public static final String PREF_PSVIEWER = "prefPSVIEWER";

    /** The preferred Look and Feel (as of 2.0, the Class Name). */
    public static final String PREF_LOOKNFEEL = "prefLOOKNFEEL";

    /** The preferred language. */
    public static final String PREF_LANGUAGE = "prefLANGUAGE";

    /** The preferred grid type. */
    public static final String PREF_GRIDTYPE = "prefGRIDTYPE";

    /** The preferred Object color. */
    public static final String PREF_COLOR = "prefCOLOR";

    /** The preferred Postscript font family. */
    public static final String PREF_PSFAMILY = "prefPSFAMILY";

    /** The preferred Text color. */
    public static final String PREF_TEXTCOLOR = "prefTEXTCOLOR";

    /** The preferred latex path. */
    public static final String PREF_LATEXPATH = "prefLATEXPATH";

    /** The preferred dvips path. */
    public static final String PREF_DVIPSPATH = "prefDVIPSPATH";

    /** The preferred canvas background color. */
    public static final String PREF_CANVASBACKGROUND = "prefCANVASBACKGROUND";

    /** The version of the preferences. */
    public static final String PREF_VERSION = "prefVERSION";

    /** The preferred canvas background color. */
    public static final String PREF_GRIDCOLOR = "prefGRIDCOLOR";

    /** The current user directory. */
    public static final String PREF_USER_DIR = "prefUSERDIR";

    // ints:

    /** The preferred screen size - x dimension. */
    public static final String PREF_SCREENSIZEX = "prefSCREENSIZEX";

    /** The preferred screen size - y dimension. */
    public static final String PREF_SCREENSIZEY = "prefSCREENSIZEY";

    /** The preferred grid size. */
    public static final String PREF_GRIDSIZE = "prefGRIDSIZE";

    /** The preferred amplitude for photon and gluon objects. */
    public static final String PREF_AMPLITUDE = "prefAMPLITUDE";

    /** The preferred Postscript font style. */
    public static final String PREF_PSSTYLE = "prefPSSTYLE";

    /** The preferred Postscript font size. */
    public static final String PREF_PSSIZE = "prefPSSIZE";

    /** The preferred TeX font size. */
    public static final String PREF_TEXSIZE = "prefTEXSIZE";

    /** The preferred TeX alignment. */
    public static final String PREF_TEXALIGN = "prefTEXALIGN";

    /** The preferred postscript text rotation angle. */
    public static final String PREF_PSROTANGLE = "prefPSROTANGLE";

    /** The preferred LaTeX text rotation angle. */
    public static final String PREF_TEXROTANGLE = "prefTEXROTANGLE";

    /** The default action. */
    public static final String PREF_DEFAULTACTION = "prefDEFAULTACTION";

    /** The preferred undo depth. */
    public static final String PREF_UNDODEPTH = "prefUNDODEPTH";

    /** The preferred color space. */
    public static final String PREF_COLORSPACE = "prefCOLORSPACE";

    /** The preferred grid style. */
    public static final String PREF_GRIDSTYLE = "prefGRIDSTYLE";

    /** The preferred default vertex size. */
    public static final String PREF_VERTEXSIZE = "prefVERTEXSIZE";


    // floats:

    /** The preferred line width. */
    public static final String PREF_LINEWIDTH = "prefLINEWIDTH";

    /** The preferred separation of double line objects. */
    public static final String PREF_DLSEP = "prefDLSEP";

    /** The preferred arrow poaition. */
    public static final String PREF_ARROWPOSITION = "prefARROWPOSITION";

    /** The preferred arrow length. */
    public static final String PREF_ARROWLENGTH = "prefARROWLENGTH";

    /** The preferred arrow width. */
    public static final String PREF_ARROWWIDTH = "prefARROWWIDTH";

    /** The preferred arrow inset. */
    public static final String PREF_ARROWINSET = "prefARROWINSET";

    // booleans:

    /** A boolean preference indicating whether to show the tool bar or not. */
    public static final String PREF_SHOWTOOL = "prefSHOWTOOL";

    /** A boolean preference indicating whether to show the status bar or not.*/
    public static final String PREF_SHOWSTATUS = "prefSHOWSTATUS";

    /** A boolean preference indicating whether to use antialias or not.*/
    public static final String PREF_ANTIALIAS = "prefANTIALIAS";

    /** A boolean preference indicating whether to draw arrows or not.*/
    public static final String PREF_ARROW = "prefARROW";

    /** A boolean preference indicating whether the grid is on or off.*/
    public static final String PREF_GRIDONOFF = "prefGRIDONOFF";

    /** A boolean preference indicating whether snapping is on or off.*/
    public static final String PREF_SNAPONOFF = "prefSNAPONOFF";

    /** A boolean preference indicating whether to show the gridbar bar or not. */
    public static final String PREF_SHOWGRIDBAR = "prefSHOWGRIDBAR";

    /** A boolean preference indicating whether to show the start-up splash window or not. */
    public static final String PREF_SHOWSPLASHWINDOW = "prefSHOWSPLASHWINDOW";

    // private
    private static final Preference[] PREFERENCES;

    private static final Preferences PREFS =
        Preferences.userNodeForPackage(JaxoPrefs.class);

    static {
        final String black = JaxoColor.getColorName(JaxoColor.BLACK);

        final String workingDir = System.getProperty("exec.workingdir");
        final String userDir = (workingDir == null) ? System.getProperty("user.dir") : workingDir;

        PREFERENCES =
            new Preference[]{
                new Preference(PREF_BROWSER, ""),
                new Preference(PREF_EDITOR, ""),
                new Preference(PREF_PSVIEWER, ""),
                new Preference(PREF_LOOKNFEEL,
                    "javax.swing.plaf.metal.MetalLookAndFeel"),
                new Preference(PREF_LANGUAGE, "english"),
                new Preference(PREF_GRIDTYPE, "rectangular"),
                new Preference(PREF_COLOR, black),
                new Preference(PREF_PSFAMILY, "Default"),
                new Preference(PREF_TEXTCOLOR, black),
                new Preference(PREF_LATEXPATH, "latex"),
                new Preference(PREF_DVIPSPATH, "dvips"),
                new Preference(PREF_CANVASBACKGROUND, "r255g255b255"),
                new Preference(PREF_VERSION, "1.4"),
                new Preference(PREF_GRIDCOLOR, black),
                new Preference(PREF_USER_DIR, userDir),
                new Preference(PREF_DEFAULTACTION, JaxoConstants.DEF_NONE),
                new Preference(PREF_UNDODEPTH, 20),
                new Preference(PREF_COLORSPACE, JaxoColor.ALL_COLORS_MODE),
                new Preference(PREF_GRIDSTYLE, 0),
                new Preference(PREF_VERTEXSIZE, 0),
                new Preference(PREF_SCREENSIZEX, 800),
                new Preference(PREF_SCREENSIZEY, 600),
                new Preference(PREF_GRIDSIZE, 16),
                new Preference(PREF_AMPLITUDE, 15),
                new Preference(PREF_PSSTYLE, 2),
                new Preference(PREF_PSSIZE, 18),
                new Preference(PREF_TEXSIZE, 6),
                new Preference(PREF_TEXALIGN, 2),
                new Preference(PREF_PSROTANGLE, 0),
                new Preference(PREF_TEXROTANGLE, 0),
                new Preference(PREF_LINEWIDTH, 1f),
                new Preference(PREF_DLSEP, 2f),
                new Preference(PREF_ARROWPOSITION, .5f),
                new Preference(PREF_ARROWLENGTH, 5f),
                new Preference(PREF_ARROWWIDTH, 2f),
                new Preference(PREF_ARROWINSET, .2f),
                new Preference(PREF_SHOWTOOL, true),
                new Preference(PREF_SHOWSTATUS, true),
                new Preference(PREF_ANTIALIAS, true),
                new Preference(PREF_ARROW, true),
                new Preference(PREF_GRIDONOFF, true),
                new Preference(PREF_SNAPONOFF, true),
                new Preference(PREF_SHOWGRIDBAR, true),
                new Preference(PREF_SHOWSPLASHWINDOW, true)
            };
    }

    private static final Map<String, Preference> NAME_TO_PREFERENCE; // String name -> its Preference
    private static final Map<String, Object> VALUES; // String name -> its value (mixed type)

    static {
        NAME_TO_PREFERENCE = new HashMap<String, Preference>(PREFERENCES.length);
        VALUES = new HashMap<String, Object>(PREFERENCES.length);

        for (int i = 0; i < PREFERENCES.length; i++) {
            NAME_TO_PREFERENCE.put(PREFERENCES[i].getName(), PREFERENCES[i]);
            VALUES.put(PREFERENCES[i].getName(), PREFERENCES[i].getDefaultValue());
        }
    }

    /** Defines the maximum numbers of recent files presented in the recent file
     *  menu.
     */
    public static final int MAX_RECENT_FILES = 10;

    /** An array of strings identifying the recently opened files. */
    private static String[] recentKEYS = new String[MAX_RECENT_FILES];

    /** The recently opened files. */
    private static String[] recentFile = new String[MAX_RECENT_FILES];


    static {
        for (int i = 0; i < MAX_RECENT_FILES; i++) {
            String rf = "";
            recentFile[i] = rf;
            rf = "recentFile".concat(Integer.toString(i + 1));
            recentKEYS[i] = rf;
        }
    }

    /** Empty private constructor: do not instantiate. */
    private JaxoPrefs() {
        // empty on purpose
    }

    /**
     * Returns an array of recently opened files.
     *
     * @return An array containing the absolute paths of recently opened files
     * or empty Strings.
     */
    public static String[] getRecentFiles() {
        final String[] copy = new String[recentFile.length];
        System.arraycopy(recentFile, 0, copy, 0, recentFile.length);
        return copy;
    }

    /**
     * Set the recently opened files to the given array of absolute path names.
     *
     * @param filePaths the absolute path names.
     *      If an entry is null, it will be replaced by an empty String.
     */
    public static void setRecentFiles(final String[] filePaths) {
        clearRecentFiles();

        for (int i = 0; i < filePaths.length; i++) {
            final String rf = filePaths[i];
            if (rf == null) {
                recentFile[i] = "";
            } else {
                recentFile[i] = rf;
            }
        }
    }

    /**
     * Sets all recent files to empty Strings.
     */
    public static void clearRecentFiles() {
        for (int i = 0; i < MAX_RECENT_FILES; i++) {
            recentFile[i] = "";
        }
    }

    /**
     * Adds the given absolute path name to the list of recntly opened files.
     *
     * @param st an absolute path name.
     */
    public static void addRecentFile(final String st) {
        int i = 0;
        for (; i < MAX_RECENT_FILES; i++) {
            if ("".equals(recentFile[i])) {
                recentFile[i] = st;
                break;
            }
        }

        if (i == MAX_RECENT_FILES) {
            System.arraycopy(recentFile, 1, recentFile, 0, recentFile.length - 1);
            recentFile[MAX_RECENT_FILES - 1] = st;
        }

    }

    /**
     * Returns the preference with 'name' as a String, or throws a
     * IllegalArgumentException if it does not correspond to a String.
     *
     * @param name The name of the preference.
     * @return The preference with 'name'.
     */
    public static String getStringPref(final String name) {
        if (hasType(name, String.class)) {
            return (String) VALUES.get(name);
        } else {
            throw new IllegalArgumentException("Not String Pref: " + name);
        }
    }

    /**
     * Returns the preference with 'name' as an integer, or throws a
     * IllegalArgumentException if it does not correspond to an int.
     *
     * @param name The name of the preference.
     * @return The preference with 'name'.
     */
    public static int getIntPref(final String name) {
        if (hasType(name, Integer.TYPE)) {
            return ((Integer) VALUES.get(name)).intValue();
        } else {
            throw new IllegalArgumentException("Not int Pref: " + name);
        }
    }

    /**
     * Returns the preference with 'name' as a float, or throws a
     * IllegalArgumentException if it does not correspond to a float.
     *
     * @param name The name of the preference.
     * @return The preference with 'name'.
     */
    public static float getFloatPref(final String name) {
        if (hasType(name, Float.TYPE)) {
            return ((Float) VALUES.get(name)).floatValue();
        } else {
            throw new IllegalArgumentException("Not float Pref: " + name);
        }
    }

    /**
     * Returns the preference with 'name' as a boolean, or throws a
     * IllegalArgumentException if it does not correspond to a boolean.
     *
     * @param name The name of the preference.
     * @return The preference with 'name'.
     */
    public static boolean getBooleanPref(final String name) {
        if (hasType(name, Boolean.TYPE)) {
            return Boolean.TRUE.equals(VALUES.get(name));
        } else {
            throw new IllegalArgumentException("Not boolean Pref: " + name);
        }
    }

    /**
     * Sets the preference with 'name' to the string str.
     *
     * @param name The name of the preference to be set.
     * @param str the preference to be set.
     */
    public static void setStringPref(final String name, final String str) {
        setPref(name, str);
    }

    /**
     * Sets the preference with 'name' to the int value newPref.
     *
     * @param name The name of the preference to be set.
     * @param newPref The preference to be set.
     */
    public static void setIntPref(final String name, final int newPref) {
        setPref(name, Integer.valueOf(newPref));
    }

    /**
     * Sets the preference with 'name' to the float value of newPref.
     *
     * @param name The name of the preference to be set.
     * @param newPref The preference to be set.
     */
    public static void setFloatPref(final String name, final float newPref) {
        setPref(name, Float.valueOf(newPref));
    }

    /**
     * Sets the preference with 'name' to the boolean value of newPref.
     *
     * @param name The name of the preference to be set.
     * @param newPref The preference to be set.
     */
    public static void setBooleanPref(final String name, final boolean newPref) {
        setPref(name, Boolean.valueOf(newPref));
    }

    /**
     * State of preferences. The only use is to restore the current state later
     * (after having made temporary changes) with {@link #setState(java.util.Map)}.
     *
     * @return Object
     */
    public static Map<String, Object> getState() {
        return new HashMap<String, Object>(VALUES);
    }

    /**
     * Restore the preferences to a previous state.
     *
     *  @param value previous state to be set, obtained from {@link #getState}.
     */
    public static void setState(final Map<String, Object> value) {
        for (int i = 0; i < PREFERENCES.length; i++) {
            setPref(PREFERENCES[i].getName(), value.get(PREFERENCES[i].getName()));
        }
    }

    /**
     * Sets the Preferences for the current session from the Preferences node.
     */
    public static void initSessionPrefs() {
        final Preferences legacy =  Preferences.userRoot().node(JaxoInfo.USER_HOME);
        setPrefsFrom(legacy);
        setPrefsFrom(PREFS);

        // re-set user dir (or should we keep it across sessions?)
        resetPref(PREF_USER_DIR);

        setStringPref(PREF_VERSION, PREFS.get(PREF_VERSION, "1.3"));

        if (!getStringPref(PREF_VERSION).equals("1.4")) {
            upgradePreferences();
            setStringPref(PREF_VERSION, "1.4");
        }
    }

    private static void setPrefsFrom(final Preferences p) {
        for (int j = 0; j < PREFERENCES.length; j++) {
            setPrefFromString(PREFERENCES[j].getName(),
            p.get(PREFERENCES[j].getName(),
            PREFERENCES[j].getDefaultValue().toString()));
        }

        for (int j = 0; j < MAX_RECENT_FILES; j++) {
            recentFile[j] = p.get(recentKEYS[j], recentFile[j]);
        }
    }



    /**
     * Re-sets the given Preference to its default value.
     *
     * @param name the name of the preference to re-set.
     */
    public static void resetPref(final String name) {
        final Preference p = NAME_TO_PREFERENCE.get(name);
        setPref(name, p.getDefaultValue());
    }

    /**
     * Saves the current set of Preferences to the user Preferences node,
     * so they will be used in later sessions.
     */
    public static void savePrefs() {
        for (int j = 0; j < PREFERENCES.length; j++) {
            PREFS.put(PREFERENCES[j].getName(),
                VALUES.get(PREFERENCES[j].getName()).toString());
        }

        for (int j = 0; j < MAX_RECENT_FILES; j++) {
            PREFS.put(recentKEYS[j], recentFile[j]);
        }
    }

    /**
     * Saves the last visited files in the Preferences node.
     */
    public static void saveRecentFiles() {
        for (int j = 0; j < MAX_RECENT_FILES; j++) {
            PREFS.put(recentKEYS[j], recentFile[j]);
        }
    }

      //
     // private
    //

    private static boolean hasType(final String name, final Class<?> type) {
        final Preference p = NAME_TO_PREFERENCE.get(name);

        return (p != null) && (p.getType() == type);
    }

    private static void upgradePreferences() {
        String lookAndFeel = getStringPref(PREF_LOOKNFEEL);

        if ("Metal".equals(lookAndFeel)) {
            lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel";
        } else if ("CDE/Motif".equals(lookAndFeel)) {
            lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
        } else if ("Windows".equals(lookAndFeel)) {
            lookAndFeel = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
        } else if ("Windows Classic".equals(lookAndFeel)) {
            lookAndFeel =
                "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel";
        } else if ("Mac OS X".equals(lookAndFeel)) {
            lookAndFeel = "apple.laf.AquaLookAndFeel";
        } else if ("GTK".equals(lookAndFeel) || "GTK+".equals(lookAndFeel)) {
            lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
        }

        setStringPref(PREF_LOOKNFEEL, lookAndFeel);
    }

    private static void setPrefFromString(final String name, final String value) {
        final Preference p = NAME_TO_PREFERENCE.get(name);

        if (p.getType() == String.class) {
            setPref(name, value);
        } else if (p.getType() == Integer.TYPE) {
            setPref(name, Integer.valueOf(value));
        } else if (p.getType() == Float.TYPE) {
            setPref(name, Float.valueOf(value));
        } else if (p.getType() == Boolean.TYPE) {
            setPref(name, Boolean.valueOf(value));
        }
    }

    private static void setPref(final String name, final Object value) {
        if (name.equals(PREF_LANGUAGE)) {
            final Object old = VALUES.get(name);
            VALUES.put(name, value);
            if ((old == null) ? (value != null) : (!old.equals(value))) {
                JaxoLanguage.languageChanged(getStringPref(PREF_LANGUAGE));
            }
        } else {
            VALUES.put(name, value);
        }
    }

    private static class Preference {
        private final String name;
        private final Class<?> type;
        private final Object defaultValue;

        Preference(final String nname, final String value) {
            this.name = nname;
            this.type = String.class;
            this.defaultValue = value;
        }

        Preference(final String nname, final int value) {
            this.name = nname;
            this.type = Integer.TYPE;
            this.defaultValue = Integer.valueOf(value);
        }

        Preference(final String nname, final float value) {
            this.name = nname;
            this.type = Float.TYPE;
            this.defaultValue = Float.valueOf(value);
        }

        Preference(final String nname, final boolean value) {
            this.name = nname;
            this.type = Boolean.TYPE;
            this.defaultValue = Boolean.valueOf(value);
        }

        public String getName() {
            return name;
        }

        public Class<?> getType() {
            return type;
        }

        public Object getDefaultValue() {
            return defaultValue;
        }
    }
}
