/**************************************************************************/
/**************************************************************************/
/*	genc5.c: M E N U  handling routines
/*
/*	Written by Bill Fitler
/**************************************************************************/
/**************************************************************************/
#ifndef MAINMODULE
#include <genccpm.h>
EXTERN BOOLEAN verbose;			/* flag: be wordy */
#endif

MLOCAL BOOLEAN eflag = FALSE;		/* user error */

/**************************************************************************/
/* bldmenu: builds a menu list from successive calls
/**************************************************************************/
    MENU *
bldmenu(muroot,typ,ptr,name,description)
    MENU *muroot;			/* root of menu list (or NULLPTR) */
    WORD typ;				/* type of menu item */
    BYTE *ptr;				/* pointer to something... */
    BYTE *name;				/* command name for this menu item */
    BYTE *description;			/* long label for this item */
{
	REG MENU *mu;			/* ptr to created item */
	REG MENU *tmu;			/* temp ptr for linked list scan */

	mu = (MENU *)malloc(sizeof(*mu));	/* allocate memory for item */
	mu->mutype = typ;
	mu->muiptr = ptr;
	mu->muname = name;
	mu->mudesc = description;
	mu->munext = NULLPTR;
	if( muroot == NULLPTR )		/* if NULL list */
		return mu;		/* return something to start with */
	for( tmu=muroot; tmu->munext != NULLPTR; tmu = tmu->munext )
		;			/* append to end of list */
	tmu->munext = mu;	
	return muroot;			/* keep list in order */
}

/**************************************************************************/
/* domenu: executes the specified commands (in cbuf) from the menu (mnu)
/**************************************************************************/
#define CMDLEN 80
#define USERR ++eflag;printf
#define ECHK(fn) _errchk(fn,cmd,cmdval)

    VOID
domenu(cbuf,mnu)
    BYTE *cbuf;				/* command buffer */
    MENU *mnu;				/* table of menu specs */
{
	REG MENU *mi;			/* ptr to menu item */
	LOCAL BYTE cmd[CMDLEN];		/* place for command & value */
	LOCAL BYTE *cmdval;		/* ptr within cmd */
	LOCAL BYTE *cp;			/* temp char ptr */
	BYTE *msetbool();		/* set a boolean value */
	BYTE *msetbyte();		/* set a numeric byte value */
	BYTE *msetword();		/* set an integer pointer */
	BYTE *msettxt();		/* set a text value */
	BYTE *(*proc)();		/* procedure pointer */
	BYTE *msetdrv();		/* set a drive value */
	BYTE *_nxtcmd();		/* command parser */
	MENU *_fndcmd();		/* command lookup */

	eflag = FALSE;			/* no errors yet */
	while((cbuf=_nxtcmd(cbuf,cmd))){ /* while there are commands in cbuf */
	    cmdval = NULLPTR;		/* init value ptr for command */
	    for( cp=cmd; *cp; cp++ ) {	/* scan command for assign op */
		if( isupper(*cp) )	/* convert all commands to LOWER CASE */
		    *cp = tolower(*cp);
		if( *cp == '=' ) {	/* found? */
		    *cp++ = NULL;	/* terminate command */
		    cmdval = cp;	/* point value to after assign op */
		    break;		/* and terminate loop */
		}
	    }
	    if( (mi=_fndcmd(cmd,mnu)) == NULLPTR ){ /* scan menu commands */
		USERR("'%s' is not a command for this menu\n",cmd); 
	    } else {			/******** cmd loop ********/
		switch(mi->mutype) {	/* handle cmd type */
		case MBOOL:		/** BOOLEAN command **/
		    ECHK(msetbool(mi->muiptr,cmdval));
		    break;
		case MBYTE:		/** BYTE command **/
		    ECHK(msetbyte(mi->muiptr,cmdval));
		    break;
		case MWORD:		/** INTEGER command **/
		    ECHK(msetword(mi->muiptr,cmdval));
		    break;
		case MTEXT:		/** TEXT command **/
		    ECHK(msettxt(mi->muiptr,cmdval));
		    break;
		case MPROC:		/** PROCEDURAL command **/
		    proc = mi->muiptr;	/* assign to function ptr */
		    ECHK(((*proc)(cmdval,mi->mudesc)));
		    break;
		case MDRIV:		/** DRIVE letter command **/
		    ECHK(msetdrv(mi->muiptr,cmdval));
		    break;
		}			/* end switch */
	    }				/********* end cmd loop *********/
	    if(eflag) {			/* user error? */
		press_return();
		*cbuf = NULL;
	    } else if(verbose)		/* are we being wordy? */
		prtmival(mi);		/* let the user know what happened */
	}				/**** end while loop ****/
}



/* _nxtcmd: parses cin into cout */
    BYTE *
_nxtcmd(cin,cout)			/* returns ptr to after next cmd */
    BYTE *cin;				/* command in buf */
    BYTE *cout;				/* command out buf */
{
	/***** handle quoted strings??? *****/
	while( isspace(*cin) )		/* skip leading spaces */
		++cin;
	if( *cin == NULL )		/* check for EOS */
		return NULLPTR;
	while( *cin && !isspace(*cin) )	/* scan to eos or whitespace */
		*cout++ = *cin++;	/* xfer to command out buf */
	*cout = NULL;			/* null terminate command out buf */
	return cin;			/* return ptr to after command */
}



/* _fndcmd: scans thru menu structure 'mu' looking for command name 'cn' */
/*		returns ptr to menu item or NULLPTR */
    MENU *
_fndcmd(cn,mus)
    BYTE *cn;				/* command name */
    MENU *mus;				/* ptr to menu list */
{
	REG WORD cnl;			/* cmd name len */
	MENU *musave;			/* extra ptr to menu item */

	cnl = strlen(cn);
	for( ; ; mus = mus->munext) {	/* scan menu list */
	    if( mus==NULLPTR )		/* if we hit the end then fail */
		return NULLPTR;
	    if( strncmp(cn,mus->muname,cnl) == 0 )	/* cn prefix of name?*/
		break;			/* yes, we have a candidate */
	}
	musave = mus;
	for( mus=mus->munext; ; mus=mus->munext ) { /* finish scanning list */
	    if( mus==NULLPTR )		/* if we hit the end then succeed */
		return musave;		/* we reached end of list: found it! */
	    if( strncmp(cn,mus->muname,cnl) == 0 )
		return NULLPTR;		/* another match: ambiguous command */
	}
}



/* _errchk: checks for error returns on functions */
    VOID
_errchk(emsg,cm,cmv)
    BYTE *emsg;				/* error message to check */
    BYTE *cm;				/* command generating the error */
    BYTE *cmv;				/* value generating the error */
{
	if( emsg ) {			/* non NULL error message return? */
		USERR("Error on command '%s",cm);
		if(cmv)
			USERR("=%s",cmv);
		USERR("': %s\n",emsg);
	}
}



/* msetbool: sets the BOOLEAN pointed to by 'bp' according to value in 'cvp' */
    BYTE *				/* return NULLPTR, or ptr to err msg */
msetbool(bp,cvp)
    BYTE *bp;				/* pointer to boolean to set */
    BYTE *cvp;				/* pointer to value to set to */
{
	REG WORD bv;
	REG BYTE *tp;

	if( cvp==NULLPTR )		/* no val: toggle boolean */
		bv = (*bp ? 4 : 1);	/* toggle by setting bv index */
	else {
	    for( tp=cvp; *tp; ++tp ) 
		if( isupper(*tp) ) *tp=tolower(*tp);
	    if( (bv=whichof(cvp,",on,yes,true,off,no,false"))==0 ) 
		return "value must be 'yes' or 'no'";
	}
	if( bv < 4 )
		*bp = -1;		/* zap to 0xFFFF */
	else 	*bp = 0;		/* zap to zero */
	return NULLPTR;
}


    BYTE *
msetbyte(bp,cvp)			/* set numeric BYTE value */
    BYTE *bp;				/* ptr to BYTE to set */
    BYTE *cvp;				/* value to set BYTE to */
{
	REG WORD val;			/* temp holding place */

	val=0;
	if(cvp != NULLPTR) val=atoih(cvp);	/* assume numbers are in HEX!! */
	if( cvp==NULLPTR || (val==0  &&  !isdigit(*cvp)) )
	    return "value must be a number";
	if( val > 255 )
	    return "value must be less than FF hex (255 decimal)";
	*bp = val;
	return NULLPTR;
}


    BYTE *
msetword(ip,cvp)
    WORD *ip;				/* ptr to WORD to set */
    BYTE *cvp;				/* value to set WORD to */
{
	REG WORD val;			/* temp holding place */

	val=0;
	if(cvp != NULLPTR) val=atoih(cvp);	/* assume numbers are in HEX!! */
	if( cvp==NULLPTR || (val==0  &&  !isdigit(*cvp)) )
	    return "value must be an unsigned hex number between 0 and FFFFh";
	*ip = val;
	return NULLPTR;
}


    WORD
atoih(cp)				/* conver ascii hex to word */
    BYTE *cp;				/* buffer number */
{
	REG WORD v;			/* resulting value */
	REG BYTE cv;			/* character value */
	BYTE *savcp;			/* save orig ptr */

	v = 0; savcp = cp;		/* init value */
	for( cv = *cp; ; cv = *++cp ){	/* for each char in cp */
	    if( isdigit(cv) )
		v = (v<<4) + (cv-'0');	/* convert digit and add */
	    else {			/* maybe it's a letter */
		if( isupper(cv) )	/* upper case letter? */
		    cv = tolower(cv);	/* convert it */
		if( cv<'a' || cv>'f')	/* range check */
		    break;		/* terminate loop if failed */
		v = (v<<4)+(10+cv-'a');	/* convert letter and add */
	    }
	}
	if( cp-savcp <= 4 )
	     return v;			/* return the value */
	else { 
	     *savcp |= 0x80; 
	     return 0; 		/* turn on hi bit, make isdigit test fail */
	}
}

    BYTE *
msettxt(tp,cvp)
    BYTE **tp;				/* place to store ptr to value */
    BYTE *cvp;				/* value to set BYTEs to */
{
	REG BYTE *tmp;			/* temp ptr for allocated storage */

	if( cvp==NULLPTR )	
	    cvp="";			/* point to null string, instead */
	tmp = malloc(1+strlen(cvp));	/* get space for string */
	strcpy(tmp,cvp);		/* copy to allocated space */
	*tp = tmp;			/* assign ptr */
	return NULLPTR;
}

    BYTE *
msetdrv(dp,cvp)
    BYTE *dp;				/* place to store ptr to value */
    BYTE *cvp;				/* value to set DRIVE to */
{
	REG WORD v;			/* work place */

	if( cvp!=NULLPTR ) {		/* check for drive letter */
	    if( isupper(*cvp) )
		*cvp = tolower(*cvp);
	    v = *cvp - 'a';		/* convert drive spec to nibble */
	}
	if( cvp==NULLPTR  ||  v<0  ||  v>15  || cvp[1] != ':' )
	     return "you must specify a drive 'A:' thru 'P:'";
	*dp = v;			/* looks okay, set it */
	return NULLPTR;			/* no errors */
}


/**************************************************************************/
/* prtmenu: displays the menu labels, values and descriptors
/**************************************************************************/
    VOID 
prtmenu(mtitle,mnu)
    BYTE *mtitle;			/* title of menu */
    MENU *mnu;				/* table specifying menu */
{
	printf("\n\n%s\n",mtitle);	/* give the menu a title */
	for( ; mnu!=NULLPTR; mnu = mnu->munext ) /* travel down list */
	    prtmival(mnu);	
}


prtmival(mnu)				/* return ptr to value of item */
    MENU *mnu;				/* item to get value of */
{
	REG BYTE *ival;
	BYTE ivbuf[10];			/* local buf for display */
	BYTE *vbp;			/* value byte ptr */
	WORD *vwp;			/* value word ptr */
	WORD v;				/* place to put value */

	printf("%12.12s ",mnu->muname); /* first the name */
	ival = ivbuf; *ival = NULL;	/* init value */
	switch(mnu->mutype) {	/* now the value */
	    case MBOOL:
		if( *(mnu->muiptr) )
		     ival="[Y]";
		else ival="[N]";
		break;
	    case MBYTE:
		vbp = mnu->muiptr;
		v = (*vbp & 0xFF);
		sprintf(ivbuf,"[%2.2x]",v);	/* display in hex */
		break;
	    case MWORD:
		vwp = mnu->muiptr;
		v = *vwp;
		sprintf(ivbuf,"[%4.4x]",v);	/* display in hex */
		break;
	    case MTEXT:
		vbp = *((BYTE **)mnu->muiptr);	/* go get the char ptr */
		if( strlen(vbp) > 4 )
		     sprintf(ivbuf,"[%-4.4s>",vbp);
		else sprintf(ivbuf,"[%s]",vbp);
		break;
	    case MPROC:
		/* leave blank */
		break;
	    case MDRIV:
		sprintf(ivbuf,"[%c:]",'A'+(*mnu->muiptr));
		break;
	}
	printf("%-6s ",ival);
	printf("%s\n",mnu->mudesc);	/* finally the description */
}


/************************************************************************
/* whichof: scans the string 'set' (delimited by 1st char in 'set')
/*	for 1st occurrence of string 'sample'.
/*	Returns 0 if 'sample' not a prefix of any item in set or if
/*		'sample' is a prefix of more than one item in set;
/*		delimiter# o.w.
/* E.G. if set = ",on,yes,true,off,no,false"  then
/*	whichof("yes",set) == 2;
/*	whichof("y",set) == 2;
/*	whichof("o",set) == 0;
/*	whichof("x",set) == 0;
/* NOTE: results not guaranteed if item delimiter (*set) is in string 'sample',
/*	or if null item is in set (adjacent delimiters).
/*************************************************************************/

/* first, a subroutine: */
/* _wget: returns NULLPTR if sa not prefix of item in st; */
/*	  returns end of item (ptr in st) otherwise */
BYTE *_wget(sa,st,de)
BYTE *sa;
BYTE *st;
BYTE de;
{
	BYTE *s2;

	for( ;; ) {					/* do forever */
		for( s2=sa; *s2 && *s2 == *st; ){
			++s2; ++st;			/* try match */
		}
		if( *s2	== NULL ){	/* success! we've matched all of sa */
			while( *st && *st != de ) ++st;	/* adv to nxt delim */
			return( --st );			/* backup one char */
		}
		while( *st  &&  *st != de ) /* no match, look for next item */
			++st;				/* adv to nxt delim */
		if( *st == NULL )
			return( NULLPTR );		/* end of list */
		++st;
	}
}

WORD whichof(sample,set)
BYTE *sample;
BYTE *set;
{
	WORD sx;
	BYTE *sp, *np;

	if( (sp=_wget(sample,set+1,*set)) == NULLPTR )
		return 0;				/* not found */
	if( _wget(sample,sp+1,*set) != NULLPTR )
		return 0;				/* ambiguous */
	for( sx=0, np=set; np<sp; np++ )
		if( *np == *set ) sx++;		/* count delimiters */
	return sx;
}




