/**
 * 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.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jline.console.completer.Completer;
import jline.console.completer.StringsCompleter;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.ow2.petals.admin.api.ContainerAdministration;
import org.ow2.petals.admin.api.PetalsAdministrationFactory;
import org.ow2.petals.admin.api.exception.ContainerAdministrationException;
import org.ow2.petals.admin.api.exception.DuplicatedServiceException;
import org.ow2.petals.admin.api.exception.MissingServiceException;
import org.ow2.petals.cli.Constants;
import org.ow2.petals.cli.api.command.exception.CommandBadArgumentNumberException;
import org.ow2.petals.cli.api.command.exception.CommandException;
import org.ow2.petals.cli.api.command.exception.CommandInvalidArgumentException;
import org.ow2.petals.cli.api.command.exception.CommandInvalidException;
import org.ow2.petals.cli.api.connection.AuthenticatedConnectionParameters;
import org.ow2.petals.cli.api.connection.ConnectionParameters;
import org.ow2.petals.cli.api.exception.NoInteractiveShellException;
import org.ow2.petals.cli.api.exception.ShellException;
import org.ow2.petals.cli.api.shell.Shell;

public final class ConnectionHelper {

    /**
     * The short name of the option setting the alias of the Petals node to
     * connect
     */
    public static final String ALIAS_SHORT_OPTION = "a";
    private static final String ALIAS_LONG_OPTION = "alias";
    private static final String ALIAS_ARG_NAME = "alias";
    private static final Option ALIAS_OPTION = OptionBuilder.
            hasArgs(1).withArgName(ALIAS_ARG_NAME)
            .withLongOpt(ALIAS_LONG_OPTION)
            .withDescription("Connection alias in the preference file.")
            .create(ALIAS_SHORT_OPTION);
    
    /**
     * The option setting the host of the Petals node to connect
     */
    private static final String HOST_SHORT_OPTION = "h";
    private static final String HOST_LONG_OPTION = "host";
    private static final String HOST_ARG_NAME = "host";
    private static final Option HOST_OPTION = OptionBuilder.
            hasArgs(1).withArgName(HOST_ARG_NAME)
            .withLongOpt(HOST_LONG_OPTION)
            .withDescription("JMX host of the remote petals ESB.")
            .create(HOST_SHORT_OPTION);
            
    /**
     * The option setting the JMX port of the Petals node to connect
     */
    private static final String PORT_SHORT_OPTION = "n";
    private static final String PORT_LONG_OPTION = "port";
    private static final String PORT_ARG_NAME = "port";
    private static final Option PORT_OPTION = OptionBuilder.
            hasArgs(1).withArgName(PORT_ARG_NAME)
            .withLongOpt(PORT_LONG_OPTION)
            .withDescription("JMX port of the remote petals ESB.")
            .create(PORT_SHORT_OPTION);
    
    /**
     * The option setting the JMX user of the Petals node to connect
     */
    private static final String USER_SHORT_OPTION = "u";
    private static final String USER_LONG_OPTION = "user";
    private static final String USER_ARG_NAME = "user";
    private static final Option USER_OPTION = OptionBuilder.
            hasArgs(1).withArgName(USER_ARG_NAME)
            .withLongOpt(USER_LONG_OPTION)
            .withDescription("JMX user of the remote petals ESB.")
            .create(USER_SHORT_OPTION);
    
    /**
     * The option setting the JMX password of the Petals node to connect
     */
    private static final String PASSWORD_SHORT_OPTION = "p";
    private static final String PASSWORD_LONG_OPTION = "password";
    private static final String PASSWORD_ARG_NAME = "password";
    private static final Option PASSWORD_OPTION = OptionBuilder.
            hasArgs(1).withArgName(PASSWORD_ARG_NAME)
            .withLongOpt(PASSWORD_LONG_OPTION)
            .withDescription("JMX password of the remote petals ESB.")
            .create(PASSWORD_SHORT_OPTION);
    
    public final static String USERNAME_QUESTION = "Username: ";

    public final static String PASSWORD_QUESTION = "Password: ";

    public static final void addConnectionOptions(Options options) {
        options.addOption(ALIAS_OPTION);
        options.addOption(HOST_OPTION);
        options.addOption(PORT_OPTION);
        options.addOption(USER_OPTION);
        options.addOption(PASSWORD_OPTION);
    }

    public static final void addConnectionUsage(StringBuilder usage, boolean withConfirmationFlag) {
        usage.append("[-").append(HOST_SHORT_OPTION).append(" <host> -")
                .append(PORT_SHORT_OPTION).append(" <port> | -").append(HOST_SHORT_OPTION).append(" <host> -")
                .append(PORT_SHORT_OPTION).append(" <port> -").append(USER_SHORT_OPTION)
                .append(" <user> -").append(PASSWORD_SHORT_OPTION).append(" <password> | -")
                .append(ALIAS_SHORT_OPTION).append(" <alias>");
        if(withConfirmationFlag) {
            usage.append(" | -y]");
        } else {
            usage.append("]");
        }
    }

    public static final ConnectionParameters parseConnectionParameters(CommandLine cmd, boolean useDefaultConnection)
            throws CommandException, PreferenceFileException {
        final Map<String, ConnectionParameters> preferenceConnectionParameters = PreferenceFileManager
                .getPreferenceConnectionParameters();
        ConnectionParameters defaultConnectionParameters = PreferenceFileManager.getDefaultConnectionParameters();
        return parseConnectionParameters(cmd, useDefaultConnection, preferenceConnectionParameters
                , defaultConnectionParameters);
    }
    
    public static final Map<String, Completer> getConnectionOptionCompleters() throws PreferenceFileException {
        final Map<String, ConnectionParameters> preferenceConnectionParameters = PreferenceFileManager
                .getPreferenceConnectionParameters();
        Set<String> aliasSet = preferenceConnectionParameters.keySet();
        Map<String, Completer> optionCompleters = new HashMap<String, Completer>();
        optionCompleters.put(ALIAS_SHORT_OPTION, new StringsCompleter(aliasSet.toArray(new String[aliasSet.size()])));
        return optionCompleters;
    }

    static final ConnectionParameters parseConnectionParameters(CommandLine cmd,
            boolean useDefaultConnection,
            final Map<String, ConnectionParameters> preferenceConnectionParameters,
            ConnectionParameters defaultConnectionParameters) throws CommandException, PreferenceFileException {
        ConnectionParameters connectionParameters;
        
        if (cmd.hasOption(HOST_SHORT_OPTION) && cmd.hasOption(PORT_SHORT_OPTION)
                && cmd.hasOption(USER_SHORT_OPTION) && cmd.hasOption(PASSWORD_SHORT_OPTION)
                && !cmd.hasOption(ALIAS_SHORT_OPTION)) {
            String host = cmd.getOptionValue(HOST_SHORT_OPTION);
            String username = cmd.getOptionValue(USER_SHORT_OPTION);
            String password = cmd.getOptionValue(PASSWORD_SHORT_OPTION);
            try {
                int port = Integer.parseInt(cmd.getOptionValue(PORT_SHORT_OPTION));
                connectionParameters = new AuthenticatedConnectionParameters(host, port, username,
                        password, null);
            } catch (NumberFormatException nfe) {
                throw new CommandInvalidArgumentException(null, HOST_OPTION, host);
            }
        } else if (!cmd.hasOption(HOST_SHORT_OPTION) && !cmd.hasOption(PORT_SHORT_OPTION)
                && !cmd.hasOption(USER_SHORT_OPTION) && !cmd.hasOption(PASSWORD_SHORT_OPTION)                
                && cmd.hasOption(ALIAS_SHORT_OPTION)) {
                connectionParameters = preferenceConnectionParameters.get(cmd.getOptionValue(ALIAS_SHORT_OPTION));
                if(connectionParameters == null) {
                throw new CommandInvalidException(null, Constants.CommandMessages.UNEXISTING_ALIAS);
                }
        } else if (!cmd.hasOption(HOST_SHORT_OPTION) && !cmd.hasOption(PORT_SHORT_OPTION)
                && !cmd.hasOption(USER_SHORT_OPTION) && !cmd.hasOption(PASSWORD_SHORT_OPTION)
                && !cmd.hasOption(ALIAS_SHORT_OPTION)) {
                if(useDefaultConnection) {
                    connectionParameters = defaultConnectionParameters;
                    if(connectionParameters == null) {
                    throw new CommandException(null,
                            Constants.CommandMessages.NO_DEFAULT_CONNECTION);
                    }
                } else {
                    connectionParameters = null;
                }
        } else {
            throw new CommandBadArgumentNumberException(null);
        }
        
        return connectionParameters;
    }

    /**
     * 
     * @param connectionParameters
     * @param shell
     * @param isYesFlagEnabledAtCmdLevel
     *            <code>true</code> if the 'yes' flag is enabled at command
     *            level
     * @return
     * @throws ContainerAdministrationException
     * @throws IOException
     * @throws CommandException
     */
    public static final AuthenticatedConnectionParameters connect(
            final ConnectionParameters connectionParameters, final Shell shell,
            final boolean isYesFlagEnabledAtCmdLevel) throws ContainerAdministrationException,
            IOException, CommandException {
        final String host = connectionParameters.getHost();
        final int port = connectionParameters.getPort();
        AuthenticatedConnectionParameters authenticatedConnectionParameters;
        if (connectionParameters instanceof AuthenticatedConnectionParameters) {
            authenticatedConnectionParameters = (AuthenticatedConnectionParameters) connectionParameters;
            connect(host, port, authenticatedConnectionParameters.getUsername(),
                    authenticatedConnectionParameters.getPassword(), shell);
        } else if (!isYesFlagEnabledAtCmdLevel) {
            try {
                do {
                    final String username = shell.askQuestion(USERNAME_QUESTION, false);
                    final String password = shell.askQuestion(PASSWORD_QUESTION, true);
                    try {
                        connect(host, port, username, password, shell);
                        authenticatedConnectionParameters = new AuthenticatedConnectionParameters(
                                host, port, username,
                                password, null);
                        break;
                    } catch (ContainerAdministrationException cae) {
                        if (cae.getMessage().endsWith(
                                "Authentication failed! Invalid username or password")) {
                            if (!shell.confirms("Retry? (y/n) ")) {
                                authenticatedConnectionParameters = null;
                                break;
                            }
                        } else {
                            throw cae;
                        }
                    }
                } while (true);
            } catch (final NoInteractiveShellException e) {
                throw new CommandException(null,
                        Constants.CommandMessages.MISSING_USERNAME_AND_PASSWORD);
            } catch (final ShellException e) {
                throw new CommandException(null, e);
            }
        } else {
            // 'yes' flag enabled at command level
            throw new CommandException(null,
                    Constants.CommandMessages.MISSING_USERNAME_AND_PASSWORD);
        }

        return authenticatedConnectionParameters;
    }

    private static final void connect(final String host, final int port, final String username,
            final String password, final Shell shell) throws ContainerAdministrationException {

        try {
            final ContainerAdministration containerAdministration = PetalsAdministrationFactory
                    .newInstance().newContainerAdministration();
            containerAdministration.connect(host, port, username, password);
            shell.setPrompt(String.format("petals-cli@%s:%d>", host, port));
        } catch (final DuplicatedServiceException e) {
            throw new ContainerAdministrationException(e);
        } catch (final MissingServiceException e) {
            throw new ContainerAdministrationException(e);
        }
    }
}
