/* ------------------------------------------------------------------------ */
/*                                sstfield.c                                */
/*                    high-level formatted input routines                   */
/*                                                                          */
/*      CopyRight (C) 1991,1992  Steven Lutrov.   All rights reserved.      */
/* ------------------------------------------------------------------------ */
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <alloc.h>
#include <mem.h>
#include <string.h>
#include <conio.h>

#include "sstvid.h"
#include "sstwin.h"
#include "sstkey.h"
#include "ssthlp.h"

#define  _isalpha(c)   (isalpha(c) || isspace(c))
#define  _isalnum(c)   (isalnum(c) || isspace(c))

/* ------------------------------------------------------------------------ */
/*                             local prototypes                             */
/* ------------------------------------------------------------------------ */
static void  addfield           (WINDOW *wnd, FIELD *fld);
static void  dispfield          (WINDOW *wnd, char *bf, char *msk);
static void  datavalue          (WINDOW *wnd, FIELD *fld, int n);
static void  postprocess        (FIELD *fld);
static void  preprocess         (FIELD *fld, int *c, int p);
static void  insertstatus       (void);
static int   readfield          (WINDOW *wnd, FIELD *fld);
static void  rjust              (char *s);
static void  rjustzf            (char *s);
static int   esdefault          (int c);
static int   spaces             (char *c);
static int   testftype          (FIELD *fld, int c);
static void  processhelp        (FIELD *fld);
static int   esinputline        (int c);
static void  deferrorfunc       (char *s);


int    insertmode = FALSE;	            /* insert mode, TRUE/FALSE */
int    (*fend) (int) = esdefault;           /* field end keystroke func */
extern int helpkey;                         /* key to activate help */

/* ------------------------------------------------------------------------ */
/*                          initialize a template                           */
/* ------------------------------------------------------------------------ */
void Finittemplate(WINDOW *wnd)
{
	FIELD *fld, *fl;

	fld = FHEAD;
	while (fld)	{
		fl = fld->fnxt;
		free(fld);
		fld = fl;
	}
	FHEAD = NULL;
}

/* ------------------------------------------------------------------------ */
/*                    establish a field in a template                       */
/* ------------------------------------------------------------------------ */
FIELD *Festablish(WINDOW *wnd, int cl, int rw, char *msk,
		      char *bf, int ty, int flags)
{
    FIELD *fld;

    if ( (fld = malloc(sizeof(FIELD))) == NULL)
		return NULL;
    fld->fmask = msk;
    fld->frow = rw;
    fld->fcol = cl;
    fld->fbuff = bf;
    fld->ftype = ty;
    fld->fprot = 0;
    fld->fnxt = fld->fprv = NULL;
    fld->fvalid = NULL;
    fld->ferror = deferrorfunc;
    fld->fvalid = NULL;
    fld->helptype = HELP_KEYED;
    fld->fhwin = NULL;
    fld->fprevalid = NULL;
    fld->flx = fld->fly = 0;
    fld->fflags = flags;
    addfield(wnd, fld);
    return fld;
}

/* ------------------------------------------------------------------------ */
/*                  creates a single input line in a window                 */
/* ------------------------------------------------------------------------ */
char *Finputline(char *p, char *bf, int x, int y, int l, int t)

{
    WINDOW *wnd;
    char *msk, *buf;
    int c;

    if ((msk = malloc(l)) == NULL)
	return (NULL);
    if ((buf = malloc(l)) == NULL)
	return (NULL);
    memset(msk,'_',l);
    memset(buf,32,l);
    msk[l-1] = '\x0';
    memcpy(buf,bf,l);
    if ((wnd = Westablish(x, y, 3, strlen(p)+l+4)) == NULL)
	return (NULL);
    Wsetcolour(wnd, WIN_BORDER,  LIGHTGRAY, WHITE, BRIGHT);
    Wsetcolour(wnd, WIN_TITLE,   LIGHTGRAY, RED, DIM);
    Wsetcolour(wnd, WIN_FACE,    LIGHTGRAY, BLACK, DIM);
    Wsetcolour(wnd, WIN_ACCENT,  GREEN,     BLACK, DIM);
    Wsetcolour(wnd, WIN_FLDFACE, LIGHTGRAY, RED,  DIM);
    Wsettitle(wnd, "[Input line]",JUST_L);
    Wshow(wnd);
    Wprompt(wnd,0,0, p);

    Finittemplate(wnd);
    Festablish(wnd, strlen(p)+2,0, msk, buf, t,0);
    Fendfunc(esinputline);
    c = Fdataentry(wnd,1);
    Fendfunc(NULL);
    buf[l-1] = '\x0';
    Wdelete(wnd);
    return (c == ENTER ? buf : bf);
}

/* ------------------------------------------------------------------------ */
/*                   display all the fields in a window                     */
/* ------------------------------------------------------------------------ */
void Ftally(WINDOW *wnd)
{
	FIELD *fld;

	fld = FHEAD;
	while (fld != NULL)	{
		datavalue(wnd, fld,WFIELD);
		fld = fld->fnxt;
	}
}

/* ------------------------------------------------------------------------ */
/*                      set a field's help window                           */
/* ------------------------------------------------------------------------ */
void Fsethelpwin(FIELD *fld, char *hwin, int x, int y)
{
	fld->fhwin=hwin;
	fld->flx = x;
	fld->fly = y;
}

/* ------------------------------------------------------------------------ */
/*                      clear a template to all blanks                      */
/* ------------------------------------------------------------------------ */
void Fcleartemplate(WINDOW *wnd)
{
	FIELD *fld;
	char *bf, *msk;

	fld = FHEAD;
	while (fld != NULL)	{
		bf = fld->fbuff;
		msk = fld->fmask;
		while (*msk)	{
			if (*msk == FIELDCHAR)
				*bf++ = ' ';
			msk++;
		}
		fld = fld->fnxt;
	}
	Ftally(wnd);
}

/* ------------------------------------------------------------------------ */
/*                      clear a template to all nulls                       */
/* ------------------------------------------------------------------------ */
void Fnulltemplate(WINDOW *wnd)
{
	FIELD *fld;
	char *bf, *msk;

	fld = FHEAD;
	while (fld != NULL)	{
		bf = fld->fbuff;
		msk = fld->fmask;
		while (*msk)	{
			if (*msk == FIELDCHAR)
				*bf++ = 0x00;
			msk++;
		}
		fld = fld->fnxt;
	}
	Ftally(wnd);
}

/* ------------------------------------------------------------------------ */
/*                  Process data entry for a screen template.               */
/* ------------------------------------------------------------------------ */
int Fdataentry(WINDOW *wnd, int p)
{
   FIELD *fld;
   int exitcode, isvalid, dir = DOWN;
   int done=FALSE;
   int oldhelpkey = helpkey;


   Ftally(wnd);
   fld = FHEAD;

   /* ---- collect data from keyboard into screen ---- */

   while (fld != NULL && done == FALSE)	{
	Hset(fld->fhwin, fld->flx, fld->fly);
	helpkey = (fld->fhelp) ? 0 : oldhelpkey;
	Wcursor(wnd, fld->fcol, fld->frow);
	if (fld->fprot == FALSE)	{
	    Wrevvideo(wnd);
	    datavalue(wnd, fld,WACCENT);
	    Wcursor(wnd, fld->fcol, fld->frow);
	    exitcode = readfield(wnd, fld); /* process */
	    isvalid = (exitcode != ESC && fld->fvalid) ?
				(*(fld->fvalid))(fld->fbuff) : OK;
	}
	else	{   /* protected field */
	     switch (dir) {
	       case   DOWN  : exitcode = RIGHT;
			      break;
	       case   UP    : exitcode = LEFT;
			      break;
	     }
	     isvalid = OK;
	}
	if (isvalid == OK)	{
	   Wresetvideo(wnd);
	   datavalue(wnd, fld,WFIELD);
	   switch (exitcode)	{		/* passed edit */
	     case F1     : if (fld->fhelp)	{
			     (*(fld->fhelp))(fld->fbuff);
			     datavalue(wnd, fld,WFIELD);
			     }
			   break;
	     case DOWN   :
	     case '\r'   :
	     case '\t'   :
	     case RIGHT  :
			   dir = DOWN;
			   if ( (exitcode == '\r')  && (p) )
				done = fend(exitcode);
			   else {
			     fld = fld->fnxt;
			     if (fld == NULL)
				fld = FHEAD;
			     processhelp(fld);
			   }
			   break;
	     case UP     :
	     case LEFT   : dir = UP;
			   fld = fld->fprv;
			   if (fld == NULL)
				fld = FTAIL;
			   processhelp(fld);
			   break;
	     case PGUP   :
	     case HOME   : dir = UP;
			   if (p)
				done = fend(exitcode);
			   else {
			     fld = FHEAD;
			     processhelp(fld);
			   }
			   break;

	     case PGDN   :
	     case END    : dir = DOWN;
			   if (p)
				done = fend(exitcode);
			   else {
			     fld = FTAIL;
			     processhelp(fld);
			   }
			   break;

	     default     : done = fend(exitcode);
			   break;
		}
	}
  }
  helpkey = oldhelpkey;
  return (exitcode);
}

/* ------------------------------------------------------------------------ */
/*                  View  data entry for a screen template.                 */
/* ------------------------------------------------------------------------ */
int Fdataview(WINDOW *wnd)
{
   int c;

   vhidecur();
   Ftally(wnd);
   c = kgetch();
   vshowcur();
   return (c);
}

/* ------------------------------------------------------------------------ */
/*                          display a window prompt                         */
/* ------------------------------------------------------------------------ */
void Wprompt(WINDOW *wnd, int x, int y, char *s)
{
	Wcursor(wnd, x, y);
	Wprintf(wnd, s);
}

/* ------------------------------------------------------------------------ */
/*                      set the help function for a field                   */
/* ------------------------------------------------------------------------ */
void Fhelpfunc(FIELD *fld, void (*func)(char *), int w)

{
  fld->fhelp = func;
  fld->helptype = w;
}

/* ------------------------------------------------------------------------ */
/*                      set the error function for a field                  */
/* ------------------------------------------------------------------------ */
void Ferrorfunc(FIELD *fld, void (*func)(char *))

{
  fld->ferror = func;
}

/* ------------------------------------------------------------------------ */
/*                      set the help function for a field                   */
/* ------------------------------------------------------------------------ */
void Fendfunc(int (*func) (int))

{
  if (func)
      fend = func;
   else
      fend = esdefault;
}

/* ------------------------------------------------------------------------ */
/*                   add a field to the end of the list                     */
/* ------------------------------------------------------------------------ */
static void addfield(WINDOW *wnd, FIELD *fld)
{
	if (FTAIL)	{
		fld->fprv = FTAIL;
		FTAIL->fnxt = fld;
	}
	FTAIL = fld;
	if (!FHEAD)
		FHEAD = fld;
}

/* ------------------------------------------------------------------------ */
/*                          display a data field                            */
/* ------------------------------------------------------------------------ */
static void dispfield(WINDOW *wnd, char *bf, char *msk)
{
     while (*msk)	{
	Wputch(wnd, *msk != FIELDCHAR ? *msk : *bf++);
	msk++;
     }
}
/* ------------------------------------------------------------------------ */
/*                          display a data field                            */
/* ------------------------------------------------------------------------ */
static void datavalue(WINDOW *wnd, FIELD *fld, int n)
{

     char *msk = fld->fmask;
     char *bf = fld->fbuff;
     int x = fld->fcol+1;

     while (*msk)	{
	displ(wnd, x++, fld->frow+1, *msk != FIELDCHAR ? *msk : *bf++, n);
	msk++;
     }
}

/* ------------------------------------------------------------------------ */
/*                     set insert/exchange cursor shape                     */
/* ------------------------------------------------------------------------ */
static void insertstatus(void)
{
   vsetcurtype(insertmode ? 0x0106 : 0x0607);
}

/* ------------------------------------------------------------------------ */
/*     process field according to flags after the field edit is complete    */
/* ------------------------------------------------------------------------ */
static void postprocess(FIELD *fld)

{
    if (fld->fflags & FLD_ZFILL)
	    rjustzf(fld->fbuff);
    else
      if (fld->fflags & FLD_RJUST)
	    rjust(fld->fbuff);
}

/* ------------------------------------------------------------------------ */
/*          process user input according to flags as they are keyed in      */
/* ------------------------------------------------------------------------ */
static void preprocess(FIELD *fld, int *c, int p)

{
    char *bf = fld->fbuff;

    if (fld->fprevalid != NULL)
	       fld->fprevalid(c);
    if (fld->fflags & FLD_TOUPPER)
	       *c = toupper(*c);
    if (fld->fflags & FLD_TOLOWER)
	       *c = tolower(*c);
    if (fld->fflags & FLD_TOPROPER) {
	     if (p == fld->fcol)    /* first char in field */
		   *c = toupper(*c);
	     if (isspace( *(bf+(p-fld->fcol-1))) )
		   *c = toupper(*c);
	    }
}

/* ------------------------------------------------------------------------ */
/*                     read a field from the keyboard                       */
/* ------------------------------------------------------------------------ */
static int readfield(WINDOW *wnd, FIELD *fld)
{
    char *mask = fld->fmask;
    char *buff = fld->fbuff;
    int done = FALSE, c, column;

    column = fld->fcol;
    while (*mask != FIELDCHAR)	{
	column++;
	mask++;
    }
    while (TRUE)	{
	Wcursor(wnd, column, fld->frow);
	c = kgetch();
	if (c < 0x80)
	      preprocess(fld,&c,column);
	(fld->ferror)(NULL); /* clear error function text */
	switch (c)	{
	     case '\b':
	     case LEFT:
			if (buff == fld->fbuff)	{
			  done = c == LEFT;
			  break;
			}  /* left */
			--buff;
			do	{
				--mask;
				--column;
			} while (*mask != FIELDCHAR);
			if (c == LEFT)
			break;
	     case DEL:
			movmem(buff+1, buff, strlen(buff));
			*(buff+strlen(buff)) = ' ';
			Wcursor(wnd, column, fld->frow);
			dispfield(wnd, buff, mask);
			break;
	     case RIGHT:
			do	{
				column++;
				mask++;
			} while (*mask && *mask != FIELDCHAR);
			buff++;
			break;
	     case INS:
			insertmode ^= TRUE;
			insertstatus();
			break;
	     case '.':
			if (fld->ftype == FLD_CURR)	{
				if (*mask++ && *buff == ' ')	{
					*buff++ = '0';
					if (*mask++ && *buff == ' ')
						*buff++ = '0';
					}
			    rjust(fld->fbuff);
			    Wcursor(wnd, fld->fcol, fld->frow);
			    dispfield(wnd, fld->fbuff, fld->fmask);
			    column = fld->fcol+strlen(fld->fmask)-2;
			    mask = fld->fmask+strlen(fld->fmask)-2;
			    buff = fld->fbuff+strlen(fld->fbuff)-2;
			    break;
			}
	     default:
			if (fend(c))	{
				done = TRUE;
				break;
			}
			if (! testftype(fld,c) )
			      break;
			if (insertmode)	{
				movmem(buff, buff+1, strlen(buff)-1);
				dispfield(wnd, buff, mask);
				Wcursor(wnd, column, fld->frow);
			}
			*buff++ = c;
			Wputch(wnd, c);
				do	{
					column++;
					mask++;
				} while (*mask && *mask != FIELDCHAR);
			if (!*mask)
			c = RIGHT;
			break;
		}   /* switch */
		if (!*mask)
			done = TRUE;
		if (done)
			break;

	}
	if (c != ESC ) 	{
		if (fld->ftype == FLD_CURR) {
			if (*mask++ && *buff == ' ') {
				*buff++ = '0';
				if (*mask++ && *buff == ' ')
					*buff++ = '0';
				}
			}
	if (c < 0x80)
	       postprocess(fld);
	 Wcursor(wnd, fld->fcol, fld->frow);
	 dispfield(wnd, fld->fbuff, fld->fmask);
	}  /* c != ESC */
	return c;
}

/* ------------------------------------------------------------------------ */
/*                       test for a correct field type                      */
/* ------------------------------------------------------------------------ */
static int testftype(FIELD *fld, int c)

{
   int s = 1;
   int f = fld->ftype;

   if (c > 0x80)
	  return(1);
   switch (f) {
       case FLD_CURR   :
       case FLD_DIGIT  :
       case FLD_DATE   :
       case FLD_INT    : if (! (s = isdigit(c) ))
			    (fld->ferror)("Numeric char only");
			 break;
       case FLD_ALNUM  : if (! (s = _isalnum(c) ))
			    (fld->ferror)("Alpha numeric char only");
			 break;
       case FLD_ALPHA  : if (! (s = _isalpha(c)))
			    (fld->ferror)(" Alpha char only ");
			 break;
       case FLD_ASCII  : if (! (s = isascii(c) ))
			    (fld->ferror)(" Ascii char only ");
			 break;
       case FLD_PRINT  : if (! (s = isprint(c) ))
			    (fld->ferror)("Printable char only");
			 break;
       case FLD_XDIGIT : if (! (s = isxdigit(c) ))
			    (fld->ferror)(" Hexadecimal only ");
			 break;
       }
   return(s);
}

/* ------------------------------------------------------------------------ */
/*                     test for an ending keystroke                         */
/* ------------------------------------------------------------------------ */
static int esdefault(int c)
{
	switch (c)	{
		case '\r':
		case '\n':
		case '\t':
		case ESC:
		case F1:
		case F2:
		case F3:
		case F4:
		case F5:
		case F6:
		case F7:
		case F8:
		case F9:
		case F10:
		case PGUP:
		case PGDN:
		case HOME:
		case END:
		case UP:
		case DOWN:
			return TRUE;
		default:
			return FALSE;
	}
}

/* ------------------------------------------------------------------------ */
/*                          right justify, space fill                       */
/* ------------------------------------------------------------------------ */
static void rjust(char *s)
{
	int len;

	len = strlen(s);
	while (*s == ' ' || *s == '0' && len)	{
		len--;
		*s++ = ' ';
	}
	if (len)
		while (*(s+(len-1)) == ' ')	{
			movmem(s, s+1, len-1);
			*s = ' ';
		}
}

/* ------------------------------------------------------------------------ */
/*                         right justify, zero fill                         */
/* ------------------------------------------------------------------------ */
static void rjustzf(char *s)
{
	int len;

	if (spaces(s))
		return;
	len = strlen(s);
	while (*(s + len - 1) == ' ')	{
		movmem(s, s + 1, len-1);
		*s = '0';
	}
}

/* ------------------------------------------------------------------------ */
/*                          test for spaces                                 */
/* ------------------------------------------------------------------------ */
static int spaces(char *c)
{
	while (*c == ' ')
		c++;
	return !*c;
}

/* ------------------------------------------------------------------------ */
/*                                                                          */
/* ------------------------------------------------------------------------ */
static void processhelp(FIELD *fld)

{
   if (fld->fhelp == NULL)
       return;
   if ( ((fld->helptype & HELP_INITIAL) || (fld->helptype & HELP_ALWAYS))
	&& !(fld->helptype & HELP_DONE) )  {
	       (*(fld->fhelp))(fld->fbuff);
	       if (fld->helptype & HELP_INITIAL)
		      fld->helptype |= HELP_DONE;  /* set as done ! */
	    }
}

/* ------------------------------------------------------------------------ */
/*                default endstroke used for single input line              */
/* ------------------------------------------------------------------------ */
static int esinputline(int c)
{
    switch (c)	{
	case '\r':
	case '\n':
	case ESC:
		return TRUE;
	default:
		return FALSE;
	}
}

static WINDOW *defewnd = NULL;
/* ------------------------------------------------------------------------ */
/*                 set the default error function for fields                */
/* ------------------------------------------------------------------------ */
static void deferrorfunc(char *s)

{
    if (s == NULL)   {
	      if (defewnd)  {
			Wdelete(defewnd);
			defewnd = NULL;
	      }
    }
    else  {
       defewnd = Westablish(50, 22, 3, max(10, strlen(s)+2));
       Wsetcolour(defewnd, WIN_ALL, RED, WHITE, BRIGHT);
       Wsettitle(defewnd, "[Input Error]",JUST_C);
       Wshow(defewnd);
       Wprintf(defewnd, s);
       vbeep(ERROR);
   }
}

