/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007-2009 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.launcher.felix;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.apache.felix.main.AutoActivator;
import org.osgi.framework.Constants;
import org.ow2.jonas.launcher.felix.util.IOUtils;
import org.ow2.jonas.launcher.felix.util.JOnASUtils;
import org.ow2.jonas.launcher.felix.util.Maven2Utils;

/**
 * Default {@link IConfigurationProvider} for Apache Felix.
 * @author Guillaume Sauthier
 */
public class DefaultConfigurationProvider implements IConfigurationProvider {

    /**
     * Property for the Felix configuration file.
     */
    private static final String FELIX_CONFIG_FILE_PROP = "jonas.felix.configuration.file";

    /**
     * Property for the Felix bundles file.
     */
    private static final String FELIX_BUNDLES_CONFIG_FILE_PROP = "jonas.felix.bundles.configuration.file";

    /**
     * @return a Felix default configuration.
     * @throws IOException configuration not found
     */
    @SuppressWarnings("unchecked")
    public Map getConfiguration() throws IOException {

        // Felix configuration properties
        Properties config;

        // Get the system property "jonas.felix.configuration.file"
        String felixConfigFilename = System.getProperty(FELIX_CONFIG_FILE_PROP);

        // Check if it exists
        if (felixConfigFilename != null && exists(felixConfigFilename)) {
            // Get the configuration file
            config = loadConfiguration(felixConfigFilename);
        } else {
            // Get the default configuration file
            config = loadDefaultConfiguration();
        }

        // Augment the basis configuration
        findAutoDeployedBundles(config);

        // Set the Felix cache root directory in $JB/work
        File base = JOnASUtils.getJOnASBase();
        File cacheRootDir = IOUtils.getSystemFile(base, "work");

        // Set the cache root directory
        config.put(Constants.FRAMEWORK_STORAGE, cacheRootDir.getAbsolutePath() + File.separator + "felix-cache");

        return config;
    }

    /**
     * Update the given configuration with start level options.
     * @param config configuration to enhance.
     * @throws IOException if default property file is not found.
     */
    private void findAutoDeployedBundles(final Properties config) throws IOException {

        // Felix bundles configuration properties
        Properties levels = null;

        // Get the system property "jonas.felix.configuration.file"
        String felixBundlesConfigFilename = System.getProperty(FELIX_BUNDLES_CONFIG_FILE_PROP);

        // Check if it exists
        if (felixBundlesConfigFilename != null && exists(felixBundlesConfigFilename)) {
            // Get the configuration file
            levels = loadConfiguration(felixBundlesConfigFilename);
        } else {
            // Get the default bundles configuration file
            levels = getAutoDeployedBundles();
        }

        // Iterates over the properties
        for (Iterator<Object> it = levels.keySet().iterator(); it.hasNext();) {

            // Process level properties
            String key = (String) it.next();

            if (key.startsWith("install.level.")) {
                // Get the associated level
                String level = key.substring("install.level".length());

                // Configure the bundle list to install
                config.put(AutoActivator.AUTO_INSTALL_PROP.concat(level), getBundleList(levels, key));
            }

            if (key.startsWith("start.level.")) {
                // Get the associated level
                String level = key.substring("start.level".length());

                // Configure the bundle list to start
                config.put(AutoActivator.AUTO_START_PROP.concat(level), getBundleList(levels, key));
            }
        }
    }

    /**
     * Get the bundle list for a given property.
     * @param levels The property list
     * @param key The searched key
     * @return The bundle list for a given property.
     * @throws IOException If the list cannot be built.
     */
    private String getBundleList(final Properties levels, final String key) throws IOException {
        String value = levels.getProperty(key);
        StringBuilder sb = new StringBuilder();

        // The bundle list separator is ','
        String[] bundles = value.split(",");
        for (int i = 0; i < bundles.length; i++) {
            String bundle = bundles[i];

            // Only process non-empty parts
            if (!"".equals(bundle)) {

                // The bundle is specified using the following format:
                // <groupId>:<artifactId>[:<version>]
                String[] artifact = bundle.split(":");

                String groupId = artifact[0].trim();
                String artifactId = artifact[1].trim();
                String version = null;
                String classifier = null;

                if (artifact.length == 3) {
                    // Is the third element a version or a
                    // classifier ?
                    classifier = getClassifier(artifact[2]);
                    if (classifier == null) {
                        // this is NOT a classifier
                        version = artifact[2].trim();
                    }
                } else if (artifact.length == 4) {
                    // We have both version + classifier
                    // first is the version
                    version = artifact[2].trim();
                    // then, the classifier
                    classifier = getClassifier(artifact[3]);
                    if (classifier == null) {
                        // in this case, this is an error
                        throw new IOException("Incorrect classifier in bundle: " + bundle);
                    }
                } else if (artifact.length > 4) {
                    // More elements, invalid
                    throw new IOException("Incorrect number of parts in bundle: " + bundle);
                }

                // no version specified, get the default one
                if (version == null) {
                    version = JOnASUtils.getVersion();
                }

                // Use the reference attribute to load the bundle
                // from the local file without copying it into the
                // cache of Felix
                sb.append("reference:");

                File repository = null;
                if (JOnASUtils.isDeveloperMode()) {
                    // Use m2 repository
                    repository = Maven2Utils.getMaven2Repository();
                } else {
                    // Use repositories/internal
                    repository = getMaven2InternalRepositoryFile();
                }
                sb.append(Maven2Utils.getBundleMaven2Location(repository.getPath(), groupId, artifactId, version, classifier)
                        .concat(" "));
            }
        }

        return sb.toString();
    }

    /**
     * @return a File pointing to $JONAS_ROOT/repositories/maven2internal
     */
    private File getMaven2InternalRepositoryFile() {
        File root = JOnASUtils.getJOnASRoot();
        return IOUtils.getSystemFile(root, "repositories/maven2-internal");
    }

    /**
     * @param value value from which the classifier will be extracted
     * @return the classifier value if this is a valid classifier, null otherwise
     */
    private static String getClassifier(final String value) {
        String classifier = null;
        String trimmed = value.trim();
        if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
            // classifier
            classifier = trimmed.substring(1, trimmed.length() - 1);
        }
        return classifier;
    }

    /**
     * @return the config file for auto deployed bundles.
     * @throws IOException file not found
     */
    private static Properties getAutoDeployedBundles() throws IOException {
        return getPropertiesResource("jonas-autodeploy-bundles.properties");
    }

    /**
     * @return the default set of Felix properties.
     * @throws IOException file not found
     */
    private static Properties loadDefaultConfiguration() throws IOException {
        return getPropertiesResource("default-config.properties");
    }

    /**
     * @param configFilename properties file name (relative to JB/conf)
     * @return the content of the resource as a {@link Properties} instance.
     * @throws IOException if file is not found or if file is not a property file.
     */
    private static Properties loadConfiguration(final String configFilename) throws IOException {
        File configFile = new File(configFilename);
        Properties config = new Properties();
        java.io.InputStream is = new FileInputStream(configFile);
        try {
            config.load(is);
        } finally {
            is.close();
        }
        return config;
    }

    /**
     * @param resource properties file name (relative to org/ow2/jonas/launcher/felix)
     * @return the content of the resource as a {@link Properties} instance.
     * @throws IOException if file is not found or if file is not a property file.
     */
    private static Properties getPropertiesResource(final String resource) throws IOException {
        Properties config = new Properties();
        java.io.InputStream is = JOnAS.class.getResourceAsStream(resource);
        config.load(is);
        return config;
    }

    /**
     * Checks if the file exists
     * @param Filename
     * @return
     */
    private boolean exists(final String Filename) {
        return new File(Filename).exists();
    }

}
