#include "ULAnalyser.h"

@implementation ULAnalyser (ULAnalyserPluginExtensions)

- (void) updateAvailablePlugins
{
	NSArray* availablePlugins;

	availablePlugins = [analysisManager pluginsForCurrentInputs];
	[pluginList removeAllItems];
	[pluginList addItemsWithTitles: availablePlugins];
	//If the last selected plugin is still in the list select
	//it. Otherwise select the first available one.
	//If there are no available plugins we show "None".

	NSDebugLLog(@"ULAnalyser", @"Available plugins %@", availablePlugins);
	
	if([availablePlugins containsObject: selectedPlugin])
		[pluginList selectItemWithTitle: selectedPlugin];
	else if([availablePlugins count] == 0)
	{
		[selectedPlugin release];
		selectedPlugin = [@"None" retain];
		[pluginList addItemWithTitle: @"None"];
		[pluginList selectItemWithTitle: selectedPlugin];
	}
	else
	{
		[pluginList selectItemAtIndex: 0];
		[selectedPlugin release];
		selectedPlugin = [pluginList titleOfSelectedItem];
		[selectedPlugin retain];
	}	
}

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

Displaying Plugin Options

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

- (void) updatePluginOptions
{
	[self displayOptionsForPlugin];
}

- (void) pluginChanged: (id) sender
{
	NSDebugLLog(@"ULAnalyser", @"Plugin changed to %@", 
			 [pluginList titleOfSelectedItem]);

	if(![selectedPlugin isEqual: [pluginList titleOfSelectedItem]])
	{
		[selectedPlugin release];
		selectedPlugin = [[pluginList titleOfSelectedItem] retain];
		[self displayOptionsForPlugin];
	}
}

- (void) displayOptionsForPlugin
{
	if([analysisManager containsInputObjects] == YES && 
		![[pluginList titleOfSelectedItem] isEqual: @"None"])
	{
		[currentOptions release];
		NS_DURING
		{
			currentOptions = [analysisManager optionsForPlugin: 
						[pluginList titleOfSelectedItem]];
		}
		NS_HANDLER
		{
			NSRunAlertPanel(@"Alert",
				[localException reason],
				@"Dismiss", 
				nil,
			nil);
		}
		NS_ENDHANDLER

		NSDebugLLog(@"ULAnalyser", @"New options %@", currentOptions);

		[currentOptions retain];
		[outlineDelegate release];
		outlineDelegate  = [[ULOutlineViewDelegate alloc]
					initWithOptions: currentOptions];
		[optionsView setDataSource: outlineDelegate];
		[optionsView setDelegate: outlineDelegate];
		[optionsView reloadData];
	}
	else if([[pluginList titleOfSelectedItem] isEqual: @"None"])
	{
		[currentOptions release];
		currentOptions = nil;
		[outlineDelegate release];
		outlineDelegate  = [[ULOutlineViewDelegate alloc]
					initWithOptions: currentOptions];
		[optionsView setDataSource: outlineDelegate];
		[optionsView setDelegate: outlineDelegate];
		[optionsView reloadData];
	}
}


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

Applying the current plugin

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

- (void) _forwardPluginNotification: (NSNotification*) aNotification
{
	NSNotification* newNotification;

	if([aNotification object] != self)
	{
		/*
		 * Sequenece of events here deserves explanation. The thing to 
		 * remember is the progress panel object and this object 
		 * i.e. ULAnalyser are accessible from both threads.
		 * 1) The thread applying the current plugin receives a notification 
		 * and this method is called (in that thread) since we registered for it
		 * in the method below - N.B Notifications are always delivered in 
		 * the thread they are sentry.
		 * 2) We want the progress panel to get this notification - or one like it 
		 * - but from the main thread NOT from this thread. This is because 
		 * the gui is not thread safe.
 		 * 3) We stop the progress panel getting this exact notification by setting
		 * it to look for a different notifcation object i.e. The object of this 
		 * notification is nil while the object the progress panel needs is this one.
		 * 4) We create a new notification with the same name but with self as the object
		 * 5) We post the notification on the main thread using 
		 * performSelectorOnMainThread:withObject:waitUntilDone:
		 * 6) The progress panel recieves the notification from the main thread. 
		 * However there is one last twist. 
		 * 7) Since below we registered this object for the same notification name 
		 * with a nil object this method will get called again but this time on 
		 * the main thread! We dont want to get caught in an infinite loop so the "if"
		 * statement above identifies if this is the original notification or the new one.
		 */

		newNotification = [NSNotification notificationWithName: [aNotification name]
			object: self
			userInfo: [aNotification userInfo]];

		[[NSNotificationCenter defaultCenter]
			performSelectorOnMainThread: @selector(postNotification:)
			withObject: newNotification
			waitUntilDone: NO];
	}
}

- (void) _threadedApplyCurrentPlugin
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	id holder;

	[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(_forwardPluginNotification:)
		name: @"ULAnalysisPluginDidCompleteStepNotification"
		object: nil];

	NS_DURING
	{
		holder = pluginResults;
		pluginResults = [analysisManager applyPlugin: [pluginList titleOfSelectedItem]
					withOptions: currentOptions];
		[pluginResults retain];
		[holder release];

		NSDebugLLog(@"ULAnalyser", @"Plugin analysis completed");

		[progressPanel performSelectorOnMainThread: @selector(setProgressInfo:)
			withObject: @"Complete"
			waitUntilDone: NO];
		[progressPanel performSelectorOnMainThread: @selector(setProgressBarValue:)
			withObject: [NSNumber numberWithDouble: 100.0]
			waitUntilDone: YES];
		sleep(1);
		[progressPanel performSelectorOnMainThread: @selector(endPanel)
			withObject: nil
			waitUntilDone: NO];

		[[NSNotificationCenter defaultCenter] removeObserver: self
			name: @"ULAnalysisPluginDidCompleteStepNotification"
			object: nil];
	}
	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) applyCurrentPlugin: (id) sender
{
	NSString* pluginString;
	id holder, anArray;

	//We dont thread energy converter since its very fast.
	if([[pluginList titleOfSelectedItem] isEqual: @"EnergyConverter"])
	{
		holder = pluginResults;
		pluginResults = [analysisManager applyPlugin: @"EnergyConverter"
					withOptions: currentOptions];
		[pluginResults retain];
		[holder release];
	}
	else if([[pluginList titleOfSelectedItem] isEqual: @"None"])
	{
		NSRunAlertPanel(@"Alert",
			@"No plugin has been selected",
			@"Dismiss",
			nil,
			nil);
	}
	else
	{
		progressPanel = [ULProgressPanel progressPanelWithTitle: @"Progress Panel"
					message: @"Processing"
					progressInfo: @"Applying Plugin ..."];
		[progressPanel setPanelTitle: [NSString stringWithFormat:
						 @"Progress - %@", [pluginList titleOfSelectedItem]]];
		[progressPanel setProgressBarValue: [NSNumber numberWithDouble: 0.0]];
		[progressPanel updateStatusOnNotification: @"ULAnalysisPluginDidCompleteStepNotification"
			fromObject: self];

		//detach the thread and wait

		threadError = NO;
		[NSThread detachNewThreadSelector: @selector(_threadedApplyCurrentPlugin)
			toTarget: self
			withObject: nil];

		[progressPanel runProgressPanel: YES];

		//check if there were errors
		//If there was an error pluginResults will either be the same
		//as before or nil. If there was no error we check the returned
		//object is valid.
		if(!threadError)
		{
			//plugin results is retained in the thread above
			if(![pluginResults isKindOfClass: [NSDictionary class]])
			{
				NSRunAlertPanel(@"Alert",
					@"Plugin returned invalid object",
					@"Dismiss",
					nil,
					nil);
				
				[pluginResults release];
				pluginResults = nil;
			}
		}	

		[progressPanel removeStatusNotification: @"ULAnalysisPluginDidCompleteStepNotification"
			fromObject: self];
	}

	pluginString = [pluginResults objectForKey: @"ULAnalysisPluginString"];
	if((anArray = [pluginResults objectForKey: @"ULAnalysisPluginDataSets"]) != nil)
	{
		//populate the data set list with names
		[pluginDataSets release];
		pluginDataSets = [anArray retain];
		[self setAvailableDataSets: pluginDataSets];
		//set the selectedDataSet to be the first one returned
		[selectedDataSet release];
		selectedDataSet = [pluginDataSets objectAtIndex: 0];
		[selectedDataSet retain];
	}	
	else
	{
		[selectedDataSet release];
		[pluginDataSets release];
		selectedDataSet = nil;
		pluginDataSets = nil;
		[self setAvailableDataSets: pluginDataSets];
	}	

	//display the selectedDataSet if there is one
	if(selectedDataSet != nil)
	{
		[dataView setDataSet: selectedDataSet];
		[dataView displayData];
	}
	else
		[dataView clearDataSet];

	if(pluginString != nil)
		[self logString: [NSString stringWithFormat: 
			@"Analysis Plugin - %@.\n\n%@", 
			[pluginList titleOfSelectedItem], 
			pluginString]];
}


//FIXME: Deprecated - Use export on the selected object instead.
//Model object should have a method "outputRepresentations"
//and "representationForType" so they can advertise
//how they can be output
- (void) outputPluginResults: (id) sender
{
	NSFileManager* defaultManager;
	NSString* saveFile, *resultsFile;
	NSSavePanel * savePanel;
	int result;

	if(selectedDataSet != nil)
	{
		savePanel = [NSSavePanel savePanel];
		[savePanel setTitle: @"Export Results"];
		[savePanel setDirectory: [[ULIOManager appIOManager] applicationDir]];
		result = [savePanel runModal];

		if(result == NSOKButton)
		{
			defaultManager = [NSFileManager defaultManager];
			saveFile = [savePanel filename];
		
			if([defaultManager fileExistsAtPath: saveFile])
				[defaultManager removeFileAtPath: saveFile
					handler: nil];

			resultsFile = [[[ULIOManager appIOManager] applicationDir] 
					stringByAppendingPathComponent:@"Results"];
			if(![[NSFileManager defaultManager] copyPath: resultsFile
				toPath: saveFile
				handler: nil])
			{
				NSRunAlertPanel(@"Error", 
					@"Couldn't output results\n Check you have write permission to the\
 chosen location",
					@"Dismiss", 
					nil, 
					nil);
			}
		}
	}
	else
		NSRunAlertPanel(@"Alert", 
			@"There are no results to output",
			@"Dismiss", 
			nil, 
			nil);
}

@end

