/* 
   Project: UL

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

   Author: Michael Johnston

   Created: 2005-05-23 13:29:49 +0200 by michael johnston
   
   Application Controller

   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 "ViewController.h"

@implementation ViewController

- (void) _makeSplashScreen
{
	NSRect mainScreenFrame;
	NSPoint mainScreenCenter;
	NSSize splashContentSize;
	NSRect splashWindowContentRect;
	NSImage* splashImage;

	splashImage = [[NSImage alloc] initWithContentsOfFile: 
			[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: @"splash.tiff"]];
	splashContentSize = [splashImage size];
	
	mainScreenFrame = [statusWindow frame];
	mainScreenCenter = NSMakePoint(mainScreenFrame.origin.x + mainScreenFrame.size.width/2, 
					mainScreenFrame.origin.y + mainScreenFrame.size.height/2);
	
	splashWindowContentRect.size = splashContentSize;
	splashWindowContentRect.origin.x = mainScreenCenter.x - splashContentSize.width/2;
	splashWindowContentRect.origin.y = mainScreenCenter.y - splashContentSize.height/2;

	splashScreen = [[NSWindow alloc] initWithContentRect: splashWindowContentRect
				styleMask: NSBorderlessWindowMask
				backing: NSBackingStoreRetained
				defer: NO];

	splashScreenImageView = [[NSImageView alloc] init];
	[splashScreenImageView setImage: splashImage];
	[(NSWindow*)splashScreen setContentView: splashScreenImageView];
	[splashScreen setLevel: NSFloatingWindowLevel];
	[splashScreen setOpaque: NO];

	[splashScreen makeKeyAndOrderFront: self];
}

+ (void)initialize
{
	NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
	id debugLevels;

	[defaults setObject: NSHomeDirectory()  forKey:@"PDBDirectory"];
	[defaults setObject: [NSHomeDirectory() stringByAppendingPathComponent: @"adun/buildOutput"]
		forKey: @"BuildOutput"];
	[defaults setObject: [NSHomeDirectory() stringByAppendingPathComponent: @"adun/UL.log"]
		forKey: @"LogFile"];
	[defaults setObject: [NSNumber numberWithDouble: 300] forKey: @"AutosaveInterval"];
	[defaults setObject: [NSNumber numberWithBool: YES] forKey: @"Autosave"];
	debugLevels = [[NSProcessInfo processInfo] debugSet];
	[debugLevels addObjectsFromArray: [[NSUserDefaults standardUserDefaults] 
		objectForKey: @"DebugLevels"]];

	[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
	[[NSUserDefaults standardUserDefaults] synchronize];
}

- (id)init
{
	id logFile;
	id icon;
	id array;

	if((self = [super init]))
	{
		//redirect output
			
		logFile = [[NSUserDefaults standardUserDefaults] stringForKey: @"LogFile"];
		if(![[NSFileManager defaultManager] isWritableFileAtPath: 
			[logFile stringByDeletingLastPathComponent]])
		{
			logFile = [[[NSUserDefaults standardUserDefaults] 
					volatileDomainForName: NSRegistrationDomain]
					valueForKey:@"LogFile"];
			NSWarnLog(@"Invalid value for user default 'LogFile'. The specificed directory is not writable");
			NSWarnLog(@"Switching to registered default %@", logFile);
		} 

		freopen([logFile cString], "w", stderr);

		//create the model controller
		modelController = [ULModelController new];

		//the methods to be forwarded to the 
		//active delegate.
		objectActions = [[NSArray alloc] initWithObjects:
					@"cut:",
					@"copy:",
					@"paste:",
					@"remove:",
					@"export:",
					@"import:",
					@"deselectAllRows:",
					nil];

		//simulation commands
		simulationCommands = [[NSArray alloc] initWithObjects:
					@"halt:",
					@"terminateProcess:",
					@"execute:",
					@"start:",
					@"restart:",
					nil]; 

		allowedActions = [NSMutableDictionary new];
		array = [NSArray arrayWithObjects: 
				@"start:", 
				@"remove:",
				@"display:",
				nil];
		[allowedActions setObject: array forKey: @"Waiting"];
		array = [NSArray arrayWithObjects: 
				@"halt:", 
				@"execute:",
				@"terminateProcess:",
				nil];
		[allowedActions setObject: array forKey: @"Running"];
		array = [NSArray arrayWithObjects: 
				@"restart:", 
				@"terminateProcess:",
				nil];
		[allowedActions setObject: array forKey: @"Suspended"];
		array = [NSArray arrayWithObject: 
				@"remove:"]; 
		[allowedActions setObject: array forKey: @"Finished"];

		//create the pasteboard 

		appPasteboard = [ULPasteboard new];

	}

	NSDebugLLog(@"ViewController", @"Completed initialisation");	

	return self;
}

- (void) awakeFromNib
{
	id columns;
	NSString* welcomeString;
	id item;
	NSMenu* mainMenu;

	[statusWindow center];
	[self _makeSplashScreen];

	[[NSApp mainMenu] setTitle:@"Adun"];

	preferencesPanel = [[ULPreferences alloc] initWithModelViewController: self];
	optionsViewController = [[ULOptionsViewController alloc]  initWithOptionsController: 
				[modelController valueForKey:@"OptionsController"]
				modelViewController: self];
	systemViewController = [[ULSystemViewController alloc]  initWithSystemController: 
				[modelController valueForKey:@"SystemController"]
				modelViewController: self];
	analyser = [[ULAnalyser alloc] initWithModelViewController: self];
	templateController = [[ULTemplateViewController alloc] init];
	processManager = [modelController valueForKey:@"ProcessManager"];

	activeDelegate = databaseBrowser;

	[selectHostButton removeAllItems];
	[selectHostButton addItemsWithTitles: [processManager valueForKey: @"Hosts"]];
	[selectHostButton selectItemAtIndex: 0];

	//register for notifications

	[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(handleServerDisconnection:)
		name: @"ULDisconnectedFromServerNotification"
		object: nil];
	[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(databaseBrowserBecameActive:)
		name: @"ULDatabaseBrowserDidBecomeActiveNotification"
		object: databaseBrowser];
	[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(statusTableBecameActive:)
		name: @"ULStatusTableDidBecomeActiveNotification"
		object: statusTable];
	[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(userLandFinishedProcess:)
		name: @"ULProcessDidFinishNotification"
		object: processManager];

	[statusWindow setDelegate: self];
	welcomeString = [NSString stringWithFormat: @"\nWelcome %@.\nWorking directory is %@\n\n",
			 NSFullUserName(), [[ULIOManager appIOManager] applicationDir]];
	[self logString: welcomeString newline: YES];
	[self logString: @"-------------------------------------------------------------------------\n"
			newline: YES];

	[statusTable setProcessManager: processManager];
}

- (void)dealloc
{
	[databaseBrowser release];
	[statusTable release];
	[appPasteboard release];
	[templateController release];
	[preferencesPanel release];
	[optionsViewController release];
	[analyser release];
	[modelController release];
	[simulationCommands release];
	[optionsViewController release];
	[objectActions release];
	[allowedActions release];
	[super dealloc];
}

- (void) applicationDidFinishLaunching:(NSNotification *)aNotification
{
	NSPort* port;
	NSMutableDictionary* userInfo;

	[self closeCreateSimulationWindow: self];

	//check now if there is a local instance of AdunServer running

	if((port = [[NSMessagePortNameServer sharedInstance] portForName: @"AdunServer"]) == nil)
		port = [[NSSocketPortNameServer sharedInstance] portForName: @"AdunServer" onHost: nil];
		
	//if there is no AdunServer listening locally alert the user and ask if they want one to be started

	if(port == nil)
		[self startAdunServer];
	
	[splashScreen orderOut: self];
	[splashScreen close];
	[splashScreenImageView release];
	[statusWindow makeKeyAndOrderFront: self];
}

- (BOOL)applicationShouldTerminate:(id)sender
{
  	return YES;
}

- (void)applicationWillTerminate:(NSNotification *)aNotif
{
	//save the database
	
	[modelController saveDatabase];
	//FIXME: Seems to be necessary - However we disable for now since
	//there is a bug in one objects dealloc method which hasnt been
	//found yet
	//[self autorelease];
}

- (BOOL)application:(NSApplication *)application openFile:(NSString *)fileName
{
}

- (void)showPrefPanel:(id)sender
{
	[preferencesPanel showPreferences: self];
}

/**
StatusTable / DatabaseBrowser directing
*/

- (void) databaseBrowserBecameActive: (NSNotification*) aNotification
{
	[statusTable setActive: NO];
	[databaseBrowser setActive: YES];
	activeDelegate = databaseBrowser;
}

- (void) statusTableBecameActive: (NSNotification*) aNotification
{
	[databaseBrowser setActive: NO];
	[statusTable setActive: YES];
	activeDelegate = statusTable;
}

/**
AdunServer launching 
**/

- (void) startAdunServer
{
	int retVal;
	NSString* launchPath;

	retVal = NSRunAlertPanel(@"Alert", 
			@"There is no AdunServer running on the local host.\nDo you wish me to start one now?\n", 
			@"Yes",
			@"No", 
			nil);

	if(retVal == NSOKButton)
	{
		launchPath = [NSHomeDirectory() 
				stringByAppendingPathComponent: @"GNUstep/Tools/AdunServer"];

		NS_DURING
		{
			[NSTask launchedTaskWithLaunchPath: launchPath
				arguments: nil];
		}
		NS_HANDLER
		{
			NSRunAlertPanel(@"Alert",
				[NSString stringWithFormat: @"Unable to lauch AdunServer - %@",
					[localException reason]],
				@"Dismiss", 
				nil,
				nil);
		}
		NS_ENDHANDLER
	}
}

- (void) handleServerDisconnection: (NSNotification*) aNotification
{
	NSString *errorString;
	NSError * error;
	NSString* disconnectedHost;
	NSDictionary* userInfo;

	userInfo = [aNotification userInfo];

	error = [userInfo objectForKey: @"ULDisconnectionErrorKey"];
	disconnectedHost = [userInfo objectForKey: @"ULDisconnectedHostKey"];

	//display the error

	errorString = [NSString stringWithFormat: @"%@\n\n%@", 
			[[error userInfo] objectForKey: NSLocalizedDescriptionKey],
			[[error userInfo] objectForKey: @"NSLocalizedRecoverySuggestionKey"]];

	[self logString: errorString newline: YES];
	NSRunAlertPanel(@"Error", errorString, @"Dismiss", nil, nil);

	//if the server that died was the local server attempt to restart it

	if([disconnectedHost isEqual: [[NSHost currentHost] name]])
		[self startAdunServer];
}

//Check the termination status of all simulations
- (void) userLandFinishedProcess: (NSNotification*) aNotification
{
	NSError* terminationError;
	NSMutableString* logString;
	NSString* errorString;

	terminationError = [[aNotification userInfo] objectForKey: @"AdTerminationErrorKey"];
	
	if(terminationError != nil)
	{
		NSRunAlertPanel(@"Simulation Error",
			[NSString stringWithFormat: @"%@\nSee log for more details.\n", 
				[[terminationError userInfo] objectForKey: @"NSLocalizedDescriptionKey"]],
				@"Dismiss",
				nil,
				nil);
		
		//check if we have a detailed description or a recovery suggestion	

		logString = [NSMutableString stringWithCapacity: 1];
		
		if((errorString = [[terminationError userInfo] objectForKey: @"AdDetailedDescriptionKey"]) != nil)
			[logString appendFormat: @"%@", errorString];

		if((errorString = [[terminationError userInfo] objectForKey: @"NSRecoverySuggestionKey"]) != nil)
			[logString appendFormat: @"%@", errorString];

		if([logString isEqual: @""])
		{
			[logString appendString: 
				[[terminationError userInfo] objectForKey: @"NSLocalizedDescriptionKey"]];
			[logString appendString: @"\nNo extra details available on cause of termination\n"];
		}
		else
			[logString insertString: 
				[[terminationError userInfo] objectForKey: @"NSLocalizedDescriptionKey"]
				atIndex: 0];

		[self logString: [NSString stringWithFormat: @"Simulation exited unexpectedly.\n%@\n", logString]
			newline: YES
			forProcess: [[aNotification userInfo] 
				objectForKey: @"ULTerminatedProcess"]];
	}
	else
	{
		[self logString: @"Simulation finished successfully\n" 
			newline: YES 
			forProcess: [[aNotification userInfo] 
				objectForKey: @"ULTerminatedProcess"]];
	}
}

//Logging

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

	endRange.location = 0;
	endRange.length = 0;
	[logOutput replaceCharactersInRange:endRange withString: string];
}

- (void) logString: (NSString*) string newline: (BOOL) newline forProcess: (ULProcess*) process
{
	NSString* processString;
	NSRange endRange;

	[self logString: string newline: newline]; 

	endRange.location = 0;
	endRange.length = 0;
	processString = [NSString stringWithFormat: @"Simulation - Name: %@. PID: %@.\n\n", 
				[process valueForKey: @"name"], [process valueForKey:@"processIdentifier"]];
	[logOutput replaceCharactersInRange:endRange withString: processString];
	endRange.location = 0;
	endRange.length = 0;
	[logOutput replaceCharactersInRange:endRange withString: 
		@"\n-------------------------------------------------------------------------\n"];

}

//Opening other windows

- (void) openAnalyser: (id) sender
{
	[analyser open: self];
}

- (void) newOptions: (id) sender
{
	[optionsViewController newOptions: (id) sender];
}

- (void) newSystem: (id) sender
{
	[systemViewController open: (id) sender];
}

- (void) testWindow: (id) sender
{
	[templateController open: self];
}

/**
Keys for the other view controllers to access the models
FIXME: In process of being deprecated
**/

- (id) SystemController
{
	return [modelController valueForKey:@"SystemController"];
}

- (id) ProcessManager
{
	return [modelController valueForKey:@"ProcessManager"];
}

- (id) metadataController
{
	return optionsViewController;
}

/**
Menu validation 
*/

//the simulation commands are in a separate category
//but we validate them here
- (BOOL) validateSimulationCommand: (NSString*) command
{
	id selectedItem;	
	NSString *status;

	if(![[appPasteboard availableTypes] containsObject: @"ULProcess"])
		return NO;

	if([appPasteboard countOfObjectsForType: @"ULProcess"] != 1)
		return NO;
			
	status = [[[appPasteboard objectsForType: @"ULProcess"] 
			objectAtIndex: 0] 
			processStatus];
	
	if([[allowedActions objectForKey: status] 
		containsObject: command])
		return YES;
	else
		return NO;

	return YES;
}

- (BOOL) validateMenuItem: (NSMenuItem*) menuItem
{
	NSString* action;
	id object, objectType;
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];


	action = NSStringFromSelector([menuItem action]);
	if([objectActions containsObject: action])
		return [activeDelegate validateMenuItem: menuItem];

	if([simulationCommands containsObject: action])
		return [self validateSimulationCommand: action];

	if([action isEqual: @"properties:"])
		return [optionsViewController validateMenuItem: menuItem];

	if([action isEqual: @"analyse:"])
		return [analyser validateMenuItem: menuItem];

	if([action isEqual: @"display:"])
	{
		//FIXME: Everything should be displayable
		objectType = [[pasteboard availableTypes] objectAtIndex: 0];
		if([objectType isEqual: @"AdDataSet"] || 
			[objectType isEqual: @"ULOptions"] || 
			[objectType isEqual: @"ULProcess"])
		{	
			return YES;
		}	
		else
			return NO;

	}

	//FIXME: preliminary load validation - load: only works
	//with simulation creation at the moment
	if([action isEqual: @"load:"])
	{
		if([activeDelegate countOfObjectsForType: @"ULSystem"] > 0)
			return YES;
		else if([activeDelegate countOfObjectsForType: @"ULOptions"] > 0)
			return YES;
		else
			return NO;
	}		

	return YES;
}

//Delegate to the activeDelegate
//We could avoid doing this by subclassing the outline and table views
//the delegates use and override/add these methods. This would 
//simplify things even more. However for now will stick with this
//method as there are more pressing things to do.

- (void) cut: (id) sender 
{ 	
	[activeDelegate cut: sender];
 }

- (void) copy: (id) sender 
{ 
	[activeDelegate copy: sender]; 
}

- (void) paste: (id) sender 
{ 
	[activeDelegate paste: sender]; 
}

- (void) remove: (id) sender 
{ 
	[activeDelegate remove: sender]; 
}

- (void) export: (id) sender
{
	[activeDelegate export: self];
}

- (void) import: (id) sender
{
	[activeDelegate import: self];
}

- (void) deselectAllRows: (id) sender
{
	[activeDelegate deselectAllRows: self];
}

/**
Delegate to other tools
**/

//FIXME: Should you be able to edit options??
- (void) display: (id) sender 
{
	ULPasteboard* pasteboard = [ULPasteboard appPasteboard];
	NSString* objectType;
	
	objectType = [[pasteboard availableTypes] objectAtIndex: 0];

	if([objectType isEqual: @"ULProcess"] || 
		[objectType isEqual: @"ULOptions"])
		[optionsViewController display: self]; 
	else
		[analyser display: self];
}

- (void) properties: (id) sender
{
	[optionsViewController properties: self];
}

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


//On closing the main window the application should quit
//but i'm not sure if calling applicationShouldTerminate 
//here is a good idea.

- (BOOL) windowShouldClose: (id) sender
{
	return NO;
}

@end

//This category will evolve to a full object

@implementation ViewController (SimulationCreation)

- (void) createProcess: (id) sender
{
	[createSimulationWindow setLevel: NSFloatingWindowLevel];
	[createSimulationWindow center];
	[createSimulationWindow makeKeyAndOrderFront: self];
}

- (void) createNewSimulation: (id) sender
{
	[createSimulationWindow close];
	host = [selectHostButton titleOfSelectedItem];
	
	NS_DURING
	{
		[modelController createProcessWithSystem: [systems objectAtIndex: 0]
			options: [options objectAtIndex: 0]
			host: host];
	}
	NS_HANDLER
	{
		if([[localException name] isEqual: NSInternalInconsistencyException])
			NSRunAlertPanel(@"Alert", [localException reason], @"Dismiss", nil, nil);
	}
	NS_ENDHANDLER
}

- (void) closeCreateSimulationWindow: (id) sender
{
	[createSimulationWindow close];
}

- (void) load: (id) sender
{
	if([activeDelegate countOfObjectsForType: @"ULSystem"] == 1)
	{
		[systems release];
		systems = [activeDelegate objectsForType: @"ULSystem"];
		[systems retain];
	}

	if([activeDelegate countOfObjectsForType: @"ULOptions"] == 1)
	{
		[options release];
		options = [activeDelegate objectsForType: @"ULOptions"];
		[options retain];
	}
	
	[optionsField setStringValue:
		[[options objectAtIndex: 0] valueForKey: @"name"]];
	[systemField setStringValue: 
		[[systems objectAtIndex: 0] valueForKey: @"name"]];
}

@end

