// Copyright 2014, Kevin Ko <kevin@faveset.com>

package com.faveset.flags;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Example usage:
 *   public class Foo {
 *     private BoolFlag mFooFlag = Flags.registerBool("foo", true, "enable foo");
 *
 *     public static void main(String[] args) {
 *       Flags.parse();
 *       if (mFooFlag.get()) {
 *         // ...
 *       }
 *     }
 *   }
 */
public class Flags {
    // The singleton flags object.
    private static Flags sFlags;

    private ArrayList<String> mNonFlagArgs;

    // Keyed by flag name.
    private Map<String, FlagParser> mFlagParsers;

    private Flags() {
        mNonFlagArgs = new ArrayList<String>();
        mFlagParsers = new HashMap<String, FlagParser>();
    }

    /**
     * Returns the singleton Flags object.
     */
    public static Flags get() {
        if (sFlags == null) {
            sFlags = new Flags();
        }
        return sFlags;
    }

    /**
     * @return the non-flag argument of given index.
     */
    public String getArg(int index) {
        return mNonFlagArgs.get(index);
    }

    /**
     * @return the number of non-flag arguments.
     */
    public int getArgSize() {
        return mNonFlagArgs.size();
    }

    /**
     * @return an array holding all non-flag arguments.
     */
    public String[] getArgs() {
        return mNonFlagArgs.toArray(new String[0]);
    }

    /**
     * @return the leading flag prefix ("-" or "--") or the empty String
     * if none is found.
     */
    private static String getFlagPrefix(String s) {
        if (s.startsWith("--")) {
            return "--";
        }
        if (s.startsWith("-")) {
            return "-";
        }
        return "";
    }

    /**
     * Call this method to parse the flags in args.
     *
     * @throws IllegalArgumentException if an unknown flag is encountered.
     */
    public static void parse(String[] args) throws IllegalArgumentException {
        get().parseImpl(args);
    }

    /**
     * Call this method to parse the flags in args.
     *
     * @throws IllegalArgumentException if an unknown flag is encountered.
     */
    private void parseImpl(String[] args) throws IllegalArgumentException {
        final int len = args.length;
        for (int ii = 0; ii < len; ii++) {
            String arg = args[ii];
            String flagPrefix = getFlagPrefix(arg);
            if (flagPrefix.isEmpty()) {
                mNonFlagArgs.add(arg);
                continue;
            }

            // Determine the flag name.
            String flagName;
            String flagValue;

            int equalsIndex = arg.indexOf("=");
            if (equalsIndex == -1) {
                flagName = arg.substring(flagPrefix.length());
                flagValue = new String();
            } else {
                flagName = arg.substring(flagPrefix.length(), equalsIndex);
                flagValue = arg.substring(equalsIndex + 1);
            }

            FlagParser parser = mFlagParsers.get(flagName);
            if (parser == null) {
                throw new IllegalArgumentException(String.format("unknown flag %s", flagName));
            }

            // Now, finalize the value if it is not embedded within the
            // flag argument.  Non-singular flags can use the following
            // argument as the value.
            if (equalsIndex == -1 && !parser.isSingular()) {
                // Check the next argument for the value.
                int nextInd = ii + 1;
                if (nextInd < len) {
                    String nextArg = args[nextInd];
                    if (getFlagPrefix(nextArg).isEmpty()) {
                        // We found the value.
                        flagValue = nextArg;
                    }
                }
            }

            parser.parse(flagValue);
        }
    }

    private void registerFlag(String name, FlagParser flag) {
        mFlagParsers.put(name, flag);
    }

    public static BoolFlag registerBool(String name, boolean defValue, String desc) {
        BoolFlag flag = new BoolFlag(defValue, desc);
        get().registerFlag(name, flag);
        return flag;
    }

    public static StringFlag registerString(String name, String defValue, String desc) {
        StringFlag flag = new StringFlag(defValue, desc);
        get().registerFlag(name, flag);
        return flag;
    }

    /**
     * Writes a help message for the configured flags using builder.
     */
    public void writeHelp(StringBuilder builder) {
        Set<String> keys = mFlagParsers.keySet();
        List<String> keyList = new ArrayList<String>(keys);
        java.util.Collections.sort(keyList);

        boolean isFirst = true;
        for (String key : keyList) {
            if (isFirst) {
                isFirst = false;
            } else {
                builder.append('\n');
            }

            FlagParser p = mFlagParsers.get(key);
            builder.append(String.format("  -%s=%s: %s",
                        key, p.getDefaultValueString(), p.getDesc()));
        }
    }
}

@@ -70,16 +70,45 @@
 flags;%0A%0A
+import java.io.PrintStream;%0A%0A
 import j
@@ -225,16 +225,153 @@
 t;%0A%0A/**%0A
+ * A %22-help%22 flag will always be installed.  It will print help for the current flag configuration%0A * and then perform a System.exit.%0A *%0A
  * Examp
@@ -727,24 +727,75 @@
 gs sFlags;%0A%0A
+    private static String sHelpFlagName = %22help%22;%0A%0A
     private 
@@ -3345,32 +3345,242 @@
 rser == null) %7B%0A
+                // See if this is a help flag.%0A                if (flagName.equals(sHelpFlagName)) %7B%0A                    writeHelp(System.out);%0A%0A                    System.exit(0);%0A                %7D else %7B%0A    
                 
@@ -3655,24 +3655,42 @@
 flagName));%0A
+                %7D%0A
             
@@ -4903,32 +4903,206 @@
 rn flag;%0A    %7D%0A%0A
+    public void writeHelp(PrintStream out) %7B%0A        StringBuilder builder = new StringBuilder();%0A        writeHelp(builder);%0A        out.println(builder.toString());%0A    %7D%0A%0A
     /**%0A     * W

Add default -help flag processing.