/*
 * Copyright 2016 MiLaboratory.com
 *
 * 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.milaboratory.cli;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

public class JCommanderBasedMain implements ActionHelper {
    // LinkedHashMap to preserve order of actions
    protected final Map<String, Action> actions = new LinkedHashMap<>();
    protected final String command;
    protected boolean printHelpOnError = false;
    protected boolean printStackTrace = false;
    protected Runnable versionInfoCallback = null;
    protected PrintStream outputStream = System.err;
    protected String[] arguments;

    public JCommanderBasedMain(String command, Action... actions) {
        this.command = command;
        for (Action action : actions)
            reg(action);
    }

    public void setOutputStream(PrintStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public PrintStream getDefaultPrintStream() {
        return outputStream;
    }

    @Override
    public String getCommandLineArguments() {
        StringBuilder builder = new StringBuilder();
        for (String arg : arguments) {
            if (builder.length() != 0)
                builder.append(" ");
            builder.append(arg);
        }
        return builder.toString();
    }

    public boolean isPrintStackTrace() {
        return printStackTrace;
    }

    public void setPrintStackTrace(boolean printStackTrace) {
        this.printStackTrace = printStackTrace;
    }

    protected void reg(Action a) {
        actions.put(a.command(), a);
    }

    public void main(String... args) throws Exception {
        // Saving current arguments
        this.arguments = args;

        if (args.length == 0) {
            printGlobalHelp();
            return;
        }

        // Setting up JCommander
        MainParameters mainParameters = getMainParameters();
        JCommander commander = new JCommander(mainParameters);
        commander.setProgramName(command);
        for (Action a : actions.values())
            commander.addCommand(a.command(), a.params());

        // Getting command name
        String commandName = args[0];

        // Getting corresponding action
        Action action = actions.get(commandName);

        try {
            if (action != null && (action instanceof ActionParametersParser)) {
                ((ActionParametersParser) action).parseParameters(Arrays.copyOfRange(args, 1, args.length));
            } else {
                commander.parse(args);

                // Print Version information if requested and exit.
                if (mainParameters instanceof MainParametersWithVersion &&
                        ((MainParametersWithVersion) mainParameters).version()) {
                    versionInfoCallback.run();
                    return;
                }

                // Print complete help if requested
                if (mainParameters.help()) {
                    // Creating new instance of jCommander to add only non-hidden actions
                    JCommander tmpCommander = new JCommander(mainParameters);
                    tmpCommander.setProgramName(command);
                    for (Action a : actions.values())
                        if (!a.getClass().isAnnotationPresent(HiddenAction.class))
                            tmpCommander.addCommand(a.command(), a.params());
                    StringBuilder builder = new StringBuilder();
                    tmpCommander.usage(builder);
                    outputStream.print(builder);
                    return;
                }

                // Getting parsed command
                // assert parsedCommand.equals(commandName)
                final String parsedCommand = commander.getParsedCommand();

                // Processing potential errors
                if (parsedCommand == null || !actions.containsKey(parsedCommand)) {
                    if (parsedCommand == null)
                        outputStream.println("No command specified.");
                    else
                        outputStream.println("Command " + parsedCommand + " not supported.");
                    outputStream.println("Use -h option to get a list of supported commands.");
                    return;
                }

                action = actions.get(parsedCommand);
            }

            if (action.params().help()) {
                printActionHelp(commander, action);
            } else {
                action.params().validate();
                action.go(this);
            }
        } catch (ParameterException pe) {
            printException(pe, commander, action);
        }
    }

    private MainParameters getMainParameters() {
        return versionInfoCallback != null ?
                new MainParametersWithVersion() :
                new MainParameters();
    }

    protected void printGlobalHelp() {
        // Creating new instance of jCommander to add only non-hidden actions
        JCommander tmpCommander = new JCommander(getMainParameters());
        tmpCommander.setProgramName(command);
        for (Action a : actions.values())
            if (!a.getClass().isAnnotationPresent(HiddenAction.class))
                tmpCommander.addCommand(a.command(), a.params());
        StringBuilder builder = new StringBuilder();
        tmpCommander.usage(builder);
        outputStream.print(builder);
    }

    protected void printActionHelp(JCommander commander, Action action) {
        StringBuilder builder = new StringBuilder();
        if (action instanceof ActionHelpProvider)
            ((ActionHelpProvider) action).printHelp(builder);
        else
            commander.usage(action.command(), builder);
        outputStream.print(builder);
    }

    protected void printException(ParameterException e,
                                  JCommander commander, Action action) {
        outputStream.println("Error: " + e.getMessage());
        if (printStackTrace)
            e.printStackTrace(new PrintStream(outputStream));
        if (printHelpOnError)
            printActionHelp(commander, action);
    }

    /**
     * Enables -v / --version parameter.
     *
     * Sets callback that will be invoked if this option is specified by user.
     *
     * {@literal null} disables -v parameter.
     *
     * @param versionInfoCallback callback to be will be invoked if user specified -v option. {@literal null} disables
     *                            -v parameter.
     */
    public void setVersionInfoCallback(Runnable versionInfoCallback) {
        this.versionInfoCallback = versionInfoCallback;
    }

    public static class MainParameters {
        @Parameter(names = {"-h", "--help"}, help = true, description = "Displays this help message.")
        public Boolean help;

        public boolean help() {
            return help != null && help;
        }
    }

    public static class MainParametersWithVersion extends MainParameters {
        @Parameter(names = {"-v", "--version"}, help = true, description = "Output version information.")
        public Boolean version;

        public boolean version() {
            return version != null && version;
        }
    }
}

@@ -3633,27 +3633,172 @@
 eters.help()
-) %7B
+ %7C%7C args.length == 1) %7B%0A                    if(args.length == 1)%0A                        System.out.println(%22Error: missing required arguments.%5Cn%22);
 %0A           

fix anoying stacktrace when no arguments specified in cli