/*
   Project: UL

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

   Author: Michael Johnston

   Created: 2005-05-23 14:10:36 +0200 by 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 "ULFramework/ULSystemController.h"

@implementation ULSystemController

- (id) init
{
	if(self = [super init])
	{
		ioManager = [ULIOManager appIOManager];
		simulationDatabase = [ULDatabaseInterface databaseInterface];
		buildSteps = [NSArray arrayWithObjects:
				@"Configuration",
				@"Topology",
				@"Merge",
				@"Interactions",
				nil];	
		[buildSteps retain];
	}	

	NSDebugLLog(@"ULSystemController", @"System Controller intialised");

	return self;
}

- (void) dealloc
{
	[systemBuilder release];
	[buildSteps release];
	[configurationFileAnalyser release];
	[super dealloc];
}

- (void) systemBuilderForForceField: (NSString*) forceFieldName
{
	NSDebugLLog(@"ULSystemController",
		@"Creating System Builder. Current Builder is %@",
	 	[systemBuilder description]);

	if(systemBuilder == nil)
	{
		[[NSNotificationCenter defaultCenter]
			 postNotificationName: @"ULSystemBuilderWillBeginInitialisationNotification" 
			 object: @"Starting once only Builder initialisation ..."];
		systemBuilder = [[ULSystemBuilder alloc] initWithFileType: @"pdb" 
				forceField: forceFieldName];
	}
	else
	{
		//[systemBuilder takeValue: pluginName forKey: @"ConfigurationPlugin"];
		[systemBuilder setValue: forceFieldName forKey: @"ForceFieldName"];
	}

}

- (void) setBuildMolecule: (NSString*) path
{
	//FIXME: this is okay if theres only one force field but otherwise ....
	//How do we know what force field to use when building the solvent
	//What do we do when the solvent is the first thing built???

	if(systemBuilder == nil)
		[self systemBuilderForForceField: @"Enzymix"];

	[systemBuilder setBuildMolecule: path];
}

- (id) buildOptions
{
	return [systemBuilder buildOptions];
}

- (id) preprocessOptions
{
	return nil;
}

- (BOOL) removeMolecule
{
	return [systemBuilder removeMolecule];
}

- (NSString*) _buildPart: (NSString*) part 
	options: (NSDictionary*) optionsDict 
	error: (NSError**) buildError
{
	NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];	
	NSString* buildInfo; 

	NS_DURING
	{
		buildInfo = nil; 
		[systemBuilder buildPart: part
			options: optionsDict
			error: buildError
			userInfo: &buildInfo];
		[notificationCenter postNotificationName: @"ULSystemBuildSectionCompleted" 
			object: part
			userInfo: [NSDictionary dictionaryWithObject: buildInfo 
					forKey: @"ULBuildSectionUserInfoKey"]];
	}
	NS_HANDLER
	{
		[notificationCenter postNotificationName: @"ULSystemBuildDidAbortNotification"
			object: [systemBuilder valueForKey:@"buildPosition"]
			userInfo: [NSDictionary dictionaryWithObject: buildInfo
					forKey: @"ULBuildSectionUserInfoKey"]];
		[localException raise];
	}
	NS_ENDHANDLER

	return [systemBuilder valueForKey: @"buildPosition"];
}

- (void) _finaliseBuild
{
	FILE* file_p;
	id buildOutput;
	NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];	

	buildOutput = [[NSUserDefaults standardUserDefaults] 
			stringForKey: @"BuildOutput"];
	
	system = [[systemBuilder valueForKey:@"system"] retain]; 
	file_p = fopen([buildOutput cString], "a"); 
	GSPrintf(file_p, @"\nBuild complete\n"); 
	fclose(file_p);

	[notificationCenter postNotificationName: @"ULSystemBuildCompletedNotification"
		object: buildOutput];
}

- (BOOL) buildSystemWithOptions: (NSDictionary*) optionsDict error: (NSError**) buildError
{
	id buildOutput;
	id buildOutputDir;
	id buildStep;
	FILE* file_p;
	NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];	
	NSEnumerator* buildEnum = [buildSteps objectEnumerator];

	//release any previous system

	if(system != nil)
		[system release];

	//check where we will output the build information

	buildOutput = [[NSUserDefaults standardUserDefaults] 
			stringForKey: @"BuildOutput"];
	buildOutputDir = [buildOutput stringByDeletingLastPathComponent];

	if(![[NSFileManager defaultManager] isWritableFileAtPath: buildOutputDir])
	{
		NSWarnLog(@"Cannot write to specified build output directory %@",
			buildOutputDir);
		buildOutput  = [[[NSUserDefaults standardUserDefaults] 
				volatileDomainForName: NSRegistrationDomain]
				valueForKey: @"BuildOutput"];
		[[NSUserDefaults standardUserDefaults] setObject: buildOutput 
			forKey: @"BuildOutput"];
		NSWarnLog(@"Writing build output to default file at %@",  buildOutput);
	}

	file_p = fopen([buildOutput cString], "w");
	GSPrintf(file_p, @"Beginning build\n\n");
	fclose(file_p);
	
	//build

	NS_DURING
	{
		[notificationCenter postNotificationName: @"ULSystemBuildWillStart" 
			object: nil];

		while(buildStep = [buildEnum nextObject])
		{
			[self _buildPart: buildStep 
				options: optionsDict 
				error: buildError];

			if(*buildError != nil)
				return NO;
		}

		[self _finaliseBuild];
	}
	NS_HANDLER
	{
		file_p = fopen([buildOutput cString], "a");
		GSPrintf(file_p, @"Build failed - cancelling\n");
		fclose(file_p);
		[systemBuilder cancelBuild];
		[localException raise];
	}
	NS_ENDHANDLER	

	return YES;

}

- (BOOL) resumeBuild: (NSDictionary*) options error: (NSError**) buildError
{
	NSString* buildInfo, *buildPosition; 

	buildPosition = [systemBuilder buildPosition];
	if([buildPosition isEqual: @"Configuration"])
		[NSException raise: NSInternalInconsistencyException
			format: @"Cannot resume - No build was started"];
	else
	{
		while(![buildPosition isEqual: @"Complete"])
		{
			[self _buildPart: buildPosition 
				options: options
				error: buildError];

			if(*buildError != nil)
				return NO;
		
			buildPosition = [systemBuilder buildPosition];
		}
	}	

	[self _finaliseBuild];

	return YES;
}

- (void) cancelBuild
{
	[systemBuilder cancelBuild];
}

/**
System doesnt have a title until the metadata is added
So when what and who should notify that the title is
available?
Hack here for now because SystemViewController calls
this after adding metadata...
*/

- (void) threadedSaveSystem: (id) param
{
	NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
	[simulationDatabase addObjectToFileSystemDatabase: system];
	[pool release];
	[NSThread exit];
}

- (void) saveSystem
{
	NSDebugLLog(@"ULSystemController", @"Saving system %@ to database",system);

	[NSThread detachNewThreadSelector: @selector(threadedSaveSystem:)
		toTarget:self
		withObject: nil];

	[[NSNotificationCenter defaultCenter] 
		postNotificationName: @"ULSystemDidChangeNotification"
		object: [system valueForKey:@"name"]];
}

- (id) System
{
	return system;
}

- (BOOL) hasSystemBuilder
{
	if(systemBuilder != nil)
		return true;

	return false;
}

@end
