/*
   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 <AppKit/AppKit.h>
#include "ULSystemViewController.h"

@implementation ULSystemViewController

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

Initialisation Methods

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

- (id) initWithSystemController: (id) object modelViewController: (id) mVC
{
	if(self = [super init])
	{
		if([NSBundle loadNibNamed: @"System" owner: self] == NO)
		{
			NSWarnLog(@"Problem loading interface");
			return nil;
		}

		systemController = [object retain];
		mainViewController = mVC;
		metadataController = [mainViewController metadataController];
		allowedFileTypes = [[NSArray arrayWithObjects: @"PDB", @"pdb", nil] retain];
		isBuilding = NO;

		//register for some notifications from about the build process state

		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuildSectionCompleted"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuildWillStart"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuildDidAbortNotification"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuilderWillBeginInitialisationNotification"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuilderWillBeginInitialisationStepNotification"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(handleNotification:)
			name: @"ULSystemBuilderCompletedInitialisationStepNotification"
			object: nil];

		[tabView setDelegate: self];
		preprocessTabViewItem = [[tabView tabViewItemAtIndex: 0] retain];
		[systemWindow center];
		[systemWindow setDelegate: self];

	}
	return self;
}

- (void) dealloc
{
	[outlineDelegate release];
	[currentOptions release];
	[systemController release];
	[allowedFileTypes release];
	[super dealloc];
}

- (void) _insertString: (NSString*) string fontType: (NSString*) fontType
{
	NSRange fontRange, range;
	NSMutableAttributedString* boldString;
	NSFont* font;
	NSFontManager *fontManager = [NSFontManager sharedFontManager];

	font = [NSFont userFontOfSize: 12];
	if([fontType isEqual: @"Bold"])
		font = [fontManager convertFont: font toHaveTrait: NSBoldFontMask];

	range = NSMakeRange([[logView textStorage] length], 0);
	[[logView textStorage] replaceCharactersInRange: range withString: string];
	fontRange = NSMakeRange(range.location, [string length]);	
 	
	[[logView textStorage] beginEditing];
	[[logView textStorage] addAttribute: NSFontAttributeName value: font range: fontRange];
	[[logView textStorage] endEditing];
	[logView scrollRangeToVisible: NSMakeRange([[logView textStorage] length], 0)];
}

- (void) handleNotification: (NSNotification*) aNotification
{
	NSRange range;
	NSMutableString* buildInfo;

	[progressView display];
	[progressIndicator display];	
	[progressBar display];	

	if([[aNotification name] isEqual: @"ULSystemBuildWillStart"])
	{
		[progressBar setDoubleValue: 0.0];
		[progressIndicator setStringValue: @"Beginning Build"];
		NSDebugLLog(@"ULSystemViewController", @"Recieved %@", [aNotification name]);
	}
	else if([[aNotification name] isEqual: @"ULSystemBuildSectionCompleted"])
	{
		[progressBar incrementBy: 25];
		[progressIndicator setStringValue: [aNotification object]];
		NSDebugLLog(@"ULSystemViewController", @"Recieved %@", [aNotification name]);

		[self _insertString: [aNotification object] fontType: @"Bold"];
		buildInfo = [NSMutableString stringWithCapacity: 1];
		[buildInfo appendString: @"\n"];
		[buildInfo appendString: [[aNotification userInfo] 
				objectForKey: @"ULBuildSectionUserInfoKey"]];
		[buildInfo appendString: @"\n"];
		[self _insertString: buildInfo fontType: @"Normal"];
	}
	else if([[aNotification name] isEqual: @"ULSystemBuildDidAbortNotification"])
	{
		[progressIndicator setStringValue: 
			[NSString stringWithFormat: @"Build Aborted! - At position %@", [aNotification object]]];
	
		[self _insertString: [aNotification object] fontType: @"Bold"];

		buildInfo = [NSMutableString stringWithCapacity: 1];
		[buildInfo appendString: @"\n"];
		[buildInfo appendString: [[aNotification userInfo] 
				objectForKey: @"ULBuildSectionUserInfoKey"]];
		[buildInfo appendString: @"\n"];
		[self _insertString: buildInfo fontType: @"Normal"];
	}	
	else if([[aNotification name] isEqual: @"ULSystemBuilderWillBeginInitialisationNotification"])
	{
		[progressBar setDoubleValue: 0.0];
		[progressIndicator setStringValue: [aNotification object]]; 
	}
	else if([[aNotification name] isEqual: @"ULSystemBuilderCompletedInitialisationStepNotification"])
	{	
		[progressBar incrementBy: 33];
		[progressIndicator setStringValue: [aNotification object]]; 
	}
	else if([[aNotification name] isEqual: @"ULSystemBuilderWillBeginInitialisationStepNotification"])
	{
		[progressIndicator setStringValue: [aNotification object]]; 
	}
	
	[progressIndicator display];	
	[progressBar display];	
	[progressView display];
}

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

Opening and closing the system window
We use the same interface for system creation , system analysis and pdb analysis
just changing the button names.
See note on changing this to delegate pattern

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

- (void) _setDelegateForOptions: (id) options
{
	[outlineDelegate release];
	outlineDelegate = [[ULOutlineViewDelegate alloc]
				initWithOptions: options];	
	[optionsView setDataSource: outlineDelegate];
	[optionsView setDelegate: outlineDelegate];
	[optionsView reloadData];
}

//opening a create system window

- (void) open: (id) sender
{
	ULIOManager* ioManager; 
	NSArray* plugins;
	NSRange range;

	//if the window is already open do nothing
	//except bring it to the front if neccessary

	if([systemWindow isVisible])
	{
		if(![systemWindow isKeyWindow])
			[systemWindow makeKeyAndOrderFront: self];
		
		return;
	}

	//scan local adun directory for plugins
	ioManager = [ULIOManager appIOManager];

	[[logView textStorage] deleteCharactersInRange: 
		NSMakeRange(0, [[logView textStorage] length])];
	
	//we have to make sure nothing is left from a previous
	//hidden solvent build

	currentOptions = nil;
	[self _setDelegateForOptions: currentOptions];
	
	[systemController removeMolecule];

	plugins = [ioManager configurationPlugins];
	[pluginList removeAllItems];
	[pluginList addItemWithTitle: @"None"];
	[pluginList addItemsWithTitles: plugins];
	[pluginList selectItemWithTitle: @"None"];

	[forcefieldLabel setStringValue: @"Force Field"];
	[forcefieldList removeAllItems];
	[forcefieldList addItemWithTitle: @"Enzymix"];
	[forcefieldList selectItemWithTitle: @"Enzymix"];

	[moleculePathField setStringValue: @""];
	[systemWindow setTitle: @"Create System"];
	[progressIndicator setStringValue: @""];
	[progressBar setDoubleValue: 0.0];
	[buttonOne setTitle: @"Create"];
	[systemWindow makeKeyAndOrderFront: self];
	[systemWindow setAutodisplay: YES];

	[buttonTwo setEnabled: NO];
	[buttonThree setEnabled: NO];
	
	if([tabView numberOfTabViewItems] == 1)
		[tabView insertTabViewItem: preprocessTabViewItem
			atIndex: 0];
	//removes some display problems with the inserted tab
	[tabView selectFirstTabViewItem: self];
	[tabView selectLastTabViewItem: self];
	[[tabView selectedTabViewItem] setLabel: @"Build"];
}

- (void) close: (id) sender
{
	[optionsView setDelegate: nil];
	[optionsView setDataSource: nil];
	[optionsView reloadData];
	[outlineDelegate release];
	outlineDelegate = nil;
	[currentOptions release];
	currentOptions =  nil;
	[moleculePathField setStringValue: @""];
	[systemController removeMolecule];
	[[logView textStorage] deleteCharactersInRange: 
		NSMakeRange(0, [[logView textStorage] length])];
} 

- (void) doButtonAction: (id) sender
{
	[self createSystem: sender];
}

- (void) doButtonThreeAction: (id) sender
{
	[self continueBuild];
}

- (void) doButtonTwoAction: (id) sender
{
	[self cancelBuild];
}

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

Creating a System

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

- (void) showFileBrowser: (id) sender
{
	id fileBrowser;
	int result;
	NSRange endRange;
	NSMutableString* failString;

	//if there is a current build prevent loading
	// another until	it ends

	if(isBuilding)
	{
		NSRunAlertPanel(@"Alert",
			@"You must continue or cancel the current build!",
			@"Dismiss", 
			nil, 
			nil);

		return;
	}

	[progressBar setDoubleValue: 0.0];
	fileBrowser = [NSOpenPanel openPanel];
	[fileBrowser setTitle: @"Load PDB"];
	[fileBrowser setDirectory: [[NSUserDefaults standardUserDefaults] stringForKey:@"PDBDirectory"]];
	[fileBrowser setAllowsMultipleSelection: NO];
	result = [fileBrowser runModalForTypes: allowedFileTypes];
	if(result == NSOKButton)
	{
		if([allowedFileTypes containsObject: [[fileBrowser filename] pathExtension]])
			[moleculePathField setStringValue: [fileBrowser filename]];
		else
			NSRunAlertPanel(@"Error", 
				[NSString stringWithFormat: @"Unknown file type - %@\nSupported types %@", 
					[[fileBrowser filename] pathExtension], allowedFileTypes],
				@"Dismiss",
				nil,
				nil);
		
		[[logView textStorage] deleteCharactersInRange: 
			NSMakeRange(0, [[logView textStorage] length])];

		endRange = NSMakeRange([[logView string] length], 0);
		failString = [NSMutableString stringWithCapacity: 1];
		NS_DURING
		{
			[systemController setBuildMolecule: [moleculePathField stringValue]];
			[currentOptions release];
			currentOptions = [systemController buildOptions];
			[currentOptions retain];
			[self _setDelegateForOptions: currentOptions];
		}
		NS_HANDLER
		{
			failString = [NSMutableString stringWithCapacity: 1];
			[failString appendString: @"Failed to load molecule ...\n"];
			[failString appendString: [localException reason]];
			[failString appendString: @"\n"];	

			if([[localException name] isEqual: @"ULBuildException"])
			{
				[failString appendString: [[localException userInfo] 
					objectForKey:
					@"ULBuildRecoverySuggestionKey"]];
				[failString appendString: @"\n"];	
			}

			[logView replaceCharactersInRange: endRange withString: failString];
			NSRunAlertPanel(@"Load Molecule Failed", 
				[localException reason], 
				@"Dismiss", 
				nil, 
				nil);
		}
		NS_ENDHANDLER		
	}
}

- (void) _logBuildError: (NSError*) buildError
{
	NSMutableString* failString;
	NSString* recoveryString;

	[self _insertString: @"*****  Detected Build Errors  *****\n\n" fontType: @"Bold"];
	failString = [NSMutableString stringWithCapacity: 1];

	[failString appendString: @"\n"];
	[failString appendString: [[buildError userInfo] 
		objectForKey: @"ULBuildErrorDetailedDesciptionKey"]];
	[failString appendString: @"\n"];
	
	[self _insertString: failString fontType: @"Normal"];

	recoveryString = [[buildError userInfo] 
				objectForKey: @"ULBuildErrorRecoverySuggestionKey"];
	if(recoveryString != nil)
	{	
		[self _insertString: @"***** Recovery Suggestion *****\n\n" 
			fontType: @"Bold"];
		[failString deleteCharactersInRange: NSMakeRange(0, [failString length])];
		[failString appendFormat: @"%@\n", recoveryString];
		[self _insertString: failString fontType: @"Normal"];
	}
}

- (void) _logULBuildException: (NSException*) localException
{
	NSMutableString* failString;

	[self _insertString: @"*****  Build Failed  *****\n\n" fontType: @"Bold"];
			
	failString = [NSMutableString stringWithCapacity: 1];
	[failString appendString: [localException reason]];
	[failString appendString: @"\n"];

	if([[localException userInfo] objectForKey: @"ULBuildExceptionDetailedDescriptionKey"] != nil)	
	{
		[failString appendString: [[localException userInfo] 
				objectForKey: @"ULBuildRecoverySuggestionKey"]];
		[failString appendString: @"\n"];	
	}

	if([[localException userInfo] objectForKey: @"ULBuildRecoverySuggestionKey"] != nil)	
	{
		[failString appendString: [[localException userInfo] 
				objectForKey: @"ULBuildRecoverySuggestionKey"]];
		[failString appendString: @"\n"];	
	}
	[self _insertString: failString fontType: @"Normal"];
	NSRunAlertPanel(@"Build Failed", [localException reason], @"Dismiss", nil, nil);
}

- (void) _logUnexpectedBuildException: (NSException*) localException
{
	NSMutableString* failString;

	failString = [NSMutableString stringWithCapacity: 1];
	[self _insertString: @"*****  Unexpected Build Failure  *****\n\n" fontType: @"Bold"];
	[failString appendString: [localException reason]];
	[failString appendString: @"\n"];	
	[failString appendString: [[localException userInfo] description]];
	[failString appendString: @"\n"];	
	[failString appendString: 
		@"Please file a support request with the pdb used attached and this build log "];
	[failString appendString: @"at the Adun Project Page:"];
	[failString appendString: @"\nhttp://gna.org/projects/adun\n"];
	[self _insertString: failString fontType: @"Normal"];
	NSRunAlertPanel(@"Unexpected build failure", 
				@"See log for details", 
				@"Dismiss",
				nil, 
				nil);
}

- (void) createSystem: (id)sender
{
	BOOL cancel = NO;
	NSString* filePath;
	NSString* output;
	NSMutableString* failString;
	NSError* buildError;
	int choice;

	/*
	 * isBuilding is set to yes until the build
	 * A) Completes
	 * B) raises an exception 
	 * C) is canceled by the user
	 */

	isBuilding = YES;

	NS_DURING
	{	
		[[logView textStorage] deleteCharactersInRange: 
			NSMakeRange(0, [[logView textStorage] length])];

		[self _insertString: @"*****  Beginning Build  *****\n\n" fontType: @"Bold"];
		if(currentOptions == nil)
			[NSException raise: @"ULBuildException"
				format: @"No molecule has been loaded"];

		buildError = nil;
		if(![systemController buildSystemWithOptions: currentOptions error: &buildError])
		{
			failString = [NSMutableString stringWithCapacity: 1];
			[failString appendString:[[buildError userInfo] 
				objectForKey: @"NSLocalizedDescriptionKey"]];
			[failString appendString: @"\n\n ** Check the log window for more details. **\n\n"];
			[failString appendString: @"Choose 'Continue' to ignore the errors "];
			[failString appendString: @" or 'Cancel' to abort."];
			[self _logBuildError: buildError];
			NSRunAlertPanel(@"Build Error",
				failString,
				@"Dismiss",
				nil,
				nil);	
			[buttonOne setEnabled: NO];
			[buttonTwo setEnabled: YES];
			[buttonThree setEnabled: YES];
		}
		else
		{
			[progressIndicator setStringValue: @"Complete"];
			[metadataController displayMetadataForModelObject: [systemController valueForKey:@"System"]
				allowEditing: YES
				runModal: YES];
		
			[self _insertString: @"*****  Build Complete  *****\n" fontType: @"Bold"];
			[systemController saveSystem];
			[systemWindow orderFront: self];

			isBuilding = NO;
		}
	}
	NS_HANDLER
	{	
		isBuilding = NO;

		if([[localException name] isEqual: @"ULBuildException"])
			[self _logULBuildException: localException];
		else
			[self _logUnexpectedBuildException: localException];
	}
	NS_ENDHANDLER
}

- (void) continueBuild
{
	NSError* buildError;
	NSMutableString* failString;

	NS_DURING
	{
		[self _insertString: @"Resuming build - ignoring previous errors.\n\n"
			fontType: @"Bold"];
		buildError = nil;
		if(![systemController resumeBuild: currentOptions error: &buildError])	
		{
			[self _logBuildError: buildError];
			failString = [NSMutableString stringWithCapacity: 1];
			[failString appendString:[[buildError userInfo] 
				objectForKey: @"NSLocalizedDescriptionKey"]];
			[failString appendString: @"\nCheck the log window for more details.\n\n"];
			[failString appendString: @"Choose 'Continue' to ignore the errors "];
			[failString appendString: @" or 'Cancel' to abort."];
			NSRunAlertPanel(@"Build Error",
				failString,
				@"Dismiss",
				nil,
				nil);	
		}	
		else
		{
			[progressIndicator setStringValue: @"Complete"];
			[metadataController displayMetadataForModelObject: [systemController valueForKey:@"System"]
				allowEditing: YES
				runModal: YES];
				
			[self _insertString: @"*****  Build Complete  *****\n" fontType: @"Bold"];
			[systemController saveSystem];
			[systemWindow orderFront: self];
			[buttonOne setEnabled: YES];
			[buttonTwo setEnabled: NO];
			[buttonThree setEnabled: NO];
			isBuilding = NO;
		}
	}
	NS_HANDLER
	{
		if([[localException name] isEqual: @"ULBuildException"])
			[self _logULBuildException: localException];
		else
			[self _logUnexpectedBuildException: localException];
			
		[buttonOne setEnabled: YES];
		[buttonTwo setEnabled: NO];
		[buttonThree setEnabled: NO];
		isBuilding = NO;
	}
	NS_ENDHANDLER
}

- (void) cancelBuild
{
	NSMutableString* failString;

	[self _insertString: @"*****  Build Canceled  *****\n\n" fontType: @"Bold"];
	failString = [NSMutableString stringWithCapacity: 1];
	[failString appendString: @"User canceled build due to above errors."];
	[failString appendString: @"\n"];
	[self _insertString: failString fontType: @"Normal"];
	[progressIndicator setStringValue: 
	[NSString stringWithFormat: @"Build Canceled"]];
	[buttonOne setEnabled: YES];
	[buttonTwo setEnabled: NO];
	[buttonThree setEnabled: NO];
	[systemController cancelBuild];
	isBuilding = NO;
}

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

Tabview Delegate methods

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

- (void)tabView: (NSTabView *)tabView didSelectTabViewItem: (NSTabViewItem *)tabViewItem
{
	id holder;

	holder = currentOptions;

	if([[tabViewItem label] isEqual: @"Preprocess"])
		currentOptions = [systemController preprocessOptions];
	else
		currentOptions = [systemController buildOptions];
		
	[currentOptions retain];
	[holder release];
	[self _setDelegateForOptions: currentOptions];
}

- (BOOL) tabView: (NSTabView *) tabView shouldSelectTabViewItem: (NSTabViewItem*) tabViewItem
{
	if(isBuilding)
	{
		NSRunAlertPanel(@"Alert",
			@"You must continue or cancel the current build!",
			@"Dismiss", 
			nil, 
			nil);
		
		return NO;
	}

	return YES;
}

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

Window Delegate Methods

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

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

- (BOOL) windowShouldClose: (id) sender
{
	if(isBuilding)
	{
		NSRunAlertPanel(@"Alert",
			@"You must continue or cancel the current build!",
			@"Dismiss", 
			nil, 
			nil);
		
		return NO;
	}
	
	return YES;
}

@end
