
#import <gamekit/gamekit.h>	// Includes the AppKit
#import <daymisckit/daymisckit.h>	// Includes the AppKit
#import <objc/typedstream.h>	// highscore tables
#import <remote/NXProxy.h>
#import <stdio.h>		// strcpy
#import <stdlib.h>		// malloc
#import <strings.h>
#import <objc/objc-runtime.h>

#define SERVERLIST (localScores ? localServers : remoteServers)
#define TABLELIST (localScores ? localTables : remoteTables)

@implementation HighScoreController

- init
{
    [super init];
	localTables = [[List alloc] init];	// holds a bunch of highscore tables
	remoteTables = [[List alloc] init];	// holds a bunch of highscore tables
	localServers = [[List alloc] init];		// holds a bunch of servers
	remoteServers = [[List alloc] init];	// holds a bunch of servers
	serverNames = [[List alloc] init];		// holds a bunch of DAYStrings
	[self setServerHost:"*"];
	lastTableNum = 0;
    return self;
}

- appDidInit:sender	// forwarded by GameBrain
{
	int i; id myTemplate;
	if (!gameInfo) gameInfo = [[NXApp delegate] gameInfo];
	// ***** it would be better to split the strings out of the .nib
	// and load the window on demand.
	if (!highScorePanel) // we need this right away for string table, etc.
		[NXApp loadNibSection:"HighScore.nib" owner:self withNames:NO];
    [highScorePanel setFrameUsingName:"Highs"];
    [highScorePanel setFrameAutosaveName:"Highs"];
	if (!preferences) preferences = [[NXApp delegate] preferencesBrain];
	if (!scoreKeeper) scoreKeeper = [[NXApp delegate] scoreKeeper];
	if (!preferences) preferences = [[NXApp delegate] preferencesBrain];
	for (i=0; i<[gameInfo numHighScoreTables]; i++) {
		// make dummy tables and set up the server's names
		char *serverName = (char *)malloc(strlen([NXApp appName]) + 16);
		sprintf(serverName, "%s%d", [NXApp appName], i);
		[serverNames addObject:[[DAYString alloc] initString:serverName]];
		[localTables addObject:[[HighScoreTable alloc] init]];
		[remoteTables addObject:[[HighScoreTable alloc] init]]; // dummy
	}
    myTemplate = [self tableProtoType];
	[self setLocalScores:YES];
	for (i=0; i<[gameInfo numHighScoreTables]; i++) { // build local service
		id newLocalServer = [[GKHSLocalServer alloc]
				initForGame:[[serverNames objectAt:i] stringValue]];
		[localServers addObject:newLocalServer];
		[newLocalServer clientCheckIn:self];
	}
	if ([preferences useServer]) {
		[self setServerHost:[preferences serverName]]; // get the hostname 1st
		[self connectToServers]; // will automatically clear localScores for us
	}
	[scoreKeeper updateTopScoreText];
	return self;
}

- connectToServers
{
	int i; id <HighScoreServer> server;
	HighScoreDistributor *distributor;
	NXConnection *conn;
	if (connected) [self closeServers];
	if (localScores) [self setLocalScores:NO];
	distributor = (HighScoreDistributor *)[NXConnection
			connectToName:"DAYHighScoreServer"
			onHost:serverHost fromZone:[self zone]];
	if (!distributor) {
		localScores = YES; // can't attach to server, so go local
		NXRunAlertPanel("Score Server",
				[strings valueForStringKey:"noServer"],
				NULL, NULL, [strings valueForStringKey:"OK"]);
		[preferences setUseServer:NO];
		return self;
	}
	conn = [(NXProxy *)distributor connectionForProxy];
	[conn registerForInvalidationNotification:self];
	[conn runFromAppKit];
	while ([remoteServers count] < [gameInfo numHighScoreTables])
		[remoteServers addObject:[Object alloc]];
	for (i=0; i<[gameInfo numHighScoreTables]; i++) { // build local service
		id junk;
		server = [distributor
				getServerFor:[[serverNames objectAt:i] stringValue]];
		if (!server) {
			localScores = YES; // can't attach to server, so go local
			NXRunAlertPanel("Score Server",
					[strings valueForStringKey:"noServer"],
					NULL, NULL, [strings valueForStringKey:"OK"]);
			[preferences setUseServer:NO];
			return self;
		}
		[(NXProxy *)server setProtocolForProxy:@protocol(HighScoreServer)];
		[server clientCheckIn:self]; // let server know I'm here
		junk = [remoteServers replaceObjectAt:i with:server];
		if (junk) {
			if ([junk isProxy]) [junk freeProxy];
			else [junk free];
		}
	}
	[scoreKeeper updateTopScoreText];
	connected = YES;
	return self;
}

- setLocalScores:(BOOL)flag
{ // if a spot is editable for a name, close it up before changing servers.
	if (localScores != flag) [highScorePanel nameEntered:self];
	localScores = flag;
	if (localScores) [clearMenu setEnabled:YES];
	else [clearMenu setEnabled:NO];
    [highScorePanel refresh];  // update matrix (like doing displayHighScores:)
	return self;
}

- closeServers
{	// should be called when game exits to tell server we're leaving
	[remoteServers makeObjectsPerform:@selector(clientDying:) with:self];
	[remoteServers empty];
	connected = NO;
	return self;
}

- (BOOL)localScores { return localScores; }
- (BOOL)connected { return connected; }

- senderIsInvalid:sender
{	// Should put up and alert panel to warn the user.
	int i; BOOL showAlert = !localScores;
	[self setLocalScores:YES];
	if (showAlert) NXRunAlertPanel("Score Server",
			[strings valueForStringKey:"serverDied"],
			NULL, NULL, [strings valueForStringKey:"OK"]);
	[preferences setUseServer:NO];
	for (i=0; i<[remoteServers count]; i++) {
	// if one died, then they all did, since they were all in same program.
		[[[remoteServers objectAt:i] connectionForProxy] free];
		[[remoteServers objectAt:i] freeProxy];
	}
	[remoteServers empty];
	connected = NO;
	return self;
}

- (char *)serverHost { return serverHost; }

- setServerHost:(const char *)name
{
	if (serverHost) free(serverHost);
	serverHost = NXCopyStringBufferFromZone(name, [self zone]);
	return self;
}

- table:(int)num
{	// we keep around this table so we know which table is displayed
	lastTableNum = num;
	[scoreKeeper updateTopScoreText];
	return [TABLELIST objectAt:num];
}

- nameForSlot:(int)slotNum inTable:(int)tableNum is:(const char *)newName
{
	id theServer = [SERVERLIST objectAt:tableNum];
	id theTable = [TABLELIST objectAt:tableNum];
	id theSlot = [theTable objectAt:slotNum];
	[theSlot setPlayerName:newName];
	// already in the server's table if local since we have pointer to
	// original table rather than a copy of it, so we only ship the slot
	// off to remote servers.
	if (!localScores) [theServer addSlot:theSlot fromClient:self];
	else [theServer save]; // ensure a table save for local scores.
	[preferences setDefaultPlayerName:newName];
	return self;
}

- (oneway)cantBeServed
{
	return self;
}

- serverForName:aString
{
	int i; id serverList = SERVERLIST;
	for (i=0; i<[serverList count]; i++) {
		if (![[serverNames objectAt:i] compareTo:aString]) {
			return [serverList objectAt:i];
		}
	}
	return nil;
}

- (int)serverNumberFromName:aString
{
	int i; id serverList = SERVERLIST;
	for (i=0; i<[serverList count]; i++) {
		if (![[serverNames objectAt:i] compareTo:aString]) {
			return i;
		}
	}
	return (-1);
}

- (oneway)sendGameInfoTo:(bycopy in id)aString 
{ // If we get this, we can assume that the server needs a template and has
	// an empty table.  Thus, since for some unexplained reason, the template
	// we send doesn't come back (and it should) we create a copy here, too.
	id requestor = [self serverForName:aString];
	id proto = [self tableProtoType];
	[requestor setTemplate:proto];
	[requestor setGameInfo:gameInfo];
	// the template method should cause the next method to bounce back to us,
	// but for some reason it doesn't!
	[self acceptTable:proto name:aString]; // make sure we have the new table
	return self;
}

- (oneway)sendSlotCode { return self; } // ***** not implemented.

- (oneway)acceptTable:(bycopy in id)aTable name:(bycopy in id)aString
{
	int spot = [self serverNumberFromName:aString];
#ifdef NOISYDEBUG
	fprintf(stderr, "HSController: -acceptTable:name:\"%s\"\n",
			[aString stringValue]);
#endif
	if (![aTable isKindOf:[HighScoreTable class]]) {
#ifdef NOISYDEBUG
		fprintf(stderr,
				"HS server %s sent a %s object instead of a HighScoreTable!\n",
				[aString stringValue], [[aTable class] name]);
#endif
		return self;
	}
	if (spot >= 0) {
		id old = [TABLELIST replaceObjectAt:spot with:aTable];
		if (![old isProxy]) {
			[old freeObjects];
			[old free];
		}
	}
	[highScorePanel refresh];
	return self;
}

- (oneway)addSlot:(bycopy in id)aSlot tableName:(bycopy in id)aString
{
	int spot = [self serverNumberFromName:aString];
	if (spot >= 0) {
		[[TABLELIST objectAt:spot] addSlot:aSlot];
	}
	[highScorePanel refresh];
	return self;
}

- (oneway)removeSlotAt:(in int)i tableName:(bycopy in id)aString
{
	int spot = [self serverNumberFromName:aString];
	if (spot >= 0) {
		[[TABLELIST objectAt:spot] removeObjectAt:i];
	}
	[highScorePanel refresh];
	return self;
}

- (oneway)replaceSlotAt:(in int)i with:(bycopy in id)aSlot
		tableName:(bycopy in id)aString
{
	int spot = [self serverNumberFromName:aString];
	if (spot >= 0) {
		[[TABLELIST objectAt:spot] replaceObjectAt:i with:aSlot];
	}
	[highScorePanel refresh];
	return self;
}

- password { return [[DAYString alloc] initString:"NONE"]; }
// user will override this

- (int)highScore:(int)num
{ // use the table that the GameBrain currently has player playing in
	return [[[TABLELIST objectAt:[[NXApp delegate] tableNum]]
			objectAt:num] finalScore];
}
- (int)highestScore { return [self highScore:0]; }

- zeroHighScores			// wipes out high scores
{
	id old = [TABLELIST replaceObjectAt:lastTableNum
			with:[[List alloc] init]]; // stuff in a dummy placeholder
	// this keeps us out of trouble with local tables which are shared by
	// the local server and the controller; the server is about to wipe it
	// out without notifying us, so we have to get rid of it ourselves.
	// If it's not local, though, then we have to free it now...
	if (![old isProxy] && !localScores) { // free it if it's local
		[old freeObjects];
		[old free];
	}
	[[SERVERLIST objectAt:lastTableNum] clearTable:self];
    [highScorePanel refresh];  // update matrix (like doing displayHighScores:)
	return self;
}

- tableProtoType // ***** needs, really, to have table# as input...
{
    int i, numSlotsToMake = [gameInfo maxHighScores];
	id newSlot, table, slotClass =
		objc_lookUpClass([[gameInfo slotType] stringValue]);
    char *tempStr = malloc(64);
    
	table = [[HighScoreTable alloc] init];
    for (i=0; i<numSlotsToMake; i++) {
		// build empty slots
		sprintf(tempStr, [strings valueForStringKey:"noName"], i + 1);
		newSlot = [[slotClass alloc] init];
		[newSlot setFinalScore:((numSlotsToMake - i) * 1000)];
		[newSlot setPlayerName:tempStr];
		[table addSlot:newSlot];
    }
	free(tempStr);
    return table;
}

- setHighScoreWindowTitle // used to set the title of the high score
{	// panel.  You may want to override to, say, change the title depending
	// upon what high score table is being displayed or something like that.
	// This method is always called before actual display of the high score
	// panel, so you can count on it always putting the title up...
	// ***** should reflect the high score table being displayed!
	char *tempString = malloc(256);

	sprintf(tempString, "%s %s\n", [NXApp appName],
			[strings valueForStringKey:"HighScoreTitle"]);
    [highScorePanel setTitle:tempString];
	free(tempString);
    return self;
}

- displayHighScores:sender 	// bring up high scores w/info loaded into it
{	// attach a menu button to this to bring up the panel
    [self setHighScoreWindowTitle]; 	// set up the window's title
    [highScorePanel refresh];			// refresh the data in the matrices
    [highScorePanel makeKeyAndOrderFront:self];	// bring up the panel itself.
    return self;
}

- putInHighScores:aSlot  	// put player in high score table if applicable
{
    int loc = 0;
	// Display panel and leave w/o save if not new high score or if
	// using non-default preferences and a network server.
	[highScorePanel setTable:[[NXApp delegate] tableNum]]; // get right table
    if (([preferences unfair] || [[NXApp delegate] playerCheated])
			&& !localScores) { // high score is ineligible
#ifdef NOISYDEBUG
		fprintf(stderr, "Score was ineligible for a high score:  ");
		if ([preferences unfair])
			fprintf(stderr, "unFair preferences  ");
		if ([[NXApp delegate] playerCheated])
			fprintf(stderr, "cheat mode on  ");
		fprintf(stderr, "\n");
#endif
		// display high score panel if not aborting a game
		if (![[NXApp delegate] aborting]) [self displayHighScores:self];
		return self;
	}
    if (![[TABLELIST objectAt:[[NXApp delegate] tableNum]]
			addSlot:aSlot where:&loc]) { // high score didn't make it
#ifdef NOISYDEBUG
		fprintf(stderr, "Score not good enough to make a high score.\n");
#endif
		// display high score panel if not aborting a game
		if (![[NXApp delegate] aborting]) [self displayHighScores:self];
		return self;
	}
	[scoreKeeper updateTopScoreText]; // make sure the stats panel's up to date
	// update title (this is needed since -displayHighScores: above
	//   isn't used to order out the high score panel when we insert
	//   a new high score into the table.
    [self setHighScoreWindowTitle];
	// set up textfield to accept player's name and bring up panel for entry
    [highScorePanel getPlayerName:loc table:[[NXApp delegate] tableNum]];
	return self;
}

- clearHighScores:sender	// zeroes the high scores -- attach to menu button
{
	[self zeroHighScores];				// clear the table
	[scoreKeeper updateTopScoreText];	// update stats panel
	return self;
}

- scoreKeeper
{
	return scoreKeeper;
}

- setScoreKeeper:newKeeper
{
	id oldKeeper = scoreKeeper;
	scoreKeeper = newKeeper;
	return oldKeeper;
}

- (BOOL)slotIsEligible:aSlot
{	// returns YES if the slot would be inserted into the current table and
	// NO if not.
	id table = [TABLELIST objectAt:[[NXApp delegate] tableNum]]; // get table
	if ([aSlot isAbove:[table objectAt:([table maxHighScores] - 1)]])
		return YES; // see if the slot is better than the lowest score in table
					// (Note that we let the slot subclass do the compare...)
	return NO;
}


@end