/*

File Dictionary.m

A dictionary is responsible for finding words that match a prespecified pattern.  Each dictionary is tied to a .xdict file that contains both words and index information to facilitate lookup.  See the file xdict.c for information about the dictionary file format.

*/

#import <appkit/appkit.h>

#import "Dictionary.h"
#import "FunctionCache.h"
#import "filing.h"

#import <streams/streams.h>
#import <stdio.h>
#import <strings.h>
#import <ctype.h>
#import <libc.h>


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


static char *	types [] = {

		"xdict",
		"XDICT",
		NULL
	};


#define INITIALCOUNT		0
#define ENTRYSIZE(n)		((sizeof(char) + sizeof(long)) * n)
#define OFFSETSIZE(i,n)		(sizeof(char) * n + sizeof(long) * i)

#define CACHESIZE		3000


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


// static void		lowercase			(char *);
static void		createDictionary	(NXStream * input, NXStream * output);
static void		openDictionary		(NXStream * input, wordIndex index);
static void		createIndex			(NXStream *, NXStream *, wordIndex, int);
static int		getWord				(NXStream *, char *, int);
static void 	getAppDirectory 	(char *);


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


@implementation Dictionary

- (wordIndex *)	getIndex 	{	return &index;	}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


- init
{
	char	directory [1000];
	
	[super  init];
	
	cache = [[FunctionCache  alloc]
	
				initKeyType: ISASTRING 
				valueType: ISAOBJECT
				capacity: CACHESIZE ];
	
	getAppDirectory(directory);
	strcat(directory, DEFAULTDICTIONARY);
	[self  useDictionary: directory];
	
	return self;
}


- free
{
	[self  notify: WILLFREE];
	if (file != NULL) NXClose(file);
	[super  free];
	
	return self;
}


- create: sender
{
	const char	* infile, * outfile;
	NXStream	* input, * output;
	
	if ((infile = fileForOpen(NULL)) != NULL)
	if ((outfile = fileForSave("xdict")) != NULL)
	{
		input = NXMapFile(infile, NX_READONLY);
		output = NXOpenMemory(NULL, 0, NX_WRITEONLY);
		createDictionary(input, output);
		
		NXSaveToFile(output, outfile);
		NXClose(input);
 		NXCloseMemory(output, NX_FREEBUFFER);
	}
	
	return self;
}


- use: sender
{
	const char	* filename;
	
	if ((filename = fileForOpen(types)) != NULL) [self  useDictionary: filename];
	return self;
}


- useDictionary: (const char *) filename
{
	if (file != NULL) NXClose(file);
	openDictionary(file = NXMapFile(filename, NX_READONLY), index);
	[self  notify: DIDCHANGE];
	
	return self;
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

Here are two methods to locate words that match a specific pattern.  The find method accepts general patterns.  Limit restricts its search to a list of previous matches and finds all words that have a given letter in a given location.

*/


- find: (char *) pattern
{
	id		words;
	
	if ((words = [cache  find: pattern]) == nil)
				words = [self  findWithoutCache: pattern];
	
	return words;
}


- find: (char *) pattern  changeAt: (int) i
{
	id		words;
	char	c;
	int		n;
	
	if ((words = [cache  find: pattern]) == nil)
	{
		if ((c = pattern[i]) != WILDCARD)
		{
			n = strlen(pattern);
			pattern[i] = WILDCARD;
			words = [self  find: pattern];
			words = [self  limit: words  toLetter: c - 'a'  at: i  forLength: n ];
			pattern[i] = c;
		}
		
		else words = [self  findWithoutCache: pattern];
	}
	
	return words;
}


- findWithoutCache: (char *) pattern
{
	char	* string;
	id		words, match;
	int		i, n;
	
	words = match = nil;
	i = n = strlen(pattern);
	
	while (i--) if (pattern[i] != WILDCARD)
	{
		match = words;
		words = [self limit: match  toLetter: pattern[i] - 'a' at: i forLength: n];
		[match  free];
	}
	
	string = (char *) malloc(strlen(pattern) + 1);
	strcpy(string, pattern);
	[cache  add: string  value: words];
	
	return words;
}


- limit: match  toLetter: (char) c  at: (int) i  forLength: (int) n
{
	int		current, matchWord;
	long	size, offset;
	int		j, count;
	id		words;
	
	if (n < MINLETTERS) return nil;
	
	matchWord = j = count = 0;
	words = [[Storage  alloc]
				initCount: 0  elementSize: sizeof(long)  description: "i" ];
	
	if (match != nil)
	{
		if ((count = [match  count]) == 0) return words;
		matchWord = WORD(match, j = 0);
	}
	
	size = ENTRYSIZE(n);
	offset = index[n].words + OFFSETSIZE(i, n);
	current = index[n].linkTable[c][i].first;
	
	while (current != LAST)
	{
		if (match == nil) [words  addElement: &current];
		else
		{
			while (matchWord > current)
				if (++j < count)
					
					matchWord = WORD(match, j);		else
					break;
			
			if (j == count) return words;
			if (matchWord == current) [words  addElement: &current];
		}
		
		NXSeek(file, size * current + offset, NX_FROMSTART);
		NXRead(file, &current, sizeof(int));
	}
	
	return words;
}


- read: (char *) string  word: (int) i  forLength: (int) n
{
	NXSeek(file, ENTRYSIZE(n) * i + index[n].words, NX_FROMSTART);
	NXRead(file, string, n);
	string[n] = '\0';
	
	return self;
}


@end


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

static void		lowercase	(string)

char		* string;
{
	while (*string != '\0')
	{
		*string = tolower(*string);
		string++;
	}
}

*/


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

Here are the routines that create and open dictionary files.

*/

static void	createDictionary (input, output)

NXStream	* input, * output;

{
	wordIndex	index;
	int			n;
	
	NXSeek(output, sizeof(wordIndex), NX_FROMSTART);
	
	for (n = MINLETTERS; n <= MAXLETTERS; n++)
	{
		NXSeek(input, 0, NX_FROMSTART);
		createIndex(input, output, index, n);
	}
	
	NXSeek(output, 0, NX_FROMSTART);
	NXWrite(output, index, sizeof(wordIndex));
}


static void	openDictionary (input, index)

NXStream	* input;
wordIndex	index;

{
	NXSeek(input, 0, NX_FROMSTART);
	NXRead(input, index, sizeof(wordIndex));
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


static void	createIndex (input, output, index, n)

NXStream	* input, * output;
wordIndex	index;
int			n;

{
	char	word [100];
	int		i, j;
	
	index[n].words = NXTell(output);
	index[n].n = 0;
	
	for (j = 0; j < LETTERS; j++)
	for (i = 0; i < MAXLETTERS; i++)
	{
		index[n].linkTable[j][i].first = LAST;
		index[n].linkTable[j][i].n = 0;
	}
	
	while (getWord(input, word, n))
	{
		NXWrite(output, word, n);
		for (i = 0; i < n; i++)
		{
			j = word[i] - 'a';
			NXWrite(output, &index[n].linkTable[j][i], sizeof(int));
			index[n].linkTable[j][i].first = index[n].n;
			index[n].linkTable[j][i].n++;
		}
		
		index[n].n++;
	}
}


static int	getWord (input, word, n)

NXStream	* input;
char		* word;
int			n;

{
	int		i, bad;
	
	i = bad = 0;
	while ((word[i] = NXGetc(input)) != EOF)
	{
		if (word[i] == '\n')
		{
			if (!bad && (i == n))
			{
				word[i] = '\0';
				return 1;
			}
			
			i = bad = 0;
		}
		
		else if (islower(word[i])) i++;
		else bad = 1;
	}
	
	return 0;
}


/* 行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行行  */


/*

Here is code to get the application's directory.  The code is taken from NeXTanswers appkit #642.

*/

static void getAppDirectory (char *appDirectory)
{
    FILE *process;
    char command[256];
    char *suffix;

    strcpy (appDirectory,NXArgv[0]);
    if (appDirectory[0] == '/') { 		/* if absolute path */
        if (suffix = rindex(appDirectory,'/')) 
            *suffix  = '\0'; 			/* remove executable name */
    } else {
	sprintf(command,"which '%s'\n",NXArgv[0]);
	process=popen(command,"r");
	fscanf(process,"%s",appDirectory);
	pclose(process);
	if (suffix = rindex(appDirectory,'/')) 
	    *suffix  = '\0'; 			/* remove executable name */
	chdir(appDirectory);
	getwd(appDirectory);
    }  
}