/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.log4j.chainsaw.prefs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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.net.URLEncoder;
import java.util.EventListener;
import java.util.Properties;

import javax.swing.event.EventListenerList;


/**
 * SettingManager allows components to register interest in Saving/Loading
 * of general application preferences/settings.
 *
 * @author Paul Smith &lt;psmith@apache.org&gt;
 * @author Scott Deboy &lt;sdeboy@apache.org&gt;
 *
 */
public final class SettingsManager {
    private static final SettingsManager instance = new SettingsManager();
    private static final String GLOBAL_SETTINGS_FILE_NAME = "chainsaw.settings.properties";
    private static final String HEADER = "Chainsaws Settings Files";
    private EventListenerList listenerList = new EventListenerList();
    private Properties defaultProperties = new Properties();

    /**
     * Initialises the SettingsManager by loading the default Properties from
     * a resource
     *
     */
    private SettingsManager() {
        //	load the default properties as a Resource
        InputStream is = null;

        try {
            is = this.getClass().getClassLoader()
                     .getResource("org/apache/log4j/chainsaw/prefs/default.properties")
                     .openStream();
            defaultProperties.load(is);

            //      defaultProperties.list(System.out);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                }
            }
        }
    }

    /**
     * Returns the singleton instance of the SettingsManager
     * @return settings manager
     */
    public static SettingsManager getInstance() {
        return instance;
    }

    /**
     * Registers the listener with the manager
     * @param listener
     */
    public void addSettingsListener(SettingsListener listener) {
        listenerList.add(SettingsListener.class, listener);
    }

    /**
     * Requests that the settings be loaded, all listeners will be notified of
     * this call, and configure themselves according to the values found in the
     * loaded settings
     *
     */
    public void loadSettings() {
        /*
         * Ok, note we ensure we have a .chainsaw directory in the users
         * home folder, and create a chainsaw.settings.properties file..
         */
        File settingsDir = getSettingsDirectory();

        if (!settingsDir.exists()) {
            settingsDir.mkdir();
        }

        loadGlobalSettings();
        loadProfileableSettings();
    }

    /**
     *
     */
    private void loadProfileableSettings() {
        EventListener[] listeners = listenerList.getListeners(SettingsListener.class);

        for (int i = 0; i < listeners.length; i++) {
            SettingsListener settingsListener = (SettingsListener) listeners[i];

            if (settingsListener instanceof Profileable) {
                Profileable p = (Profileable) settingsListener;
                loadProfileble(p);
            }
        }
    }

    private void loadProfileble(Profileable p) {
        LoadSettingsEvent event = createProfilebleEvent(p);
        p.loadSettings(event);
    }

    private LoadSettingsEvent createProfilebleEvent(Profileable p) {
        Properties loadedProperties = new Properties();
        loadedProperties.putAll(getDefaultSettings());
        loadedProperties.putAll(loadProperties(p));
        

        LoadSettingsEvent event = new LoadSettingsEvent(this, loadedProperties);

        return event;
    }

    /**
     * @param p
     * @return
     */
    private Properties loadProperties(Profileable p) {
        Properties properties = new Properties(defaultProperties);
        InputStream is = null;

        File f = new File(getSettingsDirectory(),
        		URLEncoder.encode(p.getNamespace() + ".properties"));
        
        if (!f.exists()) {
        	f = new File(getSettingsDirectory(),
            		p.getNamespace() + ".properties");        	
        }

        if (f.exists()) {
            try {
                is = new BufferedInputStream(new FileInputStream(f));

                Properties toLoad = new Properties();
                toLoad.load(is);
                properties.putAll(toLoad);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }

        return properties;
    }

    private void loadGlobalSettings() {
        EventListener[] listeners = listenerList.getListeners(SettingsListener.class);
        LoadSettingsEvent event = null;

        for (int i = 0; i < listeners.length; i++) {
            SettingsListener settingsListener = (SettingsListener) listeners[i];

            if (event == null) {
                Properties loadedProperties = loadGlobalProperties();

                //        loadedProperties.list(System.out);
                event = new LoadSettingsEvent(this, loadedProperties);
            }

            settingsListener.loadSettings(event);
        }
    }

    /**
     * Creates a SaveSettingsEvent and calls all the SettingsListeners
     * to populate the properties with configuration information
     *
     */
    public void saveSettings() {
        /*
         * Ok, note we ensure we have a .chainsaw directory in the users
         * home folder, and create a chainsaw.settings.properties file..
         */
        File settingsDir = getSettingsDirectory();

        if (!settingsDir.exists()) {
            settingsDir.mkdir();
        }

        saveGlobalSettings(settingsDir);
        saveProfileableSetting(settingsDir);
    }

    /**
     * Looks up all the Profileable's that have been registered
     * and creates a new event for each of them, and ensures that they
     * are saved within a separate external store
     * @param settingsDir
     */
    private void saveProfileableSetting(File settingsDir) {
        EventListener[] listeners = listenerList.getListeners(SettingsListener.class);
        SaveSettingsEvent event = null;

        for (int i = 0; i < listeners.length; i++) {
            SettingsListener settingsListener = (SettingsListener) listeners[i];

            if (settingsListener instanceof Profileable) {
                Profileable profileable = (Profileable) settingsListener;
                event = new SaveSettingsEvent(this, getSettingsDirectory());

                profileable.saveSettings(event);

                OutputStream os = null;

                try {
                    os = new BufferedOutputStream(new FileOutputStream(
                                new File(settingsDir,
                                		 URLEncoder.encode(profileable.getNamespace()) + ".properties")));
                    event.getProperties().store(os, HEADER);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (os != null) {
                        try {
                            os.close();
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private void saveGlobalSettings(File settingsDir) {
        EventListener[] listeners = listenerList.getListeners(SettingsListener.class);
        SaveSettingsEvent event = null;

        for (int i = 0; i < listeners.length; i++) {
            SettingsListener settingsListener = (SettingsListener) listeners[i];

            if (!(settingsListener instanceof Profileable)) {
                if (event == null) {
                    event = new SaveSettingsEvent(this, getSettingsDirectory());
                }

                settingsListener.saveSettings(event);
            }
        }

        OutputStream os = null;

        try {
            os = new BufferedOutputStream(new FileOutputStream(
                        new File(settingsDir, GLOBAL_SETTINGS_FILE_NAME)));
            event.getProperties().store(os, HEADER);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    public File getSettingsDirectory() {
        return new File(System.getProperty("user.home"), ".chainsaw");
    }

    public void configure(SettingsListener listener) {
        if (listener instanceof Profileable) {
            loadProfileble((Profileable) listener);
        } else {
            Properties loadedProperties = loadGlobalProperties();
            LoadSettingsEvent event = new LoadSettingsEvent(this,
                    loadedProperties);
            listener.loadSettings(event);
        }
    }

    /**
     * Returns the current Properties settings for this user
     * by merging the default Properties with the ones we find in their directory.
     *
     * @return
     */
    private Properties loadGlobalProperties() {
        Properties properties = new Properties(defaultProperties);
        InputStream is = null;

        File f = new File(getSettingsDirectory(), GLOBAL_SETTINGS_FILE_NAME);

        if (f.exists()) {
            try {
                is = new BufferedInputStream(new FileInputStream(f));

                Properties toLoad = new Properties();
                toLoad.load(is);
                properties.putAll(toLoad);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }

        return properties;
    }

    /**
     * Returns the loaded default settings, which can be used by
     * other classes within this package.
     * @return Properties defaults
     */
    public Properties getDefaultSettings() {
        return defaultProperties;
    }
}
