/*
 * tkArgv.c --
 *
 *	This file contains a procedure that handles table-based
 *	argv-argc parsing.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) Id
 */

#include "tkPort.h"
#include "tk.h"

/*
 * Default table of argument descriptors.  These are normally available
 * in every application.
 */

static Tk_ArgvInfo defaultTable[] = {
    {"-help",	TK_ARGV_HELP,	(char *) NULL,	(char *) NULL,
	"Print summary of command-line options and abort"},
    {NULL,	TK_ARGV_END,	(char *) NULL,	(char *) NULL,
	(char *) NULL}
};

/*
 * Forward declarations for procedures defined in this file:
 */

static void	PrintUsage _ANSI_ARGS_((Tcl_Interp *interp,
		    Tk_ArgvInfo *argTable, int flags));

/*
 *----------------------------------------------------------------------
 *
 * Tk_ParseArgv --
 *
 *	Process an argv array according to a table of expected
 *	command-line options.  See the manual page for more details.
 *
 * Results:
 *	The return value is a standard Tcl return value.  If an
 *	error occurs then an error message is left in the interp's result.
 *	Under normal conditions, both *argcPtr and *argv are modified
 *	to return the arguments that couldn't be processed here (they
 *	didn't match the option table, or followed an TK_ARGV_REST
 *	argument).
 *
 * Side effects:
 *	Variables may be modified, resources may be entered for tkwin,
 *	or procedures may be called.  It all depends on the arguments
 *	and their entries in argTable.  See the user documentation
 *	for details.
 *
 *----------------------------------------------------------------------
 */

int
Tk_ParseArgv(interp, tkwin, argcPtr, argv, argTable, flags)
    Tcl_Interp *interp;		/* Place to store error message. */
    Tk_Window tkwin;		/* Window to use for setting Tk options.
				 * NULL means ignore Tk option specs. */
    int *argcPtr;		/* Number of arguments in argv.  Modified
				 * to hold # args left in argv at end. */
    CONST char **argv;		/* Array of arguments.  Modified to hold
				 * those that couldn't be processed here. */
    Tk_ArgvInfo *argTable;	/* Array of option descriptions */
    int flags;			/* Or'ed combination of various flag bits,
				 * such as TK_ARGV_NO_DEFAULTS. */
{
    register Tk_ArgvInfo *infoPtr;
				/* Pointer to the current entry in the
				 * table of argument descriptions. */
    Tk_ArgvInfo *matchPtr;	/* Descriptor that matches current argument. */
    CONST char *curArg;		/* Current argument */
    register char c;		/* Second character of current arg (used for
				 * quick check for matching;  use 2nd char.
				 * because first char. will almost always
				 * be '-'). */
    int srcIndex;		/* Location from which to read next argument
				 * from argv. */
    int dstIndex;		/* Index into argv to which next unused
				 * argument should be copied (never greater
				 * than srcIndex). */
    int argc;			/* # arguments in argv still to process. */
    size_t length;		/* Number of characters in current argument. */
    int i;

    if (flags & TK_ARGV_DONT_SKIP_FIRST_ARG) {
	srcIndex = dstIndex = 0;
	argc = *argcPtr;
    } else {
	srcIndex = dstIndex = 1;
	argc = *argcPtr-1;
    }

    while (argc > 0) {
	curArg = argv[srcIndex];
	srcIndex++;
	argc--;
	length = strlen(curArg);
	if (length > 0) {
	    c = curArg[1];
	} else {
	    c = 0;
	}

	/*
	 * Loop throught the argument descriptors searching for one with
	 * the matching key string.  If found, leave a pointer to it in
	 * matchPtr.
	 */

	matchPtr = NULL;
	for (i = 0; i < 2; i++) {
	    if (i == 0) {
		infoPtr = argTable;
	    } else {
		infoPtr = defaultTable;
	    }
	    for (; (infoPtr != NULL) && (infoPtr->type != TK_ARGV_END);
		    infoPtr++) {
		 if (infoPtr->key == NULL) {
		     continue;
		 }
		 if ((infoPtr->key[1] != c)
			 || (strncmp(infoPtr->key, curArg, length) != 0)) {
		     continue;
		 }
		 if ((tkwin == NULL)
			 && ((infoPtr->type == TK_ARGV_CONST_OPTION)
			 || (infoPtr->type == TK_ARGV_OPTION_VALUE)
			 || (infoPtr->type == TK_ARGV_OPTION_NAME_VALUE))) {
		     continue;
		 }
		 if (infoPtr->key[length] == 0) {
		     matchPtr = infoPtr;
		     goto gotMatch;
		 }
		 if (flags & TK_ARGV_NO_ABBREV) {
		     continue;
		 }
		 if (matchPtr != NULL) {
		     Tcl_AppendResult(interp, "ambiguous option \"", curArg,
			     "\"", (char *) NULL);
		     return TCL_ERROR;
		 }
		 matchPtr = infoPtr;
	    }
	}
	if (matchPtr == NULL) {

	    /*
	     * Unrecognized argument.  Just copy it down, unless the caller
	     * prefers an error to be registered.
	     */

	    if (flags & TK_ARGV_NO_LEFTOVERS) {
		Tcl_AppendResult(interp, "unrecognized argument \"",
			curArg, "\"", (char *) NULL);
		return TCL_ERROR;
	    }
	    argv[dstIndex] = curArg;
	    dstIndex++;
	    continue;
	}

	/*
	 * Take the appropriate action based on the option type
	 */

	gotMatch:
	infoPtr = matchPtr;
	switch (infoPtr->type) {
	    case TK_ARGV_CONSTANT:
		*((int *) infoPtr->dst) = (int) infoPtr->src;
		break;
	    case TK_ARGV_INT:
		if (argc == 0) {
		    goto missingArg;
		} else {
		    char *endPtr;

		    *((int *) infoPtr->dst) =
			    strtol(argv[srcIndex], &endPtr, 0);
		    if ((endPtr == argv[srcIndex]) || (*endPtr != 0)) {
			Tcl_AppendResult(interp, "expected integer argument ",
				"for \"", infoPtr->key, "\" but got \"",
				argv[srcIndex], "\"", (char *) NULL);
			return TCL_ERROR;
		    }
		    srcIndex++;
		    argc--;
		}
		break;
	    case TK_ARGV_STRING:
		if (argc == 0) {
		    goto missingArg;
		} else {
		    *((CONST char **)infoPtr->dst) = argv[srcIndex];
		    srcIndex++;
		    argc--;
		}
		break;
	    case TK_ARGV_UID:
		if (argc == 0) {
		    goto missingArg;
		} else {
		    *((Tk_Uid *)infoPtr->dst) = Tk_GetUid(argv[srcIndex]);
		    srcIndex++;
		    argc--;
		}
		break;
	    case TK_ARGV_REST:
		*((int *) infoPtr->dst) = dstIndex;
		goto argsDone;
	    case TK_ARGV_FLOAT:
		if (argc == 0) {
		    goto missingArg;
		} else {
		    char *endPtr;

		    *((double *) infoPtr->dst) =
			    strtod(argv[srcIndex], &endPtr);
		    if ((endPtr == argv[srcIndex]) || (*endPtr != 0)) {
			Tcl_AppendResult(interp, "expected floating-point ",
				"argument for \"", infoPtr->key,
				"\" but got \"", argv[srcIndex], "\"",
				(char *) NULL);
			return TCL_ERROR;
		    }
		    srcIndex++;
		    argc--;
		}
		break;
	    case TK_ARGV_FUNC: {
		typedef int (ArgvFunc) _ANSI_ARGS_ ((char *, char *,
			CONST char *));
		ArgvFunc *handlerProc;

		handlerProc = (ArgvFunc *) infoPtr->src;
		if ((*handlerProc)(infoPtr->dst, infoPtr->key,
			argv[srcIndex])) {
		    srcIndex += 1;
		    argc -= 1;
		}
		break;
	    }
	    case TK_ARGV_GENFUNC: {
		typedef int (ArgvGenFunc)_ANSI_ARGS_((char *, Tcl_Interp *, 
			char *, int, CONST char **));
		ArgvGenFunc *handlerProc;

		handlerProc = (ArgvGenFunc *) infoPtr->src;
		argc = (*handlerProc)(infoPtr->dst, interp, infoPtr->key,
			argc, argv+srcIndex);
		if (argc < 0) {
		    return TCL_ERROR;
		}
		break;
	    }
	    case TK_ARGV_HELP:
		PrintUsage (interp, argTable, flags);
		return TCL_ERROR;
	    case TK_ARGV_CONST_OPTION:
		Tk_AddOption(tkwin, infoPtr->dst, infoPtr->src,
			TK_INTERACTIVE_PRIO);
		break;
	    case TK_ARGV_OPTION_VALUE:
		if (argc < 1) {
		    goto missingArg;
		}
		Tk_AddOption(tkwin, infoPtr->dst, argv[srcIndex],
			TK_INTERACTIVE_PRIO);
		srcIndex++;
		argc--;
		break;
	    case TK_ARGV_OPTION_NAME_VALUE:
		if (argc < 2) {
		    Tcl_AppendResult(interp, "\"", curArg,
			    "\" option requires two following arguments",
			    (char *) NULL);
		    return TCL_ERROR;
		}
		Tk_AddOption(tkwin, argv[srcIndex], argv[srcIndex+1],
			TK_INTERACTIVE_PRIO);
		srcIndex += 2;
		argc -= 2;
		break;
	    default: {
		char buf[64 + TCL_INTEGER_SPACE];
		
		sprintf(buf, "bad argument type %d in Tk_ArgvInfo",
			infoPtr->type);
		Tcl_SetResult(interp, buf, TCL_VOLATILE);
		return TCL_ERROR;
	    }
	}
    }

    /*
     * If we broke out of the loop because of an OPT_REST argument,
     * copy the remaining arguments down.
     */

    argsDone:
    while (argc) {
	argv[dstIndex] = argv[srcIndex];
	srcIndex++;
	dstIndex++;
	argc--;
    }
    argv[dstIndex] = (char *) NULL;
    *argcPtr = dstIndex;
    return TCL_OK;

    missingArg:
    Tcl_AppendResult(interp, "\"", curArg,
	    "\" option requires an additional argument", (char *) NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintUsage --
 *
 *	Generate a help string describing command-line options.
 *
 * Results:
 *	The interp's result will be modified to hold a help string
 *	describing all the options in argTable, plus all those
 *	in the default table unless TK_ARGV_NO_DEFAULTS is
 *	specified in flags.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PrintUsage(interp, argTable, flags)
    Tcl_Interp *interp;		/* Place information in this interp's
				 * result area. */
    Tk_ArgvInfo *argTable;	/* Array of command-specific argument
				 * descriptions. */
    int flags;			/* If the TK_ARGV_NO_DEFAULTS bit is set
				 * in this word, then don't generate
				 * information for default options. */
{
    register Tk_ArgvInfo *infoPtr;
    int width, i, numSpaces;
#define NUM_SPACES 20
    static char spaces[] = "                    ";
    char tmp[TCL_DOUBLE_SPACE];

    /*
     * First, compute the width of the widest option key, so that we
     * can make everything line up.
     */

    width = 4;
    for (i = 0; i < 2; i++) {
	for (infoPtr = i ? defaultTable : argTable;
		infoPtr->type != TK_ARGV_END; infoPtr++) {
	    int length;
	    if (infoPtr->key == NULL) {
		continue;
	    }
	    length = strlen(infoPtr->key);
	    if (length > width) {
		width = length;
	    }
	}
    }

    Tcl_AppendResult(interp, "Command-specific options:", (char *) NULL);
    for (i = 0; ; i++) {
	for (infoPtr = i ? defaultTable : argTable;
		infoPtr->type != TK_ARGV_END; infoPtr++) {
	    if ((infoPtr->type == TK_ARGV_HELP) && (infoPtr->key == NULL)) {
		Tcl_AppendResult(interp, "\n", infoPtr->help, (char *) NULL);
		continue;
	    }
	    Tcl_AppendResult(interp, "\n ", infoPtr->key, ":", (char *) NULL);
	    numSpaces = width + 1 - strlen(infoPtr->key);
	    while (numSpaces > 0) {
		if (numSpaces >= NUM_SPACES) {
		    Tcl_AppendResult(interp, spaces, (char *) NULL);
		} else {
		    Tcl_AppendResult(interp, spaces+NUM_SPACES-numSpaces,
			    (char *) NULL);
		}
		numSpaces -= NUM_SPACES;
	    }
	    Tcl_AppendResult(interp, infoPtr->help, (char *) NULL);
	    switch (infoPtr->type) {
		case TK_ARGV_INT: {
		    sprintf(tmp, "%d", *((int *) infoPtr->dst));
		    Tcl_AppendResult(interp, "\n\t\tDefault value: ",
			    tmp, (char *) NULL);
		    break;
		}
		case TK_ARGV_FLOAT: {
		    sprintf(tmp, "%g", *((double *) infoPtr->dst));
		    Tcl_AppendResult(interp, "\n\t\tDefault value: ",
			    tmp, (char *) NULL);
		    break;
		}
		case TK_ARGV_STRING: {
		    char *string;

		    string = *((char **) infoPtr->dst);
		    if (string != NULL) {
			Tcl_AppendResult(interp, "\n\t\tDefault value: \"",
				string, "\"", (char *) NULL);
		    }
		    break;
		}
		default: {
		    break;
		}
	    }
	}

	if ((flags & TK_ARGV_NO_DEFAULTS) || (i > 0)) {
	    break;
	}
	Tcl_AppendResult(interp, "\nGeneric options for all commands:",
		(char *) NULL);
    }
}
