/**
 * This work was created by participants in the DataONE project, and is
 * jointly copyrighted by participating institutions in DataONE. For 
 * more information on DataONE, see our web site at http://dataone.org.
 *
 *   Copyright ${year}
 *
 * Licensed 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.
 * 
 * $Id: Settings.java 13722 2014-05-02 06:16:43Z rnahf $
 */

package org.dataone.configuration;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;

import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.DefaultConfigurationBuilder;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Settings {
	
	private static Log log = LogFactory.getLog(Settings.class);
	
    private static CompositeConfiguration configuration = null;
    
    public static String STD_CONFIG_PATH = "org/dataone/configuration";
    
    static {
    	// allow commas in the property values
		AbstractConfiguration.setDefaultListDelimiter(';');
    }

    /**
     * A private constructor to be sure no instances are created.
     */
    private Settings() {
    	
    }
        
    /**
     * Get a Configuration interface that combines all resources
     * found under all of the org.dataone.configuration/config.xml
     * files from all application packages.  
     * 
     * In the case of property key collision, those from configurations
     * loaded first override those loaded later.  So, recommend creating
     * keys with a package-specific prefix to avoid unnecessary 
     * collisions.
     * 
     * System properties are always loaded first, followed by those
     * properties in an optional file (specified with the system
     * property "opt.overriding.properties.filename"), followed by
     * context-specific default properties files.
     * 
     * 
     * @return an aggregate Configuration for all properties loaded
     * @throws NoSuchMethodException 
     * @throws IllegalAccessException 
     * @throws InvocationTargetException 
     */
    public static Configuration getConfiguration() {
        if (configuration == null) {
    		
        	configuration = new CompositeConfiguration();
        	      	
        	try {
				configuration = loadTestConfigurations(configuration);
			} catch (ConfigurationException e1) {
				// if problems with TestSettings, need to stop loading regular
				// configurations, and cause downstream failures.
				return new CompositeConfiguration();
			}
        	
			// default to include all the configurations at config.xml, but can be extended
        	String configResourceName = STD_CONFIG_PATH + "/config.xml";
        	Enumeration<URL> configURLs = null;
			try {
				configURLs = Settings.class.getClassLoader().getResources(configResourceName);
				while (configURLs.hasMoreElements()) {
	        		URL configURL = configURLs.nextElement();
	        		log.debug("Loading configuration: " + configURL);
	        		DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
	        		factory.setURL(configURL);
	    			Configuration config = null;
					try {
						config = factory.getConfiguration();
		    			configuration.addConfiguration(config);
					} catch (ConfigurationException e) {
						log.error("Problem loading configuration: " + configURL, e);
					}
	        	}
			} catch (IOException e) {
				log.error("No configuration resources found for: " + configResourceName, e);
			}	
    		
        }
        return configuration;
    }
    
    
    public static Configuration getResetConfiguration() {
    	configuration = null;
    	return getConfiguration();
    }
    
    
    /**
     * The hook for inserting test configurations into the application.
     * Looks for an org.dataone.configuration.TestSettings class, and loads
     * the properties at the front of the composite configuration, effectively
     * overriding any later values loaded.
     * If TestSettings class not found, assumes not a testing application, so 
     * does not throw an exception
     * 
     * @return Configuration object containing the test configurations.
     * @throws ConfigurationException
     */
    private static CompositeConfiguration loadTestConfigurations(CompositeConfiguration configuration) 
    throws ConfigurationException 
    {
   
		try {
//			ClassLoader myClassLoader = ClassLoader.getSystemClassLoader();
			ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
			log.debug("ClassLoader type: " + myClassLoader.getClass().getSimpleName());
			Class<?> testSettings;
			testSettings = myClassLoader.loadClass("org.dataone.configuration.TestSettings");
 	
			try {
				Method getTestConfigurations = testSettings.getMethod("getConfiguration", (Class<?>[]) null);
				configuration = (CompositeConfiguration) getTestConfigurations.invoke(null, (Object[]) null);
				
			// problems loading configurations when in test situation are not
			// recoverable, because we do not want to revert to non-test (production)
			// context if we can't load test configurations.
			} catch (Exception e) {

				String message = "General Problem loading TestSettings. " + 
					e.getClass().getSimpleName() + ": " + e.getMessage();
				log.error(message,e);
				throw new ConfigurationException(message);
			}

		} catch (ClassNotFoundException e) {
			log.debug("TestSettings not found: assume production context");
			// do nothing, because will only find if d1_integration in classpath 
		}		
				
		return configuration;
    }
    
    /**
     * Include additional properties in the global configuration.
     * Properties included in the given file will override existing properties in the global configuration
     * if they are present.
     * @param fileName The properties file (path) to include
     * @throws ConfigurationException
     */
    public static void augmentConfiguration(String fileName) throws ConfigurationException {
    	Configuration config = new PropertiesConfiguration(fileName);
    	// create new composite to hold them all
    	CompositeConfiguration compositeConfiguration = new CompositeConfiguration();
    	compositeConfiguration.addConfiguration(config);
    	// add the originals back
    	int size = ((CompositeConfiguration) getConfiguration()).getNumberOfConfigurations();
    	for (int i = 0; i < size; i++) {
    		Configuration c = ((CompositeConfiguration) getConfiguration()).getConfiguration(i);
    		compositeConfiguration.addConfiguration(c);
    	}
    	configuration = compositeConfiguration;
    }
}