// UDataFile.cp
// UDataFile.h
// ----------------------------------------------------------------------------------
// File Buffer class for the Spell Checker project.
//
// Note: This file is proprietary and confidential to Art Pollard
//	and Lextek Internation.  
// Copyright 1994 Art Pollard / LexTek International
//
// A file buffer is an abstract data class representing a file on disk.
// Internally it is stored as a linked list of 'pages' in the disk file.
// A user picks 'pages' to be read and can specify a header in the file
// that is ignored.
//
// Implementation Example
// 
// UBuffer is used by the spell checker to read in compressed pages from
// disk that hold the word list within a dictionary.  A header contains
// the location of the index, the number of words and other such data.
//
// ----------------------------------------------------------------------------------
// History:
// 		Art Pollard			June 94
//			Original.  Simple read/write functions.
//		Clark Goble			08/11/94
//			Made the buffer into an abstract C++ class that was basically
//			an abstract datafile class.
//		Clark Goble			09/02/94
//			Made the buffer have a static linked list.  ie. one buffer for
//			all files.  Note: Write page doesn't check for correct file currently
// ----------------------------------------------------------------------------------

// ----------------------------------------------------------------------------------
// Includes

//#define TEST

#include "UData.h"
#include <stdio.h>
#include "UError.h"

#define	INITIALIZED		151



// ----------------------------------------------------------------------------------
//	UDataFile		- Data file constructor
// ----------------------------------------------------------------------------------
// The constructor opens a file, but keeps a list of files within a single linked 
// list that is statically stored within the class.  You pass it the file (opened)
// that you wish to associate a buffer with, the size of a 'page' in that file,
// the number of possible buffers to be created for the file, and the offset to
// the paged data.

UDataFile::UDataFile(Uio *theFile, long thePageSize, long theNumBuffers, long theOffset)
{

	sTempBuffer = NULL;		// a temporary buffer
	sOffset = 0;			// offset into the file
	sPageSize = 0;			// size of the data file
	sBufferedPages = 0;		// number of pages in buffers
	sMaxPages = 0;			// possible number of buffers
	sFirstBuffer = NULL;	// the file's buffer
	sUses = 0;

	sUses++;
	uses = &sUses;

	File = theFile;				// the data file
	Offset = theOffset;			// offset in the file to the paged data

	if (sTempBuffer == NULL )
	{
		// create a temporary buffer to hold one page
		sTempBuffer = (UInt8*) ::malloc(thePageSize);
		if (sTempBuffer == NULL)
		{	ErrorFunc(eNo_Mem, SET);
		}
	}

	TempBuffer = &sTempBuffer;

	sPageSize = thePageSize;
	PageSize = &sPageSize;

	sMaxPages = theNumBuffers;	// number of buffers
	MaxPages = &sMaxPages;

	BufferedPages = &sBufferedPages;

	FirstBuffer = &sFirstBuffer;
	// printf("%i",*uses);

} // UBuffer

// ----------------------------------------------------------------------------------
//	~UDataFile		- Data file destructor
// ----------------------------------------------------------------------------------
//

UDataFile::~UDataFile()
{
	if ( --(*uses) == 0)
	{
		::free(*TempBuffer);
		// free the other buffers
		ClearBuffers();
	}
} // UBuffer


// ------------------------------------------------------------------------
//	ReadPage		- Reads a page of data from disk
// ------------------------------------------------------------------------
// Reads a physical page in from disk into the buffer specified.  Returns the
// error state.

short
UDataFile::ReadPage(long page, UInt8 *aBuffer)
{
	long where = (page * *PageSize + Offset);	// offset into the file to read

	File->SetPos(where, SEEK_SET);
	if ( ErrorFunc(0, GET) < eNo_Err )
		return ( ErrorFunc(0, GET) );

	File->ReadData((void *)*TempBuffer, *PageSize); // bwh
	if ( ErrorFunc(0, GET) < eNo_Err )
		return ( ErrorFunc(0, GET) );

	memmove(aBuffer, *TempBuffer, *PageSize); // copy the temp buffer to a permanent one
	return (OK);
} // ReadPage

// ----------------------------------------------------------------------------------
//	WritePage		- Writes a page of data to disk
// ----------------------------------------------------------------------------------
// Writes a physical page in from disk into the buffer specified.  Returns the
// error state.

short
UDataFile::WritePage(long page, UInt8 *aBuffer)
{
	long where = (page * *PageSize + Offset);	// offset into the file to read

	File->SetPos(where, SEEK_SET);
	if ( ErrorFunc(0, GET) < eNo_Err )
		return ( ErrorFunc(0, GET) );

	File->WriteData( *TempBuffer, *PageSize);
	if ( ErrorFunc(0, GET) < eNo_Err )
		return ( ErrorFunc(0, GET) );

	memmove(aBuffer, *TempBuffer, *PageSize);		// copy the temp buffer to a permanent one
	return (OK);
} // ReadPage


// ----------------------------------------------------------------------------------
//	NewBuffer		- Buffer Constructor
// ----------------------------------------------------------------------------------
// Reads a physical page in from disk into the buffer specified.  Returns the
// error state.

UBuffer *
UDataFile::NewBuffer(long page)
{

	UBuffer	(*theBuffer) = (UBuffer *) malloc ( sizeof(UBuffer) );

	if ((page < 0 )  || (theBuffer == NULL))  // must be a possible page
	{
		if (theBuffer == NULL)
		{
			ErrorFunc(eNo_Mem, SET);
			return (NULL);
		} else
		{
			ErrorFunc(eGeneral, SET);
			free(theBuffer);
			return (NULL);				// and a real buffer
		}
	}

	if ( (ReadPage(page, theBuffer->CharBuffer) ) == eReading_File )
	{
		free(theBuffer);
		return (NULL);
	}
	theBuffer->Dirty 		= CLEAN;
	theBuffer->BlockNum 	= page;
	theBuffer->theFile 		= File;
	theBuffer->Next 		= NULL;
	return (theBuffer);
} // NewBuffer


// ----------------------------------------------------------------------------------
//	GetPage		- Buffer Constructor
// ----------------------------------------------------------------------------------
// Reads a physical page in from disk into the buffer specified.  Returns the
// error state.
UInt8 *
UDataFile::GetPage(long page)
{
	 UBuffer *Current;
	 UBuffer *Last;

	 // Make sure there is a head of the list
	 if(*FirstBuffer == NULL) {
		(*FirstBuffer) = NewBuffer(page);
		if(*FirstBuffer == NULL)
			return (NULL);
		(*BufferedPages)++;
	}

	// Is it the first element in the list?
	if( ( (*FirstBuffer)->BlockNum == page) && ( (*FirstBuffer)->theFile == File))
		return ( (*FirstBuffer)->CharBuffer);

	// Scan the List
	Current = *FirstBuffer;
	Last = *FirstBuffer;
	do{
        if(Current->Next == NULL)
			break;

		if(Current->BlockNum == page && Current->theFile == File)
			break;

		Last = Current;
		Current = Current->Next;
	}while(1);

	// Was the element in the list?  If so make this recent item the head.
	// This sorts the list by access and means lesser used items can be
	// deleted.
	 if ( (Current->BlockNum == page) && (Current->theFile == File) )
	 {
		Last->Next = Current->Next;
		Current->Next = *FirstBuffer;
		*FirstBuffer = Current;
		return ( (*FirstBuffer)->CharBuffer);
	  }

	// Page not found in buffer

	// Is there room in the buffer list to create a new buffer?
	if ( *BufferedPages < *MaxPages )
	{
		// Create a new buffer
		UBuffer (*Temp) = (UBuffer *) malloc(sizeof(UBuffer));

		// If out of memory replace the least used buffer (end of list)
		// Then make the replaced buffer the first buffer
		if (Temp == NULL )
		{	// this is the same code as farther down below, but I didn't
			// want the confused code that a goto brings

			// There wasn't room to create a new buffer, ergo we must replace one
			if(Current->Dirty == DIRTY) {
				WritePage(Current->BlockNum, Current->CharBuffer);
				// error checking
			}
			if ( ReadPage((long) page, Current->CharBuffer) == eReading_File )
				return( NULL );
			Current->BlockNum = page;
			Current->theFile = File;
			Current->Dirty = CLEAN;
			Last->Next = Current->Next; // (Current->Next should be NULL)
			Current->Next = *FirstBuffer;
			*FirstBuffer = Current;
			return( (*FirstBuffer)->CharBuffer);

		}

		if( ReadPage((long) page, Temp->CharBuffer) == eReading_File )
			return (NULL);

		/* Debyg
		printf("Creating new buffer");
		printf("*FirstBuffer before temp");
		printf("\n%s\n",(char *)Temp->CharBuffer);
		*/


		Temp->BlockNum = page;
		Temp->Dirty = CLEAN;
		Temp->theFile = File;
		Temp->Next = *FirstBuffer;
		(*FirstBuffer) = Temp;
		(*BufferedPages)++;
		return( (*FirstBuffer)->CharBuffer);
	} // Making a new buffer

	// There wasn't room to create a new buffer, ergo we must replace one
	if( Current->Dirty == DIRTY ) {
		WritePage(Current->BlockNum, Current->CharBuffer);
		// error checking
	}
	if ( ReadPage((long) page, Current->CharBuffer) == eReading_File )
		return( NULL );
	Current->BlockNum = page;
	Current->Dirty = CLEAN;
	Current->theFile = File;
	Last->Next = Current->Next; // (Last->Next should be NULL)
	Current->Next = *FirstBuffer;
	(*FirstBuffer) = Current;
	return( (*FirstBuffer)->CharBuffer);
} // GetPage


// ----------------------------------------------------------------------------------
//	ClearBuffers
// ----------------------------------------------------------------------------------
// Clears all the buffers that are used

void
UDataFile::ClearBuffers()
{
	UBuffer *Current;
	UBuffer *Temp;

	Current = *FirstBuffer;
	while ( Current != NULL )
	{
		Temp = Current;
		Current = Current->Next;

		// Check to see if the buffer has been edited
		// If so write it to disk

		if ( Temp->Dirty == DIRTY )
		{
			WritePage(Temp->BlockNum, Temp->CharBuffer);
			// error checking here
		}

		free(Temp);
	}
}

#ifdef TEST

#define	DATAMAX		6

void main()
{
	Uio*	thefile;
	UDataFile*	PageFiles[DATAMAX];

	char	response[30];
	short		Choice = 0;
	short		NumPages = 20;
	short		PageSize = 256;
	char*	BuffPointer = NULL;
	long		Offset = 0;

	// initialize things
	short		i, t;


	for(i=0; i<DATAMAX; i++)
		PageFiles[i] = NULL;


	for(;;)
	{
		printf("\nUDataFile\n\n");

		for(i=0; i<DATAMAX;i++)
		{	if (PageFiles[i] == NULL )
				printf("Buffer[%i] = FREE\n",i);
			else
				printf("Buffer[%i] = USED\n",i);
		}

		printf("\n\n");
		printf("1. Open UDataFile\n");
		printf("2. Close UDataFile\n");
		printf("3. Print Page\n");
		printf("> ");
		gets(response);
		Choice = atoi(response);
		printf("\n");
		switch(Choice)
		{	case 1:
				printf("Number of UDataFile to open (0..%i)\n",DATAMAX-1);
				printf("> ");
				gets(response);
				i = atoi(response);
				printf("\n");

				if ( ( i < 0 ) || ( i > (DATAMAX - 1) ) )
				{	printf("Illegal Range Error\n");
					break;
				}

				if ( PageFiles[i] != NULL )
				{	printf("UDataFile[%i] is already open\n",i);
					break;
				}

				printf("File to Open\n> \n");
				gets(response);
				thefile = fopen(response,"rb");
				if(thefile == NULL)
				{	printf("\nError Opening File\n");
					break;
				}

				printf("\nOffset = ");
				gets(response);
				Offset = atoi(response);

				PageFiles[i] = new UDataFile(thefile, PageSize, NumPages, Offset);
				break;
			case 2:
				printf("Number of UDataFile to close (0..%i)\n",DATAMAX-1);
				printf("> ");
				gets(response);
				i = atoi(response);
				printf("\n");

				if ( ( i < 0 ) || ( i > (DATAMAX - 1) ) )
				{	printf("Illegal Range Error\n");
					break;
				}

				if ( PageFiles[i] == NULL )
				{	printf("UDataFile[%i] is already closed\n",i);
					break;
				}

				delete PageFiles[i];
				PageFiles[i] = NULL;
				break;
			case 3:
				printf("\nUDataFile to search: ");
				gets(response);
				i = atoi(response);

				printf("\nPage to print: ");
				gets(response);
				t = atoi(response);

				BuffPointer = (char *) PageFiles[i]->GetPage(t);
				if(BuffPointer == NULL)
				{
				  printf("\nError in getting block %d",Choice);
				}

				printf("Block:\n\n%s",BuffPointer);
				break;
		} // switch

	}// for

}
#endif

