/*
 * get_args.c
 *
 *	command line argument parser
 */
#include <stdio.h>
#include <errno.h>
extern int	errno;
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include "datatypes.h"

/*
#define	min(A,B)	A<B? A : B
#define	max(A,B)	A>B? A : B
*/

void
get_args(argc, argv, Symbols, Usage, Function)
int	argc;
char	**argv;
ARGDEF	*Symbols[];
char	*Usage[], *Function[];
{
	register int	i;
	bool	helped;
	bool	Help(), ParseHelp();

	/* set default values */
	SetDefaults(Symbols);

	helped = FALSE;
	for (i=1; i<argc; i++)
		if (argv[i][0] == '?' || argv[i][0] == '-') {
			Help(Usage, Function, Symbols, "All");
			helped = TRUE;
		} else if (strncmp(argv[i], "Help",4)==0 || strncmp(argv[i],"help",4)==0) {
			ParseHelp(Usage, Function, Symbols, argv[i]);
			helped = TRUE;
		} else if (ParseArg(Symbols, argv[i])) {
			continue;
		} else {
			errno = EINVAL;
			perror(argv[i]);
			goto error_exit;
		}
	if (helped == TRUE)
		exit(0);
	else
		return;

error_exit:
	PrintUsage(stderr, Usage, Function, Symbols);
	exit(errno);
}


void
SetDefaults(Symbols)
ARGDEF	*Symbols[];
{
	register int	i;

	for (i=0; Symbols[i]; i++)
		if (SetValue(
			Symbols[i]->ad_value,
			Symbols[i]->ad_default,
			Symbols[i]->ad_defaultType
		) != TRUE) {
			/* error */
			fprintf(stderr,"Internal error: (SetDefaults) Unable to set ");
			PrintArgDef(stderr,Symbols[i]);
			errno = EINVAL;
			exit(errno);
		}
	return;
}


bool
SetValue(Val, ValStr, ValType)
VALUE	*Val;
char	*ValStr;
char	*ValType;
{
	register int	WidType;

	WidType = strlen(ValType);
	if (strncmp(ValType, "string", WidType) == 0) {
		return(SetString(Val, ValStr));
	} else if (strncmp(ValType, "dbl", WidType) == 0) {
		return(SetDbl(Val, ValStr));
	} else if (strncmp(ValType, "float", WidType) == 0) {
		return(SetFloat(Val, ValStr));
	} else if (strncmp(ValType, "int", WidType) == 0) {
		return(SetInt(Val, ValStr));
	} else if (strncmp(ValType, "boolean", WidType) == 0) {
		return(SetBool(Val, ValStr));
	} else if (strncmp(ValType, "byte", WidType) == 0) {
		return(SetByte(Val, ValStr));
	} else if (strncmp(ValType, "interval", WidType) == 0) {
		return(SetInterval(Val, ValStr));
	} else if (strncmp(ValType, "set", min(WidType,3)) == 0) {
		return(SetSet(Val, ValStr, ValType, WidType));
	} else if (strncmp(ValType, "rect", WidType) == 0) {
		return(SetRect(Val, ValStr));
	} else {
		return(FALSE);
	}
}


bool
SetString(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	Val->val_type = string;
	Val->val_u.udt_string = StrDup(ValStr);
	return(TRUE);
}


bool
SetDbl(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	double	D;

	if (sscanf(ValStr, "%lf", &D) == 1) {
		Val->val_type = dbl;
		Val->val_u.udt_dbl = D;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetFloat(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	float	F;

	if (sscanf(ValStr, "%f", &F) == 1) {
		Val->val_type = flt;
		Val->val_u.udt_flt = F;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetInt(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	int	I;

	if (sscanf(ValStr, "%d", &I) == 1) {
		Val->val_type = integer;
		Val->val_u.udt_int = I;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetBool(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	register int i, n;
	char	s[32];

	n = min(strlen(ValStr),sizeof(s)-1);
	for (i=0; i<n; i++)
		s[i] = isupper(ValStr[i])? tolower(ValStr[i]) : ValStr[i];
	s[i] = (char)NULL;
	n = i - 1;

	if (
		strncmp(s, "true", n) == 0 ||
		strncmp(s, "on", n) == 0 ||
		strncmp(s, "yes", n) == 0 ||
		strncmp(s, "1", n) == 0)
	{
		Val->val_type = boolean;
		Val->val_u.udt_bool = TRUE;
		return(TRUE);
	} else if (
		strncmp(s, "false", n) == 0 ||
		strncmp(s, "off", n) == 0 ||
		strncmp(s, "no", n) == 0 ||
		strncmp(s, "0", n) == 0)
	{
		Val->val_type = boolean;
		Val->val_u.udt_bool = FALSE;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetByte(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	char	C;
	Val->val_type = byte;
	if (sscanf(ValStr, "%c", &C) == 1) {
		Val->val_u.udt_byte = (char)C;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetInterval(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	double	lo, hi;
	Val->val_type = interval;
	if (sscanf(ValStr, "%lf,%lf", &lo,&hi) == 2) {
		Val->val_u.udt_interval.int_lo = lo;
		Val->val_u.udt_interval.int_hi = hi;
		return(TRUE);
	} else {
		return(FALSE);
	}
}


bool
SetSet(Val, ValStr, ValType, WidType)
VALUE	*Val;
char	*ValStr;
char	*ValType;
int	WidType;
{
	int	i;
	SET	*S;
	char	*LeftBrace, *RightBrace;
	char	*Start;
	char	*sp;
	bool	Escape;
	int	WidStr;
	char	s[128];
	char	*SkipBlanks(char *);

	Val->val_type = set;
	S = &Val->val_u.udt_set;
	S->list_n = 0;
	S->list_head = (LATOM *)NULL;
	S->list_tail = (LATOM *)NULL;

	if ((LeftBrace = strchr(ValStr, '{')) == (char *)NULL) {
		/*error*/
		fprintf(stderr,"(SetSet) Invalid set specification:  \"%s\" (missing left brace).\n", ValStr);
		exit(0);
	}
	Start = LeftBrace+1;
	if ((RightBrace = strchr(ValStr, '}')) == (char *)NULL) {
		/*error*/
		fprintf(stderr,"(SetSet) Invalid set specification: \"%s\" (missing right brace).\n", ValStr);
		exit(0);
	}
	WidStr = RightBrace - Start;

	for (sp=Start; *sp && *sp != '}'; ) {
/*		 sp = SkipBlanks(sp); */
		for ( Escape=FALSE,i=0; *sp && (Escape==TRUE || (Escape==FALSE && *sp != ',' && *sp != '}')); sp++) {
			if (Escape == TRUE) {
				s[i++] = *sp;
				Escape = !Escape;
			} else if (*sp == '\\' && Escape == FALSE) {
				Escape = !Escape;
			} else {
				assert(*sp != '\\' && Escape == FALSE);
				s[i++] = *sp;
			}
		}
		s[i] = (char)NULL;
		Append(S, (generic *)StrDup(s));
		if (*sp == ',')
			++sp;
	}

	return(TRUE);
}


bool
SetRect(Val, ValStr)
VALUE	*Val;
char	*ValStr;
{
	register char	*sp;
	register int	i;
	FLOAT	*R;
	char	*LeftBrace, *RightBrace;
	char	*Start;
	char	s[128];
	char	*SkipBlanks(char *);
	double	atof();

	Val->val_type = rect;
	R = Val->val_u.udt_rect.r_diag;

	if ((LeftBrace = strchr(ValStr, '{')) == (char *)NULL) {
		/*error*/
		fprintf(stderr,"(SetRect) Invalid rect specification:  \"%s\" (missing left brace).\n", ValStr);
		exit(0);
	}
	Start = LeftBrace+1;
	if ((RightBrace = strchr(ValStr, '}')) == (char *)NULL) {
		/*error*/
		fprintf(stderr,"(SetRect) Invalid rect specification: \"%s\" (missing right brace).\n", ValStr);
		exit(0);
	}

	for (sp=Start; *sp && *sp != '}'; ) {
		sp = SkipBlanks(sp);
		for ( i=0; *sp && *sp != ',' && *sp != '}'; sp++) {
			s[i++] = *sp;
		}
		s[i] = (char)NULL;
		*R++ = atof(s);
		if (*sp == ',')
			++sp;
	}

	return(TRUE);
}


char *
SkipBlanks(s)
register char	*s;
{
	while(isspace(*s))
		++s;
	return(s);
}


void
PrintUsage(Fp, Usage, Function, Symbols)
FILE	*Fp;
char	*Usage[];
char	*Function[];
ARGDEF	*Symbols[];
{
	register int	i;

	for (i=0; Usage[i]; i++)
		fputs(Usage[i], Fp);
	for (i=0; Function[i]; i++)
		fputs(Function[i], Fp);
	fprintf(Fp, "Options:\n");
	for (i=0; Symbols[i]; i++) {
		fputc('\t', Fp);
		PrintArgDef(Fp, Symbols[i]);
	}
}


bool
ParseHelp(Usage, Function, Symbols, arg)
char	*Usage;
char	*Function;
ARGDEF	*Symbols[];
char	*arg;
{
	bool	Help(); 
	char	*EqualSign;

	if (strncmp(arg, "Help", 4)==0 || strncmp(arg, "help", 4)==0) {
		if ((EqualSign = strchr(arg,'=')) == (char *)NULL)
			return(Help(Usage, Function, Symbols, "All"));
		else
			return(Help(Usage, Function, Symbols, EqualSign+1));
	}
	return(FALSE);
}


bool
Help(Usage, Function, Symbols, arg)
char	*Usage[];
char	*Function[];
ARGDEF	*Symbols[];
char	*arg;
{
	register int	i, j;
	extern	VALUE	V_Verbose;

	if (strcmp(arg, "All")==0 || strcmp(arg,"all")==0) {
		if (VtoBoolean(V_Verbose)) {
			for (i=0; Usage[i]; i++)
				fputs(Usage[i], stdout);
			for (i=0; Function[i]; i++)
				fputs(Function[i], stdout);
			fprintf(stdout, "Options:\n");
			for (i=0; Symbols[i]; i++) {
				fputc('\t', stdout);
				PrintArgDef(stdout, Symbols[i]);
				if ( Symbols[i]->ad_helptext != (char **)NULL) {
					for (j=0; Symbols[i]->ad_helptext[j]; j++)
						fputs(Symbols[i]->ad_helptext[j], stdout);
				}
				fputc('\n', stdout);
			}
		} else {
			fputs(Usage[0], stdout);
			fputs(Function[0], stdout);
			fprintf(stderr, "Options:\n");
			for (i=0; Symbols[i]; i++) {
				fputc('\t', stdout);
				PrintArgDef(stdout, Symbols[i]);
			}
		}
		return(TRUE);
	} else {
		/* find entry in symbol table	*/
		for (i=0; Symbols[i]; i++) {
			if (strcmp(arg,Symbols[i]->ad_id) == 0) {
				/* found */
				fputc('\t', stdout);
				PrintArgDef(stdout, Symbols[i]);
				if (VtoBoolean(V_Verbose) && Symbols[i]->ad_helptext != (char **)NULL) {
					for (j=0; Symbols[i]->ad_helptext[j]; j++)
						fputs(Symbols[i]->ad_helptext[j], stdout);
					fputc('\n', stdout);
				}
				return(TRUE);
			}
		}
	}
	return(FALSE);
}




bool
ParseArg(Symbols, arg)
ARGDEF	*Symbols[];
char	*arg;
{
	register int	i;
	char		*EqualSign;
	char		*ValueStr;
	int		wid;

	if ((EqualSign = strchr(arg,'=')) == (char *)NULL) {
		ValueStr = arg;
		wid = strlen(ValueStr);
	} else {
		ValueStr = EqualSign + 1;
		wid = EqualSign - arg;
	}

	/* find entry in symbol table	*/
	for (i=0; Symbols[i]; i++) {
		if (strncmp(arg,Symbols[i]->ad_id, wid) == 0) {
			/* found */
			return(ParseEntry(Symbols[i], ValueStr));
		}
	}
	
	return(FALSE);
}


bool
ParseEntry(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	char	*StartOpt, *StartType;
	char	*BarOpt;
	char	*BarType;
	int	WidOpt, WidType;

	StartOpt = Entry->ad_options;
	StartType = Entry->ad_opttype;
	do {
		if ((BarOpt = strchr(StartOpt,'|')) == (char *)NULL) {
			WidOpt = strlen(StartOpt);
		} else {
			WidOpt = BarOpt - StartOpt;
		}
		if ((BarType = strchr(StartType,'|')) == (char *)NULL) {
			WidType = strlen(StartType);
		} else {
			WidType = BarType - StartType;
		}

		if (strncmp(StartType, "string", WidType) == 0) {
			if (ParseString(Entry, StartOpt, WidOpt, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else  if (strncmp(StartType, "dbl", WidType) == 0) {
			if (ParseDbl(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "float", WidType) == 0) {
			if (ParseFloat(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "int", WidType) == 0) {
			if (ParseInt(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "boolean", WidType) == 0) {
			if (ParseBool(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "byte", WidType) == 0) {
			if (ParseByte(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "interval", WidType) == 0) {
			if (ParseInterval(Entry, arg) == TRUE)
				return(TRUE);
			else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "set", min(WidType,3)) == 0) {
			if (ParseSet(Entry, arg, StartType,WidType) == TRUE) {
				return(TRUE);
			} else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else if (strncmp(StartType, "rect", WidType) == 0) {
			if (ParseRect(Entry, arg) == TRUE) {
				return(TRUE);
			} else {
				StartOpt = ++BarOpt;
				StartType = ++BarType;
				continue;
			}
		} else {
			fprintf(stderr, "Internal Error: invalid option type string: \"%s\"\n", StartType);
			PrintArgDef(stderr,Entry);
			errno = EINVAL;
			exit(errno);
		}
	} while(StartOpt != (char)NULL && StartType != (char)NULL);
	return(TRUE);
}


bool
ParseString(Entry, Option, n, arg)
ARGDEF	*Entry;
char	*Option;
int	n;
char	*arg;
{
	if (*Option == '*') {
		 /* Match any string	*/
		Entry->ad_value->val_type = string;
		Entry->ad_value->val_u.udt_string = StrDup(arg);
		return(TRUE);
	} else if (arg!=(char *)NULL && strncmp(arg,Option,strlen(arg)) == 0) {
		/* Matches option	*/
		Entry->ad_value->val_type = string;
		Entry->ad_value->val_u.udt_string = StrDup(arg);
		return(TRUE);
	} else {
		return(FALSE);
	}
}

char	*
StrDup(Str)
char	*Str;
{
	register int	n;
	register char	*Dup;

	if (Str == (char *)NULL)
		return((char *)NULL);

	n = strlen(Str);
	if ((Dup = malloc(n+1)) == (char *)NULL) {
		perror("(StrDup) ");
		assert(Dup != (char *)NULL);
		exit(errno);
	}
	memcpy(Dup, Str, n+1);
	return(Dup);
}


bool
ParseDbl(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetDbl(Entry->ad_value, arg));
}

bool
ParseFloat(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetFloat(Entry->ad_value, arg));
}

bool
ParseInt(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetInt(Entry->ad_value, arg));
}

bool
ParseBool(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetBool(Entry->ad_value, arg));
}

bool
ParseByte(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetByte(Entry->ad_value, arg));
}

bool
ParseInterval(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetInterval(Entry->ad_value, arg));
}

bool
ParseSet(Entry, arg, Type, WidType)
ARGDEF	*Entry;
char	*arg;
char	*Type;
int	WidType;
{
	return(SetSet(Entry->ad_value, arg, Type, WidType));
}

bool
ParseRect(Entry, arg)
ARGDEF	*Entry;
char	*arg;
{
	return(SetRect(Entry->ad_value, arg));
}


void
PrintArgDef(Fp, Def)
FILE	*Fp;
ARGDEF	*Def;
{
	fprintf(Fp, "%s=", Def->ad_id);
	fprintf(Fp, "[%s]", Def->ad_options);
	fprintf(Fp, " default: %s\n", Def->ad_default);
	fflush(Fp);
}


void
PrintArg(Fp, Def)
FILE	*Fp;
ARGDEF	*Def;
{
	LATOM	*A;

	switch(Def->ad_value->val_type) {
	default:
		fprintf(Fp,"%s=<Invalid datatype>!\n", Def->ad_id);
		break;
	case boolean:
		fprintf(Fp,"%s=%s\n",
			Def->ad_id,
			Def->ad_value->val_u.udt_bool? "TRUE":"FALSE");
		break;
	case integer:
		fprintf(Fp,"%s=%d\n",Def->ad_id,Def->ad_value->val_u.udt_int);
		break;
	case dbl:
		fprintf(Fp,"%s=%lg\n",Def->ad_id,Def->ad_value->val_u.udt_dbl);
		break;
	case flt:
		fprintf(Fp,"%s=%g\n",Def->ad_id,Def->ad_value->val_u.udt_flt);
		break;
	case byte:
		fprintf(Fp,"%s=%c\n",Def->ad_id,Def->ad_value->val_u.udt_byte);
		break;
	case string:
		fprintf(Fp,"%s=\"%s\"\n",Def->ad_id,Def->ad_value->val_u.udt_string);
		break;
	case interval:
		fprintf(Fp,"%s=%g,%g\n",Def->ad_id,Def->ad_value->val_u.udt_interval);
		break;
	case set:
		fprintf(Fp,"%s={\n",Def->ad_id);
		for (A=Def->ad_value->val_u.udt_set.list_head; A; A=A->la_next)
			if (A->la_next)
				fprintf(Fp,"\t\t<%s>,\n",(char *)A->la_p);
			else
				fprintf(Fp,"\t\t<%s>\n",(char *)A->la_p);
		fprintf(Fp, "\t}\n");
		break;
	case rect:
		fprintf(
			Fp,
			"%s={%g,%g, %g,%g}\n",
			Def->ad_id,
			Def->ad_value->val_u.udt_rect.r_diag[0],
			Def->ad_value->val_u.udt_rect.r_diag[1],
			Def->ad_value->val_u.udt_rect.r_diag[2],
			Def->ad_value->val_u.udt_rect.r_diag[3]
		);
		break;
	}
}
