/* $Id: ConfigAccess.java,v 1.3 2007/05/10 00:37:53 m31 Exp $
 *
 * This file is part of the project "Hilbert II" - http://www.qedeq.org
 *
 * Copyright 2000-2007,  Michael Meyling <mime@qedeq.org>.
 *
 * "Hilbert II" 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 2 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.
 */

package org.qedeq.kernel.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import org.qedeq.kernel.trace.Trace;


/**
 * This class reads entries from property files. This class should not
 * be used outside this package.
 *
 * @version $Revision: 1.3 $
 * @author  Michael Meyling
 */
final class ConfigAccess {

    /** Config directory. */
    private final File configDirectory;

    /** Name of config file. */
    private final String configFileName;

    /** Collector for properties. */
    private Properties properties = new Properties();

    /** Config file description. */
    private final String description;

    /** Trace config write access? */
    private boolean noTrace = true;

    /**
     * Get access for a config file.
     *
     * @param   configDirectory         Directory location of config file.
     * @param   configFileName          Name of config file.
     * @param   description             Config file description
     * @throws  IOException             Config file couldn't be loaded.
     */
    public ConfigAccess(final File configDirectory, final String configFileName,
            final String description) throws IOException {
        this.configDirectory = configDirectory;
        this.configFileName = configFileName;
        this.description = description;
        try {
            load(new FileInputStream(new File(configDirectory, configFileName)));
        } catch (IOException e) {
            Trace.info(this, "ConfigAccess(File, String, String)",
                "no config file found, using default values");
        }
/*
        try {
            load("/" + configFileName, ConfigAccess.class);
        } catch (MissingResourceException e) {
            throw new IOException(e.toString());
        }
*/
        setString("startDirectory", configDirectory.getCanonicalPath());
        setString("configFileLocation", new File(configDirectory, configFileName)
                    .getCanonicalPath());
        noTrace = false;
    }

    /**
     * Get config directory.
     *
     * @return  Start directory.
     */
    public final File getConfigDirectory() {
        return configDirectory;
    }

    /**
     * Get config file name.
     *
     * @return  Config file name.
     */
    public final String getFileName() {
        return configFileName;
    }

    /**
     * Get config file.
     *
     * @return  Config file.
     */
    public final File getConfigFile() {
        return new File(getConfigDirectory(), getFileName());
    }

    /**
     * Get description for config file.
     *
     * @return  Config file description.
     */
    public final String getConfigDescription() {
        return description;
    }

    /**
     * Get properties.
     *
     * @return  properties.
     */
    private final Properties getProperties() {
        return properties;
    }

    /**
     * Load properties from stream. The properties are
     * added to the previous ones.
     *
     * @param   inStream    load from this stream
     * @throws  IOException loading failed
     */
    private final void load(final InputStream inStream) throws IOException {
        getProperties().load(inStream);
    }

    /**
     * Load properties from resource. The properties are added
     * to the previous ones.
     *
     * @param resource          read from this resource.
     * @param resourceRequestor class, which requests the resource.
     *                          The resource is loaded relatively to this class.
     * @throws IOException      Reading failed or resource was not found.
     */
    private final void load(final String resource, final Class resourceRequestor)
            throws IOException {
        final InputStream stream
            = resourceRequestor.getResourceAsStream(resource);
        if (stream == null) {
            throw new IOException("ressource was not found: " + resource);
        }
        try {
            getProperties().load(stream);
        } catch (IllegalArgumentException e) {
            throw new IOException(e.toString());
        }
    }

    /**
     * Store properties in config.
     *
     * @throws  IOException Saving failed.
     */
    public final void store() throws IOException {
        store(getConfigFile(), getConfigDescription());
    }

    /**
     * Store properties in resource.
     *
     * @param   resource    save it here (must be different from <code>null</code>)
     * @param   header      config description
     * @throws  IOException store failed
     */
    private final void store(final File resource, final String header)
            throws IOException {

        OutputStream stream = null;
        try {
            stream = new FileOutputStream(resource);
            getProperties().store(stream, header);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

    /**
     * Return String property.
     *
     * @param   name    Get this property.
     * @return  String for looked property. <code>null</code>, if property is missing.
     */
    public final String getString(final String name) {
        return getProperties().getProperty(name);
    }

    /**
     * Return String property.
     *
     * @param   name            Look for this String property
     * @param   defaultValue    Return this value if property doesn't exist.
     * @return  Value of property. Equal to default value if parameter doesn't exist.
     */
    public final String getString(final String name, final String defaultValue) {
        final String method = "getString(String, String)";
        try {
            Trace.begin(this, method);
            Trace.param(this, method, "name", name);
            Trace.param(this, method, "defaultValue", defaultValue);
            final String value = getProperties().getProperty(name);
            if (value == null) {
                setString(name, defaultValue);
                return defaultValue;
            } else {
                return value;
            }
        } finally {
            Trace.end(this, method);
        }
    }


    /**
     * Set String property.
     *
     * @param name   Set this property.
     * @param value  Set property to this value.
     */
    public final void setString(final String name, final String value) {
        final String method = "setString(String, String)";
        try {
            if (!noTrace) {
                Trace.begin(this, method);
                Trace.param(this, method, "name", name);
                Trace.param(this, method, "value", value);
            }
            getProperties().setProperty(name, value);
        } finally {
            if (!noTrace) {
                Trace.end(this, method);
            }
        }
    }

    /**
     * Get list of String properties with certain prefix.
     * Example:
     * <ul>
     * <li>module1=bluebird</li>
     * <li>module2=tiger</li>
     * <li>module3=tulip</li>
     * </ul>
     * The sequence of resulting properties is sorted by their keys.
     *
     * @param   namePrefix  Prefix of seeked property name.
     * @return  List of key sorted string properties (maybe empty).
     */
    public final String[] getStringProperties(final String namePrefix) {
        final List list = new ArrayList();
        final Enumeration keys = getProperties().keys();
        final List keyList = Collections.list(keys);
        Collections.sort(keyList);
        for (int i = 0; i < keyList.size(); i++) {
            final String key = (String) keyList.get(i);
            if (key.startsWith(namePrefix)) {
                list.add(getProperties().get(key));
            }
        }
        return (String []) list.toArray(new String[] {});
    }

    /**
     * Set int property.
     *
     * @param name   Set this property.
     * @param value  Set property to this value.
     */
    public final void setInteger(final String name, final int value) {
        setString(name, "" + value);
    }


    /**
     * Get int property.
     *
     * @param   name    look for this property
     * @return  property
     * @throws  IllegalArgumentException    Property is no valid int value
     * @throws  NullPointerException        Property doesn't exist
     */
    public final int getInteger(final String name) {
        final String intPropAsString = getProperties().getProperty(name);
        if (intPropAsString != null) {
            try {
                return Integer.parseInt(intPropAsString);
            } catch (NumberFormatException ex) {
                throw new IllegalArgumentException(
                    "int property " + intPropAsString + " has invalid format");
            }
        } else {
            throw new NullPointerException("property \"" + name + "\" not found");
        }
    }

    /**
     * Return int property.
     *
     * @param   name            Look for this integer property.
     * @param   defaultValue    Return this value if property doesn't exist.
     * @return  int value of property. Equal to default value if parameter doesn't exist.
     * @throws  IllegalArgumentException    Property is no valid int value.
     */
    public final int getInteger(final String name, final int defaultValue) {
        final String intPropAsString = getProperties().getProperty(name);
        if (intPropAsString != null) {
            try {
                return Integer.parseInt(intPropAsString);
            } catch (NumberFormatException ex) {
                throw new IllegalArgumentException(
                    "Integer-Property " + intPropAsString + " has invalid format");
            }
        } else {
            setInteger(name, defaultValue);
            return defaultValue;
        }
    }

    /**
     * Remove property.
     *
     * @param   name    Property to delete.
     */
    public final void removeProperty(final String name) {
        getProperties().remove(name);
    }

    /**
     * Remove properties with certain prefix.
     *
     * Example:
     * <ul>
     * <li>module1=bluebird</li>
     * <li>module2=tiger</li>
     * <li>module3=tulip</li>
     * </ul>
     * Calling with value <code>module</code> deletes all.
     *
     * @param   namePrefix  Prefix of seeked property name.
     */
    public final void removeProperties(final String namePrefix) {
        final Enumeration keys = getProperties().keys();
        while (keys.hasMoreElements()) {
            final String key = (String) keys.nextElement();
            if (key.startsWith(namePrefix)) {
                getProperties().remove(key);
            }
        }
    }

}
