#import <appkit/appkit.h>
#import <objc/Storage.h>
#import <sys/dir.h>
#import <sys/stat.h>
#import <stdio.h>
#import "Piper.h"

#define SERVICE_FILE "/tmp/PiperService"
#define EXEC_FILE "/tmp/PiperExec"

@implementation Piper

- pasteSelection:(id)pb userData:(const char *)userData error:(char **)msg
/*
 * This method returns data to the selection pasteboard.
 */
{
	char **pbtypes;
	id ret, selPb;

	pbtypes = (char **)[pb types];
	while (*pbtypes) {
		if (*pbtypes == NXAsciiPboardType) {		//Plain ASCII text
			ret = [pb readType:*pbtypes data:&data length:&length];
			type = *pbtypes;
			scriptName = userData;
			selPb = [Pasteboard newName:NXSelectionPboard];
			[self runScript:selPb];
			return self;
		} else if (*pbtypes == NXFilenamePboardType) {	//ASCII file name
			ret = [pb readType:*pbtypes data:&data length:&length];
			type = *pbtypes;
			scriptName = userData;
			selPb = [Pasteboard newName:NXSelectionPboard];
			[self runScript:selPb];
			return self;
		}
		pbtypes++;
	}
	/*
	 * Didn't find one we liked.
	 */
	NXRunAlertPanel(NULL, "Piper does not recognize pasteboard type", 
			NULL, NULL, NULL);
	return self;
}

- returnSelection:(id)pb userData:(const char *)userData error:(char **)msg
/*
 * This method returns data to the callers pasteboard.
 */
{
	char **pbtypes;
	id ret;

	pbtypes = (char **)[pb types];
	while (*pbtypes) {
		if (*pbtypes == NXAsciiPboardType) {		//Plain ASCII text
			ret = [pb readType:*pbtypes data:&data length:&length];
			type = *pbtypes;
			scriptName = userData;
			[self runScript:pb];
			return self;
		} else if (*pbtypes == NXFilenamePboardType) {	//ASCII file name
			ret = [pb readType:*pbtypes data:&data length:&length];
			type = *pbtypes;
			scriptName = userData;
			[self runScript:pb];
			return self;
		}
		pbtypes++;
	}
	/*
	 * Didn't find one we liked.
	 */
	NXRunAlertPanel(NULL, "Piper does not recognize pasteboard type", 
			NULL, NULL, NULL);
	return self;
}

- appDidInit:sender
{
	/*
	 * Make us the object that service requests (piperMessage) get sent to.
	 */	
	[[NXApp appListener] setServicesDelegate:self];
	
	textView = [scrollView docView];
	fontObj = [Font newFont:"Courier" size:14];	
	[textView setSel:0:0];	
	[textView setSelFont:fontObj];
	clearWindow = 1;

	return self;
}

- reloadScripts:sender
/*
 * Get the list of scripts from ~/Library/Piper. We then do surgery on
 * ourselves to replace the services section thus making the menu
 * reflect the list of scripts.
 */
{
	char appDir[MAXPATHLEN];
	int count = 0, keyFound, returnFound;
	char cmd[MAXPATHLEN], fileName[MAXPATHLEN], scriptDirName[MAXPATHLEN];
	char scriptFileName[MAXPATHLEN], script[256];
	struct stat stats;
	char *suffix, option[256], keyOption[256], returnOption[256];
	FILE *lspipe, *serviceFile, *optionsFile;

	system("rm -f /tmp/PiperService");
	strcpy(appDir, NXArgv[0]);
	if (appDir[0] == '/') { 		/* if absolute path */
		if (suffix = rindex(appDir,'/')) 
			*suffix  = '\0'; 	/* remove executable name */
	} else {
		NXRunAlertPanel(NULL, "Please launch from the Workspace!", 
				NULL, NULL, NULL);
		exit(1);
	}
	
	if ((serviceFile = fopen(SERVICE_FILE, "a")) == NULL) {
		NXRunAlertPanel(NULL, "Could not open temporary file %s", NULL, NULL, 
			NULL, SERVICE_FILE);
		exit(1);
	}
	
	/*
	 * Get a list of the scripts in ~/Library/Piper
	 */
	sprintf(scriptDir, "%s/Library/Piper", getenv("HOME"));
	if (stat(scriptDir, &stats) < 0) {
		NXRunAlertPanel(NULL, "Cannot find scripts directory %s",
			NULL, NULL, NULL, scriptDir);
		exit(1);
	}
	sprintf(cmd, "ls %s 2>/dev/null", scriptDir);
	if ((lspipe = popen(cmd, "r")) == NULL) {
		NXRunAlertPanel(NULL, "Command \"ls %s\" failed", 
			NULL, NULL, NULL, scriptDir);
		exit(1);
	}
	while (fgets(script, sizeof(script), lspipe) != NULL) {
		script[strlen(script)-1] = 0;
		sprintf(scriptDirName, "%s/%s", scriptDir, script);
		if (stat(scriptDirName, &stats) < 0 || !(stats.st_mode & S_IFDIR)) {
			NXRunAlertPanel(NULL, "%s is not a directory!", 
				NULL, NULL, NULL, scriptDirName);
			continue;
		}
		sprintf(scriptFileName, "%s/script", scriptDirName);
		if (stat(scriptFileName, &stats) < 0 || !(stats.st_mode & 0111)) {
			NXRunAlertPanel(NULL, "%s does not exist or is not executable", 
				NULL, NULL, NULL, scriptFileName);
			continue;
		}
		sprintf(fileName, "%s/options", scriptDirName);
		keyFound = returnFound = 0;
		if ((optionsFile = fopen(fileName, "r")) != NULL) {
			while (fgets(option, sizeof(option), optionsFile) != NULL) {
				if (!strncmp(option, "Key Equivalent", 14)) {
					strcpy(keyOption, option);
					keyFound++;
				} else if (!strncmp(option, "Return Type", 11)) {
					strcpy(returnOption, option);
					returnFound++;
				} else {
					NXRunAlertPanel(NULL, "Option %s not recognized", 
						NULL, NULL, NULL, option);
				}
			}
			fclose(optionsFile);
		}
		if (returnFound)
			fprintf(serviceFile, "Message: returnSelection\n");
		else
			fprintf(serviceFile, "Message: pasteSelection\n");
		fprintf(serviceFile, "Port: Piper\nMenu Item: Piper/%s\nUser Data: %s\nSend Type: NXAsciiPboardType\nSend Type: NXFilenamePboardType\n", script, script);
		if (returnFound)
			fprintf(serviceFile, returnOption);
		if (keyFound)
			fprintf(serviceFile, keyOption);
		fprintf(serviceFile, "\n");
		count++;
	}
	pclose(lspipe);
	fclose(serviceFile);
	
	if (count == 0) {
		NXRunAlertPanel(NULL, "No valid scripts found in %s", NULL, NULL, NULL,
			scriptDir);
		exit(1);
	}
	
	/*
	 * OK, now we have a new service file, so insert it in the executable.
	 */
	 sprintf(cmd, "segedit %s -r __ICON __services %s -o %s", NXArgv[0],
	 	SERVICE_FILE, EXEC_FILE);
	if (system(cmd) != 0) {
		NXRunAlertPanel(NULL, "Command %s failed!", NULL, NULL, NULL, cmd);
		exit(1);
	}
	sprintf(cmd, "rm -f %s;mv %s %s;chmod a+x %s", NXArgv[0], EXEC_FILE, 
		NXArgv[0], NXArgv[0]);
	if (system(cmd) != 0) {
		NXRunAlertPanel(NULL, "Command %s failed!", NULL, NULL, NULL, cmd);
		exit(1);
	}
	if (system("make_services") != 0) {
		NXRunAlertPanel(NULL, "Command make_services failed!", NULL, NULL, NULL);
		exit(1);
	}
	NXRunAlertPanel(NULL, "Reload of scripts complete. Please logout and back in again for changes to take effect.", NULL, NULL, NULL);
	exit(0);
	
	return self;
}

- runScript:pb
/*
 * Called when one of the Pipes menu buttons is clicked. Run the script.
 */
{
	int err, i, outputLen, outputMaxlen, textLength;
	char scriptCmd[MAXPATHLEN], *outputData;
	const char *paramString;
	FILE *pipe;
	NXStream *outputStream;
	static char outputFile[] = "/tmp/PipeXXXXXX";
	struct stat sbuf;

	sprintf(scriptDir, "%s/Library/Piper", getenv("HOME"));
	sprintf(scriptCmd, "%s/%s/script", scriptDir, scriptName);
	stat(scriptCmd, &sbuf);
	if (!(sbuf.st_mode & 0111)) {	
		NXRunAlertPanel(NULL, "No execute permission on %s", 
				NULL, NULL, NULL, scriptCmd);
		exit(1);
	}
	mktemp(outputFile);
	
	 paramString = [parameter stringValueAt:0];
	
	if (type == NXAsciiPboardType) {
		/*
		 * For ascii pasteboard types, we just read the selection
		 * and pipe it through the given script. The output is collected
		 * in a temporary file.
		 */
		sprintf(scriptCmd, "%s/%s/script %s >%s 2>/dev/console", scriptDir, 
			scriptName, paramString, outputFile);
		if ((pipe = popen(scriptCmd, "w")) == NULL) {
			NXRunAlertPanel(NULL, "Command \"%s\" failed", 
				NULL, NULL, NULL, scriptCmd);
			exit(1);
		}
		for (i=0; i<length; i++) {
			putc(data[i], pipe);
		}
		err = pclose(pipe);
		if (err != 0) {
			NXRunAlertPanel(NULL, "Command \"%s\" returned error code %d", 
				NULL, NULL, NULL, scriptName, err);
		}
	} else if (type == NXFilenamePboardType) {
		/*
		 * Selection is a filename. Run the script with the filename
		 * as the only parameter. Any output is collected in the
		 * temporary file.
		 */
		sprintf(scriptCmd, "%s/%s/script %s %s >%s 2>/dev/console", 
			scriptDir, scriptName, paramString, data, outputFile);
		err = system(scriptCmd);
		if (err != 0) {
			NXRunAlertPanel(NULL, "Command \"%s\" returned error code %d", 
				NULL, NULL, NULL, scriptName, err);
		}
	}
	
	if (clearParameter)
		[parameter setStringValue:"" at:0];
	
	/*
	 * Now the fun bit. Any output from the script will be in the temporary
	 * file outputFile. Map the file in and paste the data out to the
	 * selection pasteboard.
	 */
	outputStream = NXMapFile(outputFile, NX_READONLY);
	NXGetMemoryBuffer(outputStream, &outputData, &outputLen, &outputMaxlen);
	if (outputLen == 0) {
		NXCloseMemory(outputStream, NX_FREEBUFFER);
		vm_deallocate(task_self(), (int)data, length);
		sprintf(scriptCmd, "rm -f %s", outputFile);
		system(scriptCmd);
		return self;
	}

	[pb declareTypes:&NXAsciiPboardType num:1 owner:self];

	[pb writeType:NXAsciiPboardType data:outputData length:outputLen];

	/*
	 * Output data to our window. If it looks like RTF, interpret it that way.
	 */	
	textLength = [textView textLength];
	if (clearWindow)
		[textView setSel:0:textLength];
	else
		[textView setSel:textLength:textLength];
	if (!strncmp(outputData, "{\\rtf0", 6))
		[textView replaceSelWithRichText:outputStream];	
	else {
		[textView setSelFont:fontObj];
		[textView replaceSel:outputData];
	}

	NXCloseMemory(outputStream, NX_FREEBUFFER);
	vm_deallocate(task_self(), (int)data, length);
	sprintf(scriptCmd, "rm -f %s", outputFile);
	system(scriptCmd);

	return self;
}

- setClearParameter:sender
{
	clearParameter = !clearParameter;
	return self;
}

- setClearWindow:sender
{
	clearWindow = !clearWindow;
	return self;
}

@end
