/**
 * Copyright 2011-2017 Asakusa Framework Team.
 *
 * 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.
 */
package com.asakusafw.testdriver.hadoop;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.LocalFileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.runtime.util.hadoop.ConfigurationProvider;
import com.asakusafw.testdriver.core.TestingEnvironmentConfigurator;

/**
 * Creates {@link Configuration}s with system defaults.
 * @since 0.2.5
 * @version 0.6.0
 */
public class ConfigurationFactory extends ConfigurationProvider {

    static final Logger LOG = LoggerFactory.getLogger(ConfigurationFactory.class);

    /**
     * The system property key of {@link LocalFileSystem} implementation class name.
     * @since 0.6.0
     */
    public static final String KEY_LOCAL_FILE_SYSTEM = "asakusa.testdriver.fs"; //$NON-NLS-1$

    /**
     * The system property key of Hadoop configuration path.
     * @since 0.6.0
     */
    public static final String KEY_EXPLICIT_HADOOP_CONF = "asakusa.testdriver.hadoop.conf"; //$NON-NLS-1$

    /**
     * The system property key of Hadoop command path.
     * @since 0.6.0
     */
    public static final String KEY_EXPLICIT_HADOOP_COMMAND = "asakusa.testdriver.hadoop.command"; //$NON-NLS-1$

    /**
     * The default value of {@link LocalFileSystem} implementation class name.
     */
    static final String DEFAULT_LOCAL_FILE_SYSTEM = AsakusaTestLocalFileSystem.class.getName();

    static {
        TestingEnvironmentConfigurator.initialize();
    }

    private final Preferences preferences;

    /**
     * Creates a new instance.
     * @see #getDefault()
     */
    private ConfigurationFactory() {
        this(Preferences.getDefault());
    }

    /**
     * Creates a new instance.
     * @param defaultConfigPath the default configuration path
     * @see #getDefault()
     */
    public ConfigurationFactory(URL defaultConfigPath) {
        super(defaultConfigPath);
        this.preferences = Preferences.getDefault();
    }

    /**
     * Creates a new instance.
     * @param preferences the preferences for this instance
     * @see #getDefault()
     * @since 0.6.0
     */
    public ConfigurationFactory(Preferences preferences) {
        super(preferences.getConfigurationPath());
        this.preferences = preferences;
    }

    /**
     * Returns a default factory which refers the System Hadoop configuration path.
     * <p>
     * If Hadoop installation is not found in your system,
     * this returns a factory which use the current context classloader.
     * </p>
     * @return a default factory object
     */
    public static ConfigurationFactory getDefault() {
        return new ConfigurationFactory();
    }

    /**
     * Returns the Hadoop command path for this configuration.
     * @return the Hadoop command path, or {@code null} if it is not found
     */
    public File getHadoopCommand() {
        return preferences.getHadoopCommand();
    }

    @Override
    protected void configure(Configuration configuration) {
        if (preferences.getLocalFileSystemClassName() != null) {
            configuration.set("fs.file.impl", preferences.getLocalFileSystemClassName()); //$NON-NLS-1$
            configuration.setBoolean("fs.fs.impl.disable.cache", true); //$NON-NLS-1$
        }
    }

    /**
     * Preferences for {@link ConfigurationFactory}.
     * @since 0.6.0
     */
    public static class Preferences {

        private String localFileSystemClassName;

        private File explicitConfigurationPath;

        private File explicitCommandPath;

        private final Map<String, String> environmentVariables = new HashMap<>(System.getenv());

        /**
         * Sets the implementation class name of {@link LocalFileSystem}.
         * @param className the class name
         */
        public void setLocalFileSystemClassName(String className) {
            this.localFileSystemClassName = className;
        }

        /**
         * Sets the explicit Hadoop configuration path.
         * @param path the Hadoop configuration path, or {@code null} if it should be inferred
         */
        public void setExplicitConfigurationPath(File path) {
            this.explicitConfigurationPath = path;
        }

        /**
         * Sets the explicit Hadoop command path.
         * @param path the Hadoop command path, or {@code null} if it should be inferred
         */
        public void setExplicitCommandPath(File path) {
            this.explicitCommandPath = path;
        }

        /**
         * Returns the implementation class name of {@link LocalFileSystem}.
         * @return the class name, or {@code null} if it is not defined
         */
        public String getLocalFileSystemClassName() {
            return localFileSystemClassName;
        }

        /**
         * Returns the current environment variables.
         * Clients can modify the returned map.
         * @return the environment variables
         */
        public Map<String, String> getEnvironmentVariables() {
            return environmentVariables;
        }

        /**
         * Returns the explicitly defined Hadoop configuration path.
         * @return the explicit Hadoop configuration path, or {@code null} if it is not defined
         */
        public File getExplicitConfigurationPath() {
            return explicitConfigurationPath;
        }

        /**
         * Returns the explicitly defined Hadoop command path.
         * @return the explicit Hadoop command path, or {@code null} if it is not defined
         */
        public File getExplicitCommandPath() {
            return explicitCommandPath;
        }

        /**
         * Returns the Hadoop configuration path.
         * @return the Hadoop configuration path, or {@code null} if it is not found
         * @see #getExplicitConfigurationPath()
         */
        public URL getConfigurationPath() {
            if (explicitConfigurationPath != null) {
                try {
                    return explicitConfigurationPath.toURI().toURL();
                } catch (MalformedURLException e) {
                    LOG.error(MessageFormat.format(
                            Messages.getString(
                                    "ConfigurationFactory.errorInvalidHadoopConfigurationPath"), //$NON-NLS-1$
                            explicitConfigurationPath), e);
                }
            }
            return ConfigurationProvider.getConfigurationPath(environmentVariables);
        }

        /**
         * Returns the Hadoop command path.
         * @return the Hadoop command path, or {@code null} if it is not found
         */
        public File getHadoopCommand() {
            if (explicitCommandPath != null) {
                return explicitCommandPath;
            }
            return ConfigurationProvider.findHadoopCommand(environmentVariables);
        }

        /**
         * Returns a {@link Preferences} with default values.
         * @return default preferences
         */
        public static Preferences getDefault() {
            Preferences prefs = new Preferences();
            prefs.setLocalFileSystemClassName(System.getProperty(KEY_LOCAL_FILE_SYSTEM, DEFAULT_LOCAL_FILE_SYSTEM));
            prefs.setExplicitConfigurationPath(getFile(KEY_EXPLICIT_HADOOP_CONF));
            prefs.setExplicitCommandPath(getFile(KEY_EXPLICIT_HADOOP_COMMAND));
            return prefs;
        }

        private static File getFile(String key) {
            String value = System.getProperty(key);
            if (value == null) {
                return null;
            }
            return new File(value);
        }
    }
}

@@ -3957,17 +3957,19 @@
 an(%22fs.f
-s
+ile
 .impl.di

Fix cache configuration key.