/*	(c) Marietta Systems, Inc  1987
	All rights reserved */
/*
*	This program allows the viewing, amending, deleting and adding of
*	records to a dBase II Plus '.dbf' file.
*
*	This program only works with files with less than 21 fields!
*/
#include "mtest.h"
#ifndef LATTICE
#define memcpy memmove
#endif
/* */
#define fileend (FN[fh].start + FN[fh].rec_len * FN[fh].prime)
int  dbf_acpt(int, int, struct DBF_DEF*, enum ATTRIB);
int  dbf_disp(int, int, struct DBF_DEF*, enum ATTRIB, int, int);
int  dbf_init(int*, struct DBF_DEF**);
void dbf_mnt(int, int, struct DBF_DEF*, long*);
void do_search(int, char*, long*);
void mnt_scrn(int, char*);
void recblank(int);
int  rec_acpt(int, int, struct DBF_DEF*);
void rec_disp(int, int, struct DBF_DEF*);
void show_nbr(int, long);
int  stsearch(char*, char*);
/*
*
*	Function to accept a dbase3 field
*
*/
int dbf_acpt(fh, field, format, atb)
int fh, field;
struct DBF_DEF *format;
enum ATTRIB atb;{
	int z, basept, ret, x = _CURSOR.X, y = _CURSOR.Y;
	enum _JUST just;
	char text[255];
  /*  Error check */
	if (fh < 5 || fh > MAXFILES || field < 0 
	    || field > FN[fh].start/32 - 2)
	    {disp_err("Parameter error in dbf_acpt", 1001); return -10;}
  /*  Setup */
	for (z = 0 , basept = 1 ; z < field ; basept += format[z++].dbf_len);
	memcpy(text, &FN[fh].record[basept], format[field].dbf_len); 
	text[format[field].dbf_len] = 0; /* copy to entry area */
	switch ((int)format[field].dbf_type){
	    case 'C': just = left; break;
	    case 'L': just = left; break;
	    case 'N': just = decimal; break;
	    case 'D': just = date;
	        if (text[0] == 32) 
	            {text[10] = -80; text[11] = text[12] = 0;}
  		    else {text[12] = (char)atoi(&text[6]);  text[6] = 0;
		        text[11] = (char)atoi(&text[4]);  text[4] = 0;
		        text[10] = (char)(atoi(text) - 1980);}
	        memcpy (text, &text[10], 3);
	        break;
	    default: disp_err("Cannot edit memo field", 1002); 
	        return (INCHAR == ENTER ? 0 : 1);
	    }
  /*  perform accept */
	re_enter: ret = accept(text, just, atb, format[field].dbf_dig, 
		format[field].dbf_dec);
	if (ret < 0) return ret;  /* Pass through error code from accept */
	if (ret && INCHAR == CTL_ENT) return ret;
	switch ((int)format[field].dbf_type){
	    case 'C': break;
	    case 'L': text[0] = toupper(text[0]); 
	        if (text[0] != 'T' && text[0] != 'F')
	            {disp_err("Logical field type requires 'T' or 'F'", 1);
	            set_crsr(x,y); goto re_enter;}
	        display(text, x, y, ACC_DISP);
	        break;
	    case 'N': break;
	    case 'D': memcpy(&text[10], text, 3);
	        if (text[1] != 0) sprintf(text, "%4u%2u%2u", 
	            1980 + text[10], (int)text[11], (int)text[12]);
		    else strcpy(text, "        ");
	    }
	memcpy(&FN[fh].record[basept],text, format[field].dbf_len);
	return ret;
	}	/* end of function dbf_acpt */
/*
*
*	Function to display a dbase3 field
*
*/
int dbf_disp(fh, field, format, atb , x, y)
int fh, field, x, y;
struct DBF_DEF *format;
enum ATTRIB atb;{
	int z, basept;
	char text[255];
  /*  Error check */
	if (fh < 5 || fh > MAXFILES || field < 0 
	    || field > FN[fh].start/32 - 2)
	    {disp_err("Parameter error in dbf_disp", 1001); return -10;}
  /*  Setup */
	for (z = 0 , basept = 1 ; z < field ; basept += format[z++].dbf_len);
	memcpy(text, &FN[fh].record[basept], format[field].dbf_len); 
	text[format[field].dbf_len] = 0;
	switch ((int)format[field].dbf_type){
	    case 'N': strcpy(&text[60], text);
	        justify(decimal, text, &text[60], format[field].dbf_dig,
	            format[field].dbf_dec); 
	        break;
	    case 'D': if (text[0] == 32)  /* Null date */
		    {text[10] = -80; text[11] = text[12] = 0;}
  		    else {text[12] = (char)atoi(&text[6]);  text[6] = 0;
		        text[11] = (char)atoi(&text[4]);  text[4] = 0;
		        text[10] = (char)(atoi(text) - 1980);}
	        date_out(text, &text[10]);
	        break;
	    default: break;
	    }
  /*  perform display */
	display(text, x, y, atb);
	return 0;
	}	/* end function dbf_disp */
/*
*
*		Function to read in file and data dictionary
*
*/
int dbf_init(fields, format)
int *fields;
struct DBF_DEF **format;{
	int fh, z;
	char name[51], text[25];
   	for (z = 0; z < 8 ; KEYMATCH[z++] = 0); /* suppress function keys */
    disp_err("",1); /* force display of function keys */
  /* test if already initialized */
	strcpy(name,"dbftest.dbf");
  /* obtain file name  and setup format */
	for (;;){
	    disp_msg("",0); 
	    display("Enter dBase 3+ file name  ", 20, 1, high);
	    if (accept(name, left, alt_reverse, 50, 0) < 0 || INCHAR == QUIT)
	        goodbye(0);
	    if ((fh = fileopen(name, dbase3, update)) < 0) continue;
	    if (!fh) {disp_err("File not found - try again", 1); continue;}
	    if (FN[fh].start < 65) 
	        {disp_err("Not valid dBase3+ file",1); idleloop(18);} 
	    if (FN[fh].fnext == NULL) 
	        disp_err("Could not load data dictionary",1);
	    if (FN[fh].start < 65 || FN[fh].fnext == NULL) 
	        {fileclos(fh); continue;} 
	    break;
	    } /* end for loop */
	mk_wndw(5,15, 15,65, "Display of file header information");
	display((byte*)FN[fh].fname, 1, 2, high);
	sprintf(text, "prime  = %ld", FN[fh].prime); 
	display(text, 2, 2, high);
	sprintf(text, "start  = %d", FN[fh].start); 
	display(text, 3, 2, high);
	sprintf(text, "rec_len= %d", FN[fh].rec_len); 
	display(text, 4, 2, high);
	sprintf(text, "write  = %c", FN[fh].write); 
	display(text, 5, 2, high);
	sprintf(text, "ftype  = %d", (int)FN[fh].ftype); 
	display(text, 6, 2, high);
	if (FN[fh].fnext == NULL) 
		display("NULL pointer value in fnext", 7, 2, high);
		else display("Data dictionary loaded", 7, 2, high);
	display("<< Press ESC key to continue >>", 9, 5, low);
	while (grabchar() != ESC) disp_err("Press ESC key", 1);
	rm_wndw(); 	
	*fields = dbf_fld(fh);
	*format = (struct DBF_DEF *)FN[fh].fnext;
	return fh;
	}	/* end function dbf_init */
/*
*
*		Function to initialize the screen
*
*/
void mnt_scrn(fh, title)
int fh;
char *title;{  
  /* setup function key map */
	KEYMATCH[0] = 0X428; /* PgDn, Up arrow, Ctrl+Enter */
	KEYMATCH[1] = 0X104; /* PgUp, Down arrow */
	KEYMATCH[2] = KEYMATCH[3] = KEYMATCH[6] = KEYMATCH[5] = 0;
	KEYMATCH[4] = 0XB400; /* F3, F5, F6, F8 */
	KEYMATCH[7] = 0X4800;   /* Ctrl+PgDn, Ctrl+PgUp */
	disp_err("",1); /* force display of new function keys */
  /* Create info area at top of screen */
	display("\030:Prev field     PgUp:Prev Record     Ctrl+PgUp:1st Record ",
		1, 2, low);
	display((byte*)FN[fh].fname, 1, 65, alt_low);
	display("\031:Next field     PgDn:Next Record     Ctrl+PgDn:Last Record",
		2, 2, low);
  /* create edit window */
	if (mk_wndw(TOP_LINE + 3, 1, SCRN_LEN, SCRN_WID, title) < 0) 
	    goodbye(10);
	}  /* end function mnt_scrn */
/*
*
*		Function to display a record on the screen 
*
*/
void rec_disp(fh, fields, format)
int fh, fields;
struct DBF_DEF *format;{
	int x = 1, z, len;
	clr_wndw();
	if (FN[fh].record[0] != IN_USE) 
	    display("Record status is deleted", x++, 5, blink); 
	for (z = 0 ; z < fields ; z++){
	    display(format[z].dbf_name, x, 1, high);
	    dbf_disp(fh, z, format, reverse, x, 12);
	    len = (format[z].dbf_type != 'N' ? format[z].dbf_len :
	        fld_len(decimal, format[z].dbf_dig, format[z].dbf_dec));
	    x += len / (SCRN_WID - 12) + 1;
	    }
	}	/* end function rec_disp */
/*
*
*		Function to accept a record on the screen 
*
*/
int rec_acpt(fh, fields, format)
int fh, fields;
struct DBF_DEF *format;{
	int x = 1, z, len, kk;
	if (FN[fh].record[0] != IN_USE) x++; 
	for (z = 0 ; z < fields ; ){
	    set_crsr(x, 12);
	    if (dbf_acpt(fh, z, format, alt_reverse)) kk = INCHAR; else kk = 0;
	    switch (kk){
		case 0: 
	        case ENTER: 
	        case CRS_DN: 
		    len = (format[z].dbf_type != 'N' ? format[z].dbf_len :
	               fld_len(decimal, format[z].dbf_dig, format[z].dbf_dec));
		    x += len / (SCRN_WID - 12) + 1;
		    z++; break;
		case CTL_ENT: 
		case CRS_UP: if (z == 0) break;
		    len = (format[z-1].dbf_type != 'N' ? format[z-1].dbf_len :
	                fld_len(decimal, format[z-1].dbf_dig,
	                format[z-1].dbf_dec));
		    x -= len / (SCRN_WID - 12) + 1;
		    z--; break;
		default: return kk;		
		}
	    }
	return 0;
	}	/* end function rec_acpt */
/*
*
*       Function to show current record location on screen
*			(uses reserved functions 'set_clr' and 'scrn_map'
*/
void show_nbr(fh, rec_nbr)
int fh;
long rec_nbr;{
	char text[16];
	set_clr(99, low); /* use error status color */
	sprintf(text, "%5lu of %-5lu", rec_nbr, FN[fh].prime);
	scrn_map(text, TOP_LINE + 2, 65); /* hairy technique */
	set_clr(98, low); /* reset color */
	}
/*
*
*		Function to blank out a record
*
*/
void recblank(fh)
int fh;{
	memset(FN[fh].record, 32, FN[fh].rec_len); 
	FN[fh].record[FN[fh].rec_len] = 0;
	FN[fh].record[0] = IN_USE;
	}
/*
*
*	Find if string2 is in string1
*
*/
int stsearch(s1, s2)
char *s1, *s2;{
	int len2 = strlen(s2);
	char *ptr;
	ptr = strchr(s1, s2[0]);
	while (ptr != NULL) {
		if (!strncmp(ptr, s2, len2)) return 1;  /* found */
		ptr = strchr(&ptr[1], s2[0]);
		}
	return 0;
	}
/*
*
*		Function to search for a specified string
*
*/
void do_search(fh, text, rec_nbr)
char *text;
int fh;
long *rec_nbr;{
	int count = 0;
	long base = *rec_nbr;
	char out[15];
  /* setup */
	if (mk_wndw(5,7, 11,73, "File search through dbase3+ file") < 0) 
	    return; 
	display("Remember dates are stored as yyyymmdd ('19801231' for 12/31/80)",
	    5, 2, low);
  /* obtain search text */
	display("Enter search text ", 1, 1, high);
	if (accept(text, left, alt_reverse, 16, 0)) 
	     {disp_err("Search abandoned",1); rm_wndw(); return;}
	concat(text, 0); /* suppress trailing spaces */
	strupr(text);
  /* read around through the file */
	for (;;) { 
	    if (!fileread(fh, nextrec, rec_nbr)) 
		fileread(fh, firstrec, rec_nbr);
	    if (*rec_nbr == base) {disp_err("No matching text found", 1);
		warble(500); idleloop(5); rm_wndw(); warble(0); return;}
	    if (5 == (count++)) { 
	        sprintf(out,"Record = %6lu", *rec_nbr);
	        display(out, 2, 5, reverse);
	        if (kbhit() && disp_qry("Do you wish to stop the search"))
	            {rm_wndw(); return;}
	        count = 0;
		}
		strupr(&FN[fh].record[1]);
	    if (stsearch(&FN[fh].record[1], text))
	        {disp_err("Match found", 1); *rec_nbr -= 1L;
	        rm_wndw(); return;}
	     }
	}				
/*
*
*		Function to perform file maintenance
*
*/
void dbf_mnt(fh, fields, format, rec_nbr)
int fh, fields;
long *rec_nbr;
struct DBF_DEF *format;{
	int ret;
	byte kk = 0;
	char text[17];
  /* setup */
	mnt_scrn(fh, "Edit function for dBase3+ files");
	if ((ret = fileread(fh, firstrec, rec_nbr)) < 0) goodbye(3);
	show_nbr(fh, *rec_nbr - 1L);
	text[0] = 0;
	if (!ret) kk = INSERT;	/* Handle special case of empty file */
  /* main accept loop */
	for (;;){
	    if (!ret && !kk) recblank(fh);
	    if (!kk) {rec_disp(fh, fields, format); scrnsave(0);}
	    switch((kk ? kk : grabchar())){
		case PGUP: 
	        case CTL_ENT: kk = 0; *rec_nbr -= 2L;
	            if (*rec_nbr < 1L) 
	                {*rec_nbr = 1L; disp_err("Top of file",1);} 
	            break;
		case PGDN: 
	        case ENTER: kk = 0; 
	            if (*rec_nbr > FN[fh].prime) 
	                {*rec_nbr = FN[fh].prime; disp_err("End of file", 1);}
	            break;
		case CTL_PGUP: *rec_nbr = 1L; kk = 0; break;
		case CTL_PGDN: *rec_nbr = FN[fh].prime; kk = 0; break;
		case UNDO: *rec_nbr -= 1L; 
		    if (FN[fh].record[0] != DELETED ||
	                !disp_qry("Do you want to UNDO the delete")) 
	                {kk = 0; break;}	
	            FN[fh].record[0] = IN_USE;
		    if (filewrit(fh, rec_nbr) < 0) goodbye(6);
		    kk = 0; *rec_nbr -= 1L; break;
		case SEARCH: do_search(fh, text, rec_nbr);
	            kk = 0; break;
		case INSERT: fileseek(fh, fileend); 
	            *rec_nbr = FN[fh].prime + 1L;
	            recblank(fh);
	 	    rec_disp(fh, fields, format);
		    kk = rec_acpt(fh, fields, format);
		    if (filewrit(fh, rec_nbr) < 0) goodbye(7);
		    if (!kk && *rec_nbr > FN[fh].prime) kk = INSERT;
		    break;
		case DELETE: *rec_nbr -= 1L; kk = 0;
	            if (!disp_qry("Do you really want to delete")) break;
	            FN[fh].record[0] = DELETED;
		    if (filewrit(fh, rec_nbr) < 0) goodbye(8);
		    *rec_nbr -= 1L; break;
		case AMEND: *rec_nbr -= 1L;
	            if (FN[fh].record[0] != IN_USE) 
	                {disp_err("You cannot amend a deleted record",1);
	                kk = 0; break;}
		    kk = rec_acpt(fh, fields, format);
		    if (filewrit(fh, rec_nbr) < 0) goodbye(9);
		    break;
		case QUIT: return;
		case HELP: helpscrn(NULL); 
		default: disp_msg("",0); *rec_nbr -= 1L; kk = 0; 
	            warble(1000); idleloop(ERR_BEEP); warble(0);
	            break;
		} /* end switch */
	    if (kk && *rec_nbr) *rec_nbr -= 1L;
	    if ((ret = fileread(fh, relative, rec_nbr)) < 0) goodbye(5);
	    if (!ret) {disp_err("End of file reached",1);
	    ret = fileread(fh, lastrec, rec_nbr);}
	    show_nbr(fh, *rec_nbr - 1L);
	    } /* end for loop */
	}	/* end of dbf_mnt function */
/*
*
*		Main program 
*
*/
void main(){
struct DBF_DEF *format;
long rec_nbr = 1L;
int fh, fields;
clr_scrn("Test of dbf access");
if ((fh = dbf_init(&fields, &format)) < 1)	goodbye(2);
dbf_mnt(fh, fields, format, &rec_nbr);
fileclos(fh); 
goodbye(0);
}