/*************************************************************************
	This program is a demonstration of using arrays of structures
	for such tasks as screen painting, field definition, and data file
	searching.

	This program is submitted as public domain.  It's basic concept is
	to communicate some uses of data structures and re-useable coding.
	This program (along with other BTree and similiar structure based
	programs) is the result of a C programming part II class I taught
	at a local university.  Several of my students have stated how much
	easier project developement was with routines similiar to these.

	Mario Giannini
	Compuserve #76276,1576
	January 25, 1991
*************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <io.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <dos.h>
#include <malloc.h>   /* alloc.h for TurboC */
#include <string.h>
/*************************************************************************/
                 /* Key macros to make life easier   */
#define   ESC      0x1B
#define   F10      0x4400
#define   CURUP    0x4800
#define   CURDN    0x5000
#define   BACKSPACE 0x0008
#define   ENTER     0x000D

#define  TextColor 7
#define  DataColor 0xF

#define INDEXFLD 0x0001
                         /* Here are our basic data structure definitions */
typedef struct {
	int H, V, Len, Option, Type, FieldNum;
	char *Text, *Data;
	} FIELDS;

typedef struct {
	int Handle, RecSize;
	FIELDS *EditFields;
	char *Filename, *RecSpace;
	long Recpos;
	} RECORD;

/*************************************************************************/
/* Here are data declarations, it should be noted that the record types
**      can be changed here, and upon re-compile all of the code should
**      still work as always, interpreting the structures themselves
**/

FIELDS PersonFields[]={
	   0, 0, 10, 0, 0,        0, "First",   NULL,
	  25, 0, 10, 0, INDEXFLD, 0, "Last",    NULL,
	   0, 2, 35, 0, 0,        0, "Company", NULL,
	   0, 3, 35, 0, 0,        0, "Comment", NULL,
	   0, 0,  0, 0, 0, 0, NULL,      NULL};  /* NULL for Text is terminator */

RECORD Person={0, 0, PersonFields, "PERSON.DAT", NULL, 0L};

 FIELDS TransFields[]={
	   0, 0,  8, 0,   0, 0, "Date",   NULL,
	  25, 0, 10, 0,   0, 0, "Amount", NULL,
		0, 0,  0, 0,   0, 0, NULL,     NULL};  /* NULL for Text is terminator */
RECORD Transaction={0, 0, TransFields, "TRANS.DAT", NULL,0L};

/*************************************************************************/
/* Sort of a 'main menu' of tables we can edit */
RECORD *AllRecords[]={&Person, &Transaction, NULL};
/*************************************************************************/
/****      Function prototypes   ***/
void  main(void);
void AddItem(RECORD *Record);
void EditItem(RECORD *Record);
int  EditRecord(RECORD *Record);
void ShowScreen(RECORD *Record);
void ShowData(RECORD *Record);
void Cls(int  Color);
void Locate(int  H,int  V);
void PrintStr(char  *Str,int  Color);
void Message(char  *Str,int  Wait);
int  MyGets(char  *Dest,int  Len);
int  GetKey(void);
int  AppendRecord(RECORD *Record);
int  LocateRecord(RECORD *Rec);
int  ReadRecord(RECORD *Record);
int  WriteRecord(RECORD *Record);
void BlankRecord(RECORD *Record);
int  OpenFile(RECORD *Record);
void CloseFile(RECORD *Record);
/*************************************************************************/
/***  Start of code section                                            ***/
/***                                                                   ***/
/*************************************************************************/
void main(void)
{
	int i, NumOfRecs, Ch=0, Ch2;

	while(Ch!=ESC)
	{
		Cls(7);
		for(NumOfRecs=0;AllRecords[NumOfRecs];NumOfRecs++)
			printf("%d: %s\n", NumOfRecs+1, AllRecords[NumOfRecs]->Filename);
		printf("\nESC to quit.\n\nPlease press a key..");
		do {
			Ch=GetKey();
		} while(Ch!=ESC && (Ch<'0'||Ch-'1'>NumOfRecs));
		if(Ch==ESC)
			continue;
		Cls(7);
		printf("You Selected %s\n\n", AllRecords[Ch-'1']->Filename);
		printf("A) Add\nE) Edit\n\nESC for previous menu\n\nMake selection:");
		do {
			Ch2=getch();
			Ch2=toupper(Ch2);
		} while( Ch2!=ESC && Ch2!='A' && Ch2!='E') ;

/*
** Note how the AllRecord array is used to process table based on user
**      selection.
*/
		if(Ch2=='A')
			AddItem(AllRecords[Ch-'1']);
		if(Ch2=='E')
			EditItem(AllRecords[Ch-'1']);
	}
	
}
/*************************************************************************/
/***  Higher-level functions                                           ***/
/***                                                                   ***/
/*************************************************************************/
void AddItem(RECORD *Record)
{
	Cls(7);
	OpenFile(Record);
	ShowScreen(Record);
	BlankRecord(Record);
	Message("Press ESC to Abort, F10 to Add",0);
	if(EditRecord(Record)==F10)
		AppendRecord(Record);
	CloseFile(Record);
	Message("", 0);
}
/*************************************************************************/
void EditItem(RECORD *Record)
{
	Cls(7);
	OpenFile(Record);
	ShowScreen(Record);
	BlankRecord(Record);
	Message("Press ESC to Abort, F10 to start search",0);
	if(EditRecord(Record)==F10)
	{
		if(LocateRecord(Record))
		{
			Message("Press ESC to abort, F10 to save updates", 0);
			ReadRecord(Record);
			ShowData(Record);
			if(EditRecord(Record)==F10)
				WriteRecord(Record);
		}
		else
			Message("Criteria not found. Press any key to continue",1);
	}
	CloseFile(Record);
	Message("",0);
}
/*************************************************************************/
/***  ScreenI/O section follows                                        ***/
/***                                                                   ***/
/*************************************************************************/
int EditRecord(RECORD *Record)   /* Edits a Record  */
{
	int i=0, Ch=0, LastEntry=0;

/*
** Note how this founction interprets whatever structures are passed
**     to it at run-time.  Not much is hard-coded.
*/
	for(LastEntry=0;Record->EditFields[LastEntry].Text;LastEntry++)
		;
	while(Ch!=ESC && Ch!=F10)
	{
		Locate(Record->EditFields[i].H+strlen(Record->EditFields[i].Text)+1,
			Record->EditFields[i].V);
		Ch=MyGets(Record->EditFields[i].Data, Record->EditFields[i].Len);
		if( (Ch==ENTER||Ch==CURDN) && ++i>=LastEntry)
			i=0;
		if(Ch==CURUP && --i<0)
			i=LastEntry-1;
	}
	return(Ch);
}
/****************************************************************/
void ShowScreen(RECORD *Record)   /* Show Screen layout Data  */
{
	int i=0;

	for(i=0;Record->EditFields[i].Text;i++)
	{
		Locate(Record->EditFields[i].H, Record->EditFields[i].V);
		PrintStr(Record->EditFields[i].Text, TextColor);
	}
}
/****************************************************************/
void ShowData(RECORD *Record)   /* Show Record Data  */
{
	int i=0;

	for(i=0;Record->EditFields[i].Text;i++)
	{
		Locate(Record->EditFields[i].H+strlen(Record->EditFields[i].Text)+1, 
			Record->EditFields[i].V);
		PrintStr(Record->EditFields[i].Data, DataColor);
	}
}
/****************************************************************/
void Cls(int Color)      /* Clears the screen to any color */
{
	union REGS Regin;

	Regin.x.ax=0x0600;
	Regin.h.bh=(char)Color;
	Regin.x.cx=0;
	Regin.x.dx=0x1950;
	int86(0x10, &Regin, &Regin);
	Locate(0,0);
}
/****************************************************************/
void Locate(int H, int V)  /* Locate cursor at an Horiz, Vert coordinate */
{
	union REGS Regin;
	Regin.h.ah=2;
	Regin.h.bh=0;
	Regin.h.dh=(char)V;
	Regin.h.dl=(char)H;
	int86(0x10, &Regin, &Regin);
}
/****************************************************************/
void PrintStr(char *Str, int Color) /* Generic color print string routine */
{
	union REGS Regin;

	Regin.h.ah=9;
	Regin.h.al=*Str;
	Regin.x.cx=strlen(Str);
	Regin.h.bl=(char)Color;
	Regin.h.bh=0;
	int86(0x10, &Regin, &Regin);
	while(*Str)
	{
		Regin.h.ah=0xE;
		Regin.h.al=*Str++;
		int86(0x10, &Regin, &Regin);
	}
}
/****************************************************************/
void Message(char *Str, int Wait)  /* Simple message display routine */
{
	Locate(0,24);
	printf("                                                           ");
	Locate(0,24);
	printf("%s", Str);
	if(Wait)
		GetKey();
}
/****************************************************************/
int MyGets(char *Dest, int Len)   /* Simple line input routine  */
{
	int i, Ch=0;
	char ThisChar[2];

	ThisChar[1]=*(Dest+Len)='\0';  /* Insure NULL Terminate */
	PrintStr(Dest, DataColor);
	i=strlen(Dest);
	while( (Ch=GetKey())!=ESC && Ch!=ENTER && Ch!=CURUP && Ch!=CURDN&&Ch!=F10)
	{
		if(Ch==BACKSPACE && i)
		{
			PrintStr("\x08 \x08",DataColor);
			i--;
			continue;
		}
		if(i<Len)
		{
			ThisChar[0]= *(Dest+i)=(char)Ch;
			PrintStr(ThisChar, DataColor);
			i++;
		}
	}
	return(Ch);
}
/****************************************************************/
int GetKey()    /* like getch, but handles function keys OK */
{
	int i;

	if( (i=getch()) )
		return(i);
	return(getch()*0x100);
}
/*************************************************************************/
/***  File I/O section follows                                         ***/
/***                                                                   ***/
/***  Note that this section is where the DataBase Engine interface    ***/
/***    would appear to API's like ORACLE, dbVista, or Paradox.        ***/
/***    It may be useful to add new members to the FIELDS structure so ***/
/***    that the field number is also present (as used by some APIs).  ***/
/***                                                                   ***/
/*************************************************************************/
int AppendRecord(RECORD *Record)   /* Add record to End of file  */
{
	Record->Recpos=lseek(Record->Handle, 0L, SEEK_END);
	return(WriteRecord(Record));
}
/*************************************************************************/
int LocateRecord(RECORD *Rec)   /* Locate record */
{
	char *LocalRec, *Easier;
	static char Mess[64];
	int i, Found=0, j;

	if( (LocalRec=calloc(Rec->RecSize, sizeof(char)))==NULL)
		return(-1);
	
	/*
	**  Just a note, since this is an all-field search we would
	**     first locate the index or key fields by which to search.  But
	**     since this program has no index mechanism, this is just a comment.
	*/
	for(i=0;Rec->EditFields[i].Text;i++)   /* Locate index field */
		if(Rec->EditFields[i].Type&INDEXFLD)
			break;
	if(Rec->EditFields[i].Text)
	{
		Locate(0,17);
		printf("%s is an index (or key) field, I would search there\n",
			Rec->EditFields[i].Text);
		printf("first, if I had a DataBase Engine.\n");
		GetKey();
		Locate(0,17);
		printf("                                                                    \n");
		printf("                                    ");
		
	}
	Rec->Recpos=lseek(Rec->Handle, 0L, SEEK_SET);
/*
** Now the search begins
*/
	while(!Found && read(Rec->Handle, LocalRec, Rec->RecSize)==Rec->RecSize)
	{
		Found=1;
		for(i=0;Rec->EditFields[i].Text&&Found;i++)
		{
			if( ! *Rec->EditFields[i].Data )  /* Don't test blank search fields */
				continue;
			Easier=LocalRec+(Rec->EditFields[i].Data-Rec->RecSpace);
			j=strlen(Rec->EditFields[i].Data);
			if(strnicmp(Rec->EditFields[i].Data, Easier, j))
				Found=0;
		}
		if(!Found)
			Rec->Recpos=lseek(Rec->Handle, 0L, SEEK_CUR);
	}
	free(LocalRec);
	return(Found);
}
/*************************************************************************/
int ReadRecord(RECORD *Record)   /* Read current record */
{
	lseek(Record->Handle, Record->Recpos, SEEK_SET);
	return(read(Record->Handle, Record->RecSpace, Record->RecSize));
}
/*************************************************************************/
int WriteRecord(RECORD *Record)   /* Write current record */
{
	lseek(Record->Handle, Record->Recpos, SEEK_SET);
	return(write(Record->Handle, Record->RecSpace, Record->RecSize));
}
/*************************************************************************/
void BlankRecord(RECORD *Record)   /* Empty out all fields  */
{
	int i;
	for(i=0;Record->EditFields[i].Text;i++)
		if(Record->EditFields[i].Data)
			*(Record->EditFields[i].Data)='\0';
}
/*************************************************************************/
int OpenFile(RECORD *Record) /* Open file and create Edit Space for buffer */
{
	int i, Memory=0;

	if( (Record->Handle=open(Record->Filename, O_RDWR|O_BINARY|O_CREAT,S_IWRITE))==-1)
		return(-2);

	/* Determine and allocate needed record block size */
	for(i=0;Record->EditFields[i].Text;i++)
		Memory=Memory+Record->EditFields[i].Len+1;
	if( (Record->RecSpace=calloc(Memory,sizeof(char)))==NULL)
		return(-1);

   /* Assign field pointers into out Data Record block  */
	for(Memory=0, i=0;Record->EditFields[i].Text;i++)
	{
		Record->EditFields[i].Data=(Record->RecSpace+Memory);
		Memory=Memory+Record->EditFields[i].Len+1;
	}
	Record->RecSize=Memory;
	return(0);
}
/*************************************************************************/
void CloseFile(RECORD *Record)    /* Close File, free buffer */
{
	int i;
	free(Record->RecSpace);
	close(Record->Handle);
}


/**  End of File  **/
