package org.broadinstitute.barclay.argparser;

import org.apache.commons.lang3.tuple.Pair;

import java.io.PrintStream;
import java.util.List;

/**
 * Interface implemented by Barclay command line argument parsers.
 */
public interface CommandLineParser {

    /**
     * Parse command-line arguments in an object passed to the implementing class ctor.
     *
     * @param messageStream Where to write error messages.
     * @param args          Command line tokens.
     * @return true if command line is valid and the program should run, false if help or version was requested
     * @throws CommandLineException if there is an invalid command line
     */
    boolean parseArguments(final PrintStream messageStream, final String[] args);

    /**
     * The commandline used to run this program, including any default args that
     * weren't necessarily specified. This is used for logging and debugging.
     * <p/>
     * NOTE: {@link #parseArguments(PrintStream, String[])} must be called before
     * calling this method.
     *
     * @return The commandline, or null if {@link #parseArguments(PrintStream, String[])}
     * hasn't yet been called, or didn't complete successfully.
     */
    String getCommandLine();

    /**
     * A typical command line program will call this to get the beginning of the usage message,
     * and then append a description of the program, like this:
     *
     * commandLineParser.getStandardUsagePreamble(getClass()) + "Frobnicates the freebozzle."
     */
    String getStandardUsagePreamble(final Class<?> mainClass);

    String getVersion();

    /**
     * Return the plugin instance corresponding to the targetDescriptor class
     */
    default <T> T getPluginDescriptor(Class<T> targetDescriptor) {
        // Throw unless overridden - the legacy command line parser doesn't implement plugins
        throw new CommandLineException.CommandLineParserInternalException(
                "Command line plugins are not implemented by this command line parser"
        );
    }

    /**
     * Print a usage message based on the arguments object passed to the ctor.

     * @param printCommon True if common args should be included in the usage message.
     * @param printHidden True if hidden args should be included in the usage message.
     * @return Usage string generated by the command line parser.
     */
    String usage(final boolean printCommon, final boolean printHidden);

    /**
     * Interface for @Argument annotated enums that have user documentation.
     */
    interface ClpEnum {
        String getHelpDoc();
    }

    /**
     * Locates and returns the VALUES of all argument fields of a specified type in a given object,
     * pairing each field value with its corresponding ArgumentDefinition object.
     *
     * Must be called AFTER argument parsing and value injection into argumentSource is complete (otherwise there
     * will be no values to gather!).
     *
     * Locates named and positional argument fields of the target type, subtypes of the target type, and Collections of
     * the target type or one of its subtypes. Unpacks Collection fields, returning a separate Pair for each
     * value in each Collection.
     *
     * Searches argumentSource itself, as well as ancestor classes, and also recurses into any ArgumentCollections
     * found.
     *
     * Will return Pairs containing a null second element for fields having no value, including empty Collection fields
     * (these represent arguments of the target type that were not specified on the command line and so never initialized).
     *
     * @param type Target type. Search for Argument-annotated fields that are either of this type, subtypes of this type, or Collections of this type or one of its subtypes.
     * @param <T> Type parameter representing the type to search for and return
     * @return A List of Pairs containing all argument field values found of the target type. First element in each Pair
     *         is the ArgumentDefinition object itself, and the second element is the actual value of the argument field. The second
     *         element will be null for uninitialized fields.
     */
     <T> List<Pair<ArgumentDefinition, T>> gatherArgumentValuesOfType( final Class<T> type );

}
