/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * This class allows to manage users preferences.
 * <p>
 * Here is a short usage example:
 * <p>
 * <blockquote><pre>
 * // at application intialization
 * HashMap defaults = new HashMap();
 * defaults.put("windowSize", new Dimension(640, 480));
 * defaults.put("antialias", Boolean.TRUE);
 * PreferenceManager prefs = new PreferenceManager("application.ini", defaults);
 * try {
 *     prefs.load();
 * } catch (IOException e) {
 *     //
 * }
 * myApplication.setSize(prefs.getDimension("windowSize"));
 * myApplication.setAntialiasingOn(prefs.getBoolean("antialias"));
 *
 * // later a dialog box may customize preferences
 * myApplication.setAntialiasingOn(antialiasCheckBox.getState());
 * prefs.setBoolean("antialias", antialiasCheckBox.getState());
 *
 * // when leaving the application we need to save the preferences
 * prefs.setDimension("windowSize", myApplication.getSize());
 * prefs.setFiles("history", lastVisitedFileArray);
 * try {
 *    prefs.save()
 * } catch (IOException e) {
 *    //
 * }
 * </pre></blockquote>
 * <p>
 * @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
 * @version $Id: PreferenceManager.java,v 1.6 2003/04/11 13:59:29 vhardy Exp $
 */
public class PreferenceManager
{
    protected Properties internal = null;
    protected Map defaults = null;
    protected String prefFileName = null;
    protected String fullName = null;

    protected final static String USER_HOME = getSystemProperty("user.home");
    protected final static String USER_DIR  = getSystemProperty("user.dir");
    protected final static String FILE_SEP  = getSystemProperty("file.separator");

    private static String PREF_DIR = null;

    /**
     * Gets a System property if accessible. Returns an empty string 
     * otherwise
     */
    protected static String getSystemProperty(String prop){
        try{
            return System.getProperty(prop);
        }catch(AccessControlException e){
            return "";
        }
    }

    /**
     * Creates a preference manager.
     * @param prefFileName the name of the preference file.
     */
    public PreferenceManager(String prefFileName)
    {
        this(prefFileName, null);
    }

    /**
     * Creates a preference manager with a default values
     * initialization map.
     * @param prefFileName the name of the preference file.
     * @param defaults where to get defaults value if the value is
     * not specified in the file.
     */
    public PreferenceManager(String prefFileName, Map defaults)
    {
        this.prefFileName = prefFileName;
        this.defaults = defaults;
        internal = new Properties();
    }

    /**
     * Sets a <code>String</code> representing the directory
     * where <code>PreferenceManager</code> instances should look
     * for preferences files. The default value is <code>null</code>
     * which means the automatic mechanism for looking for preferences
     * is used.
     * @see #load
     */
    public static void setPreferenceDirectory(String dir)
    {
        PREF_DIR = dir;
    }

    /**
     * Returns a <code>String</code> representing the directory
     * where <code>PreferenceManager</code> instances should look
     * for preferences.
     * @see #load
     * @see #setPreferenceDirectory
     */
    public static String getPreferenceDirectory()
    {
        return PREF_DIR;
    }

    /**
     * Loads the preference file. If the file has already been previously
     * sucessfuly loaded or saved, it will first try to reaload it from
     * this location. Otherwise, it will try to find the file
     * in the following order: in the directory set by
     * {@link #setPreferenceDirectory} if it exists, in the user
     * home directory and then in the current user directory.
     * @exception IOException if an error occured when reading the file.
     * @see #save
     */
    public void load()
        throws IOException
    {
        FileInputStream fis = null;
        if (fullName != null)
            try {
                fis = new FileInputStream(fullName);
            } catch (IOException e1) {
                fullName = null;
            }
        if (fullName == null) {
            if (PREF_DIR != null) {
                try {
                    fis =
                        new FileInputStream(fullName =
                                            PREF_DIR+FILE_SEP+prefFileName);
                } catch (IOException e2) {
                    fullName = null;
                }
            }
            if (fullName == null) {
                try {
                    fis =
                        new FileInputStream(fullName =
                                            USER_HOME+FILE_SEP+prefFileName);
                } catch (IOException e3) {
                    try {
                        fis = new FileInputStream(fullName =
                                                  USER_DIR+FILE_SEP+prefFileName);
                    } catch (IOException e4) {
                        fullName = null;
                    }
                }
            }
        }
        if (fullName != null) {
            try {
                internal.load(fis);
            } finally {
                fis.close();
            }
        }
    }

    /**
     * Saves the preference file. If it has previously sucessfuly been
     * loaded or save it will save it at the same location. In other cases
     * it will save it in the directory set by {@link #setPreferenceDirectory}
     * if has been set and exists, otherwise in the user home directory.
     * @exception IOException if an error occured when writing the file or
     * if is impossible to write the file at all available locations.
     * @see #load
     */
    public void save()
        throws IOException
    {
        FileOutputStream fos = null;
        if (fullName != null)
            try {
                fos = new FileOutputStream(fullName);
            } catch(IOException e1) {
                fullName = null;
            }
        if (fullName == null) {
            if (PREF_DIR != null) {
                try {
                    fos =
                        new FileOutputStream(fullName =
                                             PREF_DIR+FILE_SEP+prefFileName);
                } catch (IOException e2) {
                    fullName = null;
                }
            }
            if (fullName == null) {
                try {
                    fos =
                        new FileOutputStream(fullName =
                                             USER_HOME+FILE_SEP+prefFileName);
                } catch (IOException e3) {
                    fullName = null;
                    throw e3;
                }
            }
        }
        try {
            internal.store(fos, prefFileName);
        } finally {
            fos.close();
        }
    }

    private Object getDefault(String key)
    {
        if (defaults != null)
            return defaults.get(key);
        else
            return null;
    }

    /**
     * Returns a Rectangle preference.
     */
    public Rectangle getRectangle(String key)
    {
        Rectangle defaultValue = (Rectangle)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        Rectangle result = new Rectangle();
        try {
            int x, y, w, h;
            String token;
            StringTokenizer st = new StringTokenizer(sp," ", false);
            if (!st.hasMoreTokens()) {
                // the value is not correctly formated => remove it
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            x = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            y = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            w = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            h = Integer.parseInt(token);
            result.setBounds(x,y,w,h);
            return result;
        } catch (NumberFormatException e) {
            internal.remove(key);
            return defaultValue;
        }
    }

    /**
     * Returns a Dimension preference.
     */
    public Dimension getDimension(String key)
    {
        Dimension defaultValue = (Dimension)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null)
            return defaultValue;
        Dimension result = new Dimension();
        try {
            int w, h;
            String token;
            StringTokenizer st = new StringTokenizer(sp," ", false);
            if (!st.hasMoreTokens()) {
                // the value is not correctly formated => remove it
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            w = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            h = Integer.parseInt(token);
            result.setSize(w,h);
            return result;
        } catch (NumberFormatException e) {
            internal.remove(key);
            return defaultValue;
        }
    }

    /**
     * Returns a point preference.
     */
    public Point getPoint(String key)
    {
        Point defaultValue = (Point)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        Point result = new Point();
        try {
            int x, y;
            String token;
            StringTokenizer st = new StringTokenizer(sp," ", false);
            if (!st.hasMoreTokens()) {
                // the value is not correctly formated => remove it
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            x = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            y = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            result.setLocation(x,y);
            return result;
        } catch (NumberFormatException e) {
            internal.remove(key);
            return defaultValue;
        }
    }

    /**
     * Retruns a Color preference.
     */
    public Color getColor(String key)
    {
        Color defaultValue = (Color)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        try {
            int r, g, b, a;
            String token;
            StringTokenizer st = new StringTokenizer(sp," ", false);
            if (!st.hasMoreTokens()) {
                // the value is not correctly formated => remove it
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            r = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            g = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            b = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            a = Integer.parseInt(token);
            return new Color(r, g, b, a);
        } catch (NumberFormatException e) {
            internal.remove(key);
            return defaultValue;
        }
    }

    /**
     * Returns a font preference.
     */
    public Font getFont(String key)
    {
        Font defaultValue = (Font)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        try {
            int size, type;
            String name;
            String token;
            StringTokenizer st = new StringTokenizer(sp," ", false);
            if (!st.hasMoreTokens()) {
                // the value is not correctly formated => remove it
                internal.remove(key);
                return defaultValue;
            }
            name = st.nextToken();
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            size = Integer.parseInt(token);
            if (!st.hasMoreTokens()) {
                internal.remove(key);
                return defaultValue;
            }
            token = st.nextToken();
            type = Integer.parseInt(token);
            return new Font(name, type, size);
        } catch (NumberFormatException e) {
            internal.remove(key);
            return defaultValue;
        }

    }

    /**
     * Returns a String preference.
     */
    public String getString(String key)
    {
        String sp = internal.getProperty(key);
        if (sp == null) {
            sp = (String)getDefault(key);
        }
        return sp;
    }

    /**
     * Returns an array of String preference.
     */
    public String[] getStrings(String mkey)
    {
        String last;
        int i = 0;
        ArrayList v = new ArrayList();
        while (true) {
            last = getString(mkey+i);
            i++;
            if (last == null)
                break;
            v.add(last);
        }
        if (v.size() != 0) {
            String[] str = new String[v.size()];
            return (String[])v.toArray(str);
        } else {
            return (String[])getDefault(mkey);
        }
    }

    /**
     * Returns an URL preference.
     */
    public URL getURL(String key)
    {
        URL defaultValue = (URL)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        URL url = null;
        try {
            url = new URL(sp);
        } catch (MalformedURLException ex) {
            internal.remove(key);
            return defaultValue;
        }
        return url;
    }

    /**
     * Returns an array of URLs preference.
     */
    public URL[] getURLs(String mkey)
    {
        URL last;
        int i = 0;
        ArrayList v = new ArrayList();
        while (true) {
            last = getURL(mkey+i);
            i++;
            if (last == null)
                break;
            v.add(last);
        }
        if (v.size() != 0) {
            URL[] path = new URL[v.size()];
            return (URL[])v.toArray(path);
        } else {
            return (URL[])getDefault(mkey);
        }
    }

    /**
     * Returns a File preference.
     */
    public File getFile(String key)
    {
        File defaultValue = (File)getDefault(key);
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        File file = new File(sp);
        if (file.exists())
            return file;
        else {
            internal.remove(key);
            return defaultValue;
        }
    }

    /**
     * Returns an array of Files preference.
     */
    public File[] getFiles(String mkey)
    {
        File last;
        int i = 0;
        ArrayList v = new ArrayList();
        while (true) {
            last = getFile(mkey+i);
            i++;
            if (last == null)
                break;
            v.add(last);
        }
        if (v.size() != 0) {
            File[] path = new File[v.size()];
            return (File[])v.toArray(path);
        } else {
            return (File[])getDefault(mkey);
        }
    }


    /**
     * Gets an int preference.
     */
    public int getInteger(String key)
    {
        int defaultValue = 0;
        if (getDefault(key) != null)
            defaultValue = ((Integer)getDefault(key)).intValue();
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        int value;
        try {
            value = Integer.parseInt(sp);
        } catch (NumberFormatException ex) {
            internal.remove(key);
            return defaultValue;
        }
        return value;
    }

    /**
     * Gets a float preference.
     */
    public float getFloat(String key)
    {
        float defaultValue = 0;
        if (getDefault(key) != null)
            defaultValue = ((Float)getDefault(key)).floatValue();
        String sp = internal.getProperty(key);
        if (sp == null) {
            return defaultValue;
        }
        float value;
        try {
            value = Float.parseFloat(sp);
        } catch (NumberFormatException ex) {
            setFloat(key, defaultValue);
            return defaultValue;
        }
        return value;
    }

    /**
     * Gets a boolean preference. If not found and no default returns false.
     */
    public boolean getBoolean(String key)
    {
        if (internal.getProperty(key) != null)
            return (internal.getProperty(key).equals("true"))?
                true:false;
        else
            if (getDefault(key) != null)
                return ((Boolean)getDefault(key)).booleanValue();
            else
                return false;
    }

    /**
     * Sets a Rectangle preference. If null removes it.
     */
    public void setRectangle(String key, Rectangle value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.x+" "+value.y+" "+
                                 value.width+" "+value.height);
        else
            internal.remove(key);
    }

    /**
     * Sets a Dimension preference. If null removes it.
     */
    public void setDimension(String key, Dimension value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.width+" "+value.height);
        else
            internal.remove(key);
    }

    /**
     * Sets a Point preference. If null removes it.
     */
    public void setPoint(String key, Point value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.x+" "+value.y);
        else
            internal.remove(key);
    }

    /**
     * Sets a Color preference. If null removes it.
     */
    public void setColor(String key, Color value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.getRed()+" "+
                         value.getGreen()+" "+value.getBlue()+" "+
                         value.getAlpha());
        else
            internal.remove(key);
    }

    /**
     * Sets a Font preference. If null removes it.
     */
    public void setFont(String key, Font value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.getName()+" "+value.getSize()+" "+
                         value.getStyle());
        else
            internal.remove(key);
    }

    /**
     * Sets a String preference. If null removes it.
     */
    public void setString(String key, String value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value);
        else
            internal.remove(key);
    }

    /**
     * Sets a String array preference. If null or size null removes
     * previous preference.
     */
    public void setStrings(String mkey, String[] values)
    {
        int j = 0;
        if (values != null)
            for (int i = 0; i < values.length; i++) {
                if (values[i] != null) {
                    setString(mkey+j, values[i]);
                    j++;
                }
            }
        // erase other elements
        String last;
        while (true) {
            last = getString(mkey+j);
            if (last == null)
                break;
            setString(mkey+j, null);
            j++;
        }
    }

    /**
     * Sets an URL property. If null removes it.
     */
    public void setURL(String key, URL value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.toString());
        else
            internal.remove(key);
    }

    /**
     * Sets an array of URLs property. If null or size null removes
     * previous preference.
     */
    public void setURLs(String mkey, URL[] values)
    {
        int j = 0;
        if (values != null)
            for (int i = 0 ; i < values.length; i++) {
                if (values[i] != null) {
                    setURL(mkey+j, values[i]);
                    j++;
                }
            }
        // erase other elements
        String last;
        while (true) {
            last = getString(mkey+j);
            if (last == null)
                break;
            setString(mkey+j, null);
            j++;
        }
    }

    /**
     * Sets a File property. If null removes it.
     */
    public void setFile(String key, File value)
    {
        if (value != null && !value.equals(getDefault(key)))
            internal.setProperty(key, value.getAbsolutePath());
        else
            internal.remove(key);
    }

    /**
     * Sets an array of Files property. If null or size null removes
     * previous preference.
     */
    public void setFiles(String mkey, File[] values)
    {
        int j = 0;
        if (values != null)
            for (int i = 0 ; i < values.length; i++) {
                if (values[i] != null) {
                    setFile(mkey+j, values[i]);
                    j++;
                }
            }
        // erase other elements
        String last;
        while (true) {
            last = getString(mkey+j);
            if (last == null)
                break;
            setString(mkey+j, null);
            j++;
        }
    }

    /**
     * Sets an int property.
     */
    public void setInteger(String key, int value)
    {
        if (getDefault(key) != null &&
            ((Integer)getDefault(key)).intValue() != value)
            internal.setProperty(key, Integer.toString(value));
        else
            internal.remove(key);
    }

    /**
     * Sets a float property.
     */
    public void setFloat(String key, float value)
    {
        if (getDefault(key) != null &&
            ((Float)getDefault(key)).floatValue() != value)
            internal.setProperty(key, Float.toString(value));
        else
            internal.remove(key);
    }

    /**
     * Sets a boolean property.
     */
    public void setBoolean(String key, boolean value)
    {
        if (getDefault(key) != null &&
            ((Boolean)getDefault(key)).booleanValue() != value) {
            internal.setProperty(key, value?"true":"false");
	} else {
            internal.remove(key);
	}
    }
}
