/* File: MessageApp.m - NextStep Interface to Speaker/Mach messages
 *
 * By: Christopher Lane
 * Symbolic Systems Resources Group
 * Knowledge Systems Laboratory
 * Stanford University
 *
 * Date: 11 April 1994
 *
 * Copyright: 1991, 1993 & 1994 by The Leland Stanford Junior University.
 * This program may be distributed without restriction for non-commercial use.
 */

#import "MessageApp.h"
#import "DefaultsTable.h"

#import <c.h>
#import <ctype.h>

#import <sys/dir.h>

#import <netinfo/ni.h>

#import <objc/NXStringTable.h>

#define HELPFILE "Help"
#define PORTSFILE "Ports"
#define MESSAGESFILE "Messages"
#define DEFAULTSFILE "Defaults"
#define TRANSLATIONSFILE "Translations"

#define NETINFODOMAIN "NetInfoDomain"
#define APPLICATIONPATHS "ApplicationPaths"
#define HOSTPATH "HostPath"

#define STATUSKEY "status"
#define NAME (ni_name) "name"

#define PUBLICPORT "public"
#define LOCALHOST "localhost"

#define VERSION __DATE__
#define BUFFERSIZE (MAX(MSG_SIZE_MAX, FILENAME_MAX))

#define EMPTY ""
#define APPSUFFIX ".app"
#define PATHSEPARATOR "/"
#define ARGUMENTSEPARATOR ':'
#define DIRECTORYSEPARATOR ":"
#define STATUSFORMAT "%d (%s)"
#define DESCRIPTIONFORMAT "%s:"

#define streq(s, t) (strcmp(s, t) == 0)

struct argument { void *array[NX_MAXMSGPARAMS]; };

union overlay { struct { void *l, *r; } v; double d; };

typedef enum { DESCRIPTION = 0, TYPE, ARGUMENT } ParamObjects;

int compare(const void *a, const void *b) { return(strcmp(*((char **) a), *((char **) b))); }

@implementation MessageApp

- appDidInit:sender
{	
	NXStream *stream;
	char pathnamebuf[MAXPATHLEN];

	bundle = [NXBundle bundleForClass:[self class]];
	
	if ([bundle getPath:pathnamebuf forResource:DEFAULTSFILE ofType:"strings"])
		[[[DefaultsTable alloc] initFromFile:pathnamebuf] registerDefaults:[self appName]];
	
	fileTable = [[HashTable alloc] initKeyDesc:@encode(char *)];

	if ([bundle getPath:pathnamebuf forResource:TRANSLATIONSFILE ofType:"strings"])
		[stringTable readFromFile:pathnamebuf];

	[self loadPopUpList:[hostList target] fromNetInfo:getDefault(HOSTPATH)];
	
	[hostList setTitle:LOCALHOST];
	
	[hostField setStringValue:LOCALHOST];
	
	[self loadPopUpList:[applicationList target] fromDirectory:getDefault(APPLICATIONPATHS)];
	
	[applicationList setTitle:NX_WORKSPACEREQUEST];
	
	[applicationField setStringValue:NX_WORKSPACEREQUEST];

	if ([bundle getPath:pathnamebuf forResource:PORTSFILE ofType:"strings"]) {
		[portTable readFromFile:pathnamebuf];
 		[self loadPopUpList:[portList target] fromHashTable:portTable];
		}

	[portList setTitle:PUBLICPORT];
	
	[portField setStringValue:PUBLICPORT];
	
	if ([bundle getPath:pathnamebuf forResource:MESSAGESFILE ofType:"strings"]) {
		[messageTable readFromFile:pathnamebuf];
 		[self loadPopUpList:[messageList target] fromHashTable:messageTable];
		}
	
	if ([bundle getPath:pathnamebuf forResource:HELPFILE ofType:"rtf"]) {
		if((stream = NXMapFile(pathnamebuf, NX_READONLY)) != NULL) {
			[helpScrollView readRichText:stream];
			NXCloseMemory(stream, NX_FREEBUFFER);
			}
		}

	[self setMessageFromField:messageField];
	
	[messageWindow makeKeyAndOrderFront:self];
	
	return self;
}

- windowWillClose:sender { return [self terminate:sender]; }

- sendMessage:sender
{
	int status;
	port_t port;
	char buffer[BUFFERSIZE];
	struct argument arguments;
	id speaker = [self appSpeaker];
	const char *s, *host, *application, *portname, *message, *paramTypes;
	
	[statusField setStringValue:EMPTY]; NXPing();
	
	if(streq(host = [hostField stringValue], LOCALHOST)) host = NULL;
	
	application = [applicationField stringValue];
	
	if(streq(portname = [portField stringValue], PUBLICPORT)) port = NXPortFromName(application, host);
	else if((s = [portTable valueForStringKey:portname]) != NULL) port = (port_t) atoi((char *) s);
	else port = (port_t) [portField intValue];

	[speaker setSendPort:port];
	
	paramTypes = [self loadArguments:arguments.array];
		
	message = [messageField stringValue];
	
	if((s = [self getParamTypes:message]) == NULL || !streq(paramTypes, (char *) s))
		[self setParamTypes:paramTypes forMessage:message];
		
	status = [speaker selectorRPC:message paramTypes:(char *) paramTypes, arguments];

	if(streq(portname, PUBLICPORT)) (void) port_deallocate(task_self(), port);
	
	if((s = [self lookupConstant:status forKey:STATUSKEY]) != NULL) {
		(void) sprintf(buffer, STATUSFORMAT, status, (char *) s);
		[statusField setStringValue:buffer];
		}
	else [statusField setIntValue:status];
		
	[self unloadArguments:arguments.array andUpdate:(BOOL) (status == SEND_SUCCESS)];
	
	return self;
}

- (const char *) loadArguments:(void **) array
{
	char c, *p;
	unsigned int i, j;
	union overlay datum;
	const char *s, *portname;
	static char buffer[NX_MAXMSGPARAMS + 1];
	
	p = buffer;
	
	for(i = 0, j = 0; i < argumentCount && j < NX_MAXMSGPARAMS; i++, j++) {
	
		if((c = *[params[i].type title]) == '\0') break;

		switch(c) {
			case 'b' : array[j] = (void *) [params[i].argument stringValue];
			           array[j + 1] = (void *) strlen((char *) array[j]);
			           j++;
			           break;
			case 'B' : array[j] = (char **) malloc(sizeof(char *));
			           array[j + 1] = malloc(sizeof(int));
			           j++;
			           break;
			case 'c' : array[j] = (void *) [params[i].argument stringValue]; break;
			case 'C' : array[j] = (char **) malloc(sizeof(char *)); break;
			case 'd' : datum.d = [params[i].argument doubleValue];
			           array[j++] = datum.v.l;
			           array[j] = datum.v.r;
			           break;
			case 'D' : array[j] = malloc(sizeof(double)); break;
			case 'i' : array[j] = (void *) [params[i].argument intValue]; break;
			case 'I' : array[j] = malloc(sizeof(int)); break;
			case 'r' :
			case 's' : if(streq((portname = [params[i].argument stringValue]), PUBLICPORT))
			            	array[j] = (void *) ((c == 'r') ? [[self appSpeaker] sendPort] : [[self appListener] listenPort]);
                       else if((s = [portTable valueForStringKey:portname]) != NULL) array[j] = (void *) atoi((char *) s);
			           else array[j] = (void *) [params[i].argument intValue];
			           break;
			case 'R' : case 'S' : array[j] = malloc(sizeof(port_t)); break;
			default  : break;
			}

		*p++ = c;
		
		if(isupper(c)) [params[i].argument setStringValue:EMPTY];
		}
		
	*p = '\0';

	return(buffer);
}

- unloadArguments:(void **) array andUpdate:(BOOL) flag
{
	int value;
	const char *s;
	unsigned int i, j;
	char c, buffer[BUFFERSIZE];
	
	for(i = 0, j = 0; i < argumentCount && j < NX_MAXMSGPARAMS; i++, j++) {
	
		if((c = *[params[i].type title]) == '\0') break;
		
		if(flag)
			switch(c) {
				case 'B' : (void) strncpy(buffer, *((char **) array[j]), *((int *) array[j + 1]));
				           buffer[*((int *) array[j + 1])] = '\0';
				           [params[i].argument setStringValue:buffer];
				           break;
				case 'C' : [params[i].argument setStringValue:*((char **) array[j])]; break;
				case 'D' : [params[i].argument setDoubleValue:*((double *) array[j])]; break;
				case 'I' : [params[i].argument setIntValue:(value = *((int *) array[j]))];
				           if((s = [self lookupConstant:value forKey:[params[i].description stringValue]]) != NULL) {
				            	(void) sprintf(buffer, STATUSFORMAT, value, s);
				            	[params[i].argument setStringValue:buffer];
				            	}
				           break;
				case 'R' : case 'S' : [params[i].argument setIntValue:*((int *) array[j])]; break;
				default  : break;
				}
				
		switch(c) {
			case 'B' : free(array[j]);
			case 'b' : case 'd' : j++;
			default : if(isupper(c)) free(array[j]); break;
			}
		}
		
	return self;
}

- setHostField:object { hostField = object; return self; }

- setHostList:object
{
	[[[(hostList = object) target] setTarget:self] setAction:@selector(setHostfromMatrix:)];
	
	return self;
}

- setApplicationField:object { applicationField = object; return self; }

- setApplicationList:object
{
	[[[(applicationList = object) target] setTarget:self] setAction:@selector(setApplicationfromMatrix:)];

	return self;
}

- setPortField:object { portField = object; return self; }

- setPortList:object
{
	[[[(portList = object) target] setTarget:self] setAction:@selector(setPortfromMatrix:)];

	return self;
}

- setMessageField:object { messageField = object; return self; }

- setMessageList:object
{
	[[[(messageList = object) target] setTarget:self] setAction:@selector(setMessagefromMatrix:)];
		
	return self;
}

- loadPopUpList:popUpList fromNetInfo:(const char *) directory
{
	ni_id dir;
	int i, j = 0;
	void *handle;
	ni_status status;
	ni_namelist *namelist;
	ni_entrylist entrylist;
	id table = [[HashTable alloc] initKeyDesc:@encode(char *)];

	if((status = ni_open(NULL, (ni_name) getDefault(NETINFODOMAIN), &handle)) == NI_OK) {
		if((status = ni_pathsearch(handle, &dir, (ni_name) directory)) == NI_OK)
			if((status = ni_list(handle, &dir, NAME, &entrylist)) == NI_OK) {
				for(i = 0; i < entrylist.ni_entrylist_len; i++)
					if((namelist = entrylist.ni_entrylist_val[i].names) != NULL)
//						for(j = 0; j < namelist->ni_namelist_len; j++) /* Uncomment line to get aliases */
							[table insertKey:(void *) NXCopyStringBuffer(namelist->ni_namelist_val[j]) value:nil];
				ni_entrylist_free(&entrylist);
				}
		ni_free(handle);
		}

	[self loadPopUpList:popUpList fromHashTable:table];
	
	[[table freeObjects] free];
	
	return self;
}

- loadPopUpList:popUpList fromDirectory:(const char *) directoryList
{
	int n;
	DIR *dirp;
	struct direct *dp;
	char *s, *t, *scratch;
	id table = [[HashTable alloc] initKeyDesc:@encode(char *)];
	
	s = strtok((scratch = NXCopyStringBuffer(directoryList)), DIRECTORYSEPARATOR);
		
	while(s != NULL) {
		if((dirp = opendir(s)) != NULL) {
			for(dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
				n = strlen(dp->d_name) - strlen(APPSUFFIX);
				if(n > 0 && streq((dp->d_name + n), APPSUFFIX)) {
					*((t = NXCopyStringBuffer(dp->d_name)) + n) = '\0';
					[table insertKey:(const void *) t value:nil];
					}
				}
			(void) closedir(dirp);
			}
			
		s = strtok(NULL, DIRECTORYSEPARATOR);
		}
		
	NX_FREE(scratch);
	
	[self loadPopUpList:popUpList fromHashTable:table];
	
	[[table freeObjects] free];
	
	return self;
}

- loadPopUpList:popUpList fromHashTable:hashTable
{
	const void *key, *value, **base;
	NXHashState state = [hashTable initState];
	unsigned int i, count = [hashTable count];
	
	base = calloc((size_t) count, sizeof(const void *));
	
	for(i = 0; [hashTable nextState:&state key:&key value:&value]; i++) base[i] = (const void *) key;

	(void) qsort(base, (size_t) count, sizeof(const void *), compare);
	
	for(i = 0; i < count; i++) [popUpList addItem:(const char *) base[i]];

	free(base);
	
	return self;
}

- setStatusField:object { statusField = object; return self; }

- setArgumentsBox:object
{
	unsigned int i, j, count;
	id sublist, list = [[(argumentsBox = object) contentView] subviews];
	
	argumentCount = [list count];
	
	for(i = 0; i < argumentCount; i++) {
		count = [(sublist = [[[list objectAt:i] contentView] subviews]) count];
		
		for(j = 0; j < count; j++) {
			switch([[sublist objectAt:j] tag]) {
				case DESCRIPTION : params[i].description = [sublist objectAt:j]; break;
				case TYPE : params[i].type = [sublist objectAt:j];
				            [[[params[i].type target] setTarget:self] setAction:@selector(setTypeFromMatrix:)];
				            break;
				case ARGUMENT : params[i].argument = [sublist objectAt:j]; break;
				default : break;
				}
			}
		}
		
	return self;
}

- setTypeFromMatrix:sender
{		
	char c;
	BOOL isInput;
	const char *s;
	unsigned int i;
	
	for(i = 0; i < argumentCount; i++) {
		if((s = [[params[i].type target] selectedItem]) == NULL || (c = *s) == '\0') continue;
		
		if(!(isInput = (BOOL) islower(c))) [params[i].argument setStringValue:EMPTY];
		
		[[[params[i].argument setSelectable:YES] setEditable:isInput] setBackgroundGray:(isInput) ? NX_WHITE : NX_LTGRAY];
		}
				
	return self;
}

- (const char *) getParamTypes:(const char *) name
{
	if([messageTable isKey:(const void *) name]) return([messageTable valueForKey:(const void *) name]);

	return(NULL);
}

- setParamTypes:(const char *) value forMessage:(const char *) name
{
	if([messageTable isKey:(const void *) name])
		free([messageTable insertKey:(const void *) name value:(void *) NXCopyStringBuffer(value)]);
	else [messageTable insertKey:(const void *) NXCopyStringBuffer(name) value:(void *) NXCopyStringBuffer(value)];

	return self;
}

- setButton:button fromField:field
{
	id target = [button target];
	const char *title = [field stringValue];
	
	[button setTitle:title];
	
	if([target indexOfItem:title] == -1) [target insertItem:title at:0];
	
	return self;
}

- setField:field fromMatrix:matrix
{
	[field setStringValue:[[matrix selectedCell] title]];
	
	return self;
}

- setHostFromField:sender { return [self setButton:hostList fromField:sender]; }

- setHostfromMatrix:sender { return [self setField:hostField fromMatrix:sender]; }

- setApplicationFromField:sender { return [self setButton:applicationList fromField:sender]; }

- setApplicationfromMatrix:sender { return [self setField:applicationField fromMatrix:sender]; }

- setPortFromField:sender { return [self setButton:portList fromField:sender]; }

- setPortfromMatrix:sender { return [self setField:portField fromMatrix:sender]; }

- setMessageFromField:sender
{
	[self setButton:messageList fromField:sender];
	
	return [self setMessage:[sender stringValue]];
}

- setMessagefromMatrix:sender
{
	[self setField:messageField fromMatrix:sender];

	return [self setMessage:[[sender selectedCell] title]];
}

- setMessage:(const char *) string
{
	BOOL isInput;
	unsigned int i, segmentCount = 0;
	const char *paramTypes = [self getParamTypes:string];
	char *r, *s, *segments[NX_MAXMSGPARAMS], buffer[BUFFERSIZE], *scratch;
	
	[statusField setStringValue:EMPTY];
	
	r = scratch = NXCopyStringBuffer(string);
	
	while((s = index(r, ARGUMENTSEPARATOR)) != NULL) { /* Can't use strtok() due to '::' case! */
		segments[segmentCount++] = r;
		*s++ = '\0';
		r = s;
		}
	
	for(i = 0; i < argumentCount; i++) {
		if(i < segmentCount) {
			(void) sprintf(buffer, DESCRIPTIONFORMAT, segments[i]);
			[params[i].description setStringValue:buffer];
			if(paramTypes != NULL) {
				[params[i].type setEnabled:NO];
				if(!(isInput = islower(paramTypes[i])) || paramTypes[i] != *[params[i].type title])
					[params[i].argument setStringValue:EMPTY];
				[[[params[i].argument setSelectable:YES] setEditable:isInput] setBackgroundGray:(isInput) ? NX_WHITE : NX_LTGRAY];
				[params[i].type setTitle:[[[params[i].type target] findCellWithTag:(int) paramTypes[i]] title]];
				}
			else {
				[[params[i].type setTitle:[[[params[i].type target] findCellWithTag:0] title]] setEnabled:YES];
				[[[params[i].argument setEditable:YES] setBackgroundGray:NX_WHITE] setStringValue:EMPTY];
				}
			}
		else {
			[params[i].description setStringValue:EMPTY];
			[[params[i].type setTitle:[[[params[i].type target] findCellWithTag:0] title]] setEnabled:NO];
			[[[[params[i].argument setSelectable:NO] setEditable:NO] setBackgroundGray:NX_LTGRAY] setStringValue:EMPTY];
			}
		}

	NX_FREE(scratch);

	return self;
}

- (const char *) lookupConstant:(int) value forKey:(const char *) key
{
	id table;
	const char *file;
	char buffer[BUFFERSIZE];

	if((file = [stringTable valueForStringKey:key]) != NULL) {
		if([fileTable isKey:(const void *) file]) table = (id) [fileTable valueForKey:(const void *) file];
		else {
			[fileTable insertKey:(const void *) file value:(void *) (table = [[NXStringTable alloc] init])];
			if ([bundle getPath:buffer forResource:file ofType:"strings"]) [table readFromFile:buffer];
			}
		(void) sprintf(buffer, "%d", value);
		return([table valueForStringKey:(const char *) buffer]);
		}

	return(NULL);
}

- setVersion:object { [(version = object) setStringValue:VERSION]; return self; }

- (int) msgVersion:(const char **) aString ok:(int *) flag
{
	*aString = [version stringValue];
	*flag = YES;

	return SEND_SUCCESS;
}

@end
