
#import <stdio.h>		// strcpy
#import <stdlib.h>		// malloc
#import <strings.h>
#import <objc/typedstream.h>	// highscore tables
#import <daymisckit/daymisckit.h>
#import <gamekit/gamekit.h>
#import <appkit/appkit.h>
#import <remote/NXProxy.h>


@implementation HighScoreTable

- init		// designated initializer sets up game variables
{		// to sensible values.
    [super init];
	tag = 0;
	title = [[DAYString alloc] init];
	emptySlot = [[HighScoreSlot alloc] init]; // ***** use proper class
	maxHighScores = MAXSCORES;
	return self;
}

- setEmptySlotClass:classObject
{
	if (emptySlot) [emptySlot free];
	emptySlot = [[classObject alloc] init];
	return self;
}

- setStringValue:(const char *)aString
{
	[title setStringValue:aString];
	return self;
}

- (BOOL)samePlayer:slot1 :slot2
{	// if the login names are same, assume same user.  You can override this
	// to go by machine name or whatever.
	if (![slot1 userName] || ![slot2 userName]) return NO;
	if (strcmp([slot1 userName], [slot2 userName])) return NO;
	return YES;
}

- (int)numEntriesByPlayer:aSlot // return # of high scores player has
{
	int i, count = 0;
	for (i=0; i<[self count]; i++)
		if ([self samePlayer:[self objectAt:i] :aSlot]) count++;
	return count;
}

- (int)lowestSlotByPlayer:aSlot // return lowest scoring slot by player
{
	int i;
	// look for appropriate slot from end of list to front
	for (i=[self count]-1; i>=0; i--)
		if ([self samePlayer:[self objectAt:i] :aSlot])
			return i;
	// return [self count] if didn't find a slot that matches criteria
	return [self count];
}

- (const char *)stringValue { return [title stringValue]; }
- (int)tag	{ return tag; }
- setTag:(int)newTag { tag = newTag; return self; }
- (int)maxHighScores { return maxHighScores; }
- setMaxHighScores:(int)newMax
{
	maxHighScores = newMax;
	return self;
}

- (int)maxScoresPerPlayer { return maxScoresPerPlayer; }
- setMaxScoresPerPlayer:(int)newMax
{
	maxScoresPerPlayer = newMax;
	return self;
}

- (BOOL)addSlot:newSlot // keeps the table sorted as slots are added.
{
	int i;
	return [self addSlot:newSlot where:&i];
}

- (BOOL)addSlot:newSlot where:(int *)w // keeps table sorted as slots added.
{	// the key for the sort is from slots' -isAbove method...
	int i; id xtraObj;
	for (i=0; i<[self count]; i++) {	// find where to add slot
		if ([newSlot isAbove:[self objectAt:i]]) break;
	} // insert it
	[self insertObject:newSlot at:i]; // insert regardless
	if (maxScoresPerPlayer) { // 0 == unlimited entries
		if ([self numEntriesByPlayer:newSlot] > maxScoresPerPlayer) {
			// have one more entry by this player than allowed, so
			// we'll delete the lowest.
			int xtraSlotNum = [self lowestSlotByPlayer:newSlot];
#ifdef NOISYDEBUG
			fprintf(stderr,
				"HighScoreTable:  removing extra entry by player %s.\n",
				[newSlot userName]);
#endif
			// delete the actual slot and free it.
			xtraObj = [self removeObject:[self objectAt:xtraSlotNum]];
			if ([xtraObj isProxy]) [xtraObj freeProxy];
			else [xtraObj free];
			// if this was the slot we just added, then return
			// that we didn't add it after all...
			if (i == xtraSlotNum) {
				*w = [self count];
				return NO;
			}
		}
	}
	// prune the table so it doesn't grow too big.  max # is set by init.
	// done by deleting slots from the end to the front until size is OK.
	while ([self count] > maxHighScores) {
			xtraObj = [self removeLastObject];
			if ([xtraObj isProxy]) [xtraObj freeProxy];
			else [xtraObj free];
	}
	*w = i;	// return by reference where we added it.
	if (i >= maxHighScores) return NO;
	return YES;
}

- objectAt:(unsigned)index
{
	if (index >= [self count]) return emptySlot;
	return [super objectAt:index];
}

- free
{
	[emptySlot free];
	return [super free];
}

- copy
{	// build a new table with new slots (i.e. don't just copy id pointers,
	// actually make new objects)
	int i; id aCopy = [[[self class] alloc] init];
	[aCopy setEmptySlotClass:[emptySlot class]];
	[aCopy setMaxHighScores:maxHighScores];
	for (i=0; i<[self count]; i++) {
		[aCopy addSlot:[[self objectAt:i] copy]];
	}
	return aCopy;
}

- read:(NXTypedStream *)stream
{ // don't save emptySlot since it is easy enough to create
	[super read:stream];
	NXReadTypes(stream, "iii", &maxHighScores, &maxScoresPerPlayer, &tag);
	emptySlot = NXReadObject(stream);
	title = NXReadObject(stream);
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream, "iii", &maxHighScores, &maxScoresPerPlayer, &tag);
	NXWriteObject(stream, emptySlot); // saved to preserve class type
	NXWriteObject(stream, title);
	return self;
}

// NXTransport protocol implementation:  List must contain objects that
// also use this protocol; otherwise it won't xfer by copy properly!
- encodeUsing:(id <NXEncoding>)portal
{
	int i, n = [self count];
	[portal encodeData:&n ofType:"i"];
	[portal encodeData:&maxHighScores ofType:"i"];
	[portal encodeData:&maxScoresPerPlayer ofType:"i"];
	[portal encodeData:&tag ofType:"i"];
	for (i=0; i<n; i++) {
			[portal encodeObjectBycopy:[self objectAt:i]];
	}
	[portal encodeObjectBycopy:emptySlot];
	[portal encodeObjectBycopy:title];
	return self;
}

- decodeUsing:(id <NXDecoding>)portal
{
	int i, n;
	[portal decodeData:&n ofType:"i"];
	[portal decodeData:&maxHighScores ofType:"i"];
	[portal decodeData:&maxScoresPerPlayer ofType:"i"];
	[portal decodeData:&tag ofType:"i"];
	[self freeObjects]; // make sure that we're empty
	for (i=0; i<n; i++) {
			[self addObject:[portal decodeObject]];
	}
	emptySlot = [portal decodeObject];
	title = [portal decodeObject];
	return self;
}

- encodeRemotelyFor:(NXConnection *)connection
	freeAfterEncoding:(BOOL *)flagp isBycopy:(BOOL)isByCopy
{
	if (isByCopy) return self; //encode object (copy it)
	// super will encode the proxy otherwise
	return [super encodeRemotelyFor:connection
				freeAfterEncoding:flagp isBycopy:isByCopy];
}


@end
