/*
	ZillionClientServer.m
	Wed Dec 11 17:47:57 CET 2002
	copyright: Stefan Böhringer (stefan.boehringer@uni-essen.de) (2002, 2003)
	This code is governed by a BSD license as expounded in the LICENSE file
 */


#import	"ZillionClientServer.h"
#import	"ZillionServer.h"
#import	"NSFileManager+directories.h"

@implementation ZillionClientServer

- init
{
	if (!(self = [super init])) return self;
	_jobs = [[NSMutableDictionary alloc] init];
	return self;
}

- (void)dealloc
{
	[_jobs release];
	[_masterServer release];
	[super dealloc];
}

/*
	methods to respawn new processes
 */

- (NSString *)DOname {
	[NSException raise:NSInternalInconsistencyException format:@"method is abstract"];
	return nil;
}

- (NSArray *)respawningArgumentsWithId:(NSString *)doNameId {
	[NSException raise:NSInternalInconsistencyException format:@"method is abstract"];
	return nil;
}

- (Protocol *)respawningProtocol {
	[NSException raise:NSInternalInconsistencyException format:@"method is abstract"];
	return 0;
}

- (NSString *)respawningDOnameWithId:(NSString *)doNameId {
//	NSProcessInfo	*pInfo = [NSProcessInfo processInfo];
	return [NSString stringWithFormat:@"%@_%d",
		[self DOname], doNameId];
}

#define	RETRY_COUNT	2
// how many seconds are to be waited until a connection is to be established
#define	INITIAL_DELAY	1
#define	RETRY_DELAY		2.5

- respawnedObjectWithId:(NSString *)doNameId doObserveConnection:(BOOL)doObserve
{
	NSProcessInfo	*pInfo = [NSProcessInfo processInfo];
	NSString		*respawnedDOName = [self respawningDOnameWithId:doNameId];
	Protocol		*myProtocol = [self respawningProtocol];
	NSArray			*arguments = [self respawningArgumentsWithId:doNameId];
	id				newObject = nil;
	int				retries;

	NSLog(@"RespawningName:%@", respawnedDOName);
	[NSTask launchedTaskWithLaunchPath:[[pInfo arguments] objectAtIndex:0] arguments:arguments];

	[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:INITIAL_DELAY]];
	for (retries = RETRY_COUNT;
		! (newObject = [NSConnection rootProxyForConnectionWithRegisteredName:
			respawnedDOName host:nil]) && retries; retries--)
				[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:RETRY_DELAY]];

	if (newObject) {
		//[newObject retain];
		// avoid exception raising <!><?>
		[newObject setProtocolForProxy:myProtocol];

		if (doObserve) {
			[[NSNotificationCenter defaultCenter] addObserver:self
				selector:@selector(respawnedConnectionDidDie:)
				name:NSConnectionDidDieNotification
				object:[newObject connectionForProxy]];
		}
	} else {
		NSLog(@"Couldn't respawn process %@", respawnedDOName);
	}

	return newObject;
}
- respawnedObject { return [self respawnedObjectWithId:nil]; }

- respawnedObjectWithId:(NSString *)doNameId
{
	return [self respawnedObjectWithId:doNameId doObserveConnection:NO];
}

/*
	<§> Jobs
 */

- (NSBundle *)bundleForPackedBundle:(NSData *)packedBundle
{
	NSString		*tempDir;
	int				pid = [[NSProcessInfo processInfo] processIdentifier];
	NSFileManager	*fileManager = [NSFileManager defaultManager];
	NSBundle		*jobBundle;
	Class			jobClass;

//	uncommenting the next two lines causes a crash
//	if ([masterServer isKindOfClass:[NSDistantObject class]])
//		[(NSDistantObject *)masterServer setProtocolForProxy:@protocol(ZillionServerProtocol)];

	tempDir = [NSTemporaryDirectory() stringByAppendingPathComponents:
		[NSString stringWithFormat:@"%d", pid],
		[NSString stringWithFormat:@"%d", bundleSeq++],
		nil];
	[fileManager createAllDirectoriesComposingPath:tempDir attributes:nil];

	NSLog(@"Deserializing incoming job [placed @ %@]...", tempDir);
	[fileManager writeDataRepresentation:packedBundle toPath:tempDir];

	jobBundle = [NSBundle bundleWithPath:tempDir];
	jobClass = [jobBundle principalClass];
	if (![jobClass conformsToProtocol:@protocol(ZillionJobProtocol)]) {
		NSLog(@"Didn't find appropriate code in bundle to submit as job [principleClass %@]",
			NSStringFromClass(jobClass));
		return nil;
	}
	NSLog(@"...done");
	return jobBundle;
}

- (NSArray *)jobIds { return [_jobs allKeys]; }
- (NSMutableDictionary *)jobSpecificationForJobId:(NSString *)jobId
{
	NSMutableDictionary	*specification = [_jobs objectForKey:jobId];
	if (!specification) {
		specification = [NSMutableDictionary dictionary];
		[_jobs setObject:specification forKey:jobId];
	}
	return specification;
}
- (void)setJobSpecification:(NSDictionary *)jobSpec forId:(NSString *)jobId
{
	[_jobs setObject:[jobSpec mutableCopy] forKey:jobId];
}

- (Class)jobClassForJobId:(NSString *)jobId
{
	NSMutableDictionary	*jobSpec = [self jobSpecificationForJobId:jobId];
	NSBundle			*jobBundle = JOBBUNDLE(jobSpec);
	return [jobBundle principalClass];
}

- (id <ZillionJobProtocol>)instantiateJobFromBundle:(NSBundle *)jobBundle
	withResources:(NSData *)jobResources
{
	Class			jobClass = [jobBundle principalClass];
	NSString		*jobId;
	NSMutableDictionary	*jobSpec;
	id <ZillionJobProtocol>	job;

	NSLog(@"%@", jobClass);
	job = [[jobClass alloc] initWithBundle:jobBundle resources:jobResources jobHandler:self];
	jobId =  [job jobId];
	jobSpec = [self jobSpecificationForJobId:jobId];

	[jobSpec setObject:jobBundle forKey:@"bundle"];
	if (! jobId)
		[NSException raise:NSInvalidArgumentException format:
			@"Couldn't retrieve jobId from bundle"];
	NSLog(@"did instantiate %@ %@", jobId, jobBundle);
	return job;
}

- (id <ZillionJobProtocol>)instantiateJobWithId:(NSString *)jobId
{
	NSMutableDictionary	*jobSpec = [self jobSpecificationForJobId:jobId];
	NSBundle			*jobBundle = JOBBUNDLE(jobSpec);

	if (!jobBundle) {
		// no instance exists now
		jobBundle = [self bundleForPackedBundle:
			[[self masterServer] packedJobBundleForJobId:jobId]];
		SETJOBBUNDLE(jobSpec, jobBundle);
	}
	return [self instantiateJobFromBundle:jobBundle
		withResources:[jobSpec objectForKey:JOB_RESOURCES_KEY]];
}

/*
	the masterServer is expected to exist for the longest time of all
	processes and is therefore not retained
 */
 
// <!><%>
- (void)setMasterServer:(id <ZillionServerProtocol>)masterServer {
#	ifndef _DO_TEST_CASE
		return;
#	else
		[masterServer retain];
		[_masterServer release];
		_masterServer = masterServer;
#	endif
}

- (id <ZillionServerProtocol>)masterServer { 
#	ifndef	_DO_TEST_CASE
	_masterServer = [[NSConnection rootProxyForConnectionWithRegisteredName:ZILLION_SERVER_NAME
		host:nil] retain];

	if (!_masterServer) {
		NSLog(@"Failed to establish connection ");
	}
	[_masterServer setProtocolForProxy:@protocol(ZillionServerProtocol)];
#	endif
	return _masterServer;
}

- (NSString *)serverTemporaryPath { return @"/Library/Zillion"; }
- (NSString *)outcomeDirectory {
	return [[self serverTemporaryPath] stringByAppendingPathComponent:@"Outcomes"];
}

- (NSString *)temporaryDirectory
{
	NSString	*tempDir = [[NSFileManager defaultManager]
		temporaryPathInTempPathWithPrefix:@"zillion/temp"];
	NSLog(@"%@", tempDir);
	return tempDir;
}

- (NSString *)outcomeDirectoryForJobId:(NSString *)jobId
{
	return [[self outcomeDirectory] stringByAppendingPathComponent:jobId];
}


- (void)submitOutcome:(NSData *)outcome ofJob:(NSString *)jobId forSeedId:(NSData *)seedId
	storeHints:(NSDictionary *)someStoreHint
{
	[NSException raise:NSInternalInconsistencyException format:@"abstract method called"];
}

- (id <ZillionJobProtocol>)jobInstance:(NSString *)jobId
{
	[NSException raise:NSInternalInconsistencyException format:@"abstract method called"];
	return nil;
}


- (void)submitOutcome:(NSData *)outcome ofJob:(NSString *)jobId forSeed:seed
	storeHints:(NSDictionary *)someStoreHints
{
	// incomplete implemenation
	// handle via instance: enrich Protocol <i><!>
	[self submitOutcome:outcome ofJob:jobId forSeedId:[[self jobInstance:jobId]
		dataRepresentationOfSeed:seed]
		storeHints:someStoreHints];
}

- (void)jobUnitDidEndForJobId:(NSString *)jobId seed:seed {
	[NSException raise:NSInternalInconsistencyException format:@"abstract method called"];
}

- (NSDictionary *)outcomeDictionaryForJobId:(NSString *)jobId
{
	[NSException raise:NSInternalInconsistencyException format:@"abstract method called"];
	return nil;
}


@end
