/* File: Parameters.java
 * Contains: Methods for command-line arguments and Applet parameters
 * Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Created: February 1998
 * Updated: Sun Dec  2 03:58:04 2001 by Jeff Dalton
 * Copyright: (c) 1998, AIAI, University of Edinburgh
 */

package ix.util;

import java.util.*;
import java.io.*;
import ix.util.Debug;
import ix.util.Seq;

/**
 * The Parameters class contains methods that allow information from
 * application command-line arguments and from Applet parameters to
 * be used in a uniform way.  The class cannot be instantiated.
 *
 * @see java.util.Properties
 * @see java.lang.System
 */

public abstract class Parameters {

    private static AccessRecordingProperties parameters
        = new AccessRecordingProperties();

    public static void setParameter(String pname, String value) {
	if (Debug.on)
	    Debug.noteln("Setting parameter \"" + pname + "\" = " +
			 "\"" + value + "\"");
	parameters.put(pname, value);
    }

    public static Properties getParameters() {
	return parameters;
    }

    public static String getParameter(String pname) {
	return parameters.getProperty(pname);
    }

    public static String getParameter(String pname, String defaultValue) {
	return parameters.getProperty(pname, defaultValue);
    }

    public static boolean haveParameter(String pname) {
	return parameters.getProperty(pname) != null;
    }

    public static int getInt(String pname) {
	String value = parameters.getProperty(pname);
	if (value == null)
	    throw bogusParameter("int", pname, "null");
	else {
	    try {
		// N.B. decode(value).intValue() rather than parseInt(value).
		// This allows 0x and # to indicate hex and 0 octal.
		return Integer.decode(value).intValue();
	    }
	    catch (NumberFormatException e) {
		Debug.noteException(e);
		throw bogusParameter("int", pname, value);
	    }
	}
    }

    public static int getInt(String pname, int defaultValue) {
	if (haveParameter(pname))
	    return getInt(pname);
	else
	    return defaultValue;
    }

    public static int getInt(String pname, int radix, int defaultValue) {
	String value = parameters.getProperty(pname);
	if (value == null)
	    return defaultValue;
	else {
	    try {
		return Integer.parseInt(value, radix);
	    }
	    catch (NumberFormatException e) {
		Debug.noteException(e);
		throw bogusParameter("int", pname, value);
	    }
	}
    }

    public static boolean getBoolean(String pname) {
	return getBoolean(pname, false);
    }

    public static boolean getBoolean(String pname, boolean defaultValue) {
	String value = parameters.getProperty(pname);
	if (value == null)
	    return defaultValue;
	else if (value.equals("")) 	// -pname without =value
	    return true;
	else if (value.equals("true"))
	    return true;
	else if (value.equals("false"))
	    return false;
	else
	    throw bogusParameter("boolean", pname, value);
    }

    private static RuntimeException bogusParameter(
                     String type, String pname, String value) {
	return new RuntimeException("Parameter \"" + pname + "\" " +
				    "has a non-" + type + " value: " +
				    value);
    }

    public static void checkParameterUse() {
	if (!allParametersWereUsed()) {
	    Vector unused = new Vector();
	    for (Enumeration keys = parameters.propertyNames();
		 keys.hasMoreElements(); ) {
		String name = (String)keys.nextElement();
		if (!parameters.accessedProperty(name))
		    unused.addElement(name);
	    }
	    Debug.assert(!unused.isEmpty());
	    Debug.noteln("");
	    Debug.noteln
		("Warning: The following parameters have not yet been used:");
	    Debug.noteElements(unused.elements(), "   ");
	    Debug.noteln("");
	}
    }

    public static boolean allParametersWereUsed() {
	for (Enumeration keys = parameters.propertyNames();
	     keys.hasMoreElements(); ) {
	    if (!parameters.accessedProperty((String)keys.nextElement()))
		return false;
	}
	return true;
    }

    /* *
     * * Methods for command-line arguments
     * */

    /**
     * Parse a String[] of command-line arguments.  The syntax of an
     * argument is -name=value.  The value assigned to a name can the
     * be obtained as a String by calling getParameter("name").  Other
     * get-methods can return values of other types.<p>
     *
     * If no value is given, the value is the empty string "".
     * For getBoolean(name), this is equivalent to "true".<p>
     *
     * The syntax -not name, or -no name, is equivalent to -name=false.<p>
     *
     * The syntax -load filename can be used to read parameter values
     * from a file.  The file should contain lines in name=value syntax.
     */
    public static void processCommandLineArguments(String[] argv) {
	// boolean errors = false;
	Enumeration args = Seq.elements(argv);
	while (args.hasMoreElements()) {
	    String arg = (String)args.nextElement();
	    String[] part = Util.breakStringAtFirst(arg, "=");
	    String pname = part[0], value = part[1];
	    if (!pname.startsWith("-"))
		complain(pname, "does not start with \"-\"");
	    else
		pname = pname.substring(1);
	    if (pname.startsWith("n") &&
		(pname.equals("not") || pname.equals("no"))) {
		// Handle -not pname and -no pname which set to "false"
		if (!value.equals(""))
		    complain(arg, "has " + pname + " with =");
		else if (args.hasMoreElements()) {
		    pname = (String)args.nextElement();
		    setParameter(pname, "false");
		}
		else
		    complain(pname, "was not followed by a parameter name");
	    }
	    else if (pname.equals("load")) {
		// handle -load filename
		if (!value.equals(""))
		    complain(arg, "has " + pname + " with =");
		else if (args.hasMoreElements())
		    loadParameters((String)args.nextElement());
		else
		    complain(pname, "was not followed by a file name");
	    }
	    else 
		// An ordinary pname=value parameter
		setParameter(pname, value);
	}

    }

    private static void complain(String pname, String message) {
	Debug.warn("Parameter \"" + pname + "\" " + message);
    }

    public static void loadParameters(String filename) {
	Properties props = new Properties();
	try {
	    props.load(new FileInputStream(filename));
	}
	catch (IOException e) {
	    Debug.noteException(e);
	    return;
	}
	for (Enumeration keys = props.propertyNames();
	     keys.hasMoreElements(); ) {
	    String pname = (String)keys.nextElement();
	    setParameter(pname, (String)props.get(pname));
	}
    }


    /* *
     * * Methods for Applet parameters
     * */

    // /\/: Not written yet


    static class AccessRecordingProperties extends Properties {

	Hashtable accessTable = new Hashtable();

	public AccessRecordingProperties() {
	    super();
	}

	public String getProperty(String key) {
	    accessTable.put(key, Boolean.TRUE);
	    return super.getProperty(key);
	}

	public String getProperty(String key, String defaultValue) {
	    accessTable.put(key, Boolean.TRUE);
	    return super.getProperty(key, defaultValue);
	}

	public boolean accessedProperty(String key) {
	    return accessTable.get(key) != null;
	}

    }

}
