// ==========================================================================
// $Id: Option.java,v 1.2 2002/08/30 20:48:19 hitoshi Exp $
// Option parser
// ==========================================================================
// Copyright (C) 1999 MAEDA Atusi
// Copyright (C) 2002 Yamauchi Hitoshi
//
// This software can be redistributed only under GNU copyleft.
//
// creator MAEDA Atusi Á°ÅÄ ÆØ»Ê
// ==========================================================================
// $Revision: 1.2 $
// $Date: 2002/08/30 20:48:19 $
// $Author: hitoshi $
// ==========================================================================
package de.mpg.mpisb.timerclock;

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.Iterator;
import java.util.Properties;
import java.util.ListIterator;
import java.util.ArrayList;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import java.applet.Applet;
import java.io.OutputStream;
import java.io.PrintStream;

/**
 * Option parser
 */
public final class Option 
extends HashMap 
{
  public final static Object
  NO_ARG = "no arg",
  REQUIRED_ARG = "required arg";
  public interface ArgType {
    Object parse(String arg) throws IllegalArgumentException;
    String getDescription();
  }
  public final static ArgType
  INT = new ArgType() {
    public Object parse(String arg) throws IllegalArgumentException {
      return Integer.decode(arg);
    }
    public String getDescription() {
      return "int";
    }
  },
  DOUBLE = new ArgType() {
    public Object parse(String arg) throws IllegalArgumentException {
      return new Double(arg);
    }
    public String getDescription() {
      return "double";
    }
  },
  STRING = new ArgType() {
    public Object parse(String arg) throws IllegalArgumentException {
      return arg;
    }
    public String getDescription() {
      return "string";
    }
  },
  BOOLEAN = new ArgType() {
    public Object parse(String arg) throws IllegalArgumentException {
      if (arg.equalsIgnoreCase("true") ||
	  arg.equalsIgnoreCase("yes") ||
	  arg.equalsIgnoreCase("on") ||
	  arg.equalsIgnoreCase("1")) {
	return Boolean.TRUE;
      } else if (arg.equalsIgnoreCase("false") ||
	  arg.equalsIgnoreCase("no") ||
	  arg.equalsIgnoreCase("off") ||
	  arg.equalsIgnoreCase("0")) {
	return Boolean.FALSE;
      } else {
	throw new IllegalArgumentException("bad boolean value : " + arg);
      }
    }
    public String getDescription() {
      return "boolean";
    }
  };

  String name;
  String shortName;
  Object argDefault;
  ArgType argType;
  Object defaultValue;
  String description;
  String argDescription;
  public Option(String name, String shortName, Object argDefault,
		ArgType argType, Object defaultValue, String description,
		String argDescription) {
    this.name = name;
    this.shortName = shortName;
    this.argDefault = argDefault;
    this.argType = argType;
    this.defaultValue = defaultValue;
    this.description = description;
    this.argDescription = argDescription;
  }
  public Option(String name, String shortName, Object argDefault,
		ArgType argType, Object defaultValue, String description) {
    this(name, shortName, argDefault, argType, defaultValue, description, null);
  }
  public Option(String name, String shortName, Object argDefault,
		ArgType argType, Object defaultValue) {
    this(name, shortName, argDefault, argType, defaultValue, null);
  }
  public Option(String name, String shortName, Object argDefault,
		ArgType argType) {
    this(name, shortName, argDefault, argType, null);
  }
  public Option(String name, String shortName, String description) {
    this(name, shortName, NO_ARG, BOOLEAN, null, description);
  }
  public Option(String name, String shortName) {
    this(name, shortName, NO_ARG, BOOLEAN);
  }

  /**
   * return the option name (long)
   */
  public String getName()
  {
    return(this.name);
  }
  /**
   * return the option name (short)
   */
  public String getShortName()
  {
    return(this.shortName);
  }
  /**
   * return the option's argument default (Option.NO_ARG is no argument is required.)
   */
  public Object getArgDefault()
  {
    return(this.argDefault);
  }
  /**
   * return the argument type : INT, DOUBLE, STRING, BOOLEAN
   */
  public ArgType getArgType()
  {
    return(this.argType);
  }
  /**
   * return the 
   */
  public Object getDefaultValue()
  {
    return(this.defaultValue);
  }
  /**
   * return the description
   */
  public String getDescription()
  {
    return(this.description);
  }
  /**
   * return the argument description
   */
  public String getArgDescription()
  {
    return(this.argDescription);
  }

  private static void unknownOptionError(String optionName)
  throws IllegalArgumentException {
    throw new IllegalArgumentException("Unknown option : " + optionName);
  }
  private static void argumentRequiredError(String optionName)
  throws IllegalArgumentException {
    throw new IllegalArgumentException(optionName + " option requires argument");
  }
  private static void put(Map map, Option opt, Object value) {
    if (opt.name != null) {
      map.put(opt.name, value);
    }
    if (opt.shortName != null) {
      map.put(opt.shortName, value);
    }
  }
  private static void parseLongOption(Map map, Map longMap, ListIterator i,
				      String name, String valueStr)
  throws IllegalArgumentException {
    Option d = (Option)longMap.get(name);
    if (d == null) {
      unknownOptionError(name);
    }
    if (valueStr != null) {
      if (d.argDefault == NO_ARG) {
	throw new IllegalArgumentException(name + " option does not take argument");
      }
      put(map, d, d.argType.parse(valueStr));
    } else {
      if (d.argDefault == REQUIRED_ARG) {
	if (! i.hasNext()) {
	  argumentRequiredError(name);
	}
	put(map, d, d.argType.parse((String)i.next()));
      } else if (d.argDefault == NO_ARG) {
	put(map, d, Boolean.TRUE);
      } else {
	put(map, d, d.argDefault);
      }
    }
  }
  private static void parseShortOption(Map map, Map shortMap, ListIterator i,
				       String arg)
  throws IllegalArgumentException {
    for (int j = 0; j < arg.length(); j++) {
      String c = arg.substring(j, j + 1);
      Option d = (Option)shortMap.get(c);
      if (d == null) {
	unknownOptionError(c);
      }
      String valueStr;
      if (d.argDefault == NO_ARG) {
	put(map, d, Boolean.TRUE);
      } else {
	Object value;
	if (++j < arg.length()) {
	  // The rest of the string is considered to be an arg to this option.
	  put(map, d, d.argType.parse(arg.substring(j)));
	  return;
	} else if (d.argDefault == REQUIRED_ARG) {
	  // Arg to this option is next item.
	  if (!i.hasNext()) {
	    argumentRequiredError(c);
	  }
	  put(map, d, d.argType.parse((String)i.next()));
	  return;
	} else {
	  put(map, d, d.argDefault);
	}
      }
    }
  }
  public static int putCommandLine(Option[] desc, String[] args, Map map)
  throws IllegalArgumentException {
    HashMap longMap = new HashMap(desc.length * 2);
    HashMap shortMap = new HashMap(desc.length * 2);
    for (int i = 0; i < desc.length; i++) {
      if (desc[i].name != null) {
	longMap.put(desc[i].name, desc[i]);
      }
      if (desc[i].shortName != null) {
	shortMap.put(desc[i].shortName, desc[i]);
      }
      if (desc[i].defaultValue != null) {
	put(map, desc[i], desc[i].defaultValue);
      }
    }
    ArrayList argv = new ArrayList(args.length);
    for (int i = 0; i < args.length; i++) {
      argv.add(args[i]);
    }
    ListIterator i = argv.listIterator();
    while (i.hasNext()) {
      String arg = (String)i.next();
      if (arg.equals("--")) {
	// End of option marker.
	break;
      } else if (arg.startsWith("--")) {
	// Long option.
	int eqSignPos = arg.indexOf('=');
	String name, valueStr;
	if (eqSignPos >= 0) {
	  parseLongOption(map, longMap, i, arg.substring(2, eqSignPos),
			  arg.substring(eqSignPos + 1));
	} else {
	  parseLongOption(map, longMap, i, arg.substring(2), null);
	}
      } else if (arg.startsWith("-") && arg.length() > 1) {
	// Short option.
	parseShortOption(map, shortMap, i, arg.substring(1));
      } else {
	// Non-option.
	return i.nextIndex() - 1;
      }
    }
    return i.nextIndex();
  }
  public static class CommandLine {
    private Map options;
    private int nonOptionIndex;
    CommandLine(Map options, int nonOptionIndex) {
      this.options = options;
      this.nonOptionIndex = nonOptionIndex;
    }
    public Map getOptions() {
      return options;
    }
    public int getNonOptionIndex() {
      return nonOptionIndex;
    }
  }
  public static CommandLine parseCommandLine(Option[] desc, String[] args)
  throws IllegalArgumentException {
    Map options = new HashMap();
    int nonOptionIndex = putCommandLine(desc, args, options);
    return new CommandLine(options, nonOptionIndex);
  }
  public static void putParameters(Option[] desc, Applet applet, Map map)
  throws IllegalArgumentException {
    for (int i = 0; i < desc.length; i++) {
      if (desc[i].defaultValue != null) {
	put(map, desc[i], desc[i].defaultValue);
      }
      if (desc[i].name != null) {
	String valueStr = applet.getParameter(desc[i].name);
	if (valueStr != null) {
	  put(map, desc[i], desc[i].argType.parse(valueStr));
	}
      }
    }
  }
  public static Map parseParameters(Option[] desc, Applet applet)
  throws IllegalArgumentException {
    Map map = new HashMap();
    putParameters(desc, applet, map);
    return map;
  }
  public static void putProperties(Option[] desc, Properties prop, Map map)
  throws IllegalArgumentException {
    for (int i = 0; i < desc.length; i++) {
      if (desc[i].defaultValue != null) {
	put(map, desc[i], desc[i].defaultValue);
      }
      if (desc[i].name != null) {
	String valueStr = prop.getProperty(desc[i].name);
	if (valueStr != null) {
	  put(map, desc[i], desc[i].argType.parse(valueStr));
	}
      }
    }
  }
  public static Map parseProperties(Option[] desc, Properties prop)
  throws IllegalArgumentException {
    Map map = new HashMap();
    putProperties(desc, prop, map);
    return map;
  }
  private static String getDescription(Option d) {
    return d.argDescription != null ?
	  d.argDescription : d.argType.getDescription();
  }

  /** 
   * get help string
   */
  public static String getHelpString(String header, Option[] desc) 
  {
    String s = header +"\n";
    s += ("Options:" + "\n");
    for(int i = 0; i < desc.length; i++) 
    {
      if(desc[i].shortName != null) 
      {
	s += ("-" + desc[i].shortName + " ");
      }
      else 
      {
	s += "   ";
      }
      if(desc[i].name != null) 
      {
	s += ("--" + desc[i].name);
	if(desc[i].argDefault == REQUIRED_ARG) 
	{
	  s += ("=" + getDescription(desc[i]) + "\n");
	} 
	else if(desc[i].argDefault != NO_ARG) 
	{
	  s += ("[=" + getDescription(desc[i]) + "]\n");
	} 
	else 
	{
	  s += "\n";
	}
      }
      if(desc[i].description != null) 
      {
	s += "        ";
	s += (desc[i].description + "\n");
      }
    }
    return(s);
  }

  /**
   * print out help
   */
  public static void printHelp(OutputStream out, String header, Option[] desc) 
  {
    PrintStream o = new PrintStream(out);
    o.println(getHelpString(header, desc));
    o.flush();
  }

  /**
   * print out help with resource bundle 
   */
  public static void printHelp(OutputStream out, String header, Option[] desc,
			       ResourceBundle bundle)
    throws MissingResourceException 
  {
    PrintStream o = new PrintStream(out);
    o.println(bundle.getString(header));
    o.println(bundle.getString("options"));
    for (int i = 0; i < desc.length; i++) {
      if (desc[i].shortName != null) {
	o.print("-" + desc[i].shortName + " ");
      } else {
	o.print("   ");
      }
      if (desc[i].name != null) {
	o.print("--" + desc[i].name);
	if (desc[i].argDefault == REQUIRED_ARG) {
	  o.println("=" + getDescription(desc[i]));
	} else if (desc[i].argDefault != NO_ARG) {
	  o.println("[=" + getDescription(desc[i]) + "]");
	} else {
	  o.println();
	}
      }
      if (desc[i].description != null) {
	o.print("        ");
	o.println(bundle.getString(desc[i].description));
      }
    }
    o.flush();
  }
}
