/*
 *  FILE
 *	clop.c
 *
 *  DESCRIPTION
 *	Command Line Option Parser
 *
 *  AUTHOR
 *	Anders 'ALi' Lindgren
 *	Mälarblick 8
 *	S-161 51 Bromma
 *	Sweden
 *
 *  TODO
 *	float (Conditional compilation)
 *	New infirmation passing system. (Rename clop_list & clop_array).
 *	move getargs to its own file.
 *	En global Escape-flagga?
 *	Bättre ALiLib-interfacing. (Flytta Clop.h, stdlib.h etc.)
 *	En clop-error-meddelande rutin. Den skall returnera en pekare till
 *	 en (statisk) sträng som innehåller ett clop-felmeddelande. Rutinen
 *	 skall vara fristående så att man slipper strängarna om man inte
 *	 vill ha dom.
 */

#include <proto/exec.h>

#include "stdlib.h"
#include "string.h"

#include "clop.h"

#ifndef NULL
#define NULL 0L
#endif


/*
 *  DEFINITION
 *	ESCAPE
 *
 *  DESCRIPTION
 *	Special character escape.
 *	Example: "allan\"hej" -> allan"hej
 */

#define ESCAPE '\\'


/*
 *  DEFINITION
 *	isdigit
 *
 *  DESCRIPTION
 *	Check if it's a digit.
 *	Why don't use the one in ctype.h? It's to long for just one test.
 */

#define isdigit(x) ( ((x) >= '0') && ((x) <= '9') )


/*
 *  FORWARD REFERENCES
 */

extern int _catol(unsigned char *, long, long *);
extern int _getargs(unsigned char *, unsigned char * *);


/*
 *  FUNCTION
 *	clop()
 *
 *  DESCRIPTION
 *	Command Line Option Parser
 *
 *	This is the frontend of this module. It accepts a clop_array
 *	prefilled with options, and a clop_list with nothing but
 *	error information at the moment. And of course, it accepts
 *	a zero-terminated line with options.
 *
 *  RETURNS
 *	A pointer to the first argument which isn't an option, or
 *	a NULL if an error occured. If there is only options on
 *	the line, a pointer to the terminating NIL is returned.
 */

unsigned char * __stdargs
clop(unsigned char * line, struct clop_list * clop_list,
			   struct clop_array * clop_array)
{
    register int i;
    register unsigned char ch;
    unsigned char * start_of_args;
    int k,j,m;

    i = 0;
    start_of_args = line;

    while (1) {

	do {					/* Skip blanks.		*/
	    ch = line[i++];
	} while (ch && (ch == ' '));

	start_of_args = & line[i-1];

	if (ch) {				/* If not end of line	*/

	    if (ch == '-') {			/* Option?		*/
		if (ch = line[i++]) {

		    k = -1;
		    for (j=0 ; clop_array[j].type != CLOPT_END ; j++) {
			if (clop_array[j].option == ch) {
			    k=j;
			}
		    }

		    if (k != -1) {		/* Existing option	*/

				/*
				 * Check if an option which only can be
				 * present once is present several times.
				 */
			if ( (clop_array[k].inflags & CLOPIF_SINGLE) &&
			     (clop_array[k].outflags & CLOPOF_PRESENT) ) {
			    clop_list->error = CLOPE_MULTIPLE_SINGLE;
			    clop_list->failing_opt = ch;
			    return(NULL);
			}

			clop_array[k].outflags |= CLOPOF_PRESENT;

			switch(clop_array[k].type) {
			case CLOPT_NUMBER:
			case CLOPT_UNUMBER:
				/*
				 * Note that the same code is used for both
				 * signed and unsigned number. This can be
				 * done since value is an union, and "number"
				 * and "unumber" actiually has the same
				 * storage area.
				 */
			    if ( (m = _catol(& line[i], clop_array[k].type,
			    			&clop_array[k].value.number)
						) != -1 ) {
				i += m;
			    } else {
				clop_list->error = CLOPE_ILLEGAL_NUMBER;
				clop_list->failing_opt = ch;
				return(NULL);
			    }
			    break;

			case CLOPT_STRING:

				/*
				 * If this option was supplied several
				 * times, the last one rules (and the
				 * previous strings are released.)
				 * Why? Well, you could supply a env:
				 * default option line, and then the user
				 * could replace that value with his own.
				 */
			    if ( clop_array[k].value.string ) {
			    	if ( clop_array[k].outflags & CLOPOF_ALLOCATED ) {
				    free( clop_array[k].value.string );
				    clop_array[k].outflags &= ~CLOPOF_ALLOCATED;
				}
				clop_array[k].value.string = NULL;
			    }

			    if ( (m = _getargs(& line[i], & clop_array[k].
						value.string) ) != -1 ) {
				i += m;
				clop_array[k].outflags |= CLOPOF_ALLOCATED;
			    } else {
				clop_list->error = CLOPE_ILLEGAL_STRING;
				clop_list->failing_opt = ch;
				return(NULL);
			    }
			    break;

			case CLOPT_BOOL:
			    switch(line[i]) {
			    case ' ':
			    case '\0':
				clop_array[k].value.bool =
					! clop_array[k].value.bool;
				break;

			    case '0':
			    case '1':
				clop_array[k].value.bool = line[i++]-'0';

				if ((line[i] != ' ') && (line[i] != '\0')) {
				    clop_list->error = CLOPE_ILLEGAL_BOOL;
				    clop_list->failing_opt = ch;
				    return(NULL);
				}
				break;

			    default:
				clop_list->error = CLOPE_ILLEGAL_BOOL;
				clop_list->failing_opt = ch;
				return(NULL);
			    } /* Bool switch */

			    break;

			default:
			    clop_list->error = CLOPE_UNKNOWN_TYPE;
			    clop_list->failing_opt = ch;
			} /* switch */

		    }
		    else {
				/* option not found */
			clop_list->error = CLOPE_UNKNOWN_OPTION;
			clop_list->failing_opt = ch;
			return(NULL);
		    }

		} /* The line was ended by a "-" */
		else {
		    return(start_of_args);
		}

	    } /* No more options. */
	    else {
		return(start_of_args);
	    }

	} /* End of the line. Return a pointer to the last '\0' */
	else {
	    return(start_of_args);
	}
    } /* while (1) */
}


/*
 *  FUNCTION
 *	_catol
 *
 *  RETURNS
 *	The length of the string processed.
 *
 *  DESCRIPTION
 *	Convert an ascii string to a long, or a unsigned long.
 *
 *	"type" is set to the current type (NUMBER or UNUMBER).
 *
 *	"value" is declared as a long, but it can also work as
 *	a unsiged long.
 *
 *  RETURNS
 *	The number of characters processed, or -1 if an error occured.
 *
 *  NOTE
 *	No overflow chech is made, but I don't really think it's necesary.
 */

static int
_catol(unsigned char * string, long type, long * value)
{
    long number = 0;
    int i = 0;
    unsigned char ch;
    int negative = 0;

	/* Check minus sign */
    if ( (string[0] == '-') && (type == CLOPT_NUMBER) ) {
	negative = 1;
	i=1;
    }

    ch = string[i++];
    if ( isdigit(ch) ) {
	
	do {
	    number = ((number << 3) + (number << 1)) + ch - '0';
	    ch = string[i++];
	} while ( isdigit(ch) );

	if ( (ch == '\0') || (ch == ' ') ) {

	    if (negative) {
		number = -number;
	    }

	    (* value) = number;
	    return(i-1);
	}
    }
    return(-1);	/* Error */
}


/*
 *  FUNCTION
 *	_getargs
 *
 *  DESCRIPTION
 *	Get Argument String
 *	This function gets an argument string. It can handle quotes etc.
 *
 *  RETURNS
 *	the number of characters parsed, or -1 if an error occured.
 */

int
_getargs(unsigned char * str, unsigned char * * destptr)
{
    unsigned char tmpbuf[256];

    unsigned char ch;
    long quoted = 0;
    int  s = 0;	/* source index */
    int  d = 0;	/* dest index */
    int  quit = 0;

    while ( str[s] == ' ') {	/* Skip initial blanks	*/
	s++;
    }

    if (str[s] == '"') {	/* String quoted?	*/
	quoted = 1;
	s++;
    }

    while(! quit) {
	switch (ch = str[s++]) {
	case '\0':
	    quit = 1;
	    break;

	case ' ':
	    if (quoted) {
		tmpbuf[d++] = ' ';
	    }
	    else {
		quit = 1;
	    }
	    break;

	case '"':
	    if (quoted) {
		quit = 1;
	    }
	    else {
		tmpbuf[d++] = '"';
	    }
	    break;

	case ESCAPE:
	    if ( (ch = str[s++]) == '\0' ) {
		tmpbuf[d++] = ESCAPE;
		quit = 1;
	    }
	    /* Fall through */

	default:
	    tmpbuf[d++] = ch;
	}
    } /* while */

    if ((* destptr) = (unsigned char *)malloc(d+1)) {
	memcpy((void *)* destptr, (void *)tmpbuf, d);
	(* destptr)[d] = '\0';
	return(s);	/* Return the parsed length */
    }

    return(-1);
}
