/*
   Project: UL

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   This application is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This application is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

#include "ULAnalyser.h"

//Private category handling the toolbar
@interface ULAnalyser (NSToolbarDelegate)
@end

@implementation ULAnalyser

- (id) initWithModelViewController: (id) mVC
{
	if((self = [super init]) != nil)
	{
		if([NSBundle loadNibNamed: @"Results" owner: self] == NO)
		{
			NSWarnLog(@"Problem loading interface");
			return nil;
		}
		
		analysisManager = [ULAnalysisManager new];
		mainViewController = mVC;
		outlineDelegate = nil;
		threadError = NO;
		selectedDataSet = nil;
		pluginDataSets = nil;
		pluginResults = nil;
		progressPanel = nil;
		selectedPlugin = nil;
		loadedObjects = [NSMutableArray new];
		selectedObjects = [NSMutableArray new];
		checkCount = 0;
		loadableTypes = [[NSArray alloc] initWithObjects: 
					@"ULSimulation",
					@"ULProcess",
					@"AdDataSet",
					@"ULSystem",
					@"Structure",
					nil];

		classMap = [NSDictionary dictionaryWithObjectsAndKeys:
				@"Simulation", @"ULSimulation",
				@"Data Set", @"AdDataSet",
				@"System", @"ULSystem",
				@"Structure", @"Structure", nil];
		[classMap retain];	
	}

	return self;
}

/**FIXME: mainViewController should be accessible through some class method*/

- (id) init
{
	return [self initWithModelViewController: nil];
}

- (void) _setToolbarImages
{
	id path;

	path = [[NSBundle mainBundle] pathForImageResource: @"document-save.png"];
      	if (path != nil)
	{
		saveImage = [[NSImage alloc] initWithContentsOfFile: path];
	}
	
	path = [[NSBundle mainBundle] pathForImageResource: @"apply.png"];
      	if (path != nil)
	{
		applyImage = [[NSImage alloc] initWithContentsOfFile: path];
	}
	
	path = [[NSBundle mainBundle] pathForImageResource: @"reload.png"];
      	if (path != nil)
	{
		reloadImage = [[NSImage alloc] initWithContentsOfFile: path];
	}
}

- (void)awakeFromNib
{
	ULIOManager* ioManager; 
	NSArray* plugins, *columns;

	ioManager = [ULIOManager appIOManager];
	
	[self updateAvailablePlugins];
	
	[optionsView sizeToFit];
	[window center];
	[window setDelegate: self];

	columns = [loadedObjectsTable tableColumns];
	[[[columns objectAtIndex: 0] headerCell] setStringValue: @"Data Name"];
	[[columns objectAtIndex: 0] setIdentifier: @"dataName"];
	[[[columns objectAtIndex: 0] dataCell] setAlignment: NSCenterTextAlignment];
	[[[columns objectAtIndex: 1] headerCell] setStringValue: @"Data Type"];
	[[columns objectAtIndex: 1] setIdentifier: @"dataType"];
	[[[columns objectAtIndex: 1] dataCell] setAlignment: NSCenterTextAlignment];
	[loadedObjectsTable setDataSource: self];
	[loadedObjectsTable setDelegate: self];
	[loadedObjectsTable setAllowsMultipleSelection: YES];
  
  	[self _setToolbarImages];
	toolbar = [[NSToolbar alloc] initWithIdentifier: @"AnalyseToolbar"];
	[toolbar setAllowsUserCustomization: NO];
	[toolbar setDelegate: self];
	[window setToolbar: toolbar];
	[toolbar release];
	 
 	[self setupGnuplotInterface];
}

- (void) dealloc
{
	[classMap release];
	[loadableTypes release];
	[selectedObjects release];
	[loadedObjects release];
	[analysisManager release];
	[self gnuplotDealloc];
	[outlineDelegate release];
	[selectedDataSet release];
	[pluginDataSets release];
	[pluginResults release];
	[super dealloc];
}

- (void) handleThreadError: (NSException*) anException
{
	NSRunAlertPanel(@"Error",
			[anException reason],
			@"Dismiss", 
			nil,
			nil);
	
	//In this object the subthreads obtain results while the main thread
	//displays progress. When the subthread finishes the main thread expects
	//to use the results. When a thread error occurs we dont want the main thread
	//to do what it normally would. Unfortunately exceptions dont work well with
	//the main (gui) thread often casuing it to stop responding. To get around
	//this if there is an error we set the variable threadError. When the subthread
	//exits and the main thread resumes it can check for this error. It is important
	//to make sure this is set to NO at the start of the thread and that subthreads
	//dont use it.

	threadError = YES;
}

- (void) displayObjectInfo: (id) object
{
	NSRange endRange;

	endRange.location = 0;
	endRange.length = 0;
	[resultsLog replaceCharactersInRange:endRange withString: 
		@"-------------------------------------------------------------------------\n"];
	endRange.location = 0;
	endRange.length = 0;
	//FIXME: implement model object method that returns an informative string
	[resultsLog replaceCharactersInRange:endRange withString: [object availableInfo]];
	endRange.location = 0;
	endRange.length = 0;
	[resultsLog replaceCharactersInRange:endRange withString: 
		[NSString stringWithFormat: @"Trajectory - %@:\n", [(AdModelObject*)object name]]];
}

- (void) setAvailableDataSets: (NSArray*) array
{
	[dataSetList removeAllItems];
	[dataSetList addItemWithTitle: [(AdDataSet*)[array objectAtIndex: 0] name]];
	[dataSetList selectItemAtIndex: 0];
}

//Called when user selects a different data set
//from the data set list
- (void) dataSetDidChange: (id) sender
{
	NSString* name;
	NSEnumerator* dataSetEnum;
	AdDataSet* dataSet;

	name = [dataSetList titleOfSelectedItem];
	dataSetEnum = [pluginDataSets objectEnumerator];
	while(dataSet = [dataSetEnum nextObject])
		if([[dataSet name] isEqual: name])
		{
			[selectedDataSet release];
			selectedDataSet = dataSet;
			[selectedDataSet retain];
			[dataView setDataSet: selectedDataSet];
			[dataView displayData];
			break;
		}
}

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

Opening and Closing the view

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

- (void) open: (id) sender
{
	NSRange endRange;

	commandRange.location = [[gnuplotInterface textStorage] length];
	[self updateAvailablePlugins];
	[window makeKeyAndOrderFront: self];
}

- (void) close: (id) sender
{
	[selectedObjects removeAllObjects];
	[loadedObjects removeAllObjects];
	[loadedObjectsTable reloadData];
	[dataView clearDataSet];
	[pluginList selectItemWithTitle: @"None"];
	[selectedDataSet release];
	[dataSetList removeAllItems];
	[pluginDataSets release];
	[pluginResults release];
	[currentOptions release];
	selectedDataSet = nil;
	currentOptions = nil;
	pluginDataSets = nil;
	pluginResults = nil;
	[optionsView setDataSource: nil];
	[optionsView setDelegate: nil];
	[optionsView reloadData];
	[resultsLog 
		replaceCharactersInRange: NSMakeRange(0, [[resultsLog textStorage] length])
		withString: @""];
	[window orderOut: self];
}


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

 Validation

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

//initial implementation of load validation

- (NSNumber*) _validateLoad: (id) sender
{
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];
	id type, process;
	BOOL retval;

	//only load if ULAnalyser is not the current pasteboard
	//owner (Since then the object is already loaded).

	if(checkCount == [pasteboard changeCount])
		return [NSNumber numberWithBool: NO];
	
	type = [pasteboard availableTypeFromArray: loadableTypes];
	if(type != nil)
	{
		//If were loading a process check its been started
		if([type isEqual: @"ULProcess"])
		{
			process = [pasteboard objectForType: type];
			if([[process processStatus] isEqual: @"Waiting"])
				retval = NO;
			else
				retval = YES;
		}
		else
			retval = YES;
	}	
	else	
		retval = NO;

	return [NSNumber numberWithBool: retval];	
}

//Only valid when the dataSet currently being displayed
//by ULAnalyserDataSetView i.e. the selectedDataSet, 
//is in pluginDataSets
- (NSNumber*) _validateSave: (id) sender
{
	if(selectedDataSet != nil)
		if([pluginDataSets containsObject: selectedDataSet])
			return [NSNumber numberWithBool: YES];
	
	return [NSNumber numberWithBool: NO];
}

- (NSNumber*) _validateRemove: (id) sender
{
	if([selectedObjects count] != 0)
		return [NSNumber numberWithBool: YES];

	return [NSNumber numberWithBool: NO];	
}

- (NSNumber*) _validateDisplay: (id) sender
{
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];
	NSArray* availableTypes;

	availableTypes = [pasteboard availableTypes];
	if([availableTypes containsObject: @"AdDataSet"])
		return [NSNumber numberWithBool: YES];
	else
		return [NSNumber numberWithBool: NO];
}

- (NSNumber*) _validateAnalyse: (id) sender
{
	if([window isKeyWindow])
		return NO;
	else	
		return [self _validateLoad: sender];
}

- (BOOL) validateMenuItem: (NSMenuItem*) menuItem
{
	id action;	
	SEL selector;
	
	//NSPopUpButton is also a menu - however we want
	//it to be always active

	if([menuItem action] == NULL)
		return YES;
	
	if([NSStringFromSelector([menuItem action]) isEqual: @"deselectAllRows:"])
	{
		if([[loadedObjectsTable selectedRowIndexes] count] > 0)
			return YES;	
		else
			return NO;
	}	

	if([NSStringFromSelector([menuItem action]) isEqual: @"loadExternal:"])
			return YES;	

	action = [NSStringFromSelector([menuItem action]) capitalizedString];
	action = [NSString stringWithFormat: @"_validate%@", action];
	selector = NSSelectorFromString(action);

	if([self respondsToSelector: selector])
		return [[self performSelector: selector] boolValue];
	else 
		return NO;
}

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

Menu Commands

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

//These methods deal with loading ULSimulation
//objects data in a threaded manner.

- (void) _threadedLoadSimulation: (id) object 
{
	NSAutoreleasePool* pool = [NSAutoreleasePool new];
	id holder;
	
	NS_DURING
	{
		sleep(0.5);
		[object loadData];
		[progressPanel performSelectorOnMainThread: @selector(setProgressInfo:)
			withObject: @"Caching energies ..."
			waitUntilDone: NO];
		[progressPanel performSelectorOnMainThread: @selector(setProgressBarValue:)
			withObject: [NSNumber numberWithDouble: 40.0]
			waitUntilDone: NO];
		//FIXME: This causes the simulation to load its energies
		//but its fairly hacky
		[object availableInfo];	
		[progressPanel performSelectorOnMainThread: @selector(setProgressBarValue:)
			withObject: [NSNumber numberWithDouble: 100.0]
			waitUntilDone: YES];
		[progressPanel performSelectorOnMainThread: @selector(setProgressInfo:)
			withObject: @"Complete"
			waitUntilDone: NO];

		sleep(1.0);

		[progressPanel performSelectorOnMainThread: @selector(endPanel)
			withObject: nil
			waitUntilDone: NO];
	}
	NS_HANDLER
	{
		NSWarnLog(@"Caught plugin exception %@, %@, %@", 
			[localException name], 
			[localException reason],
			[localException userInfo]);	
		[self performSelectorOnMainThread: @selector(handleThreadError:)
			withObject: localException 
			waitUntilDone: YES];
		[progressPanel performSelectorOnMainThread: @selector(endPanel)
			withObject: nil
			waitUntilDone: NO];
	}	
	NS_ENDHANDLER

	[pool release];
	[NSThread exit];
}

- (void) _loadSimulationData: (id) simulation
{
	if(selectedDataSet != nil)
		[dataView clearDataSet];

	progressPanel = [ULProgressPanel progressPanelWithTitle: @"Loading Data"
				message: @"Loading Simulation"
				progressInfo: @"Accessing trajectory ..."];
	[progressPanel setProgressBarValue: [NSNumber numberWithDouble: 0.0]];

	//detach the thread

	threadError = NO;
	[NSThread detachNewThreadSelector: @selector(_threadedLoadSimulation:)
		toTarget: self
		withObject: simulation];
	[progressPanel runProgressPanel: YES];
}

/*
- (void) reloadSimulation: (id) sender
{
	[self _loadSimulationData];
}*/

- (void) _flushProcessEnergies: (ULProcess*) process
{
	NSMutableDictionary* commandDict;
	NSError* error;
	NSString* alertTitle;
	id result;

	if([[process valueForKey:@"processStatus"] isEqual: @"Running"])
	{
		commandDict = [NSMutableDictionary dictionary];
		[commandDict setObject: @"flushEnergies"
			forKey: @"command"];
		result = [[ULProcessManager appProcessManager]  
				execute: commandDict 
				error: &error 
				process: process];
	
		if(error != nil)
		{
			alertTitle = [NSString stringWithFormat: 
					@"Alert: %@", 
					[error domain]];
			NSRunAlertPanel(alertTitle, 
				[[error userInfo] objectForKey: NSLocalizedDescriptionKey], 
				@"Dismiss", 
				nil, 
				nil);
		}
	}
}

- (id) _retrieveControllerResults: (ULProcess*) process
{
	NSMutableDictionary* commandDict;
	NSError* error;
	NSString* alertTitle;
	id result;

	result = nil;
	if([[process valueForKey:@"processStatus"] isEqual: @"Running"])
	{
		commandDict = [NSMutableDictionary dictionary];
		[commandDict setObject: @"controllerResults"
			forKey: @"command"];
		result = [[ULProcessManager appProcessManager]  
				execute: commandDict 
				error: &error 
				process: process];
	
		if(error != nil)
		{
			alertTitle = [NSString stringWithFormat: 
					@"Alert: %@", 
					[error domain]];
			NSRunAlertPanel(alertTitle, 
				[[error userInfo] objectForKey: NSLocalizedDescriptionKey], 
				@"Dismiss", 
				nil, 
				nil);
		}
	}

	return result;
}

- (void) load: (id) sender
{
	NSString* type;
	NSEnumerator* dataEnum;
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];
	id object, process, dataSet;
	
	//FIXME: Should support loading of mulitple objects simultaneously
	
	process = nil;
	type = [pasteboard availableTypeFromArray: loadableTypes];
	object = [pasteboard objectForType: type];

	//if the object is a ULSimulation we load up its data now
	//if its a ULProcess we flush its energies first

	if([type isEqual: @"ULSimulation"])
		[self _loadSimulationData: object];
	else if([type isEqual: @"ULProcess"])
	{
		process = object;
		//extract the simulation data from the process
		[self _flushProcessEnergies: process];
		object = [process simulationData];
		type = @"ULSimulation";
		[self _loadSimulationData: object];
		
	
	}	
	
	//check for errors after preprocessing

	if(!threadError)
		//FIXME: All model objects should return an info string
		if([type isEqual: @"ULSimulation"])	
			[self displayObjectInfo: object];

	//Add the object to the loaded objects list
	
	[loadedObjects addObject: object];

	if(process != nil)
	{
		//check if there are any controller results
		//from the running simulation
		object = [self _retrieveControllerResults: process];
		NSDebugLLog(@"Controller results are %@", object);
		if(object != nil)
		{
			dataEnum = [object objectEnumerator];
			while(dataSet = [dataEnum nextObject])
				[loadedObjects addObject: dataSet];
		}		
	}		

	//Update the loaded objects table
	[loadedObjectsTable reloadData];
}

- (void) remove: (id) sender
{
	NSEnumerator* selectedObjectsEnum;
	id object;

	selectedObjectsEnum = [selectedObjects objectEnumerator];
	while(object = [selectedObjectsEnum nextObject])
		[loadedObjects removeObject: object];
	
	[loadedObjectsTable reloadData];
	
	[selectedObjects removeAllObjects];
	[analysisManager removeAllInputObjects];
	if([loadedObjects count] > 0) 	
	{
		[loadedObjectsTable selectRowIndexes: 
			[NSIndexSet indexSetWithIndex: 0]
			byExtendingSelection: NO];
	}		
	[self updateAvailablePlugins];
	[self updatePluginOptions];
}

- (void) analyse: (id) sender
{
	[self load: self];
	[self open: self];
}

//Displays the selected data set
- (void) display: (id) sender
{
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];
	id object, type;

	//If we are not supplying the data then we must
	//load it first
	if([pasteboard changeCount] != checkCount)
	{
		[self load: self];
		object = [loadedObjects lastObject];
	}
	else
	{
		//we are supplying the data (we go through the
		//pasteboard anyway)
		type = [[pasteboard availableTypes] objectAtIndex: 0];
		object = [pasteboard objectForType: type];
	}

	[self setAvailableDataSets: [NSArray arrayWithObject: object]];
	[selectedDataSet release];
	selectedDataSet = [object retain];
	[dataView setDataSet: object];
	[dataView displayData];
	if(![window isKeyWindow])
		[self open: self];
}

//Only valid when the dataSet currently being displayed
//by ULAnalyserDataSetView is in pluginDataSets
- (void) save: (id) sender
{
	id dataSet, databaseInterface;
	AdModelObject *inputObject;
	NSEnumerator* resultsEnum, *inputObjectsEnum;
	
	//check there is an object available

	if(selectedDataSet == nil)
	{
		NSRunAlertPanel(@"Alert",
			@"No data available to be saved.",
			@"Dismiss", 
			nil,
			nil);
		return;
	}	
	
	//check if the object has already been saved
	
	databaseInterface = [ULDatabaseInterface databaseInterface];
	if(![databaseInterface objectInFileSystemDatabase: selectedDataSet])
	{	
		//FIXME: Change way of calling properties display tool
		[[mainViewController metadataController]
			displayMetadataForModelObject: selectedDataSet
			allowEditing: YES
			runModal: YES];
	
		//The analysis manager saves the data set taking
		//care of all references
		[analysisManager saveOutputDataSet: selectedDataSet];

		//Reset the dataSet in the dataView and update the 
		//available data sets so the name change will be refelected

		[self setAvailableDataSets: [NSArray arrayWithObject: selectedDataSet]];
		[dataView setDataSet: selectedDataSet];
		[dataView displayData];
		[window makeKeyAndOrderFront: self];
	}
	else
		NSRunAlertPanel(@"Error",
			@"Displayed data set already saved to database",
			@"Dismiss", 
			nil,
			nil);
}

- (void) deselectAllRows: (id) sender
{
	int row;
	id selectedRows;
	
	selectedRows = [loadedObjectsTable selectedRowIndexes];
	if([selectedRows count] == 0)
		return;

	row = [selectedRows firstIndex];
	while(row != NSNotFound)
	{
		[loadedObjectsTable deselectRow: row];
		row = [selectedRows indexGreaterThanIndex: row];
	}
	
	[loadedObjectsTable setNeedsDisplay: YES];
	[selectedObjects removeAllObjects];
	[analysisManager removeAllInputObjects];
	[self updateAvailablePlugins];
	[self updatePluginOptions];
} 

- (void) logString: (NSString*) string
{
	NSRange endRange;

	endRange.location = 0;
	endRange.length = 0;
	[resultsLog replaceCharactersInRange:endRange withString: 
		@"-------------------------------------------------------------------------\n"];
	endRange.location = 0;
	endRange.length = 0;
	[resultsLog replaceCharactersInRange:endRange withString: string];
}

//for loading pdbs
- (void) loadExternal: (NSString*) string
{
	id fileBrowser;
	int result;
	NSArray* allowedFileTypes;
	Structure* structure;

	fileBrowser = [NSOpenPanel openPanel];
	[fileBrowser setTitle: @"Load External Object"];
	[fileBrowser setDirectory: [[NSUserDefaults standardUserDefaults] stringForKey:@"PDBDirectory"]];
	[fileBrowser setAllowsMultipleSelection: NO];
	allowedFileTypes = [NSArray arrayWithObjects: @"pdb", @"PDB", nil];
	result = [fileBrowser runModalForTypes: allowedFileTypes];
			
	if(result == NSOKButton)
	{
		if(![allowedFileTypes containsObject: [[fileBrowser filename] pathExtension]])
			NSRunAlertPanel(@"Error", 
				[NSString stringWithFormat: @"Unknown file type - %@\nSupported types %@", 
					[[fileBrowser filename] pathExtension], allowedFileTypes],
				@"Dismiss",
				nil,
				nil);

		structure = [StructureFactory newStructureFromPDBFile: [fileBrowser filename]];
		[loadedObjects addObject: structure];
		[loadedObjectsTable reloadData];
	}

}

/**
Pasteboard Methods
**/

- (NSArray*) availableTypes
{
	NSString* type;

	if([selectedObjects count] > 0)
	{
		type = NSStringFromClass([[selectedObjects objectAtIndex: 0] class]);
		return [NSArray arrayWithObject: type];
	}	
	else
		return [NSArray array];
		
}

- (id) objectForType: (NSString*) type;
{
	if([[self availableTypes] containsObject: type])
		return [selectedObjects objectAtIndex: 0];
	else
		return nil;
}

- (NSArray*) objectsForType: (NSString*) type
{
	if([[self availableTypes] containsObject: type])
		return [NSArray arrayWithObject: [selectedObjects objectAtIndex: 0]];
	else
		return nil;

}

- (int) countOfObjectsForType: (NSString*) type
{
	if([[self availableTypes] containsObject: type])
		return 1;
	else
		return 0;

}

- (void) pasteboardChangedOwner: (id) pasteboard
{
	[self deselectAllRows: self];
}

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

Loaded Objects Table Data Source Methods

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

- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
	return [loadedObjects count];
}

- (id)tableView:(NSTableView *)aTableView 
	objectValueForTableColumn:(NSTableColumn *)aTableColumn 
	row:(int)rowIndex
{
	id object;

	object = [loadedObjects objectAtIndex: rowIndex];
	if([[aTableColumn identifier] isEqual: @"dataType"])
		return [classMap objectForKey: NSStringFromClass([object class])];
	else	
	{
		if([object isKindOfClass: [AdModelObject class]])
			return [(AdModelObject*)object name];
		else
			return [object pdbcode] == nil ? @"Unknown" : [object pdbcode];
	}		
}

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

Loaded Objects Table Delegate Methods

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

- (void) tableViewSelectionDidChange: (NSNotification*) aNotification
{
	int row;
	id selectedRows;
	
	[selectedObjects removeAllObjects];
	[analysisManager removeAllInputObjects];
	selectedRows = [loadedObjectsTable selectedRowIndexes];
	if([selectedRows count] == 0)
		return;

	row = [selectedRows firstIndex];
	while(row != NSNotFound)
	{
		[selectedObjects addObject: 
			[loadedObjects objectAtIndex: row]];
		[analysisManager addInputObject: 
			[loadedObjects objectAtIndex: row]];
		row = [selectedRows indexGreaterThanIndex: row];
	}

	[self updateAvailablePlugins];
	[self updatePluginOptions];
}

- (BOOL) tableView: (NSTableView*) table shouldSelectRow: (int) row
{
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];

	if([pasteboard changeCount] != checkCount)
	{
		[pasteboard setPasteboardOwner: self];
		checkCount = [pasteboard changeCount];
	}

	return YES;
}	

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

Window Delegate Methods

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

- (void) windowDidResize: (NSNotification*) aNotification
{
	[optionsView sizeToFit];	
}

- (void) windowWillClose: (NSNotification*) aNotification
{
	[self close: self];
}

@end

/*
 *
 * Extensions to ULAnalyser to
 * enable gnuplot integration.
 *
*/

@implementation ULAnalyser (ULAnalyserGnuplotExtensions)

- (void) gnuplotDealloc
{
	[pipey release];
	[outPipe release];
	[gnuplotOutput release];
	[gnuplotError release];
	[gnuplot terminate];
	[gnuplot release];
	[history release];
}

- (void) setupGnuplotInterface
{
	NSRange endRange;
	ULIOManager* ioManager = [ULIOManager appIOManager];

	[gnuplotInterface setDelegate: self];
	pipey =  [NSPipe new]; 
	outPipe = [NSPipe new];
	gnuplotOutput =  [[pipey fileHandleForWriting] retain]; 
	gnuplotError = [[outPipe fileHandleForReading] retain];
	gnuplot = [NSTask new];
	//FIXME: Read launch path from defaults
	[gnuplot setLaunchPath: @"/usr/bin/gnuplot"];
	[gnuplot setCurrentDirectoryPath: [ioManager applicationDir]];
	[gnuplot setStandardInput: pipey];
	[gnuplot setStandardError: outPipe];
	[gnuplot launch];

	history = [[NSMutableArray arrayWithCapacity: 1] retain];
	historyDepth = 100;
	currentHistoryPosition = 0;
	
	endRange.location = 0;
	endRange.length = 0;
	[gnuplotInterface replaceCharactersInRange:endRange withString:@"gnuplot> "];

	gnuplotPrompt.location = 0;
	gnuplotPrompt.length = [[gnuplotInterface textStorage] length];

}

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

Gnuplot History

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

- (void) _addStringToHistory: (NSString*) string
{
	[history addObject: string];

	if([history count] == historyDepth)
		[history removeObjectAtIndex: 0];

	currentHistoryPosition = [history count];
}

- (NSString*) _previousStringFromHistory
{
	id string;

	NS_DURING
	{
		currentHistoryPosition--;
		string = [history objectAtIndex: currentHistoryPosition];
		return string;
	}
	NS_HANDLER
	{
		if([[localException name] isEqual: NSRangeException])
		{
			if([history count] != 0)
			{
				currentHistoryPosition = 0;
				string = [history objectAtIndex: currentHistoryPosition];
				return string;
			}
			else
				return @"";
		}
	}
	NS_ENDHANDLER
}

- (NSString*) _nextStringFromHistory
{
	id string;

	NS_DURING
	{
		currentHistoryPosition++;
		string = [history objectAtIndex: currentHistoryPosition];
		return string;
	}
	NS_HANDLER
	{
		if([[localException name] isEqual: NSRangeException])
		{
			currentHistoryPosition = [history count];
			return @"";
		}
	}
	NS_ENDHANDLER
}

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

Gnuplot TextView Delegate Methods 

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

- (BOOL) textView: (NSTextView*) aTextView doCommandBySelector:(SEL)aSelector
{
	NSRange endRange;
	NSString* string, *errorString;
	NSData* data;

	if([NSStringFromSelector(aSelector) isEqual: @"insertNewline:"])
	{
		commandRange.length = [[aTextView textStorage] length] - commandRange.location;
		string = [[[aTextView textStorage] attributedSubstringFromRange: commandRange] string];
		[self _addStringToHistory: string];
		string = [NSString stringWithFormat: @"%@\n", string];
		data  = [string dataUsingEncoding: NSASCIIStringEncoding];
		[gnuplotOutput writeData: data];
		endRange.location = [[aTextView textStorage] length];
		endRange.length = 0;
		[aTextView replaceCharactersInRange:endRange withString:
			[NSString stringWithFormat: @"\n", string]];

		endRange.location = [[aTextView textStorage] length];
		gnuplotPrompt.location = endRange.location;
		endRange.length = 0;
		[aTextView replaceCharactersInRange:endRange withString: @"gnuplot> "];
		gnuplotPrompt.length = [[aTextView textStorage] length] - gnuplotPrompt.location;
		//make sure the cursor appears after the ">"
		endRange.location = [[aTextView textStorage] length];
		[aTextView setSelectedRange: endRange];
		[aTextView scrollRangeToVisible: endRange];
		commandRange.location = [[aTextView textStorage] length];
		return YES;
	}
	else if([NSStringFromSelector(aSelector) isEqual: @"moveUp:"])
	{
		string = [self _previousStringFromHistory];
		commandRange.length = [[aTextView textStorage] length] - commandRange.location;
		[aTextView replaceCharactersInRange:commandRange withString: string];
		return YES;
	}	
	else if([NSStringFromSelector(aSelector) isEqual: @"moveDown:"])
	{
		string = [self _nextStringFromHistory];
		commandRange.length = [[aTextView textStorage] length] - commandRange.location;
		[aTextView replaceCharactersInRange:commandRange withString: string];
		return YES;
	}

	return NO;
}

- (BOOL) textView: (NSTextView*) aTextView
	shouldChangeTextInRange:  (NSRange) range
	replacementString: (NSString*) string
{
	NSRange intersectionRange;
	intersectionRange = NSIntersectionRange(range, gnuplotPrompt);
	
	if(intersectionRange.length == 0)
		return YES;
	else
		return NO;
}

@end

@implementation ULAnalyser (NSToolbarDelegate)

- (NSToolbarItem*)toolbar: (NSToolbar*)toolbar
    itemForItemIdentifier: (NSString*)itemIdentifier
willBeInsertedIntoToolbar: (BOOL)flag
{
  NSToolbarItem *toolbarItem = AUTORELEASE([[NSToolbarItem alloc]
					     initWithItemIdentifier: itemIdentifier]);

	if([itemIdentifier isEqual: @"ApplyItem"])
	{
		[toolbarItem setLabel: @"Apply"];
		[toolbarItem setImage: applyImage];
		[toolbarItem setTarget: self];
		[toolbarItem setAction: @selector(applyCurrentPlugin:)];     
		[toolbarItem setTag: 0];
	}
	else if([itemIdentifier isEqual: @"SaveItem"])
	{
		[toolbarItem setLabel: @"Save"];
		[toolbarItem setImage: saveImage];
		[toolbarItem setTarget: self];
		[toolbarItem setAction: @selector(save:)];     
		[toolbarItem setTag: 1];
	}
	else if([itemIdentifier isEqual: @"ReloadItem"])
	{
		[toolbarItem setLabel: @"Reload"];
		[toolbarItem setImage: reloadImage];
		[toolbarItem setTarget: self];
		//[toolbarItem setAction: @selector(save:)];     
		[toolbarItem setTag: 2];
	}

	 
  return toolbarItem;
}

- (NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar*)toolbar
{
  return [NSArray arrayWithObjects: @"ApplyItem", 
		  @"SaveItem",
		  @"ReloadItem",
		  nil];
}

- (NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar*)toolbar
{ 
  return [NSArray arrayWithObjects: @"ApplyItem", 
		  @"SaveItem", 
		  @"ReloadItem",
		  nil];
}

- (NSArray*) toolbarSelectableItemIdentifiers: (NSToolbar*)toolbar
{ 
  return [NSArray arrayWithObjects: @"ApplyItem", 
		  @"SaveItem", 
		  @"ReloadItem",
		  nil];
}
@end
