/*
   Project: Adun

   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 "AdunKernel/AdunIOManager.h"

static id ioManager;

@implementation AdIOManager

/*
 * Connect to and disconnecting from the local
 * AdServer instance
 */

- (BOOL) connectToServer: (NSError**) error;
{
	id reason, userInfo;

	NSDebugLLog(@"Server", @"Server debug - Attempting to  connecting to AdServer using message ports");

	serverConnection = [NSConnection connectionWithRegisteredName: @"AdunServer" 
				host: nil 
				usingNameServer: [NSMessagePortNameServer sharedInstance]];
	
	if(serverConnection == nil)
	{
		NSDebugLLog(@"Server", @"Server debug - Unable to find AdunServer on message ports.");
		NSDebugLLog(@"Server", @"Server debug - Checking for a distributed computing enabled server");
		serverConnection = [NSConnection connectionWithRegisteredName: @"AdunServer" 
				host: nil 
				usingNameServer: [NSSocketPortNameServer sharedInstance]];
	}

	if(serverConnection != nil)
	{
		[serverConnection retain];
		serverProxy = [[serverConnection rootProxy] retain];
	
		NSDebugLLog(@"Server", @"Server debug - Connected to server");
		NSDebugLLog(@"Server" ,@"Server debug - Stats are %@", [[serverProxy connectionForProxy] statistics]);

		//supply interface using an NSProtocolChecker
	
		checkerInterface = [NSProtocolChecker protocolCheckerWithTarget: self 
						protocol: @protocol(AdCommandInterface)];
		[checkerInterface retain];
		[serverProxy useInterface: checkerInterface forProcess:  [[NSProcessInfo processInfo] processIdentifier]];
		return YES;
	}
	else
	{
		reason = @"Unable to connect to server!";
		userInfo = [NSMutableDictionary dictionary];
		[userInfo setObject: reason forKey: NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain: @"AdCoreErrorDomain" 
				code: 1
				userInfo: userInfo];
		return NO;
	}	
}

- (void) acceptRequests
{
	int pid;

	if(serverConnection != nil)
	{
		pid = [[NSProcessInfo processInfo] processIdentifier];
		[serverProxy acceptingRequests: pid];
	}
}

- (void) closeConnection: (NSError*) error
{
	NSDebugLLog(@"Server", @"Server debug - Closing connection to server. Statistics are %@", 
			[[serverProxy connectionForProxy] statistics]);
	NSDebugLLog(@"Server", @"Server debug - Connection %@", [serverProxy connectionForProxy]);
	[serverProxy closeConnectionForProcess: [[NSProcessInfo processInfo] processIdentifier]
			error: error];
	[serverProxy release];
	[serverConnection invalidate];
	[serverConnection release];
	[checkerInterface release];
	serverProxy = nil;
}

- (void) sendControllerResults: (NSArray*) results
{
	[serverProxy controllerData: results 
		forProcess: [[NSProcessInfo processInfo] processIdentifier]];
}

/*
 * Setup Methods
 */

+ (id) appIOManager
{
	if(ioManager == nil)
		ioManager = [[AdIOManager alloc]
				initWithEnvironment: [AdEnvironment globalEnvironment]];
	return ioManager;
}

- (void) _setupAdunDirectories
{
	NSString *temp;
	BOOL isDir;
	NSString *currentDir;
	
	userHome = NSHomeDirectory();
	
	GSPrintf(stderr, @"\nHome directory is %@\n", userHome);

	//get process information and setup user directory information

	adunInfo = [NSProcessInfo processInfo];
	currentDir = [self currentDirectoryPath];
	adunUserDir =  [[userHome stringByAppendingPathComponent: @"adun"] retain];
	//FIXME: This should be provided from the command line or from UL
	resultsDir = [[adunUserDir stringByAppendingPathComponent: @"Database/Simulations/"] retain];
	controllerDir = [[adunUserDir stringByAppendingPathComponent: @"Plugins/Controllers"] retain];
	
	GSPrintf(stderr, @"Current Dir: %@\n", currentDir);
	GSPrintf(stderr, @"User Dir: %@\n", adunUserDir);
	GSPrintf(stderr, @"Controller Dir: %@\n", controllerDir);
}

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(ioManager != nil)
		return ioManager;

	if(self = [super initWithEnvironment: object observe: value])
	{
		if(ioManager == nil)
			ioManager = self;

		fileManager = [NSFileManager defaultManager];
		fileStreams = [NSMutableDictionary new];
		[fileStreams setObject: [NSValue valueWithPointer: stdout] forKey: @"Standard"];
		[fileStreams setObject: [NSValue valueWithPointer: stdout] forKey: @"Error"];
		simulationData = [NSMutableDictionary new]; 
		outputDir = nil;
		controllerOutputDir = nil;
		
		[self _setupAdunDirectories];
	}

	return self;
}

- (id) initWithEnvironment: (id) object
{
	return [self initWithEnvironment: object observe: YES];
}

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

- (void) dealloc
{

	[self closeAllStreams];
	[fileStreams release];
	if(serverProxy != nil)
		[self closeConnection: nil];
	[adunUserDir release];
	[resultsDir release];
	[controllerDir release];
	[outputDir release];
	[controllerOutputDir release];
	[simulationData release];
	ioManager = nil;
	[super dealloc];
}

/* 
 * Loading of simulation data
 */

- (NSMutableArray*) _convertULSystemsToDataSources: (NSArray*) ulSystems
{
	int i;
	id dataSourceArray, dataSource;

	NSDebugLLog(@"AdIOManager", @"Creating data sources");
	dataSourceArray = [NSMutableArray arrayWithCapacity: 1];
	for(i=0; i<(int)[ulSystems count]; i++)
	{
		dataSource = [AdDataSource dataSourceForULSystem: [ulSystems objectAtIndex: i] 
				withEnvironment: environment];
		[dataSourceArray addObject: dataSource];
	
		//FIXME: the following is a hack until its decided how systems should be described,
		//what keywords should be used to describe them, how many there should be etc. etc.
	
		if(i==0)
			[dataSource setValue: @"Solute" forKey: @"Type"];
		if(i==1)
			[dataSource setValue: @"Solvent" forKey: @"Type"];
	}

	NSDebugLLog(@"AdIOManager", @"Data Sources are %@", dataSourceArray);

	return dataSourceArray;
}

- (void) _loadServerData
{
	int pid;
	id options, ulSystems, dataSourceArray;

	pid = [[NSProcessInfo processInfo] processIdentifier];

	if(serverConnection == nil)
		[NSException raise: NSInternalInconsistencyException
			format: @"Not connected to server"];

	NSDebugLLog(@"Server", @"Server debug - Retrieving options");
	if((options = [serverProxy getOptionsForProcess: pid]) == nil)
		[NSException raise: NSInternalInconsistencyException
			format: @"Server returned nil for options"];
	NSDebugLLog(@"Server", @"Server debug - Options are %@", options);
	
	NSDebugLLog(@"Server", @"Server debug - Retrieving systems");
	if((ulSystems = [serverProxy getSystemsForProcess: pid]) == nil)
		[NSException raise: NSInternalInconsistencyException
			format: @"Server returned nil for system"];
	
	dataSourceArray = [self _convertULSystemsToDataSources: ulSystems];
	[simulationData setObject: dataSourceArray forKey: @"systemDataSources"];
	[simulationData setObject: options forKey: @"options"];
}

- (void) _loadCommandLineData: (NSDictionary*) dict
{
	NSString* templateFile, *systemFile;
	NSEnumerator* systemEnum;
	NSMutableArray* ulSystems = [NSMutableArray array];
	NSKeyedUnarchiver* unarchiver;
	id template, system, systemFiles;
	id dataSourceArray;
	
	//read in the data

	templateFile = [dict objectForKey: @"templateFile"];
	NSDebugLLog(@"AdIOManager", @"Retrieving template from file %@", templateFile);
	template = [NSKeyedUnarchiver unarchiveObjectWithFile: templateFile];
	if(template == nil)
		[NSException raise: NSInternalInconsistencyException
			format: [NSString stringWithFormat: 
			@"Could not load template from %@", templateFile]];
	else		
		NSDebugLLog(@"AdIOManager", @"Complete. Unarchived template %@", template);

	systemFiles = [dict objectForKey: @"systemFiles"];
	NSDebugLLog(@"AdIOManager", @"Retrieving system from files %@", systemFiles);
	systemEnum = [systemFiles objectEnumerator];
	while(systemFile = [systemEnum nextObject])
	{
		system = [NSKeyedUnarchiver unarchiveObjectWithFile: systemFile];
		if(system == nil)
			[NSException raise: NSInternalInconsistencyException
				format: [NSString stringWithFormat: 
				@"Could not load template from %@", templateFile]];
		else		
			NSDebugLLog(@"AdIOManager", @"Complete. Unarchived system %@", system);
		[ulSystems addObject: system];
	}	
		
	dataSourceArray = [self _convertULSystemsToDataSources: ulSystems];
	[simulationData setObject: dataSourceArray forKey: @"systemDataSources"];
	[simulationData setObject: template forKey: @"options"];
	NSDebugLLog(@"AdIOManager", @"Extraction and conversion complete");
}

- (id) loadProcessData: (NSDictionary*) dict
{
	NSString* inputSourceName;

	//options contains information on where to load the input from

	if((inputSourceName = [dict valueForKey: @"inputSourceName"]) == nil)
		[NSException raise: NSInvalidArgumentException
			format: @"An input source (CommandLine or Server) must be specified"];

	if([inputSourceName isEqual: @"CommandLine"])
		[self _loadCommandLineData: dict];
	else if([inputSourceName isEqual: @"Server"])
		[self _loadServerData];
	else
		[NSException raise: NSInvalidArgumentException
			format: [NSString stringWithFormat:
			 @"Invalid input source (%@) specified. Valid options are CommandLine or Server", inputSourceName]];
	
	return nil;
}

/*
 * Output directories
 */

- (void) _createSimulationOutputDirectory
{
	NSString *temp;
	BOOL isDir;
	id logFile, oldLogFile;
	NSString* contents;
	
	NSLog(@"Output Dir is %@", outputDir);
	
	if(!([fileManager fileExistsAtPath: outputDir]))
	{
		NSWarnLog(@"Directory %@ doesn't exist. Creating it.\n", outputDir);
		[fileManager createDirectoryAtPath: outputDir attributes: nil];
	}

	//check if dir is writable

	if(![fileManager isWritableFileAtPath: outputDir])
	{
		NSWarnLog(@"Directory %@ is not writable\n", outputDir);
		[NSException raise: NSInternalInconsistencyException
			format: @"Cannot create output directory %@", outputDir];
	}

	GSPrintf(stderr, @"Output directory is %@.\n", outputDir);

	//set up output files

	[self openFile: [outputDir stringByAppendingPathComponent: @"trajectory.ad"]
 		usingName: @"TrajectoryFile"
		flag: @"w+"];
	[self openFile: [outputDir stringByAppendingPathComponent: @"energy.ad"]
 		usingName: @"EnergyFile"
		flag: @"w+"];
	

	if([[NSUserDefaults standardUserDefaults] boolForKey: @"RedirectOutput"])
	{
		fflush(stderr);
		logFile = [outputDir stringByAppendingPathComponent: @"AdunCore.log"];
		oldLogFile = [[NSUserDefaults standardUserDefaults] stringForKey: @"LogFile"];
		if(![[NSFileManager defaultManager] isWritableFileAtPath:
			 [logFile stringByDeletingLastPathComponent]])
		{
			NSWarnLog(@"Cannot redirect to output dir!");
		}
		else
		{
			/*
			 * movePath:toPath:handler doesnt work - may have something to
			 * do with the fact that oldLogFile is stderr when we try to move it.
			 * Work around by reading in old file and writing to the new one.
			 */
			contents = [NSString stringWithContentsOfFile: oldLogFile];
			freopen([logFile cString], "w", stderr);
			GSPrintf(stderr, @"%@\n", contents);
			[[NSFileManager defaultManager] removeFileAtPath: oldLogFile
				handler: nil];
		}
	}

	fflush(stderr);
}

- (void) _createControllerOutputDirectory
{
	BOOL isDir;

	//remove any previous directory with this name

	if([fileManager fileExistsAtPath: controllerOutputDir])
		if(![fileManager removeFileAtPath: controllerOutputDir
			handler: nil])
		{
			[NSException raise: NSInternalInconsistencyException
				format: @"Unable to remove directory blocking creation of \
new controller output directory %@", controllerOutputDir];
		}
				
	if(![fileManager createDirectoryAtPath: controllerOutputDir attributes: nil])
		[NSException raise: NSInternalInconsistencyException
			format: @"Unable to create controller output directory %@", 
				controllerOutputDir];
}

- (void) setSimulationOutputDirectory: (NSString*) aPath
{
	if(aPath == outputDir)
		return;
	else
		[outputDir release];

	if([aPath isAbsolutePath])
	{
		outputDir = [aPath retain];
	}
	else
	{
		//we create the directory in the current directory
		aPath = [aPath lastPathComponent];
		outputDir = [[fileManager currentDirectoryPath] 
				stringByAppendingPathComponent: aPath];	
		[outputDir retain];		
	}	

	[self _createSimulationOutputDirectory];
}

- (void) setControllerOutputDirectory: (NSString*) aPath
{
	if(aPath == controllerOutputDir)
		return;
	else
		[controllerOutputDir release];
		
	if([aPath isAbsolutePath])
	{
		controllerOutputDir = [aPath retain];
	}
	else
	{
		//we create the directory in the current directory
		aPath = [aPath lastPathComponent];
		controllerOutputDir = [[fileManager currentDirectoryPath] 
				stringByAppendingPathComponent: aPath];	
		[controllerOutputDir retain];		
	}

	[self _createControllerOutputDirectory];
}

/*
 * Input/Output related methods
 */

- (FILE*) openFile: (NSString*) file  usingName: (NSString*) name flag: (NSString*) fileFlag
{
	int length;
	const char* filename;
	const char* flag;
	FILE* file_p;

	if(file == nil)
	{
		NSWarnLog(@"There is no file called %@\n", file);
		return NULL;
	}
	
	if(![fileManager fileExistsAtPath: file])
		NSWarnLog(@"File %@ does not exist. Will create it if flag indicates\n", file);

	filename = [file cString];
	flag = [fileFlag cString];

	//open the file

	file_p = fopen(filename, flag);
	if(file_p == NULL)
	{
		NSWarnLog(@"File %@ does not exist and flag is %@\n", file, fileFlag);
		return NULL;
	}
	else
		[fileStreams setObject: [NSValue valueWithPointer: file_p] forKey: name];

	return file_p;
}

- (FILE*) getStreamForName: (NSString*) name
{
	return (FILE*)[[fileStreams objectForKey: name] pointerValue];
}

- (void) closeStreamWithName: (NSString*) name
{
	fclose([self getStreamForName: name]);
	[fileStreams removeObjectForKey: name];
}

- (void) closeAllStreams
{
	NSEnumerator *enumerator = [fileStreams keyEnumerator];
	id key;

	while(key = [enumerator nextObject])
		if(![key isEqual:@"Error"] && ![key isEqual: @"Standard"])
			[self closeStreamWithName: key];
}

- (void) writeMatrix: (AdMatrix*) Matrix toStream: (FILE*) stream
{
	int i, j;

	for(i=0; i<Matrix->no_rows; i++)
	{
		for(j=0; j<Matrix->no_columns; j++)
			fprintf(stream, "%-12lf", Matrix->matrix[i][j]);
		fprintf(stream, "\n");
	}
}

/* 
 * Accessors 
 */

- (NSString*) simulationOutputDirectory 
{
	return [[outputDir retain] autorelease];
}

- (NSString*) controllerOutputDirectory
{
	return [[controllerOutputDir retain] autorelease];
}

- (NSString*) controllerDirectory
{
	return [[controllerDir retain] autorelease];
}

- (NSString*) adunUserDirectory
{
	return [[adunUserDir retain] autorelease];
}

- (NSString*) resultsDirectory
{
	return [[resultsDir retain] autorelease];
}

- (NSString*) currentDirectoryPath
{
	return [fileManager currentDirectoryPath];
}

- (NSMutableDictionary*) simulationOptions
{
	return [simulationData valueForKey: @"options"];
}

- (id) systemDataSources
{
	return [simulationData valueForKey: @"systemDataSources"];	
}

/*
 * Commands
 */

- (void) setCore: (id) object
{
	core = object;
}

- (id) core
{
	return core;
}

- (id) execute: (NSDictionary*) commandDict error: (NSError**) errorResult;
{
	NSString* command;
	SEL commandSelector;
	id result;

	NSDebugLLog(@"Execute", @"Recieved %@", commandDict);

	if((command = [commandDict objectForKey: @"command"]) == nil)
		[NSException raise: NSInvalidArgumentException
			format: @"The command dictionary is missing the command key"];
	
	NSDebugLLog(@"Execute", @"Command is %@. Querying core %@ for validity", command, core);

	if(![core validateCommand: command])	
	{
		result = nil;
		*errorResult = [NSError errorWithDomain: @"AdCoreErrorDomain"
				code: 0
				userInfo: [NSDictionary dictionaryWithObject:
					[NSString stringWithFormat: @"The supplied command (%@) is invalid", command]
					forKey: NSLocalizedDescriptionKey]];

		return result;
	}	
	
	commandSelector = NSSelectorFromString([NSString stringWithFormat:@"%@:", command]);
	
	NSDebugLLog(@"Execute", @"Command validated. Exectuing");

	result = [core performSelector: commandSelector 
			withObject: [commandDict objectForKey: @"options"]];
	NSDebugLLog(@"Execute", @"Command executed. Results %@", result);
	*errorResult = [core errorForCommand: command];
	NSDebugLLog(@"Execute", @"Error is %@", *errorResult);

	return result;
}

- (NSMutableDictionary*) optionsForCommand: (NSString*) command;
{
	return [core optionsForCommand: command];
}

- (NSArray*) validCommands
{
	return [core validCommands];
}


@end

