/*
   Project: UL

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

   Author: Michael Johnston

   Created: 2005-05-23 14:11:56 +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/ULProcess.h"

@implementation ULProcess

- (id) initWithSystems: (id) sys options: (id) opt
{
	id path;
	NSEnumerator* systemEnum;
	id system;

	if(self = [super init])
	{
		processIdentifier = -1;
		[generaldata setObject: @"N/A" forKey: @"Length"];
		[generaldata setObject: @"N/A" forKey: @"Host"];
		[generaldata setObject: @"N/A" forKey: @"Started"];
		[generaldata setObject: @"Unknown" forKey: @"Status"];
		[generaldata setObject: [NSNumber numberWithInt: processIdentifier]
			forKey: @"Process Identifier"];
		[dataDict setObject: [NSMutableDictionary dictionary] forKey: @"Systems"];
		systems = sys;
		systemEnum = [systems objectEnumerator];
		while(system = [systemEnum nextObject])
		{
			path = [NSString stringWithFormat: @"Systems.%@", [system valueForKeyPath: @"metadata.Name"]]; 
			[dataDict setValue: [system dataDictionary] 
				forKeyPath: path];
		}
		
		[systems retain];
		
		options = opt;
		[dataDict setObject: [options dataDictionary] 
			forKey: @"Options"];

		[options retain];
		dataSets = nil;
		simulationDataDirectory = nil;
		controllerOutputDirectory = nil;
	}

	return self;
}

+ (id) processWithSystem: (id) sys options: (id) opt
{
	id process;
	NSMutableArray* systemArray;

	systemArray = [NSMutableArray arrayWithObject: sys];
	process = [ULProcess processWithSystems: systemArray options: opt];
	
	return process;
}

+ (id) processWithSystems: (id) sys options: (id) opt
{
	id process;

	process = [[ULProcess alloc] initWithSystems: (id) sys options: (id) opt];
	[process autorelease];

	return process;
}

- (void) dealloc
{
	[controllerOutputDirectory release];
	[simulationDataDirectory release];
	[dataSets release];
	[systems release];
	[options release];
	[simulationData release];

	[super dealloc];
}

- (int) processIdentifier
{
	return [[generaldata objectForKey: @"Process Identifier"] intValue];
}

- (void) setProcessIdentifier: (int) number
{
	[generaldata setObject: [NSNumber numberWithInt: number] forKey: @"Process Identifier"];
}

- (id) length
{
	return [generaldata objectForKey: @"Length"];
}

- (NSString*) processHost
{
	return [generaldata objectForKey: @"Host"];
}

- (id) systems
{
	return systems;
}
- (id) options
{
	return options;
}

- (NSString*) processStatus
{
	return [generaldata objectForKey: @"Status"];
}

- (id) started
{
	return [generaldata objectForKey: @"Started"];
}

- (void) setProcessStatus: (NSString*) value
{
	[generaldata setObject: value forKey: @"Status"];
}

- (void) setProcessHost: (NSString*) value
{
	[generaldata setObject: value forKey: @"Host"];
}

- (void) setLength: (id) value
{
	[generaldata setObject: value forKey: @"Length"];
}

- (void) setStarted: (NSDate*) value
{
	[generaldata setObject: [dateFormatter stringForObjectValue: value]
		 forKey: @"Started"];
}

- (void) setControllerResults: (NSArray*) results
{
	NSEnumerator* resultsEnum, *matrixEnum;
	id dataSet, matrix;;
	
	NSDebugLLog(@"ULProcess", 
		@"Recieved controller data %@", results);
	
	resultsEnum = [results objectEnumerator];
	while(dataSet = [resultsEnum nextObject])
	{
		matrixEnum = [[dataSet dataMatrices] objectEnumerator];
		while(matrix = [matrixEnum nextObject])
			[matrix printMatrix];
	}

	if(dataSet != results)
	{
		[dataSets release];
		dataSets = [results retain];
	}
}

- (NSArray*) controllerResults
{
	return [[dataSets retain] autorelease];
}

/**
Returns the object representing the 
simulation data generated by the process so far
*/
- (id) simulationData
{
	return [[simulationData retain] autorelease];
}

/*******
NSCoding Methods
********/

//\note If we encode the process we should retain the ids of
//the system and options and the results dir.
//at the moment these shouldnt be used

- (id) initWithCoder: (NSCoder*) decoder
{
	[super initWithCoder: decoder];

	if([decoder allowsKeyedCoding])
	{
		systems = [[decoder decodeObjectForKey:@"Systems"] retain];
		options= [[decoder decodeObjectForKey:@"Options"] retain];
		host = [[decoder decodeObjectForKey:@"Host"] retain];
	}
	else
	{
		systems = [[decoder decodeObject] retain];
		options= [[decoder decodeObject] retain];
		host = [[decoder decodeObject] retain];
	}

	return self;
}

- (void) encodeWithCoder: (NSCoder*) encoder
{
	[super encodeWithCoder: encoder];

	if([encoder allowsKeyedCoding])	
	{
		[encoder encodeObject: systems forKey:@"Systems"];
		[encoder encodeObject: options forKey:@"Options"];
	}
	else
	{
		[encoder encodeObject: systems];
		[encoder encodeObject: options];
	}
	
}

/********
ULClientInterface Methods
********/

- (bycopy NSMutableDictionary*) transmitOptionsForProcess: (int) pid
{
	return [options valueForKey:@"Options"];
}

- (NSString*) _setFormat: (int) number
{
	if(number == 0)
		return @"00";
	else if(number < 10)
		return [NSString stringWithFormat: @"0%d", number];
	else
		return [NSString stringWithFormat: @"%d", number];
}

- (void) processDidTerminate: (NSError*) error
{
	NSTimeInterval interval;
	NSString* hourSt, *minuteSt, *secondSt;
	int hour, minute, second;
	NSMutableDictionary* userInfo;
	id system;
	NSEnumerator *systemEnum;

	NSDebugLLog(@"ULProcess", @"Recieved termination message");
	
	[self setValue: @"Finished" forKey: @"processStatus"];

	interval = -1*[date timeIntervalSinceNow];
	hour = (int)floor(interval/3600);
	interval -= hour*3600;
	minute = (int)floor(interval/60);
	interval -= minute*60;
	second = ceil(interval);
	hourSt = [self _setFormat: hour];
	minuteSt = [self _setFormat: minute];
	secondSt = [self _setFormat: second];

	processIdentifier = -1;
	[self setValue: [NSString stringWithFormat: @"%@:%@:%@", hourSt, minuteSt, secondSt]
		forKey: @"length"];

	//Happy St. Patricks Day! :-)

	userInfo  = [NSMutableDictionary dictionary];
	[userInfo setObject: self forKey: @"ULTerminatedProcess"];
	if(error != nil)
		[userInfo setObject: error forKey: @"AdTerminationErrorKey"];

	//Add output references to the system and options that generated
	//the simulation
	
	[options addOutputReferenceToObject: simulationData];
	systemEnum = [systems objectEnumerator];
	while(system = [systemEnum nextObject])
		[system addOutputReferenceToObject: simulationData];
	
	NSDebugMLLog(@"ULProcess", @"Posting notification with dictionary %@", userInfo);

	[[NSNotificationCenter defaultCenter]
		postNotificationName: @"ULProcessDidFinishNotification"
		object: self
		userInfo: userInfo];
}

- (void) processWillStart
{
	BOOL success;
	id databaseDir, link;
	id dataStorage, system;
	NSEnumerator *systemEnum;
	NSString* outputDir, *controllerName, *temp;
	
	//Create ULSimulation object that will represent the simulation data.

	outputDir = [self valueForKeyPath: @"options.Options.Application.IO.OutputPrefix"];

	if([outputDir isEqual: @"default"])
		outputDir = @"output";
	
	simulationData = [[ULSimulation alloc] initWithName: outputDir]; 
	//Get storage for the simulation data
	dataStorage = [[ULDatabaseInterface databaseInterface]
				createFileSystemStorageForSimulation: simulationData];	
	[simulationData setDataStorage: dataStorage];			

	simulationDataDirectory = [dataStorage storagePath];
	[simulationDataDirectory retain];
	
	NSDebugMLLog(@"ULProcess", @"The simulation directory is %@", simulationDataDirectory);
	NSDebugMLLog(@"ULProcess", @"Simulation metadata %@", [simulationData allData]);
	
	//To aid debugging etc. we create a link to the simulationDir,
	//in the same directory, called "simulationName" 
	
	databaseDir = [[ULIOManager appIOManager] databaseDir];
	link = [databaseDir stringByAppendingPathComponent: @"Simulations"];
	link = [link stringByAppendingPathComponent: outputDir];

	//Remove any previous link with the same name 
	if([[NSFileManager defaultManager] fileExistsAtPath: link])
	{
		success = [[NSFileManager defaultManager] 
			removeFileAtPath: link 
			handler: nil];
		if(!success)
			NSWarnLog(@"Unable to remove link - %@", link);
	}		

	if(![[NSFileManager defaultManager] createSymbolicLinkAtPath: link
		pathContent: simulationDataDirectory])
	{
		NSWarnLog(@"Failed to create link to simulation data directory %@", 
			simulationDataDirectory);
	}

	//FIXME: Temporary - we add the link name to the ULSimulation object
	[simulationData setValue: link forMetadataKey: @"DataLink"];

	//Create path for controller output if required

	controllerName = [options valueForKeyPath: @"options.Controller"];
	if(![controllerName isEqual: @"Standard"])
	{
		temp = [NSString stringWithFormat: @"%@.%@",
			[simulationData name], 
			controllerName];
		controllerOutputDirectory = [[[ULIOManager appIOManager] 
						controllerOutputDir]
						stringByAppendingPathComponent: temp];
		[controllerOutputDirectory retain];		
		NSDebugMLLog(@"ULProcess", 
			@"The controller output directory is %@",
			controllerOutputDirectory);
	}	

	//set simulation input references 
	//FIXME: This will have to be expanded when templates are used
	//instead of options.

	[simulationData addInputReferenceToObject: options];
	systemEnum = [systems objectEnumerator];
	while(system = [systemEnum nextObject])
		[simulationData addInputReferenceToObject: system];
}

- (NSString*) simulationOutputDirectory
{
	return simulationDataDirectory;
}

- (NSString*) controllerOutputDirectory
{
	return controllerOutputDirectory;
}

@end
