/*
 * Copyright (C) 2011 Klaus Reimer <k@ailis.de>
 * See LICENSE.md for licensing information.
 */

package de.ailis.microblinks.l.lctrl.shell;

import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;

import java.util.Arrays;

import de.ailis.microblinks.l.lctrl.resources.Resources;

/**
 * Base class for all CLI programs.
 *
 * @author Klaus Reimer (k@ailis.de)
 */
public abstract class CLI
{
    /** The command-line program name. */
    private final String name;

    /** The short options. */
    private final String shortOpts;

    /** The long options. */
    private final LongOpt[] longOpts;

    /** Debug mode. */
    private boolean debug = false;

    /**
     * Constructor.
     *
     * @param name
     *            The command-line program name.
     * @param shortOpts
     *            The short options.
     * @param longOpts
     *            The long options.
     */
    protected CLI(final String name, final String shortOpts, final LongOpt[] longOpts)
    {
        this.name = name;
        this.shortOpts = shortOpts;
        this.longOpts = longOpts;
    }

    /**
     * Displays command-line help.
     */
    private void showHelp()
    {
        System.out.println(Resources.getText("help.txt"));
    }

    /**
     * Displays version information.
     */
    private void showVersion()
    {
        System.out.println(Resources.getText("version.txt"));
    }

    /**
     * Displays the help hint.
     */
    protected void showHelpHint()
    {
        System.out.println("Use --help to show usage information.");
    }

    /**
     * Prints error message to stderr and then exits with error code 1.
     *
     * @param message
     *            The error message.
     * @param args
     *            The error message arguments.
     */
    protected void error(final String message, final Object... args)
    {
        System.err.print(this.name);
        System.err.print(": ");
        System.err.format(message, args);
        System.err.println();
        showHelpHint();
        System.exit(1);
    }

    /**
     * Processes all command line options.
     *
     * @param args
     *            The command line arguments.
     * @throws Exception
     *             When error occurs.
     * @return The index of the first non option argument.
     */
    private int processOptions(final String[] args) throws Exception
    {
        final Getopt opts = new Getopt(this.name, args, this.shortOpts, this.longOpts);
        int opt;
        while ((opt = opts.getopt()) >= 0)
        {

            switch (opt)
            {
                case 'h':
                    showHelp();
                    System.exit(0);
                    break;

                case 'V':
                    showVersion();
                    System.exit(0);
                    break;

                case 'D':
                    this.debug = true;
                    break;

                case '?':
                    showHelpHint();
                    System.exit(111);
                    break;

                default:
                    processOption(opt, opts.getOptarg());
            }
        }
        return opts.getOptind();
    }

    /**
     * Processes a single option.
     *
     * @param option
     *            The option to process
     * @param arg
     *            The optional option argument
     * @throws Exception
     *             When an error occurred.
     */
    protected abstract void processOption(final int option, final String arg) throws Exception;

    /**
     * Executes the program with the specified arguments. This is called from the run() method after options has been
     * processed. The specified arguments array only contains the non-option arguments.
     *
     * @param args
     *            The non-option command-line arguments.
     * @throws Exception
     *             When something goes wrong.
     */
    protected abstract void execute(String[] args) throws Exception;

    /**
     * Runs the program.
     *
     * @param args
     *            The command line arguments.
     */
    public void run(final String[] args)
    {
        try
        {
            final int commandStart = processOptions(args);
            execute(Arrays.copyOfRange(args, commandStart, args.length));
        }
        catch (final Exception e)
        {
            if (this.debug)
            {
                e.printStackTrace(System.err);
            }
            error(e.getMessage());
            System.exit(1);
        }
    }

    /**
     * Checks if program runs in debug mode.
     *
     * @return True if debug mode, false if not.
     */
    public boolean isDebug()
    {
        return this.debug;
    }
}
