/******************************************************************************
 IrstLM: IRST Language Model Toolkit
 Copyright (C) 2006 Marcello Federico, ITC-irst Trento, Italy

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

******************************************************************************/

#include	<stdio.h>
#include	<stdlib.h>
#include	<ctype.h>
#include	<string.h>

#include	"cmd.h"

static Enum_T	BoolEnum[] = {
	{	"FALSE",	0	},
	{	"TRUE",		1	},
	{	0,		0	}
};

#ifdef	NEEDSTRDUP
char	*strdup();
#endif

#define	FALSE	0
#define	TRUE	1

#define	LINSIZ		10240
#define	MAXPARAM	256

static char	*GetLine(),
		**str2array();
static int	Scan(),
		SetParam(),
		SetEnum(),
		SetSubrange(),
		SetStrArray(),
		SetGte(),
		SetLte(),
		CmdError(),
		EnumError(),
		SubrangeError(),
		GteError(),
		LteError(),
		PrintParam(),
		PrintEnum(),
		PrintStrArray();

static Cmd_T	cmds[MAXPARAM+1];
static char	*SepString = " \t\n";

#if defined(__STDC__)
#include	<stdarg.h>
int DeclareParams(const char *ParName, ...)
#else
#include	<varargs.h>
int DeclareParams(ParName, va_alist)
char	*ParName;
va_dcl
#endif
{
	va_list		args;
	static int	ParamN = 0;
	int		j,
			c;
	char		*s;

#if defined(__STDC__)
	va_start(args, ParName);
#else
	va_start(args);
#endif
	for(;ParName;) {
		if(ParamN==MAXPARAM) {
			fprintf(stderr, "Too many parameters !!\n");
			break;
		}
		for(j=0,c=1; j<ParamN&&(c=strcmp(cmds[j].Name,ParName))<0; j++)
			;
		if(!c) {
			fprintf(stderr,
				"Warning: parameter \"%s\" declared twice.\n",
				ParName);
		}
		for(c=ParamN; c>j; c--) {
			cmds[c] = cmds[c-1];
		}
		cmds[j].Name = ParName;
		cmds[j].Type = va_arg(args, int);
		cmds[j].Val = va_arg(args, void *);
		switch(cmds[j].Type) {
		case CMDENUMTYPE:	/* get the pointer to Enum_T struct  */
			cmds[j].p = va_arg(args, void *);
			break;
		case CMDSUBRANGETYPE:	/* get the two extremes		     */
			cmds[j].p = (void*) calloc(2, sizeof(int));
			((int*)cmds[j].p)[0] = va_arg(args, int);
			((int*)cmds[j].p)[1] = va_arg(args, int);
			break;
		case CMDGTETYPE:	/* get lower or upper bound	     */
		case CMDLTETYPE:
			cmds[j].p = (void*) calloc(1, sizeof(int));
			((int*)cmds[j].p)[0] = va_arg(args, int);
			break;
		case CMDSTRARRAYTYPE:	/* get the separators string	     */
			cmds[j].p = (s=va_arg(args, char*))
				    ? (void*)strdup(s) : 0;
			break;
		case CMDBOOLTYPE:
			cmds[j].Type = CMDENUMTYPE;
			cmds[j].p = BoolEnum;
			break;
		case CMDDOUBLETYPE:	/* nothing else is needed	     */
		case CMDINTTYPE:
		case CMDSTRINGTYPE:
			break;
		default:
			fprintf(stderr, "%s: %s %d %s \"%s\"\n",
				"DeclareParam()", "Unknown Type",
				cmds[j].Type, "for parameter", cmds[j].Name);
			exit(1);
		}
		ParamN++;
		ParName = va_arg(args, char *);
	}
	cmds[ParamN].Name = NULL;
	va_end(args);
	return 0;
}

int GetParams(n, a, CmdFileName)
int	*n;
char	***a;
char	*CmdFileName;
{
	char	*Line,
		*ProgName;
	int	argc = *n;
	char	**argv = *a,
		*s;
	FILE	*fp;
	int	IsPipe;

#ifdef	MSDOS
#define	PATHSEP '\\'
	char	*dot = NULL;
#else
#define	PATHSEP '/'
#endif

	if(!(Line=malloc(LINSIZ))) {
		fprintf(stderr, "GetParams(): Unable to alloc %d bytes\n",
			LINSIZ);
		exit(1);
	}
	if((ProgName=strrchr(*argv, PATHSEP))) {
		++ProgName;
	} else {
		ProgName = *argv;
	}
#ifdef	MSDOS
	if(dot=strchr(ProgName, '.')) *dot = 0;
#endif
	--argc;
	++argv;
	for(;;) {
		if(argc && argv[0][0]=='-' && argv[0][1]=='=') {
			CmdFileName = argv[0]+2;
			++argv;
			--argc;
		}
		if(!CmdFileName) {
			break;
		}
		IsPipe = !strncmp(CmdFileName, "@@", 2);
		fp = IsPipe
		     ? popen(CmdFileName+2, "r")
		     : strcmp(CmdFileName, "-")
		       ? fopen(CmdFileName, "r")
		       : stdin;
		if(!fp) {
			fprintf(stderr, "Unable to open command file %s\n",
				CmdFileName);
			exit(1);
		}
		while(GetLine(fp, LINSIZ, Line) && strcmp(Line, "\\End")) {
			if(Scan(ProgName, cmds, Line)) {
				CmdError(Line);
			}
		}
		if(fp!=stdin) {
			if(IsPipe) pclose(fp); else fclose(fp);
		}
		CmdFileName = NULL;
	}
	while(argc && **argv=='-' && (s=strchr(*argv, '='))) {
		*s = ' ';
		sprintf(Line, "%s/%s", ProgName, *argv+1);
		*s = '=';
		if(Scan(ProgName, cmds, Line)) CmdError(*argv);
		--argc;
		++argv;
	}
	*n = argc;
	*a = argv;
#ifdef MSDOS
	if(dot) *dot = '.';
#endif
	free(Line);
	return 0;
}

int PrintParams(ValFlag, fp)
int	ValFlag;
FILE	*fp;
{
	int	i;

	fflush(fp);
	if(ValFlag) {
		fprintf(fp, "Parameters Values:\n");
	} else {
		fprintf(fp, "Parameters:\n");
	}
	for(i=0; cmds[i].Name; i++) PrintParam(cmds+i, ValFlag, fp);
	fprintf(fp, "\n");
	fflush(fp);
	return 0;
}

int SPrintParams(a, pfx)
char	***a,
	*pfx;
{
	int	l,
		n;
	Cmd_T	*cmd;

	if(!pfx) pfx="";
	l = strlen(pfx);
	for(n=0, cmd=cmds; cmd->Name; cmd++) n += !!cmd->ArgStr;
	a[0] = calloc(n, sizeof(char*));
	for(n=0, cmd=cmds; cmd->Name; cmd++) {
		if(!cmd->ArgStr) continue;
		a[0][n] = malloc(strlen(cmd->Name)+strlen(cmd->ArgStr)+l+2);
		sprintf(a[0][n], "%s%s=%s", pfx, cmd->Name, cmd->ArgStr);
		++n;
	}
	return n;
}

static int CmdError(opt)
char	*opt;
{
	fprintf(stderr, "Invalid option \"%s\"\n", opt);
	fprintf(stderr, "This program expectes the following parameters:\n");
	PrintParams(FALSE, stderr);
	exit(0);
}

static int PrintParam(cmd, ValFlag, fp)
Cmd_T	*cmd;
int	ValFlag;
FILE	*fp;
{
	fprintf(fp, "%4s", "");
	switch(cmd->Type) {
	case CMDDOUBLETYPE:
		fprintf(fp, "%s", cmd->Name);
		if(ValFlag) fprintf(fp, ": %22.15e", *(double *)cmd->Val);
		fprintf(fp, "\n");
		break;
	case CMDENUMTYPE:
		PrintEnum(cmd, ValFlag, fp);
		break;
	case CMDINTTYPE:
	case CMDSUBRANGETYPE:
	case CMDGTETYPE:
	case CMDLTETYPE:
		fprintf(fp, "%s", cmd->Name);
		if(ValFlag) fprintf(fp, ": %d", *(int *)cmd->Val);
		fprintf(fp, "\n");
		break;
	case CMDSTRINGTYPE:
		fprintf(fp, "%s", cmd->Name);
		if(ValFlag) {
			if(*(char **)cmd->Val) {
				fprintf(fp, ": \"%s\"", *(char **)cmd->Val);
			} else {
				fprintf(fp, ": %s", "NULL");
			}
		}
		fprintf(fp, "\n");
		break;
	case CMDSTRARRAYTYPE:
		PrintStrArray(cmd, ValFlag, fp);
		break;
	default:
		fprintf(stderr, "%s: %s %d %s \"%s\"\n",
			"PrintParam",
			"Unknown Type",
			cmd->Type,
			"for parameter",
			cmd->Name);
		exit(1);
	}
	return 0;
}

static char *GetLine(fp, n, Line)
FILE	*fp;
int	n;
char	*Line;
{
	int	j,
		l,
		offs=0;

	for(;;) {
		if(!fgets(Line+offs, n-offs, fp)) {
			return NULL;
		}
		if(Line[offs]=='#') continue;
		l = strlen(Line+offs)-1;
		Line[offs+l] = 0;
		for(j=offs; Line[j] && isspace(Line[j]); j++, l--)
			;
		if(l<1) continue;
		if(j > offs) {
			char	*s = Line+offs,
				*q = Line+j;

			while((*s++=*q++))
				;
		}
		if(Line[offs+l-1]=='\\') {
			offs += l;
			Line[offs-1] = ' ';
		} else {
			break;
		}
	}
	return Line;
}

static int Scan(ProgName, cmds, Line)
char	*ProgName,
	*Line;
Cmd_T	*cmds;
{
	char	*q,
		*p;
	int	i,
		hl,
		HasToMatch = FALSE,
		c0,
		c;

	p = Line+strspn(Line, SepString);
	if(!(hl=strcspn(p, SepString))) {
		return 0;
	}
	if((q=strchr(p, '/')) && q-p<hl) {
		*q = 0;
		if(strcmp(p, ProgName)) {
			*q = '/';
			return 0;
		}
		*q = '/';
		HasToMatch=TRUE;
		p = q+1;
	}
	if(!(hl = strcspn(p, SepString))) {
		return 0;
	}
	c0 = p[hl];
	p[hl] = 0;
	for(i=0, c=1; cmds[i].Name&&(c=strcmp(cmds[i].Name, p))<0; i++)
		;
	p[hl] = c0;
	if(!c) return SetParam(cmds+i, p+hl+strspn(p+hl, SepString));
	return HasToMatch && c;
}

static int SetParam(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	if(!*s && cmd->Type != CMDSTRINGTYPE) {
		fprintf(stderr,
			"WARNING: No value specified for parameter \"%s\"\n",
			cmd->Name);
		return 0;
	}
	switch(cmd->Type) {
	case CMDDOUBLETYPE:
		if(sscanf(s, "%lf", (double*)cmd->Val)!=1) {
			fprintf(stderr,
				"Float value required for parameter \"%s\"\n",
				cmd->Name);
			exit(1);
		}
		break;
	case CMDENUMTYPE:
		SetEnum(cmd, s);
		break;
	case CMDINTTYPE:
		if(sscanf(s, "%d", (int*)cmd->Val)!=1) {
			fprintf(stderr,
				"Integer value required for parameter \"%s\"\n",
				cmd->Name);
			exit(1);
		}
		break;
	case CMDSTRINGTYPE:
		*(char **)cmd->Val = (strcmp(s, "<NULL>") && strcmp(s, "NULL"))
				     ? strdup(s)
				     : 0;
		break;
	case CMDSTRARRAYTYPE:
		SetStrArray(cmd, s);
		break;
	case CMDGTETYPE:
		SetGte(cmd, s);
		break;
	case CMDLTETYPE:
		SetLte(cmd, s);
		break;
	case CMDSUBRANGETYPE:
		SetSubrange(cmd, s);
		break;
	default:
		fprintf(stderr, "%s: %s %d %s \"%s\"\n",
			"SetParam",
			"Unknown Type",
			cmd->Type,
			"for parameter",
			cmd->Name);
		exit(1);
	}
	cmd->ArgStr = strdup(s);
	return 0;
}

static int SetEnum(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	Enum_T	*en;

	for(en=(Enum_T *)cmd->p; en->Name; en++) {
		if(*en->Name && !strcmp(s, en->Name)) {
			*(int *) cmd->Val = en->Idx;
			return 0;
		}
	}
	return EnumError(cmd, s);
}

static int SetSubrange(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	int	n;

	if(sscanf(s, "%d", &n)!=1) {
		fprintf(stderr,
			"Integer value required for parameter \"%s\"\n",
			cmd->Name);
		exit(1);
	}
	if(n < *(int *)cmd->p || n > *((int *)cmd->p+1)) {
		return SubrangeError(cmd, n);
	}
	*(int *)cmd->Val = n;
	return 0;
}

static int SetGte(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	int	n;

	if(sscanf(s, "%d", &n)!=1) {
		fprintf(stderr,
			"Integer value required for parameter \"%s\"\n",
			cmd->Name);
		exit(1);
	}
	if(n<*(int *)cmd->p) {
		return GteError(cmd, n);
	}
	*(int *)cmd->Val = n;
	return 0;
}

static int SetStrArray(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	*(char***)cmd->Val = str2array(s, (char*)cmd->p);
	return 0;
}

static int SetLte(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	int	n;

	if(sscanf(s, "%d", &n)!=1) {
		fprintf(stderr,
			"Integer value required for parameter \"%s\"\n",
			cmd->Name);
		exit(1);
	}
	if(n > *(int *)cmd->p) {
		return LteError(cmd, n);
	}
	*(int *)cmd->Val = n;
	return 0;
}

static int EnumError(cmd, s)
Cmd_T	*cmd;
char	*s;
{
	Enum_T	*en;

	fprintf(stderr,
		"Invalid value \"%s\" for parameter \"%s\"\n", s, cmd->Name);
	fprintf(stderr, "Valid values are:\n");
	for(en=(Enum_T *)cmd->p; en->Name; en++) {
		if(*en->Name) {
			fprintf(stderr, "    %s\n", en->Name);
		}
	}
	fprintf(stderr, "\n");
	exit(1);
}

static int GteError(cmd, n)
Cmd_T	*cmd;
int	n;
{
	fprintf(stderr,
		"Value %d out of range for parameter \"%s\"\n", n, cmd->Name);
	fprintf(stderr, "Valid values must be greater than or equal to  %d\n",
		*(int *)cmd->p);
	exit(1);
}

static int LteError(cmd, n)
Cmd_T	*cmd;
int	n;
{
	fprintf(stderr,
		"Value %d out of range for parameter \"%s\"\n", n, cmd->Name);
	fprintf(stderr, "Valid values must be less than or equal to  %d\n",
		*(int *)cmd->p);
	exit(1);
}

static int SubrangeError(cmd, n)
Cmd_T	*cmd;
int	n;
{
	fprintf(stderr,
		"Value %d out of range for parameter \"%s\"\n", n, cmd->Name);
	fprintf(stderr, "Valid values range from %d to %d\n",
		*(int *)cmd->p, *((int *)cmd->p+1));
	exit(1);
}

static int PrintEnum(cmd, ValFlag, fp)
Cmd_T	*cmd;
int	ValFlag;
FILE	*fp;
{
	Enum_T	*en;

	fprintf(fp, "%s", cmd->Name);
	if(ValFlag) {
		for(en=(Enum_T *)cmd->p; en->Name; en++) {
			if(*en->Name && en->Idx==*(int *)cmd->Val) {
				fprintf(fp, ": %s", en->Name);
			}
		}
	}
	fprintf(fp, "\n");
	return 0;
}

static int PrintStrArray(cmd, ValFlag, fp)
Cmd_T	*cmd;
int	ValFlag;
FILE	*fp;
{
	char	*indent,
		**s = *(char***)cmd->Val;
	int	l = 4+strlen(cmd->Name);

	fprintf(fp, "%s", cmd->Name);
	indent = malloc(l+2);
	memset(indent, ' ', l+1);
	indent[l+1] = 0;
	if(ValFlag) {
		fprintf(fp, ": %s", s ? (*s ? *s++ : "NULL") : "");
		if(s) while(*s) {
			fprintf(fp, "\n%s %s", indent, *s++);
		}
	}
	free(indent);
	fprintf(fp, "\n");
	return 0;
}

static char **str2array(s, sep)
char	*s,
	*sep;
{
	char	*p,
		**a;
	int	n = 0,
		l;

	if(!sep) sep = SepString;
	p = s += strspn(s, sep);
	while(*p) {
		p += strcspn(p, sep);
		p += strspn(p, sep);
		++n;
	}
	a = calloc(n+1, sizeof(char *));
	p = s;
	n = 0;
	while(*p) {
		l = strcspn(p, sep);
		a[n] = malloc(l+1);
		memcpy(a[n], p, l);
		a[n][l] = 0;
		++n;
		p += l;
		p += strspn(p, sep);
	}
	return a;
}
