// ConvertSelection implementation
// Copyright   1996 Stefan Schneider

#import "ConvertSelection.h"
#import <ctype.h>					// isspace()

#define LS(file, title)	\
	NXLoadLocalizedStringFromTableInBundle(#file, nil, #title, NULL)

static NXAtom allTypes[4] = { NULL, NULL, NULL, NULL };

static char message[4096];

@implementation ConvertSelection

// internal methods

+ initialize
{
	if (self == [ConvertSelection class]) {
		allTypes[0] = NXUniqueString("NeXT RTFD pasteboard type");
		allTypes[1] = NXRTFPboardType;
		allTypes[2] = NXAsciiPboardType;
	}
	return self;
}
			
- (char *) scanQuoted: (char *)string into: (char *)token
// scans <white space>"<any quoted string>"
// returns pointer to first char after second quote or NULL if error
{
	while (isspace(*string))
		string++;
	if (*string != '\"')
		return NULL;
	
	for (++string; *string != '\"'; string++) {
		if (*string == '\0')
			return NULL;
		if (*string == '\\') {
			string++;
			switch (*string) {
				case '\0':
					return NULL;
				case 'a':
					*(token++) = '\a';
					break;
				case 'b':
					*(token++) = '\b';
					break;
				case 'f':
					*(token++) = '\f';
					break;
				case 'n':
					*(token++) = '\n';
					break;
				case 'r':
					*(token++) = '\r';
					break;
				case 't':
					*(token++) = '\t';
					break;
				case 'v':
					*(token++) = '\v';
					break;
				case '\"':
					*(token++) = '\"';
					break;
				case '\\':
					*(token++) = '\\';
					break;
				default:
					*(token++) = *string;
			}
		} else
			*(token++) = *string;
	}
	
	*token = '\0';
	return string + 1;
}

- (BOOL) loadTable: (const char *)userData error: (char **)errMessage
{
	char table_path[MAXPATHLEN + 1];
	struct stat new_stat_buf;
	
	// construct table_path from app_dir and userData
	
	if ((userData == NULL) || (userData[0] == '\0')) {
		sprintf(message, LS(CS, ERR_NO_USER_DATA), app_dir);
		NXLogError(message);
	 	*errMessage = message;
		return NO;
	}
	sprintf(table_path, "%s/%s", app_dir, userData);
	
	// stat that file
	
	if (stat(table_path, &new_stat_buf) != 0) {
		sprintf(message, LS(CS, ERR_CANT_STAT_FILE), table_path);
		NXLogError(message);
	 	*errMessage = message;
		return NO;
	}
	
	// if it's new, load that file as new table
	
	if (([keys count] == 0) || (stat_buf.st_dev != new_stat_buf.st_dev) || (stat_buf.st_ino != new_stat_buf.st_ino)) {
		FILE *file;
		char string[2048];
		
		[keys empty];
		[values empty];
		
		// open table_path
		
		if ((file = fopen(table_path, "r")) == NULL) {
			sprintf(message, LS(CS, ERR_CANT_OPEN_FILE), table_path, sys_errlist[errno]);
			NXLogError(message);
			*errMessage = message;
			return NO;
		}
		
		// scan lines for "key" = "value" pairs, add to key/value lists
		
		while (fgets(string, 2047, file)) {
			char key[1024], value[1024];
			char *s;
			
			if ((s = [self scanQuoted:string into:key]) == NULL)
				continue;
			while (isspace(*s))
				s++;
			if (*s != '=')
				continue;
			if ([self scanQuoted:++s into:value] == NULL)
				continue;
		
			[keys addObject:(id)NXUniqueString(key)];
			[values addObject:(id)NXUniqueString(value)];
		}
		
		fclose(file);
		stat_buf = new_stat_buf;
	}
	
	return YES;
}

- convertText
{
	int i, count = [keys count];

	for (i = 0; i < count; i++) {
		NXAtom key = (NXAtom)[keys objectAt:i];
		NXAtom value = (NXAtom)[values objectAt:i];
		
		[text setSel:0 :0];
		while ([text findText:key ignoreCase:NO backwards:NO wrap:NO] == YES)
			[text replaceSel:value];
	}
	
	return self;
}

// service methods

- convertSelection: (id)pboard userData: (const char *)userData error: (char **)errMessage
{
	// ev. load table
	
	if ([self loadTable:userData error:errMessage] == NO)
		return self;

	[window disableDisplay];
	
	// fetch pasteboard contents
	
	[text setText:NULL];
	[text selectAll:self];
	[text readSelectionFromPasteboard:pboard];
	
	// make a backup copy
	
	if (backup_stream == NULL)
		backup_stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	else
		NXSeek(backup_stream, 0, NX_FROMSTART);
	
	[text writeRTFDSelectionTo:backup_stream];
	NXFlush(backup_stream);
	
	// walk text, replace according to table
	
	[self convertText];

	// stuff contents back to pasteboard
	
	[text selectAll:self];
	[text writeSelectionToPasteboard:pboard types:allTypes];
	
	// done
	
	[[window reenableDisplay] displayIfNeeded];

	return self;
}

- revertSelection: (id)pboard userData: (const char *)userData error: (char **)errMessage
{
	NXStream *new_stream;
	
	// check whether there is something to revert
	
	if (backup_stream == NULL) {
		strcpy(message, LS(CS, ERR_NO_BACKUP_DATA));
		NXLogError(message);
		*errMessage = message;
		return self;
	}

	[window disableDisplay];
	
	// fetch pasteboard contents
	
	[text setText:NULL];
	[text selectAll:self];
	[text readSelectionFromPasteboard:pboard];
	
	// exchange contents with backup copy 

	new_stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	[text writeRTFDSelectionTo:new_stream];
	NXFlush(new_stream);
	
	NXSeek(backup_stream, 0, NX_FROMSTART);
	[text setText:NULL];
	[text selectAll:self];
	[text replaceSelWithRTFD:backup_stream];
	NXCloseMemory(backup_stream, NX_FREEBUFFER);
	backup_stream = new_stream;
	
	// stuff backup contents back to pasteboard
	
	[text selectAll:self];
	[text writeSelectionToPasteboard:pboard types:allTypes];
	
	// done
	
	[[window reenableDisplay] displayIfNeeded];
	
	return self;
}

// methods overridden from superclass

- appDidInit: sender
{
	const char *last_slash;
	
	if (last_slash = strrchr(NXArgv[0], '/'))
		app_dir = NXUniqueStringWithLength(NXArgv[0], last_slash - NXArgv[0]);
	else
		app_dir = NXArgv[0];
	
	keys = [[List alloc] initCount:100];
	values = [[List alloc] initCount:100];
	
	backup_stream = NULL;

	[[NXApp appListener] setServicesDelegate:self];
	[self setDelegate:self];
	
	return self;
}

- terminate: sender
{
	[keys free];
	[values free];
	
	if (backup_stream)
		NXCloseMemory(backup_stream, NX_FREEBUFFER);
	
	return [super terminate:sender];
}

@end
