/*/
     Project Arashi: CStringDictionary.c
     Major release: Version 1.1d2, 9/5/95

     Last modification: Thursday, June 1, 1995, 1:23
     Created: Friday, December 4, 1992, 11:45

     Copyright © 1992-1995, Juri Munkki
/*/

/*
**	See include file for more details on how this class works.
*/
#include "CStringDictionary.h"
#include <RamFiles.h>

/*
**	Given a counted string of length len, HashString
**	will return a value from 0 to HASHTABLESIZE-1.
**	This value can be used as the hashing value for
**	that string.
*/ 
short	HashString(
	unsigned char 	*string,
	short			len)
{	
	if(len>0)
	{	return (string[0]+string[len-1]+string[len>>1]) & (HASHTABLESIZE - 1);
	}
	else
		return 0;
}
/*
**	Initialize a CStringDictionary. The directory will be empty.
*/
void	CStringDictionary::IStringDictionary()
{
	int		i;
	
	dictCount = 0;					//	Dictionary is empty.
	logicalDictSize = 0;
	realDictSize = 0;
	dictionary = (DictEntry **)NewHandle(0);
	
	logicalWordListSize = 0;		//	Dictionary words are listed separately.
	realWordListSize = 0;
	wordList = (unsigned char **)NewHandle(0);
		
	for(i=0;i<HASHTABLESIZE;i++)	//	Hash table is empty.
	{	hashTable[i] = -1;
	}

	IBaseObject();
}

/*
**	Return count of items in dictionary.
*/
short	CStringDictionary::GetDictionarySize()
{
	return dictCount;
}

/*
**	Copy the string for entry index to theEntry
*/
void	CStringDictionary::GetIndEntry(
	short		index,
	StringPtr	theEntry)
{
	DictEntry	*thisEntry;
	StringPtr	entryString;
	short		i;
	
	if(index >= 0 && index < dictCount)
	{
		thisEntry = index + (DictEntry *) *dictionary;
		entryString = thisEntry->nameOffset + *wordList;
		
		i = 1+entryString[0];
		while(i--)
		{	theEntry[i] = entryString[i]; 
		}
	}
}

short	CStringDictionary::GetIndEntrySize(
	short	index)
{
	DictEntry	*thisEntry;
	StringPtr	entryString;
	short		i;
	
	if(index >= 0 && index < dictCount)
	{
		thisEntry = index + (DictEntry *) *dictionary;
		entryString = thisEntry->nameOffset + *wordList;
		
		return entryString[0];
	}
	else
		return	0;
}

/*
**	Add a dictionary entry regardless
**	of wether it already is there or not. Normally you would
**	first look for a dictionary entry before calling this
**	routine. Call this routine if you are absolutely sure
**	that the entry isn't already in the dictionary.
**
**	NOTE: A negative return value signifies and error.
*/
tokentype	CStringDictionary::AddDictEntry(
	unsigned char	*entry,
	short			len)
{
	long			where;
	tokentype		entryIndex = 0;
	long			theErr = noErr;
	short			hashed;
	SignedByte		state;
	DictEntry		*entryP;
	
	if(len<0) len = *entry;
	if(len>255) len = 255;

	state = HGetState((Handle) this);	//	Lock the object for a while.
	HLock((Handle)this);
	
	//	Increase the dictionary size, if memory allows.
	if(IncreaseByClump(	(Handle)dictionary,
						&realDictSize,
						&logicalDictSize,
						sizeof(DictEntry),
						DICTIONARYCLUMPSIZE) == noErr)
	{
		where = logicalWordListSize;	//	New word is written at this offset.
		
		//	Increase the word list size, if memory allows.
		if(IncreaseByClump(	(Handle)wordList,
							&realWordListSize,
							&logicalWordListSize,
							len+1,
							WORDCLUMPSIZE) == noErr)
		{	BlockMoveData(entry+1,1+where+*wordList,len);	//	Write the word.
			(*wordList)[where] = len;					//	Store the length
			entryIndex = dictCount++;					//	Get a new token value (or index).
			entryP = entryIndex + *dictionary;			//	A pointer to the entry.
			
			hashed = HashString(entry+1,len);			//	Hash the entry by adding it
			entryP->hashLink = hashTable[hashed];		//	in front of all other entries
			entryP->nameOffset = where;					//	in the linked list of words
			hashTable[hashed] = entryIndex;				//	hashing to the same entry.
		}
		else
		{	logicalDictSize -= sizeof(DictEntry);		//	Memory was full or handle locked.
			theErr = memFullErr;						//	Assume that memory was full...
		}
	}
	else
	{	theErr = memFullErr;							//	Memory was full or handle locked.
	}

	HSetState((Handle)this,state);			//	Restore the object lock state.
	return theErr ? theErr : entryIndex;	//	Return an error or the entry.
}

/*
**	Look for an entry and return it if found. Return
**	-1 if the entry is not found.
*/
tokentype	CStringDictionary::SearchForEntry(
	unsigned char	*entry,
	short			len)
{
	short			hashWalk;
	DictEntry		*thisEntry;
	unsigned char	*p1,*p2;
	
	if(len<0) len = *entry;
	if(len>255) len = 255;
	
	hashWalk = hashTable[HashString(entry+1,len)];	//	Find linked list head.
	while(hashWalk >= 0)							//	While there are items in the list.
	{	p1 = entry+1;
		thisEntry = hashWalk + *dictionary;
		p2 = thisEntry->nameOffset + *wordList;		//	Compare the entries:
		
		if(len == *p2++)							//	Compare length.
		{	int	i;
		
			i = len;
			while((i > 0) & (*p1++ == *p2++))		//	Compare characters.
				i--;
		
			if(i == 0)
				return hashWalk;					//	Found! Return index.
			else
				hashWalk = thisEntry->hashLink;		//	Not found, look at next entry.
		}
		else
		{	hashWalk = thisEntry->hashLink;			//	Not found, look at next entry.
		}
	}

	return -1;		//	Return -1 when entry was not found.
}

/*
**	Look for an entry and if doesn't exist, create it.
**	This routine only returns an error if there is a
**	problem with memory management.
*/
tokentype	CStringDictionary::FindEntry(
	unsigned char 	*entry,
	short			len)
{
	tokentype	theToken;
	
	theToken = SearchForEntry(entry,len);
	if (theToken >= 0) 
		return theToken;
	else
		return AddDictEntry(entry,len);
}

/*
**	Unlock and dispose storage.
*/
void	CStringDictionary::Dispose()
{
	ForceUnlock();
	DisposHandle((Handle)wordList);
	DisposHandle((Handle)dictionary);

	inherited::Dispose();
}

/*
**	Lock everything.
*/
void	CStringDictionary::Lock()
{
	if(!lockCounter)
	{	HLock((Handle)wordList);
		HLock((Handle)dictionary);
	}
	inherited::Lock();

}
/*
**	Unlock everything.
*/
void	CStringDictionary::Unlock()
{
	if(lockCounter == 1)
	{	HUnlock((Handle)wordList);
		HUnlock((Handle)dictionary);
	}
	inherited::Unlock();
}

/*
**	Read the dictionary string from a STR# resource.
*/
void	CStringDictionary::ReadFromStringList(
	short strListID)
{
	Str255	theString;
	short	stringCount,i;
	
	stringCount = **(short **)GetResource('STR#',strListID);
	for(i=1;i<=stringCount;i++)
	{	GetIndString(theString,strListID,i);
		AddDictEntry(theString, *theString);
	}
}

Handle	CStringDictionary::WriteToHandle()
{
	Handle	result;
	long	neededSize;
	short	ind;
	short	stringCount;
	
	stringCount = GetDictionarySize();
	neededSize = sizeof(stringCount);
	
	for(ind = 0; ind < stringCount; ind++)
	{	neededSize += 1+GetIndEntrySize(ind);
	}

	result = NewHandle(neededSize);
	if(result)
	{	unsigned char	*p;
		
		
		HLock(result);
		p = sizeof(stringCount) + (unsigned char *)*result;
		for(ind = 0;ind < stringCount; ind++)
		{	GetIndEntry(ind, (StringPtr)p);
			p += 1+p[0];
		}
		HUnlock(result);
	}
	
	return result;
}

void	CStringDictionary::ReadFromHandle(
	Handle	source)
{
	short	ind;
	short	stringCount;
	char	cMemTags;
	
	if(source)
	{	unsigned char	*p;
	
		cMemTags = HGetState(source);
		HLock(source);
		p = (unsigned char *) *source;
		
		stringCount = *(short *) p;
		p += sizeof(short);
		for(ind = 0;ind < stringCount; ind++)
		{	AddDictEntry(p, -1);
			p += 1+p[0];
		}
		
		HSetState(source, cMemTags);
	}
}