/* 
 *
 * copyright by Moritz Willers (willers@butp.unibe.ch)
 *
 * Fri Jan 20 16:41:18 GMT+0100 1995
 *
 */

/* 
 * in ispell 3.1.12 the report of the offset has been changed.
 * To be able to compile nextispell with any version of ispell
 * here is a define which can be set to an integer value.
 * It says in which direction the selection should be shifted
 *
 * e.g
 *
 * -1 is: shift one left
 * +2 is: shift two right
 *
 *  0 is the correct value for 3.1.08 and earlier
 * -1 is the correct value for 3.1.12 and up
 *
 */

#define OFFSET_CORR -1

#import "Dictionaire.h"
#import "common.h"

	FILE	*fromIspell;	// a stream
	int		toIspell;		// a fd
	int		ispellpid, linesize;
	char	misspelled[BUFSIZ];
	BOOL	texmode, tersemode;

/* ********************     private functions     ************************ */

int makepipe(int *rd, int *wrt)
{
     int     piperesult, fd[2];

     piperesult = pipe(fd);

     *rd = fd[0];
     *wrt = fd[1];

     return piperesult;
}

void empty_pipe(FILE *fileptr)
{
    char buf[BUFSIZ];
	while ( fgets(buf, BUFSIZ, fileptr) && (*buf != '\n') );
	return;
}

void waitforispell(int pid)
{
	union wait statusp;

	while (	pid != wait3(&statusp, WUNTRACED, NULL) );
	if ( kill(pid, SIGCONT) == -1 )
		perror("SIG_CONT failed");
	return;
}

BOOL parsefortex(char *language)
{
	BOOL	tex = NO;
	char	*tmp;
	
	if ( (tmp = strrchr(language, '-')) ) {
//	nextispell now has defaults for tex and non tex versions seperately
//  Fri Jan 27 14:37:54 GMT+0100 1995, mw
//		*tmp = '\0';
		tmp++;
		tex = (strcmp(tmp, "tex")) ? NO : YES;
	}

	return tex;
}

void changetexmode(BOOL tex)
{
	if ( tex )
		write(toIspell, "+\n", 2);
	else
		write(toIspell, "-\n", 2);
	waitforispell(ispellpid);
}

void texcheck(BOOL tex)
{
	// maybe we've been in the midst of a bracket the last time
	// and ispell is ignoring everything. So switch out of
	// and back into tex mode
	if ( tex ) {
		write(toIspell, "-\n", 2);
		waitforispell(ispellpid);
		write(toIspell, "+\n", 2);
		waitforispell(ispellpid);
	}
	return;
}

/* ******************************************************************** */

@implementation Dictionaire

- init
{
	return [self initForLanguage:"English"];
}

- initForLanguage:(const char *)language
{
    int		fromI, toI, fromD, toD;
	char	**argv;
	char	*buf, *bufptr;
	int		i;
	
	[super init];
	
	dictLanguage = (char *)malloc((strlen(language)+1)*sizeof(char));
	strcpy(dictLanguage, language);

	texmode = parsefortex(dictLanguage);
		
    *misspelled = '\0';
    
    if (makepipe(&fromI,&toD)) 
    {
		perror("nextispell: Couldn't create pipe");
		[self free];
		return nil;
    }
    if (makepipe(&fromD,&toI))
    {
		perror("nextispell: Couldn't create pipe");
		[self free];
		return nil;
    }
    

    switch (ispellpid = vfork())
    {
		case -1:
			perror("nextispell: Couldn't fork");
			[self free];
			return nil;
		case  0:
			close(toI);
			close(fromI);
			
			if ( dup2(fromD, 0) == -1 ) {
				perror("nextispell child: Error establishing read pipe");
				exit(1);
			}
			if ( dup2(toD, 1)   == -1 ) {
				perror("nextispell child: Error establishing write pipe");
				exit(1);
			}
			// determine how to start ispell
			argv = (char **)malloc(5*sizeof(*argv));
			if ( NXGetDefaultValue("nextispell", "ispell") )
				newstringfrom(&argv[0], NXGetDefaultValue("nextispell", "ispell"));
			else {
				char ispellpath[MAXPATHLEN];
				if ( *findispell(ispellpath) != '\0' )
					newstringfrom(&argv[0], ispellpath);
				else {
					printf("nextispell child: Don't know how to exec ispell\n");
					exit(1);
				}
			}
			newstringfrom(&argv[1], "-a");
			newstringfrom(&argv[2], "-s");
			// parse the default value
			i = 3;
			newstringfrom(&buf, NXGetDefaultValue("nextispell", dictLanguage));
			for ( bufptr = buf; *bufptr; bufptr++ ) {
				if ( NXIsSpace(*bufptr) ) {
					*bufptr = '\0';
					newstringfrom(&argv[i], buf);
					i++; buf = bufptr+1;
					argv = (char **)realloc(argv, (i+3)*sizeof(*argv));
				}
			}
			newstringfrom(&argv[i++], buf);
			argv[i] = NULL;
			
			// put child in another process group
			// or the enire NeXT apps will hang
			// they are all in the same pgrp
			setpgrp(0, getpid());

			// change child into ispell
			execv(argv[0], argv);
			perror("nextispell child: Failed to exec ispell");
			exit(1);
		default:
			close(fromD);
			close(toD);
					
			fromIspell = fdopen(fromI, "r");
			toIspell   = toI;
			
			// put ispell into terse mode, may speed things up
			if ( (buf = (char *)NXGetDefaultValue("nextispell", "tersemode")) )
				tersemode = ( strcmp(buf, "NO") ) ? YES : NO;
			else
				tersemode = NO;
			if ( tersemode ) {
				write(toIspell, "!\n", 2);
				waitforispell(ispellpid);
			}
			// put ispell into tex mode
			if ( texmode )
				changetexmode(texmode);
			// determine the size of lines which are send to ispell
			if ( (buf = (char *)NXGetDefaultValue("nextispell", "linesize")) ) {
				sscanf(buf, "%d", &linesize);
				linesize = ( linesize > 900 ) ? 900 : linesize;
			}
			else
				linesize = 127;

			break;
    }
    return self;
}

- (const char*)language
{
	return (const char*)dictLanguage;
}

- free
{
	free(dictLanguage);
    if (toIspell)
		close(toIspell);
    if (fromIspell)
		fclose(fromIspell); 
    
    return [super free];
}

/* ***********************    delegate methods    ************************* */

- (BOOL)spellServer:(NXSpellServer *)sender 
	findMisspelledWord:(int *)start 
	length:(int *)length 
	inLanguage:(const char *)language
	inTextStream:(id <NXReadOnlyTextStream>)textStream 
	startingAt:(int)startPosition
	wordCount:(int *)number 
	countOnly:(BOOL)flag
{
    char readbuf[BUFSIZ], writebuf[BUFSIZ];
	char rest[128];
    char *readbufptr;
    int len;
    int offset, linelength = 0;
    char misspelledWord[BUFSIZ];
    BOOL loop;
    
    if (flag)
    {
		*number = -1; // is not able to do pure wordcounting
		fprintf(stderr, "nextispell is not able to do pure wordcounting\n");
		return NO;
    }
    
    if ([textStream isAtEOTS])
		return NO;
    
	// test whether ispell is around 
	if ( kill(ispellpid, 0) == -1 ) {
		perror("nextispell: quitting service, ispell is gone");
		[self free];
		return NO;
	}
    
    readbufptr = readbuf;
    *start = startPosition;
	*rest = '\0';
	
	if ( startPosition != 0 )		// set stream outside a word
		do {
			[textStream readCharacters:readbufptr count:1];
			(*start)++;
		} while (!NXIsSpace(*readbufptr) && ![textStream isAtEOTS]);
	
	// check tex mode before entering the main loop 
	if ( texmode )
		texcheck(texmode);

    // main loop
    do {
		// read the next 'linesize' characters form the text stream
		// and save the last bit (no word maybe) into 'rest'
		strcpy(readbuf, rest);
		len = strlen(rest);
		readbufptr = &readbuf[len];
		len += [textStream readCharacters:readbufptr count:linesize];
		readbufptr = readbuf;
		readbuf[len] = '\0';
		// fill with spaces instead of newlines.
		// We will send ispell only a single line
		for (readbufptr = readbuf; *readbufptr != '\0'; readbufptr++) {
			if (*readbufptr == '\n')
				*readbufptr = ' ';
		}
		
		// set pointer back to the last white space
		if ( ![textStream isAtEOTS] )
			for ( readbufptr = &readbuf[len-1];
				  !NXIsSpace(*readbufptr) && (readbufptr != readbuf);
				  readbufptr--);
		
		strcpy(rest, readbufptr);	// save rest
		*readbufptr++ = '\n';		// terminate with '\n' for ispell
		*readbufptr = '\0';			// terminate string
		linelength = strlen(readbuf)-1;	// without '\n'
		
		if ( linelength == 0 )
			return NO;
			
		// send ispell the the line
		write(toIspell, "^", 1);	// to protect the rest
		write(toIspell, readbuf, strlen(readbuf));
			
		waitforispell(ispellpid);
			
		// read stuff from ispell line by line
		loop = YES;
		while (loop) {
			if ( fgets(writebuf, BUFSIZ, fromIspell) )
				switch(*writebuf)
				{
					case '*':
					case '+':
					case '-':
						(*number)++;
						break;
					case '&':
					case '?':
						strcpy(misspelled, writebuf);
						sscanf(writebuf, "%*s %s %*d %d", misspelledWord, &offset);
						if ([sender isInUserDictionary:(const char *)misspelledWord caseSensitive:YES])
						{
							(*number)++;
							break;
						}
						*length = strlen(misspelledWord);
						*start += offset + OFFSET_CORR;
						
						empty_pipe(fromIspell);
						*number = ( tersemode ) ? -1 : *number;
						return YES;
					case '#':
						strcpy(misspelled, writebuf);
						sscanf(writebuf, "%*s %s %d", misspelledWord, &offset);
						if ([sender isInUserDictionary:(const char *)misspelledWord caseSensitive:YES])
						{
							(*number)++;
							break;
						}
						*length = strlen(misspelledWord);
						*start += offset + OFFSET_CORR; 

						empty_pipe(fromIspell);
						*number = ( tersemode ) ? -1 : *number;
						return YES;
					case '\n':
						*start += linelength;
						loop = NO;
					default:
						break;
				}	// switch
		}	// while (loop)
	}
    while (![textStream isAtEOTS]);

        
    return NO; // no misspelled words found
}

- (void)spellServer:(NXSpellServer *)sender 
	suggestGuessesForWord:(const char *)word 
	inLanguage:(const char *)language
{
    char buf[BUFSIZ];
    char *bufptr, *guess;
   
    if (*misspelled)
    {
		switch (*misspelled)
		{
			case '&':
			case '?':
				bufptr = strchr(misspelled, ':');
				do
				{
					guess = bufptr + 2;
					if ((bufptr = strchr(guess, ',')))
						*bufptr = '\0';
					else
					{
						bufptr = strchr(guess, '\n');
						*bufptr = '\0';
						bufptr = NULL;
					}
					[sender addGuess:guess];
				} while (bufptr);
			case '#':
				; // no guesses
		}
		*misspelled = '\0';
		return;
    } // else
	
	if ( texmode )
		texcheck(texmode);
	
    write(toIspell, "^", 1);
    write(toIspell, word, strlen(word));
    write(toIspell, "\n", 1);
	
	waitforispell(ispellpid);

	fgets(buf, BUFSIZ, fromIspell);
    
    switch (*buf)
    {
		case  '*':
		case  '+':
		case  '-':
			[sender addGuess:word];
			break;
		case '\n':	// we're in terse mode
			[sender addGuess:word];
			return;
		case '&':
		case '?':
			bufptr = strchr(buf, ':');
			do
			{
				guess = bufptr + 2;
				if ( (bufptr = strchr(guess, ',')) )
					*bufptr = '\0';
				else
				{
					bufptr = strchr(guess, '\n');
					*bufptr = '\0';
					bufptr = NULL;
				}
				[sender addGuess:guess];
			} while (bufptr);
		case '#':
		default :
			; // no guesses
    }
	
	// read the empty line
	fgets(buf, BUFSIZ, fromIspell);

    return;
}

- (void)spellServer:(NXSpellServer *)sender
	didForgetWord:(const char *)word
	inLanguage:(const char *)language
{
	// ispell can't take words out of the dictionary
	fprintf(stderr, "nextispell: Can't take word \"%s\" out of personal dictionary. Please do it by hand.\n", word);
	return;
}

- (void)spellServer:(NXSpellServer *)sender
	didLearnWord:(const char *)word
	inLanguage:(const char *)language
{
	write(toIspell, "*", 1);
    write(toIspell, word, strlen(word));
    write(toIspell, "\n", 1);
	waitforispell(ispellpid);
	write(toIspell, "#\n", 2);
	waitforispell(ispellpid);
	return;
}

@end
