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

import static org.ow2.petals.cli.shell.ShellFactory.DEBUG_LONG_OPTION;
import static org.ow2.petals.cli.shell.ShellFactory.DEBUG_SHORT_OPTION;
import static org.ow2.petals.cli.shell.ShellFactory.YESFLAG_LONG_OPTION;
import static org.ow2.petals.cli.shell.ShellFactory.YESFLAG_SHORT_OPTION;

import java.io.InputStream;
import java.util.logging.LogManager;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.ow2.petals.cli.api.command.exception.CommandInvalidException;
import org.ow2.petals.cli.api.connection.ConnectionParameters;
import org.ow2.petals.cli.api.exception.DuplicatedCommandException;
import org.ow2.petals.cli.connection.ConnectionHelper;
import org.ow2.petals.cli.shell.AbstractShell;
import org.ow2.petals.cli.shell.ShellFactory;
import org.ow2.petals.cli.shell.exception.ShellCreationException;

/**
 * Start the petals Command Line Interface.
 * @author Sebastien Andre - EBM WebSourcing
 */
public class Main {

    /**
     * Header part of the usage message
     */
    public final static String USAGE_HEADER =
    		"Petals JMX Command Line Interface"
    		+ System.getProperty( "line.separator" );

    /**
     * Usage part of the usage message
     */
    private final static String USAGE_USAGE;

    /**
     * Footer part of the usage message
     */
    public final static String USAGE_FOOTER =
    		"\nWhich evolution would you like on Petals? Share it! http://www.petalslink.com/feedback"
    		+ System.getProperty( "line.separator" );

    /**
     * The short name of the option printing help on command line
     */
    private final static String HELP_SHORT_OPTION = "H";

    /**
     * The long name of the option printing help on command line
     */
    private final static String HELP_LONG_OPTION = "help";

    /**
     * The short name of the option printing the version of Petals CLI
     */
    private final static String VERSION_SHORT_OPTION = "V";

    /**
     * The long name of the option printing the version of Petals CLI
     */
    private final static String VERSION_LONG_OPTION = "version";

    static {
        final StringBuilder usage = new StringBuilder().append("[-").append(DEBUG_SHORT_OPTION)
                .append("] ");
        usage.append("[-").append(YESFLAG_SHORT_OPTION).append("] ");
        ConnectionHelper.addConnectionUsage(usage, false);
        usage.append("[-").append(HELP_SHORT_OPTION).append(" | -").append(VERSION_SHORT_OPTION)
                .append(" | ").append(ShellFactory.getUsage()).toString();
        USAGE_USAGE = usage.toString();
    }

    public static Options createOptions() {
        final Options options = new Options();

        options.addOption(DEBUG_SHORT_OPTION, DEBUG_LONG_OPTION, false,
                "Print stack trace and debugging informations");

        options.addOption(YESFLAG_SHORT_OPTION, YESFLAG_LONG_OPTION, false,
                "Enable automatic confirmation ('yes' flag)");

        ConnectionHelper.addConnectionOptions(options);

        final OptionGroup optionsGrp = new OptionGroup();
        optionsGrp.addOption(OptionBuilder.withLongOpt(HELP_LONG_OPTION).hasArg(false)
                .withDescription("Print this help message and exit.").create(HELP_SHORT_OPTION));
        optionsGrp
                .addOption(OptionBuilder.withLongOpt(VERSION_LONG_OPTION).hasArg(false)
                        .withDescription("Print the version number and exit.")
                        .create(VERSION_SHORT_OPTION));

        ShellFactory.addShellOptions(optionsGrp);
        options.addOptionGroup(optionsGrp);

        return options;
    }

    /**
     * Print version informations
     */
    private static void printVersion() {
        StringBuffer sb = new StringBuffer();

        sb.append(USAGE_HEADER).append(' ');
        sb.append(Main.class.getPackage().getImplementationVersion()).append(System.getProperty( "line.separator" ));

        sb.append(System.getProperty("java.runtime.name")).append(' ');
        sb.append(System.getProperty("java.runtime.version")).append(System.getProperty( "line.separator" ));

        sb.append(System.getProperty("os.name")).append(' ');
        sb.append(System.getProperty("os.version")).append(System.getProperty( "line.separator" ));

        System.out.print(sb.toString());
    }

    /**
     * Print the usage message
     */
    private static void printUsage() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.setWidth(USAGE_FOOTER.length());
        formatter.printHelp(USAGE_HEADER, USAGE_USAGE, Main.createOptions(), USAGE_FOOTER);
    }

    /**
     * Main entry point to run Petals CLI
     */
    public static void main(String[] args) {
        final Main main = new Main();
        main.initLogging();
        System.exit(main.run(args));
    }

    /**
     * By default, the logging system is initialized from the JAR-embedded
     * configuration file '<code>petals-cli-logging.properties</code>'. At
     * runtime, it is possible to change this configuration using the system
     * property '<code>java.util.logging.config.file</code>'.
     */
    public final void initLogging() {
        try {
            final String configurationPath = System.getProperty("java.util.logging.config.file");
            if (configurationPath == null) {
                final InputStream is = this.getClass().getResourceAsStream(
                        "/petals-cli-logging.properties");
                if (is == null) {
                    LogManager.getLogManager().readConfiguration();
                } else {
                    LogManager.getLogManager().readConfiguration(is);
                }
            } else {
                LogManager.getLogManager().readConfiguration();
            }
        } catch (final Exception e) {
            System.err.println("Error initializing logging system");
            e.printStackTrace();
        }
    }

    /**
     * Parse options and command line arguments
     *
     * @param args
     *            Arguments from the command line
     * @return system return code
     */
    public int run(String[] args) {
        int exitCode = 0;

        final CommandLineParser parser = new PosixParser();
        try {
            // We can't use a static attribute for options because an error will
            // occurs in unit tests: an instance of options can be parsed once.
            final CommandLine cmd = parser.parse(Main.createOptions(), args);

            try {

                if (cmd.hasOption(VERSION_SHORT_OPTION)) {
                    printVersion();

                } else if (cmd.hasOption(HELP_SHORT_OPTION)) {
                    printUsage();

                } else {
                    final AbstractShell shell = ShellFactory.getInstance().newShell(cmd, args);
                    if (shell != null) {

                        // We add a shutdown hook to disconnect Petals CLI if
                        // needed
                        final Runtime runtime = Runtime.getRuntime();
                        runtime.addShutdownHook(new Thread("Disconnection hook") {
                            public void run() {
                                shell.disconnectIfNeeded();
                            }
                        });

                        try {
                            final ConnectionParameters connectionParameters = ConnectionHelper
                                    .parseConnectionParameters(cmd, true);
                            shell.setConnectionParameters(connectionParameters);

                            shell.run();
                            exitCode = shell.getExitStatus();

                        } catch (final CommandInvalidException e) {
                        	String msg = e.getMessage() != null ? e.getMessage() : "An unexpected error occurred. Use the -" + DEBUG_SHORT_OPTION + " option for more information.";
                            System.err.println("ERROR: " + msg + "\n");
                            printUsage();
                            exitCode = 1;

                        } catch (final Exception e) {
                        	String msg = e.getMessage() != null ? e.getMessage() : "An unexpected error occurred. Use the -" + DEBUG_SHORT_OPTION + " option for more information.";
                            System.err.println("ERROR: " + msg + "\n");
                            if (cmd.hasOption(DEBUG_SHORT_OPTION)) {
                                e.printStackTrace();
                            }
                            exitCode = 1;
                        }

                        // Disconnect if needed to prevent resource consumption
                        // if the command 'disconnect' was not used
                        shell.disconnectIfNeeded();

                    } else {
                        System.err
                                .println("ERROR: Unable to determine the execution mode: console, command, file or inlined\n");
                        printUsage();
                        exitCode = 1;
                    }
                }
            } catch (final ShellCreationException e) {
                System.err.println("ERROR creating the shell: " + e.getMessage());
                e.printStackTrace();
                exitCode = 1;
            } catch (final DuplicatedCommandException e) {
                // A command has been registered twice. This has not to occur
                // otherwise it's a bug
                System.err.println("BUG : ****  Command registered twice: " + e.getCommandName()
                        + " ****");
                exitCode = 1;
            }
        } catch (final ParseException e) {
            System.err.println("ERROR: " + e.getMessage() + "\n");
            printUsage();
            exitCode = 1;
        }

        return exitCode;
    }
}
