/**
 * Copyright (c) 2010-2012 EBM WebSourcing, 2012-2013 Linagora
 *
 * This program/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 (at your
 * option) any later version.
 *
 * This program/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 program/library; If not, see <http://www.gnu.org/licenses/>
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.cli.connection;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.ow2.petals.cli.Constants;
import org.ow2.petals.cli.api.connection.AuthenticatedConnectionParameters;
import org.ow2.petals.cli.api.connection.ConnectionParameters;
import org.ow2.petals.cli.http.EmbeddedHttpServerConfig;

public final class PreferenceFileManager {

    /**
     * Name of the environment variable defining the preference file of Petals
     * CLI.
     */
    public static final String PREFERENCE_ENV_VAR = "PETALS_CLI_PREFS";

    static final String PREFERENCE_FILE_NAME = "petals-cli.default";

    /**
     * Property name identifying the default alias
     */
    public static final String DEFAULT_ALIAS_PROPERTY = "alias.default";

    private static final String HOST_PROPERTY_SUFFIX = ".host";

    private static final String PORT_PROPERTY_SUFFIX = ".port";

    private static final String USERNAME_PROPERTY_SUFFIX = ".username";

    private static final String PASSWORD_PROPERTY_SUFFIX = ".password";

    private static final String PASSPHRASE_PROPERTY_SUFFIX = ".passphrase";

    /**
     * Property name identifying the HTTP listening port of the embedded HTTP server
     */
    public static final String EMBEDDED_HTTP_PORT = "embedded.http.port";

    /**
     * The default HTTP listening port of the embedded HTTP server
     */
    public static final int DEFAULT_EMBEDDED_HTTP_PORT = 7500;

    private static final Collection<String> RECOGNIZED_SUFFIXES = Collections
            .unmodifiableCollection(Arrays.asList(new String[] { HOST_PROPERTY_SUFFIX,
                    PORT_PROPERTY_SUFFIX, USERNAME_PROPERTY_SUFFIX, PASSWORD_PROPERTY_SUFFIX,
                    PASSPHRASE_PROPERTY_SUFFIX }));

    private static ConnectionParameters defaultConnectionParameters;

    /**
     * Connection parameters per alias
     */
    private static HashMap<String, ConnectionParameters> preferenceConnectionParameters;
    
    /**
     * Embedded HTTP server parameters
     */
    private static EmbeddedHttpServerConfig embeddedHttpServerParameters;

    /**
     * Flag enabling/disabling preference parameter initialization
     */
    private static boolean isPreferenceParametersInitialized = false;
    
    /**
     * Lock of the preference parameter initialization
     */
    private static final Object preferenceParametersLock = new Object();

    private PreferenceFileManager() {
        // Private constructor because only static methods
    }

    /**
     *
     * @throws MissingDefaultPreferenceFileException
     *             The default preference ({@value #PREFERENCE_FILE_NAME}) file
     *             is missing
     * @throws PreferenceFileNotFoundException
     *             The preference file was not found
     * @throws PreferenceFileException
     *             An error occurs reading the preference file
     */
    public static final ConnectionParameters getDefaultConnectionParameters() throws PreferenceFileException {
        intializePreferenceParameters();
        return defaultConnectionParameters;
    }

    /**
     *
     * @throws MissingDefaultPreferenceFileException
     *             The default preference ({@value #PREFERENCE_FILE_NAME}) file
     *             is missing
     * @throws PreferenceFileNotFoundException
     *             The preference file was not found
     * @throws PreferenceFileException
     *             An error occurs reading the preference file
     */
    public static final Map<String, ConnectionParameters> getPreferenceConnectionParameters()
            throws PreferenceFileException {
        intializePreferenceParameters();
        return preferenceConnectionParameters;
    }

    /**
     * 
     * @throws MissingDefaultPreferenceFileException
     *             The default preference ({@value #PREFERENCE_FILE_NAME}) file is missing
     * @throws PreferenceFileNotFoundException
     *             The preference file was not found
     * @throws PreferenceFileException
     *             An error occurs reading the preference file
     */
    public static final EmbeddedHttpServerConfig getEmbeddedHttpServerParameters() throws PreferenceFileException {
        intializePreferenceParameters();
        return embeddedHttpServerParameters;
    }

    public static final void reset() {
        synchronized (preferenceParametersLock) {
            isPreferenceParametersInitialized = false;
            preferenceConnectionParameters = null;
            defaultConnectionParameters = null;
        }
    }

    /**
     * Entry point of the preference parameter initialization.
     * 
     * @throws MissingDefaultPreferenceFileException
     *             The default preference ({@value #PREFERENCE_FILE_NAME}) file is missing
     * @throws PreferenceFileNotFoundException
     *             The preference file was not found
     * @throws PreferenceFileException
     *             An error occurs reading the preference file
     */
    private static final void intializePreferenceParameters() throws MissingDefaultPreferenceFileException,
            PreferenceFileNotFoundException, PreferenceFileException {
        synchronized (preferenceParametersLock) {
            if (!isPreferenceParametersInitialized) {
                List<String> errors =new ArrayList<String>();
    
                final File preferenceFilePath = getPreferenceFile();
    
                if (preferenceFilePath == null) {
                    throw new MissingDefaultPreferenceFileException();
                } else if (preferenceFilePath.exists()) {
                    Properties preferenceProperties = loadPreferenceProperties(preferenceFilePath);
                    String defaultConnectionAlias = initializeDefaultConnectionAlias(preferenceProperties, errors);
                    preferenceConnectionParameters = initializePreferenceConnectionParametersFromProperties(preferenceProperties, errors);
                    embeddedHttpServerParameters = initializePreferenceEmbeddedHttpServerParametersFromProperties(
                            preferenceProperties, errors);

                    int errorNumber = errors.size();
                    if (errorNumber > 0) {
                        StringBuilder errorMessage = new StringBuilder(String.format(
                                Constants.PreferenceFileMessages.PREFERENCE_FILE_ERRORS, errorNumber));
                        for (String error : errors) {
                            errorMessage.append("\n\t - " + error);
                        }
                        throw new PreferenceFileException(errorMessage.toString());
                    }
    
                    defaultConnectionParameters = preferenceConnectionParameters.get(defaultConnectionAlias);

                } else {
                    // The preference file does not exist
                    throw new PreferenceFileNotFoundException(preferenceFilePath);
                }

            }
            isPreferenceParametersInitialized = true;
        }
    }

    protected static final String initializeDefaultConnectionAlias(Properties preferenceProperties, List<String> errors)
            throws PreferenceFileException {
        assert preferenceProperties != null;
        assert errors != null;

        String defaultConnectionAlias = preferenceProperties.getProperty(DEFAULT_ALIAS_PROPERTY);

        if (defaultConnectionAlias == null) {
            String errorMessage = String.format(
                    Constants.PreferenceFileMessages.MISSING_PROPERTY, DEFAULT_ALIAS_PROPERTY);
            errors.add(errorMessage);
        } else {
            preferenceProperties.remove(DEFAULT_ALIAS_PROPERTY);
        }

        return defaultConnectionAlias;
    }

    protected static final HashMap<String, ConnectionParameters> initializePreferenceConnectionParametersFromProperties(
            Properties preferenceProperties, List<String> errors) throws PreferenceFileException {
        assert preferenceProperties != null;
        assert errors != null;

        HashMap<String, ConnectionParameters> preferenceConnectionParameters = new HashMap<String, ConnectionParameters>();

        List<String> treatedAlias = new ArrayList<String>();
        Set<String> propertyNames = preferenceProperties.stringPropertyNames();
        for (String propertyName : propertyNames) {
            int endOfEntryAlias = propertyName.lastIndexOf('.');
            if (endOfEntryAlias == -1) {
                String errorMessage = String
                        .format(Constants.PreferenceFileMessages.INCORRECT_PROPERTY_NAME, propertyName);
                errors.add(errorMessage);
                continue;
            }

            String propertySuffix = propertyName.substring(endOfEntryAlias);
            if(!RECOGNIZED_SUFFIXES.contains(propertySuffix)) {
                String errorMessage = String.format(
                        Constants.PreferenceFileMessages.INCORRECT_PROPERTY_NAME, propertyName);
                errors.add(errorMessage);
                continue;
            }

            String alias = propertyName.substring(0, endOfEntryAlias);
            if (!treatedAlias.contains(alias)) {
                treatedAlias.add(alias);

                String hostPropertyKey = alias + HOST_PROPERTY_SUFFIX;
                String host = preferenceProperties.getProperty(hostPropertyKey);
                if (host == null) {
                    String errorMessage = String.format(
                            Constants.PreferenceFileMessages.MISSING_PROPERTY,
                            hostPropertyKey);
                    errors.add(errorMessage);
                }

                String portPropertyKey = alias + PORT_PROPERTY_SUFFIX;
                String portString = preferenceProperties.getProperty(portPropertyKey);
                try {

                    int port;
                    if (portString == null) {
                        String errorMessage = String.format(
                                Constants.PreferenceFileMessages.MISSING_PROPERTY,
                                portPropertyKey);
                        errors.add(errorMessage);
                        port = -1;
                    } else {
                        port = Integer.parseInt(portString);
                    }

                    final String username = preferenceProperties.getProperty(alias
                            + USERNAME_PROPERTY_SUFFIX);
                    final String password = preferenceProperties.getProperty(alias
                            + PASSWORD_PROPERTY_SUFFIX);
                    final String passPhrase = preferenceProperties.getProperty(alias
                            + PASSPHRASE_PROPERTY_SUFFIX);
                    if (host != null && port != -1 && username != null && password != null) {
                        ConnectionParameters connectionParameters = new AuthenticatedConnectionParameters(
                                host, port, username, password, passPhrase);
                        preferenceConnectionParameters.put(alias, connectionParameters);
                    } else if (host != null && port != -1 && username == null
                            && password == null) {
                        ConnectionParameters connectionParameters = new ConnectionParameters(
                                host, port);
                        preferenceConnectionParameters.put(alias, connectionParameters);
                    } else if (username == null && password != null) {
                        String errorMessage = String.format(
                                Constants.PreferenceFileMessages.MISSING_USER_PROPERTY, alias);
                        errors.add(errorMessage);
                    } else if (password == null && username != null) {
                        String errorMessage = String.format(
                                Constants.PreferenceFileMessages.MISSING_PASSWORD_PROPERTY,
                                alias);
                        errors.add(errorMessage);
                    }
                } catch (NumberFormatException nfe) {
                    String errorMessage = String.format(
                            Constants.PreferenceFileMessages.INCORRECT_PROPERTY_VALUE,
                            portString, portPropertyKey);
                    errors.add(errorMessage);
                }
            }
        }

        return preferenceConnectionParameters;
    }

    /**
     * <p>
     * Get the preferences file name.
     * </p>
     * <p>
     * The preferences file is defined through the environment variable
     * {@value #PREFERENCE_ENV_VAR}, otherwise it is defined by the resource
     * {@value #PREFERENCE_FILE_NAME} of the classpath.
     * </p>
     *
     * @return The preferences file name, or <code>null</code> if the default
     *         preference resource ({@value #PREFERENCE_FILE_NAME}) can not be
     *         found on the classpath
     */
    protected static final File getPreferenceFile() {
        final String preferenceEnvVarPath = System.getenv(PREFERENCE_ENV_VAR);
        File preferenceFilePath = null;
        if (preferenceEnvVarPath == null || preferenceEnvVarPath.trim().isEmpty()) {

        	final URL defaultPrefFileUrl = Thread.currentThread().getContextClassLoader().getResource(PREFERENCE_FILE_NAME);
            try {
                if (defaultPrefFileUrl != null) {
                    preferenceFilePath = new File(defaultPrefFileUrl.toURI());
                }

            } catch (final URISyntaxException e) {
                // NOP: this exception has not to occur
            }
        } else {
            preferenceFilePath = new File(preferenceEnvVarPath);
        }

        return preferenceFilePath;
    }

    private static final Properties loadPreferenceProperties(File preferenceFilePath) throws PreferenceFileException {
        Properties preferenceProperties = new Properties();

        try {
            InputStream is = new FileInputStream(preferenceFilePath);
            preferenceProperties.load(is);
        } catch(IOException ioe) {
            throw new PreferenceFileException(Constants.PreferenceFileMessages.UNREADABLE_PREFERENCE_FILE, ioe);
        }

        return preferenceProperties;
    }

    private static final EmbeddedHttpServerConfig initializePreferenceEmbeddedHttpServerParametersFromProperties(
            final Properties preferenceProperties, final List<String> errors) throws PreferenceFileException {
        assert preferenceProperties != null;
        assert errors != null;
        
        final String httpPortStr = preferenceProperties.getProperty(EMBEDDED_HTTP_PORT);
        if (httpPortStr != null && !httpPortStr.isEmpty()) {
            return new EmbeddedHttpServerConfig(Integer.parseInt(httpPortStr));
        } else {
            return new EmbeddedHttpServerConfig(DEFAULT_EMBEDDED_HTTP_PORT);
        }
    }
}
