package com.minres.scviewer.e4.application.options;

/**
 * This class holds the information for a <i>set</i> of options. A set can hold any number of 
 * <code>OptionData</code> instances which are checked together to determine success or failure. 
 * <p>
 * The approach to use this class looks like this:
 * <p>
 * <ol>
 * <li> The user uses any of the <code>Options.addSet()</code> (e. g. {@link Options#addSet(String)}) to create 
 *      any number of sets required (or just relies on the default set, if only one set is required)
 * <li> The user adds all required option definitions to each set
 * <li> Using any of the <code>Options.check()</code> methods, each set can be checked whether the options
 *      that were specified on the command line satisfy its requirements
 * <li> If the check was successful for a given set, several data items are available from this class:
 *      <ul>
 *      <li> All options defined for the set (through with e. g. values, details, and multiplicity are available)
 *      <li> All data items found (these are the items on the command line which do not start with the prefix, 
 *           i. e. non-option arguments)
 *      <li> All unmatched arguments on the command line (these are the items on the command line which start 
 *           with the prefix, but do not match to one of the options).
 *           Programs can elect to ignore these, or react with an error
 *      </ul>
 * </ol>
 */

public class OptionSet {

  private final static String CLASS = "OptionSet";

  private java.util.ArrayList<OptionData>       options             = new java.util.ArrayList<OptionData>();
  private java.util.HashMap<String, OptionData> keys                = new java.util.HashMap<String, OptionData>();
  private java.util.ArrayList<String>           unmatched           = new java.util.ArrayList<String>();
  private java.util.ArrayList<String>           data                = new java.util.ArrayList<String>();
  private String                                setName             = null;
  private int                                   minData             = 0;
  private int                                   maxData             = 0;
  private Options.Prefix                        prefix              = null;
  private Options.Multiplicity                  defaultMultiplicity = null;

/**
 * Constructor
 */

  OptionSet(Options.Prefix prefix, Options.Multiplicity defaultMultiplicity, String setName, int minData, int maxData) {
    if (setName == null)   throw new IllegalArgumentException(CLASS + ": setName may not be null");
    if (minData < 0)       throw new IllegalArgumentException(CLASS + ": minData must be >= 0");
    if (maxData < minData) throw new IllegalArgumentException(CLASS + ": maxData must be >= minData");
    this.prefix              = prefix;
    this.defaultMultiplicity = defaultMultiplicity;
    this.setName             = setName;
    this.minData             = minData;
    this.maxData             = maxData;
  }

/**
 * Get a list of all the options defined for this set
 * <p>
 * @return A list of {@link OptionData} instances defined for this set
 */

  public java.util.ArrayList<OptionData> getOptionData() {
    return options;
  }

/**
 * Get the data for a specific option, identified by its key name (which is unique)
 * <p>
 * @param key The key for the option
 * <p>
 * @return The {@link OptionData} instance
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or unknown in this set
 */

  public OptionData getOption(String key) {
    if (key == null) throw new IllegalArgumentException(CLASS + ": key may not be null");
    if (!keys.containsKey(key)) throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
    return keys.get(key);
  }

/**
 * Check whether a specific option is set, i. e. whether it was specified at least once on the command line. 
 * <p>
 * @param key The key for the option
 * <p>
 * @return <code>true</code> or <code>false</code>, depending on the outcome of the check
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or unknown in this set
 */

  public boolean isSet(String key) {
    if (key == null) throw new IllegalArgumentException(CLASS + ": key may not be null");
    if (!keys.containsKey(key)) throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
    return keys.get(key).getResultCount() > 0 ? true : false;
  }

/**
 * Getter method for <code>setName</code> property
 * <p>
 * @return The value for the <code>setName</code> property
 */

  public String getSetName() {
    return setName;
  }

/**
 * Getter method for <code>minData</code> property
 * <p>
 * @return The value for the <code>minData</code> property
 */

  public int getMinData() {
    return minData;
  }

/**
 * Getter method for <code>maxData</code> property
 * <p>
 * @return The value for the <code>maxData</code> property
 */

  public int getMaxData() {
    return maxData;
  }

/**
 * Return the data items found (these are the items on the command line which do not start with the prefix, i. e. non-option arguments)
 * <p>
 * @return A list of strings with all data items found
 */

  public java.util.ArrayList<String> getData() {
    return data;
  }

/**
 * Return all unmatched items found (these are the items on the command line which start with the prefix, but do not
 * match to one of the options)
 * <p>
 * @return A list of strings with all unmatched items found
 */

  public java.util.ArrayList<String> getUnmatched() {
    return unmatched;
  }

/**
 * Add a non-value option with the given key, and the default prefix and multiplicity
 * <p>
 * @param key The key for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 */

  public OptionSet addOption(String key) {
    return addOption(key, defaultMultiplicity);
  }

/**
 * Add a non-value option with the given key and multiplicity, and the default prefix
 * <p>
 * @param key          The key for the option
 * @param multiplicity The multiplicity for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 *                                  or if <code>multiplicity</code> is <code>null</code>
 */

  public OptionSet addOption(String key, Options.Multiplicity multiplicity) {
    return addOption(key, false, Options.Separator.NONE, false, multiplicity);
  }

/**
 * Add a value option with the given key and separator, no details, and the default prefix and multiplicity
 * <p>
 * @param key       The key for the option
 * @param separator The separator for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 *                                  or if <code>separator</code> is <code>null</code>
 */

  public OptionSet addOption(String key, Options.Separator separator) {
    return addOption(key, false, separator, true, defaultMultiplicity);
  }

/**
 * Add a value option with the given key, separator, and multiplicity, no details, and the default prefix
 * <p>
 * @param key          The key for the option
 * @param separator    The separator for the option
 * @param multiplicity The multiplicity for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 *                                  or if <code>separator</code> or <code>multiplicity</code> are <code>null</code>
 */

  public OptionSet addOption(String key, Options.Separator separator, Options.Multiplicity multiplicity) {
    return addOption(key, false, separator, true, multiplicity);
  }

/**
 * 
 * Add a value option with the given key and separator, possibly details, and the default prefix and multiplicity
 * <p>
 * @param key       The key for the option
 * @param details   A boolean indicating whether details are expected for the option
 * @param separator The separator for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 *                                  or if <code>separator</code> is <code>null</code>
 */

  public OptionSet addOption(String key, boolean details, Options.Separator separator) {
    return addOption(key, details, separator, true, defaultMultiplicity);
  }

/**
 * Add a value option with the given key, separator, and multiplicity, possibly details, and the default prefix
 * <p>
 * @param key          The key for the option
 * @param details      A boolean indicating whether details are expected for the option
 * @param separator    The separator for the option
 * @param multiplicity The multiplicity for the option
 * <p>
 * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
 * <p>
 * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
 *                                  or if <code>separator</code> or <code>multiplicity</code> are <code>null</code>
 */

  public OptionSet addOption(String key, boolean details, Options.Separator separator, Options.Multiplicity multiplicity) {
    return addOption(key, details, separator, true, multiplicity);
  }

/**
 * The master method to add an option. Since there are combinations which are not 
 * acceptable (like a NONE separator and a true value), this method is not public.
 * Internally, we only supply acceptable combinations.
 */

  OptionSet addOption(String key, 
                      boolean details, 
                      Options.Separator separator, 
                      boolean value, 
                      Options.Multiplicity multiplicity) {

    if (key == null) throw new IllegalArgumentException(CLASS + ": key may not be null");
    if (multiplicity == null) throw new IllegalArgumentException(CLASS + ": multiplicity may not be null");
    if (separator == null) throw new IllegalArgumentException(CLASS + ": separator may not be null");
    if (keys.containsKey(key))  throw new IllegalArgumentException(CLASS + ": the key " 
        + key + " has already been defined for this OptionSet");

    OptionData od = new OptionData(prefix, key, details, separator, value, multiplicity);
    options.add(od);
    keys.put(key, od);

    return this;
    
  }

}


