/*
	ZillionClient.m
	Mon Dec  9 17:17:22 CET 2002

 */


#import	"ZillionClient.h"
#import	"ZillionServer.h"
#include <sys/times.h>

#define	IS_MASTER_CLIENT	(![self masterClient])

@implementation ZillionClient

/*
	The idea for respawning a new client is similar to why a server should be respawned.
	Look at ZillionServer.m for more elaboration on this topic.
 */

#define	ZILLION_CLIENT_NAME	@"zillionClient"
- (NSString *)DOname {
#	if 0
	// <!> this may not be a unique identifier
	NSArray	*ips = [[NSHost currentHost] addresses];
	int	i, count;

	NSLog(@"%@", [[NSHost currentHost] addresses]);
	for (i = 0, count = [ips count]; i < count; i++) {
		if (![[ips objectAtIndex:i] isEqualToString:@"127.0.0.1"])
			return [ips objectAtIndex:i];
	}
	return @"";
#	endif
	/*
		we try to use the hostname as unique identifier (in the DO namespace)
		if that fails we try for the PID which is probable but not garuanteed to
		be unique
	 */
	NSArray	*hostnames = [[NSHost currentHost] names];
	int		i, count;

	for (i = 0, count = [hostnames count]; i < count; i++)
		if (![[hostnames objectAtIndex:i] hasPrefix:@"localhost"]) break;

	return [NSString stringWithFormat:@"%@_%@", ZILLION_CLIENT_NAME,
		i < count
		? [hostnames objectAtIndex:i]
		: [NSNumber numberWithInt:[[NSProcessInfo processInfo] processIdentifier]]
	];
}

- (NSArray *)respawningArgumentsWithId:(NSString *)doNameId {
	return [NSArray arrayWithObjects:@"--DOname", [self respawningDOnameWithId:doNameId],
		@"--doRespawn=NO", nil];
}
- (Protocol *)respawningProtocol { return @protocol(ZillionClientProtocol); }
- (NSString *)respawningDOnameWithId:(NSString *)doNameId {
	NSString	*name = [NSString stringWithFormat:@"%@_%@", [self clientId], doNameId];
	return name;
}


#define	DEFAULT_LOAD	1

- initWithRespawnStatus:(BOOL)respawnStatus andMasterServer:(id <ZillionServerProtocol>)masterServer
{
	if (!(self = [super init])) return self;

	jobClientDictionary = [[NSMutableDictionary alloc] init];
	_respawnStatus = respawnStatus;
	_attributes = [[NSDictionary alloc] initWithObjectsAndKeys:
		[NSNumber numberWithInt:DEFAULT_LOAD], @"load", nil];
	threads = [[NSMutableDictionary alloc] init];
	clientAdminLock = [[NSLock alloc] init];

	[self setMasterServer:masterServer];

	return self;
}
- init
{
	return [self initWithRespawnStatus:NO andMasterServer:nil];
}
- (void)dealloc
{
	[clientAdminLock release];
	[jobClientDictionary release];
	[_attributes release];
	[threads release];
	[super dealloc];
}

- (id <ZillionClientProtocol>)masterClient { return _masterClient; }
- (void)setMasterClient:(id <ZillionClientProtocol>)masterClient {

#if	1
	// this is a hack to get a fresh client reference
	if (masterClient) {
		masterClient = [NSConnection rootProxyForConnectionWithRegisteredName:
			ZILLION_CLIENT_NAME	host:nil];
		if (! masterClient)
			[NSException raise:NSInternalInconsistencyException
				format:@"couldn't pull in master client from proxy"];
	}
#endif

	[masterClient retain];
	[_masterClient release];
	_masterClient = masterClient;
}

- (NSString *)clientId { return [self DOname]; }
- (BOOL)clientToGoDown { return _clientToGoDown; }
- (void)terminate {
	NSEnumerator				*clientEnum;
	id <ZillionClientProtocol>	client;

	NSLog(@"zillionClient is terminating [through -terminate]");
	for (clientEnum = [jobClientDictionary objectEnumerator]; (client = [clientEnum nextObject]); )
		if (client != self) [client terminate];

	// <i> gracefully fade jobs
	_clientToGoDown = YES;
}

- (NSDictionary *)attributes {
	return _attributes;
}


- (id <ZillionClientProtocol>)clientForJob:(NSString *)jobId
{
	id <ZillionClientProtocol>	client;

	client = [jobClientDictionary objectForKey:jobId];

	if (client) {
		return client;
	}
	if (!_respawnStatus) {
		[jobClientDictionary setObject:self forKey:jobId];
		return self;
	}
	NSLog(@"Respawning client for job: %@", jobId);
	client = [self respawnedObjectWithId:jobId];
	[client setMasterClient:self];
	[jobClientDictionary setObject:client forKey:jobId];
	return client;
}

typedef struct {
	struct tms	start, stop;
} ZillionClock;

void			ZillionStartClock(ZillionClock *t)
{
		times(&t->start);
}
	
unsigned long	ZillionGatherClock(ZillionClock *t)
{
	times(&t->stop);
	return (t->stop.tms_utime + t->stop.tms_stime + t->stop.tms_cutime + t->stop.tms_cstime)
		 - (t->start.tms_utime + t->start.tms_stime + t->start.tms_cutime + t->start.tms_cstime);
}


- (oneway void)forkedJobWithSpecification:(NSDictionary *)spec
{
	NSAutoreleasePool		*threadPool = [[NSAutoreleasePool alloc] init];
	id <ZillionJobProtocol>	job = [spec objectForKey:ZillionJobKey];
	id						seed = [job
		seedObjectFromDataRepresentation:[spec objectForKey:ZillionSeedKey]];
	ZillionClock	t;
	ulong			ticks;

	ZillionStartClock(&t);

	NSLog(@"we are now in the forked process");
	[job startJobUnitWithSeed:seed];

	ticks = ZillionGatherClock(&t);
	NSLog(@"Ticks spent executing job %d", ticks);

	[self jobUnitDidEndForJobId:[job jobId] seed:seed];
	[threadPool release];
	[NSThread exit];
}

- (void)forkThreadForJobId:(NSString *)jobId withSeed:(NSData *)seed
{
	id <ZillionJobProtocol>	job;
	NSDictionary			*jobSpecification;

	job = [self instantiateJobWithId:jobId];
	NSLog(@"Seed:%@, Job:%@", seed, job);
	jobSpecification = [NSDictionary dictionaryWithObjectsAndKeys:
		seed, ZillionSeedKey, self, ZillionClientKey, job, ZillionJobKey, nil];
	NSLog(@"Forking job thread [%@]...", jobId);

	NS_DURING
 		[NSThread
			detachNewThreadSelector:@selector(forkedJobWithSpecification:)
			toTarget:self
			withObject:jobSpecification
		];
	NS_HANDLER
		NSLog (@"Exception while executing job[%@]: %@", jobId, localException);
	NS_ENDHANDLER
}


- (void)dispatchUnitFromSeed:(NSData *)seed forJobId:(NSString *)jobId
{
	id <ZillionClientProtocol>	client = [self clientForJob:jobId];
	NSLog(@"About to dispatch unit for job: %@ client being:%@", jobId, client);

	[client forkThreadForJobId:jobId withSeed:seed];
	[self increaseLoad];
	NSLog(@"Job %@ did start [load is now:%d]", jobId, load);
}

- (int)currentLoad { return load; }
- (void)decreaseLoad { load--; NSLog(@"decrease load being called"); }
- (void)increaseLoad { load++; }

- (id <ZillionJobProtocol>)jobInstance:(NSString *)jobId
{
	return [[self masterServer] jobInstance:jobId];
}

- (void)submitOutcome:(NSData *)outcome ofJob:(NSString *)jobId forSeed:seed
	storeHints:(NSDictionary *)someStoreHints
{
	NSData	*seedId = [[self jobInstance:jobId] dataRepresentationOfSeed:seed];

	NS_DURING
	NSLog(@"storing outcome");
	[[self masterServer] submitOutcome:outcome ofJob:jobId forSeedId:seedId
		storeHints:someStoreHints];
	NS_HANDLER
		NSLog (@"Exception while storing results[%@]: %@", jobId, localException);
	NS_ENDHANDLER
}

- (void)jobUnitDidEndForJobId:(NSString *)jobId seed:seed
{
	NSData	*seedId = [[self jobInstance:jobId] dataRepresentationOfSeed:seed];

	NSLog(@"%lx", _masterClient);
	[[self masterClient] decreaseLoad];
	NSLog(@"Descreased load to %d", [[self masterClient] currentLoad]);

	// <i> we do not yet know how to determine time consumption of the thread

	NSLog(@"Will instruct server to end job unit");
	[[self masterServer] jobUnitDidEndForJobId:jobId seedId:seedId
		timePenalty:1];
	NSLog(@"master server did end job unit [%@]", [self masterClient]);

}

- (void)stopUnitsForJobId:(NSString *)jobId
{
	//<i> stop threads or stop the client being forked which handles the jobs with jobId
}

@end
