/*
 Author: Andrew Lowry

 Columbia University Center for Computing Activities, July 1986.
 Copyright (C) 1986, 1987, Trustees of Columbia University in the City
 of New York.  Permission is granted to any individual or institution
 to use, copy, or redistribute this software so long as it is not sold
 for profit, provided this copyright notice is retained.
*/
/* cmswi
**
** Code to parse switches.  Parsing succeeds if current input
** uniquely matches a non-ignored switch in the supplied switch
** table.  Global variable 'cmswbeg' contains the character that
** must precede the switch text, and global variable 'cmswend'
** contains a character that may be used to terminate a switch.
** The initial values for these variables are "/" for cmswbeg, and
** ":" for cmswend.
**
** A switch must begin with the character in cmswbeg.  Subsequent
** text up to but not including the cmswend character or a break
** character (according to the break table in effect) is then matched
** against the supplied switch table.  If a unique match results,
** the parse value is the _swval field of the matching switch.
** If the switch was terminated by the cmswend character, that
** character is consumed by the parse, and the CM_SWT flag is set
** in the CSB.  Otherwise, the flag is cleared, and the delimiter
** character is not consumed by the parse.  The cmswend character is
** allowed, however, only on switches for which the character actually 
** appears in the switch table.
**
** For completion, if the current input uniquely matches a single switch,
** that switch is completed, with the cmswend character if it is present
** on the switch in the switch table.  Partial completion completes
** only up to punctuation.  Full completion adds a space and wakeup
** as well.
**
** Standard help prints a list of all visible switches that might match
** the current input.  The cmswbeg character is prepended to each switch
** in the listing, and the cmswend character will appear on those switches
** in which it appears in the supplied switch table.
**
** The break table specifies what characters are allowed in the interior
** of switches (that is, everything except the cmswbeg character at the
** beginning and the cmswend character, if present, at the end).  Note
** that any white space following the cmswbeg character is considered
** part of the switch text, so whitespace will generally kill the parse.
**/

#define	SWIERR				/* switch error tbl allocated here */

#include "ccmdlib.h"			/* get standard symbols */
#include "cmfncs.h"		/* and internal symbols */

/* Forward declaration of handler routines */

int swiprs(), swihlp(), swicplt();
    	
static brktab swibrk = {    	    	/* standard break table */
  {					/* 1st char break array */
					/* all but letters, digits, hyphen */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
    0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  },
  {					/* subsequent char break array */
					/* same as above */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
    0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  }
};

ftspec ft_swi = { swiprs, swihlp, swicplt, 0, &swibrk }; /* handler structure */

char cmswbeg = '/';		/* standard punctuation to start switch */
char cmswend = ':';		/* standard switch terminator */




/* swiprs - Check that the first character in the input matches the cmswbeg
** character.  Then find matching switches, and succeed if there is only
** one.
**/

PASSEDSTATIC int
swiprs(text,textlen,fdbp,parselen,value)
char *text;
int textlen,*parselen;
fdb *fdbp;
pval *value;
{
  int mcount;				/* number of matches */
  swtch *mat;				/* the matching switch */
  char *term;				/* char terminating the switch */
  switab *st;				/* the switch table */
  
  if (textlen == 0)
    return(CMxINC);			/* nothing to parse */
  if ((*text & CC_CHR) != cmswbeg) 
    return(SWIxBEG);			/* beginning char doesn't match */
  text++;				/* skip past opening char */
  textlen--;

  mcount = match(text,textlen,fdbp,&mat,&term);	/* find matching switches */
  if (mcount == 0)
    return(SWIxNM);			/* no match */
  else if (term == NULL)		/* not terminated? */
    return(CMxINC);			/* ambiguous parse */
  else if (mcount > 1)
    return(SWIxAMB);			/* ambiguous */

  st = (switab *) fdbp->_cmdat;		/* unique - get table address */
  while ((mat)->_swflg & SWI_ABR)	/* track down abbreviations */
    if (((mat)->_swval >= st->_stcnt) || /* abbrev ptr out of bounds? */
	((mat)->_swval < 0)
       )
      return(SWIxABR);			/* bad abbrev chain */
    else
      mat = &(st->_stwds[mat->_swval]);	/* move down chain */

  if ((*term & CC_CHR) == cmswend) {	/* terminated with cmswend? */
    if (mat->_swswi[strlen(mat->_swswi)-1] != cmswend) /* but not in tbl */
      return(SWIxEND);			/* not allowed */
    term++;				/* consume special terminator */
    cmcsb._cmflg |= CM_SWT;		/* and flag that it was present */
  }
  else
    cmcsb._cmflg &= ~CM_SWT;		/* else clear the flag */

  *parselen = (term - text) + 1;	/* set # of consumed chars */
					/*  (including opening punctuation) */
  value->_pvkey = mat->_swval;		/* set return value */
  return(CMxOK);			/* and return success */
}



/* swicplt - If the current input is ambiguous, we beep.  Otherwise
** we complete with the remainder of the identified switch.  In the
** latter case, full completion adds a space and a wakeup, and partial
** completion stops at punctuation.  If the cmswend character is present
** in the identified switch, it is included in the completion text.
**/

PASSEDSTATIC int
swicplt(text,textlen,fdbp,full,cplt,cpltlen)
char *text,**cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
{
  int mcount;				/* number of matches */
  swtch *mat;				/* the matching switch */
  char *term;				/* char terminating the switch */
  switab *st;				/* the switch table */
  char lastchar;			/* last char in completion text */

  *cplt = NULL;				/* assume empty completion */

  if (textlen <= 1)			/* no input (besides opening char) */
    return(CMP_BEL);			/* so just beep */

  text++; textlen--;			/* skip past opening char */
  mcount = match(text,textlen,fdbp,&mat,&term); /* find matches */
  if (mcount > 1)			/* ambiguous */
    return(CMP_BEL);			/* so just beep */
  else {
    st = (switab *) fdbp->_cmdat;	/* unique - get table address */
    while ((mat)->_swflg & SWI_ABR)	/* track down abbreviations */
      mat = &(st->_stwds[mat->_swval]);	/* move down chain */

    *cplt = mat->_swswi+textlen;	/* point to completion text */
    *cpltlen = strlen(mat->_swswi)-textlen; /* get length of remainder */
    if (*cpltlen < 0) {			/* can happen on weird abbrev */
      *cplt = NULL;			/* no completion in that case */
      lastchar = NULCHAR;
    }
    else
      lastchar = (*cplt)[(*cpltlen)-1];	/* else get last completion char */
    if (full)				/* full completion? */
      if (lastchar == cmswend)		/* cplt contains  special char? */
	return(CMP_GO);			/* then no space but wakeup */
      else
        return(CMP_SPC | CMP_GO);	/* otherwise add space and wakeup */
    else
      return(CMP_PNC);			/* partial stops after punctuation */
  }
}



/* swihlp - Construct a set of matching switches for the current input,
** then print them in tabular form.  Do not print invisible switches.
** If no switches match, print a special indication.  The help message
** starts with "switch, one of the following:".  If custom help has
** been given, "switch, " is left off of this string.
**/

PASSEDSTATIC int
swihlp(text,textlen,fdbp,cust)
char *text;
int textlen,cust;
fdb *fdbp;
{
  int mcount;				/* number of matches */
  swtch *mat;				/* a matching switch */
  char *term;				/* ptr to input terminator */
  switab *st;				/* switch table */
  int i,j;
  int swtlen;				/* lengths of individual switchs */
  int cols;				/* # of switches printed per line */
  int curcol;				/* current table column */
  int maxlen = 0;			/* maximum switch length */

  if (!cust)
    cmxputs("switch, ");		/* start of msg with no custom help */
  cmxputs("one of the following:");	/* remainder of the first line */
      
  if (textlen == 0)			/* no input? */
    mcount = match(text,0,fdbp,&mat,&term); /* then all will match */
  else
    mcount = match(text+1,textlen-1,fdbp,&mat,&term); /* find matches */
  if (mcount == 0) {			/* no match */
    cmxputs(" (No switches match current input)"); /* indicate void */
    return(CMxOK);
  }
  st = (switab *) fdbp->_cmdat;		/* get switch table */
  mat = st->_stwds;			/* point to first switch */
  for (i = 0; i < st->_stcnt; i++) {	/* first pass to find longest swt */
    if ((mat->_swflg & SWI_MAT) && 	/* matching switch... */
        ((mat->_swflg & SWI_INV) == 0)	/* and not invisible */
       ) {
      swtlen = strlen(mat->_swswi);	/* get switch length  */
      if (swtlen > maxlen)
	maxlen = swtlen;		/* update longest size */
    }
    mat++;				/* and move to next switch */
  }
  maxlen += 4;				/* adjust for prefix and */
					/*  column separation */

  /* Following calculation goes as follows:
  ** Let w be the screen width.  Naively, we would calculate that
  ** we could list floor(w/maxlen) switches per line, each taking up
  ** maxlen columns on the screen.  But since each line is indented
  ** two spaces, we should subtract 2 from the effective screen width.
  ** Then we add 3 to the effective width because we will not need to
  ** print three spaces after the last switch in a line.  So the
  ** effective screen width is w-2+3 = w+1 = cmcsb._cmcmx+2, since
  ** cmcsb._cmcmx is one less than the screen width.  So we want
  ** floor((cmcsb._cmcmx+2)/maxlen) switches per line.
  **/

  /* XXX see my comments in cmkey.c about this -- chris */

  cols = (cmcsb._cmcmx+2) / maxlen;	/* number of columns per line */

  mat = st->_stwds;			/* point to first switch */
  curcol = 0;				/* currently printing first column */
  for (i = 0; i < st->_stcnt; i++) {	/* second pass to print matches */
    if ((mat->_swflg & SWI_MAT) &&	/* matching switch? */
	((mat->_swflg & SWI_INV) == 0)	/* and visible? */
       ) {
      if (curcol == 0) {
	cmxnl();			/* new line for first column */
	cmxputs(" ");			/* and offset a bit */
      }
      cmxputc(cmswbeg);			/* print opening punctuation */
      cmxputs(mat->_swswi);		/* print the switch */
      if (curcol < (cols-1))		/* space out if not last column */
	for (j = strlen(mat->_swswi); j < maxlen; j++)
	   cmxputc(SPACE);
      curcol = (curcol+1) % cols;	/* and move to next column */
    }
    mat++;				/* move to next switch */
  }
  return(CMxOK);			/* all done */
}



/* match - Auxiliary routine used by switch handlers.
**
** Purpose:
**   Step through all the switches in a table, and set their SWI_MAT
**   flags according to whether or not they match a given input string.
**   Returns the number of matching switches, and if only one switch
**   matches, a pointer to that switch.  If more than one switch
**   matches, but one switch matches exactly, it is returned as if
**   it were the only matching switch.  SWI_INV, SWI_NOR and SWI_ABR
**   flags do not affect this operation, except that an exact match
**   to a SWI_NOR flagged switch is not considered an exact match.
**   If an individual switch is terminated by the cmswend character,
**   that character is considered not present for matching purposes.
**
** Input arguments:
**   text - A pointer to the first input character.
**   textlen - The number of input characters.
**   fdbp - A pointer to the FDB pointing to the switch table
**     and break table to be used.
** 
** Output arguments:
**   mat - A pointer to the matching switch, if exactly one switch
**     matched or if one of the matching switches was an exact match.
**   term - A pointer to the first character following the input that
**     was used to match switches, if any characters were left after
**     that input.  Otherwise, NULL is returned here.  (If the cmswend
**     character follows the switch, term will be left pointing at it.)
**
** Returns: The number of matching switches (or 1 for an exact match).
**/

static int
match(text,textlen,fdbp,mat,term)
char *text,**term;
int textlen;
fdb *fdbp;
swtch **mat;
{
  int mcount = 0;			/* number of matches seen */
  int inlen;				/* # of chars to match in input */
  int len;				/* size of switch without cmswend */
  int i;
  int exact = FALSE;			/* true if exact match occurs */
  swtch *swis;				/* for stepping through table */
  brktab *btab;				/* break table to use */
  switab *st;				/* switch table to search */
  
  if ((btab = fdbp->_cmbrk) == NULL)	/* get supplied break table */
    btab = &swibrk;			/* or use default */

  for (inlen = 0; inlen < textlen; inlen++) /* find # of usable chars */
    if (((text[inlen] & CC_CHR) == cmswend) || /* stop at special end char */
        BREAK(btab,text[inlen],inlen)	/* or first break char */
       )
      break;
  if (inlen == textlen)			/* no break char? */
    *term = NULL;			/* then set no terminator */
  else
    *term = text+inlen;			/* else point to it for caller */
  
  st = (switab *) fdbp->_cmdat;		/* point to switch table */
  swis = st->_stwds;			/* point to first switch */
  for (i = 0; i < st->_stcnt; i++) {	/* step through table */
    if (match1(swis->_swswi,text,inlen)) {
      swis->_swflg |= SWI_MAT;		/* this switch matches */
      if (!exact) {			/* if no prior exact match */
	*mat = swis;			/* then save pointer to return */
        mcount++;			/* and count it */
      }
      len = strlen(swis->_swswi);	/* get length of matching switch */
      if (swis->_swswi[len-1] == cmswend) /* but don't count ending punct */
	len--;
      if (len == inlen)
        if ((swis->_swflg & SWI_NOR) == 0) { /* and not ignored? */
	  exact = TRUE;			/* flag this exact match */
	  mcount = 1;			/* only one match now */
        }
    }
    else
      swis->_swflg &= ~SWI_MAT;		/* no match -- turn off flag */

    swis++;				/* move on to next switch */
  }
  return(mcount);			/* give back # of matches */
}



/* match1 - Auxiliary routine for match 
**
** Purpose:
**   Decides whether or not two strings match up to a given number of
**   characters.  Case of letters is ignored in the comparison.
**
** Input arguments:
**   s1, s2 - Pointers to the strings to be compared.
**   slen - Number of characters to compare.
**
** Output arguments: None.
** Returns: TRUE for a match, FALSE otherwise.
**/

static int
match1(s1,s2,slen)
char *s1,*s2;
int slen;
{
  char c1,c2;			/* individual chars to compare */
  while (slen-- > 0) {		/* step through strings */
    c1 = (*s1++) & CC_CHR;		/* pick up next pair of chars */
    c2 = (*s2++) & CC_CHR;
    if ((c1 >= 'a') && (c1 <= 'z'))
      c1 -= 'a'-'A';		/* upper case first char */
    if ((c2 >= 'a') && (c2 <= 'z'))
      c2 -= 'a'-'A';		/* upper case second char */
    if (c1 != c2)
      return(FALSE);		/* mismatch */
  }
  return(TRUE);			/* all chars matched */
}
