/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007,2008 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 1any 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.felix.framework.cache.BundleCache;
import org.apache.felix.main.AutoActivator;
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";

    /**
     * List of excluded bundles.
     */
    private List<String> excluded = null;

    /**
     * @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 files
            config = loadDefaultConfiguration();
        }

        // Augment the basis configuration
        findAutoDeployedBundles(config);

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

        // Set the cache directory
        config.put(BundleCache.CACHE_PROFILE_DIR_PROP, cache.getAbsolutePath());

        return config;
    }

    /**
     * Update the given config 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 {

        // Get property file content
        Properties levels = getAutoDeployedBundles();

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

            // Only process start level properties
            String key = (String) it.next();
            if (key.startsWith("start.level.")) {

                // Get the associated level
                String level = key.substring("start.level".length());
                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 (!getExcludedBundleList().contains(groupId + ":" + artifactId)) {
                            // This element is not excluded
                            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();
                            }

                            if (JOnASUtils.isDeveloperMode()) {
                                // Use m2 repository
                                sb.append(Maven2Utils.getBundleMaven2Location(groupId,
                                        artifactId,
                                        version,
                                        classifier).concat(" "));
                            } else {
                                // Use lib/bundles
                                File toDeploy = new File(getLibBundlesFile(),
                                                         getArtifactPath(artifactId, version, classifier));
                                String url = toDeploy.toURI().toURL().toExternalForm();
                                sb.append(url.concat(" "));
                            }
                        } // Else this element is excluded, do nothing
                    }
                }

                // Creates the real property
                config.put(AutoActivator.AUTO_START_PROP.concat(level), sb.toString());
            }
        }
    }

    /**
     * @return a File pointing to $JR/lib/bundles
     */
    private File getLibBundlesFile() {
        File root = JOnASUtils.getJOnASRoot();
        return IOUtils.getSystemFile(root, "lib/bundles");
    }

    /**
     * Create the artifact jar name.
     * @param artifactId Id if the artifact.
     * @param version Artifact's version
     * @param classifier Artifact's classifier (may be null)
     * @return a correct jar filename.
     */
    private String getArtifactPath(final String artifactId,
                                   final String version,
                                   final String classifier) {

        String path = artifactId.concat("-").concat(version);
        if (classifier != null) {
            path = path.concat("-").concat(classifier);
        }

        path = path.concat(".jar");
        return path;
    }

    /**
     * @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;
    }

    protected List<String> getExcludedBundleList() {
        if (excluded == null) {
            excluded = new ArrayList<String>();

            // Use Text UI ?
            if (!Boolean.getBoolean("jonas.felix.tui.enabled")) {
                excluded.add("org.apache.felix:org.apache.felix.shell.tui");
            }

            // Use GUI ?
            if (!Boolean.getBoolean("jonas.felix.gui.enabled")) {
                excluded.add("org.apache.felix:org.apache.felix.shell.gui");
                excluded.add("org.apache.felix:org.apache.felix.shell.gui.plugin");
            }
        }

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

}
