// CMainDict.cp
// CMainDict.h
// ----------------------------------------------------------------------------------
// The main dictionary class.
//
// This dictionary is regular disk based dictionary.
//
// Note: This file is proprietary and confidential to Art Pollard
//	and Lextek International.  
// Copyright 1994 Art Pollard / LexTek International
//
//
// ----------------------------------------------------------------------------------
// History:
// 		Art Pollard			June 94
//			Original
//		Clark Goble			08/14/94
//			Checked it over and made a few modifications as part of the
//			C++ conversion.
// ----------------------------------------------------------------------------------


#include <stdio.h>
#include <stdlib.h>

#include "CMnDct.h"
#include "UHead.h"
#include "UError.h"
#include "UExtend.h"

#define CHARACTER_ARRAY_OFFSET   (512)
/**** TEST #defs *****/
//#define TEST
//#define TRACE
/*********************/

// ----------------------------------------------------------------------------------
// CMainDict	-- Constructor
// ----------------------------------------------------------------------------------
// Allocates the space for a dictionary and then tries to open the dictionary
// specified by DictFileName from disk.  If it can it proceeds.
// The function also tests the dictionary to see if it is readable and then
// allocates the space it needs as well as the space taken up by the buffers
// (number determined at init time).

CMainDict::CMainDict(long NumBuffers, FioParam * theFile)
	: CCompress(), CDict(NumBuffers, theFile), UExtended()
{

	#ifdef TRACE
	printf("\nOpenDictionary()");
	#endif

	DictFile = NULL;

	location.LastWord = (char *) malloc(MAXWORDLENGTH +1);
	location.Word = (char *) malloc(MAXWORDLENGTH +1);

	if ((location.LastWord == NULL) || (location.Word == NULL))
	{	ErrorFunc(eNo_Mem, SET);
		return;
	}

	memset(location.LastWord,0,MAXWORDLENGTH+1);
	memset(location.Word,0,MAXWORDLENGTH+1);
	location.CurrentNibble = 0;
	location.EndOfPage = FALSE;
	location.CurrentPage = 65530L;
	// set the CurrentPage to a page which will never exist.
	// this will force an ititialize for all the variables
	// the first time through the Find routine.

	// at some point, we should check to make sure that
	// all the variables have an option of being initialized
	// in the other functions as well.

	DictFile = new Uio(theFile);

	#ifdef _MACINTOSH_
	DictFile->Open(fsRdPerm);
	#else
	DictFile->Open();
	#endif

	if ( ErrorFunc(0, GET) < eNo_Err )	{
		return;
	}

	// Check to ensure it is a real dictionary file by examining it's header for
	// 'magic numbers'

	if (  ( UHeaders::ReadMagicNumber1(DictFile) != 6790643L ) ||
		( UHeaders::ReadMagicNumber2(DictFile) != 17890054L ))
		// 4294967283, 4294966022
	{
		ErrorFunc(eNot_Dictionary, SET);
		DictFile->Close();
		return;
	}

	//  Read the header information
	NumPages = UHeaders::ReadNumPages(DictFile);
	PageSize = UHeaders::ReadPageSize(DictFile);
	PageOffset = UHeaders::ReadPageOffset(DictFile);
	IndexOffset = UHeaders::ReadIndexOffset(DictFile);
	Version = UHeaders::ReadVersion(DictFile);
	FWFilterOffset = UHeaders::ReadBloomFilterOffset(DictFile);
	FWFilterSize = UHeaders::ReadBloomFilterSize(DictFile);
	LoadCharacterArray(DictFile,CHARACTER_ARRAY_OFFSET);


	// Check for errors
	if (ErrorFunc(0, GET)< eNo_Err)
	{ 	DictFile->Close();
		return;
	}

	// Create the index and word list


	// CREATE AND READ IN THE FREQUENT WORD FILTER

	FrequentWords = new UBloom(FWFilterSize,NUMBLOOMFILTERPROBES,By_Byte);
	if(FrequentWords == NULL) {
		ErrorFunc(eNo_Mem, SET);
		DictFile->Close();
	}
	if( FrequentWords->FRead(DictFile,FWFilterOffset) == eReading_File)
	{
		DictFile->Close();
	}

	// Buffer the dictionary on page boundaries

	Buffers = new UDataFile(DictFile, PAGESIZE, NumBuffers, PageOffset);
	if(Buffers == NULL)
	{	ErrorFunc(eNo_Mem, SET);
		DictFile->Close();
	}

	// Check for other errors
	if (ErrorFunc(0, GET) < eNo_Err)
	{	DictFile->Close();
		return;
	}

	/* CREATE THE INDEX, READ IT IN, AND THEN CREATE THE POINTERS FOR THE PAGES */
	Index = new UIndex();


	if(Index == NULL) {
		ErrorFunc(eNo_Mem, SET);
		DictFile->Close();
	}

	if ( Index->Read(DictFile,IndexOffset) < eNo_Err )
	{
		DictFile->Close();
	}

	if( Index->CreateWordPointers() < eNo_Err)
	{
		DictFile->Close();
	}
}


// ----------------------------------------------------------------------------------
// ~CMainDict	- Destructor
// ----------------------------------------------------------------------------------

CMainDict::~CMainDict()
{
	#ifdef TRACE
	printf("\nFreeDictionary()");
	#endif

	DictFile->Close();
	
	if (FrequentWords != NULL)
		delete FrequentWords;

	if (location.Word != NULL)
		free (location.Word);
	if (location.LastWord != NULL)
		free (location.LastWord);

	if(Index != NULL)
		delete Index;

	if(Buffers != NULL)
		delete Buffers;

	if(DictFile != NULL)
		delete DictFile;

//	if ( Compresser != NULL )
//		delete Compresser;

}

// ----------------------------------------------------------------------------------
// First	-- returns first word
// ----------------------------------------------------------------------------------
// You must have allocated enough space for the word.

short 
CMainDict::First(char *Word)
{
	#ifdef TRACE
	printf("\nFirstWord()");
	#endif

	short theError;
	// Read in the first page from the buffer
	theError = Buffers->ReadPage(0, location.DictPage);
	
	if (ErrorFunc(0, GET) < eNo_Err)
	{	// error set already
		return FALSE;
	}
		
	location.CurrentNibble = 0;
	location.EndOfPage = FALSE;
	
	location.LastWord[0]= 0;

	theError = GetWord( (UInt8 *)location.Word, 
			(UInt8 *) location.DictPage, 
			(short *)&location.CurrentNibble);
			
	if (theError != OK)
	{	// error set already
		return(theError);
	}
	
	strcpy(Word, location.Word);
	return (OK); // doesn't really mean anything...
}


// ----------------------------------------------------------------------------------
// Current	-- copies the current word
// ----------------------------------------------------------------------------------
// You must have allocated enough space for the word

void
CMainDict::Current(char *Word)
{
	#ifdef TRACE
	printf("\nCurrentWord()");
	#endif
	strcpy(Word,location.Word);
}

// ----------------------------------------------------------------------------------
// Next	-- Copies the next word and advances the 'cursor'
// ----------------------------------------------------------------------------------
// You must have allocated enough space for the word


short 
CMainDict::Next( char *Word)
{
	
	#ifdef TRACE
	printf("\nNextWord()");
	#endif

	if(location.EndOfPage != TRUE) 
	{
		if ( GetWord( (UInt8 *)location.Word, 
			(UInt8 *) location.DictPage, 
			(short *)&location.CurrentNibble) != OK )
		{
			// if not OK then we are at the end of the dictionary
			location.EndOfPage = TRUE;
		} else {
			strcpy(Word, location.Word );
		}

	}

	if(location.EndOfPage == TRUE) 
	{
		/* question must be posed again since conditions may have changed. */
		location.CurrentNibble = 0;
		if (location.CurrentPage < NumPages-1) 
		{
			location.CurrentPage++;
			
			if (ErrorFunc(0, GET) < eNo_Err)
			{	return (ErrorFunc(0, GET));
			}


			location.DictPage = Buffers->GetPage(location.CurrentPage);

			if (ErrorFunc(0, GET) < eNo_Err)
			{	if (location.DictPage != NULL)
					free (location.DictPage);
				location.DictPage = NULL;
				return (ErrorFunc(0, GET));
			}
			
			location.CurrentNibble = 0;
			location.EndOfPage = FALSE;
		}
		else 
		  return (EOI);

		location.EndOfPage = FALSE;
		
		// Swap current word with last word
		//location.LastWord = location.Word;
		strcpy(location.LastWord,location.Word);

		// Get the word on top of the old last word
		GetWord( (UInt8 *)location.Word, (UInt8 *) location.DictPage, 
			(short *)&location.CurrentNibble);
		
		strcpy(Word,location.Word);
	}
	

	return (OK);
}

// ----------------------------------------------------------------------------------
// Find -- Finds a word in the dictionary
// ----------------------------------------------------------------------------------
// If the word is found the dictionary's cursor is left pointing and FOUND is
// returned.  OTherwise the cursor points to the next greater word and NOTFOUND
// is returned.


short
CMainDict::Find(char *Word)
{
	short   Result;
	unsigned short   OldBlock;
	char *Temp;
	short CompareResult;

	#ifdef TRACE
	printf("\nFindWordInDict()");
	#endif

	OldBlock = location.CurrentPage;
	location.CurrentPage = Index->FindPage(location.CurrentPage, (UInt8 *) Word);

   #ifdef TRACE
   printf("\nPage #%d",location.CurrentPage);
   #endif

	location.DictPage = Buffers->GetPage(location.CurrentPage);
	if( (location.DictPage == NULL) || (ErrorFunc(0, GET) < eNo_Err) )
	{
		return (NOTFOUND);
	 }
		 /* GET THE VARIOUS WORDS IN THE BUFFER UNTIL STRING = OR > IS REACHED */

	if ( OldBlock == location.CurrentPage)
	{
   		#ifdef TRACE
         printf("\nCompare: Word %s  Last Word %s",Word,location.LastWord);
         #endif
		  	CompareResult = EStrCmp(Word,location.LastWord);

		if( CompareResult > 0)
		{
      	#ifdef TRACE
      	printf("\nCompare2: Word %s  Last Word %s",Word,location.LastWord);
         #endif
			CompareResult  = EStrCmp( Word, location.Word);
			if(CompareResult < 0)
				return (NOTFOUND);
			if(CompareResult == 0)
				 return (FOUND);
				else
				{
					 // The word we are looking for is greater than the current word.  Since it
					 // is on the same page, we can continue scanning from where we are at until
				 // we either find the word or not.
				 for(;;) {
               if(location.EndOfPage == TRUE)
						break;
					// Swap word with last word
					Temp = location.LastWord;
					location.LastWord = location.Word;
					location.Word = Temp;
					// this next line can probably be optomized out sometime....
					strcpy(location.Word,location.LastWord);

					// Get the next word
					if(GetWord( (UInt8 *)location.Word,
						(UInt8 *) location.DictPage, (short *)&location.CurrentNibble )
						!= OK)
					break;
               #ifdef TRACE
               printf("\nReading Word %s",location.Word);
               #endif

					Result = EStrCmp(location.Word,Word);

					if ( Result == 0 )
						return (FOUND);
					if (Result > 0)
						return (NOTFOUND);
				}
				location.EndOfPage = TRUE;
				return (NOTFOUND);
			}
		}
		else if(CompareResult < 0) {

			// The Word is less than location.LastWord.  That means we have to start scanning
			// from the begining of the buffer again to find it.  So, let's reset everything.
			location.CurrentNibble = 0;
			location.EndOfPage = FALSE;
			location.LastWord[0] = 0;
			location.Word[0] = 0;

			for(;;) {

				// Swap word with last word
				Temp = location.LastWord;
				location.LastWord = location.Word;
				location.Word = Temp;
				// this next line can probably be optomized out sometime....
				strcpy(location.Word,location.LastWord);

				// Get the next word
				if(GetWord( (UInt8 *)location.Word,
					(UInt8 *) location.DictPage, (short *)&location.CurrentNibble )
					!= OK) break;

				Result = EStrCmp(location.Word,Word);

				if ( Result == 0 )
					return (FOUND);
				if (Result > 0)
					return (NOTFOUND);

			}
				location.EndOfPage = TRUE;
				return (NOTFOUND);
		}
		else { // Word matches LastWord (for some reason or another.
		  return (FOUND);
		}
	}

	//  OK, the page which the word is (supposed to be) on is not the same as
	//  the current page.  So, we need to be sure that we reset everything.
	location.CurrentNibble = 0;
	location.EndOfPage = FALSE;
	location.LastWord[0] = 0;
	location.Word[0] = 0;

	for(;;) {

		// Swap word with last word
		Temp = location.LastWord;
		location.LastWord = location.Word;
		location.Word = Temp;
		// this next line can probably be optomized out sometime....
		strcpy(location.Word,location.LastWord);

		// Get the next word
		if(GetWord( (UInt8 *)location.Word,
			(UInt8 *) location.DictPage, (short *)&location.CurrentNibble )
			!= OK) break;

		Result = EStrCmp(location.Word,Word);

		if ( Result == 0 )
			return (FOUND);
		if (Result > 0)
			return (NOTFOUND);
	}


	location.EndOfPage = TRUE;
	return (NOTFOUND);
}

// ----------------------------------------------------------------------------------
// Check -- Checks to see if a word is a word
// ----------------------------------------------------------------------------------
// First checks 1) the frequent word filter, 2) document words, 3) user words,
// 4) skip words, and last the actual dictionary.  If it was found there then
// the word is inserted into DocumentWords filter.  This returns either OK, NOTOK,
// and ERROR

short
CMainDict::Check(char *Word)
{
	short Length;
	short ReturnValue;

	#ifdef TRACE
	printf("\nCheckWord()");
	#endif

	Length = strlen(Word);

   #ifdef TRACE
   printf("\nFrequent Word Test %s",Word);
   #endif
	if( FrequentWords->Test(Word,Length) == FOUND)
		return (OK);

	// Check in this dictionary
   #ifdef TRACE
   printf("\nFinding In Pages    %s",Word);
   #endif
    ReturnValue = Find(Word);
    if (ReturnValue == FOUND)
    {
		return (OK);
	} else
		if ( ReturnValue < eNo_Err )
			return (ReturnValue);
    return (NOTFOUND);
}

#ifdef TEST

short main()
      {
       DICTIONARY *Dict;

       char DictName[20];
       char UserDictName[20];
       short  DocWordSize;
       short  UserWordSize;
       short  SkipWordSize;
       long  NumBuffers;
       short  Page;
       short  CurrentNibble;

       char Choice[20];
       short  Result;
       char Word[MAXWORDLENGTH];
       short  ReturnValue;
       Uio *File;

       for(;;) {
	 printf("\n\n1) Open Dictionary");
	 printf("\n2) Find Word");
	 printf("\n3) Find First Word");
	 printf("\n4) Find Next Word");
	 printf("\n5) Dump Words to a File");
	 printf("\n6) CheckWord");
	 printf("\n7) Get Pages Words");
//	 printf("\n8) Add User Word To Dict");
//	 printf("\n9) Add Skip Word");
	 printf("\n0) Quit\n");
	 gets(Choice);
	 Result = atoi(Choice);
	 switch (Result) {
	 case 1:
		printf("\nDictionary Name :");
		gets(DictName);
/*		printf("\nUser Dictionary Name :");
		gets(UserDictName);
		printf("\nDocument Word Cache Size :");
		gets(Choice);
		DocWordSize = atoi(Choice);
		printf("\nUser Word Cache Size :");
		gets(Choice);
		printf("\nSkip Word Cache Size :");
		gets(Choice);
		SkipWordSize = atoi(Choice);
		UserWordSize = atoi(Choice);
*/
		printf("\nNumber of Buffers :");
		gets(Choice);
		NumBuffers = atoi(Choice);

		Dict = OpenDictionary(DictName,NumBuffers);
		if(Dict == NULL) printf("\n\a\aError Opening Dictionary....");
		break;
	 case 2:
		printf("\nWord to Find :");
		gets(Choice);
		ReturnValue = FindWordInDict(Dict,Choice);
		if(ReturnValue == FOUND) printf("\nThe Word %s Was Found ",Choice);
		else printf("\nThe Word %s Was NOT Found",Choice);
		break;
	 case 3:
		if(FirstWord(Dict,Word) == NULL) printf("\nAn Error Occured ....");
		else printf("\nThe First Word Was %s",Word);
		break;
	 case 4:
		ReturnValue = NextWord(Dict,Word);
		if(ReturnValue == EOI) printf("\nEnd Of Index");
		if(ReturnValue == ERROR) printf("\nAn Error Condition Has Occured");
		if(ReturnValue == OK)printf("\nThe Next Word Is %s",Word);
		break;
	 case 5:

		break;
	 case 6:
		printf("\nWord To Check :");
		gets(Choice);
		if(CheckWord(Dict,Choice) == OK)
		 printf("\nThe Word %s IS SPELLED CORRECTLY",Choice);
		else printf("\nThe Word %s IS NOT SPELLED CORRECTLY",Choice);
		break;
	 case 7:
		printf("\nPage of Dictionary to Read :");
		gets(Choice);
		Page = atoi(Choice);

		location.DictPage = GetBlock(Buffers,Page);
		if(location.DictPage == NULL) {
		 /* AN ERROR OCCURED HERE .... */
		}

		location.CurrentNibble = 0;
		location.EndOfPage = FALSE;

		while(GetWord(location.Word,location.DictPage, (short *)&location.CurrentNibble) != 0 )
		  printf("\n%s",location.Word);
		break;
/*	 case 8:
		printf("\nWord To Add To User Dict :");
		gets(Choice);
		AddUserWord(Dict,Choice);
		break;
	 case 9:
		printf("\nWord To Add To Skip List :");
		gets(Choice);
		AddSkipWord(Dict,Choice);
		break;
*/
	 case 0:
		exit(1);
		break;
	 }
       }
      }
#endif

