/*
 * NetworkPlayer
 * description: a networked subclass of Player.
 * history:
 *	3/15/93 [Erik Kay] - created
 *	6/2/93  [Erik Kay] - reworked much of the code
 *	6/8/93	[Erik Kay] - better error recovery
 *	6/8/93	[Erik Kay] - bycopy support for transmitting boards
 */

#import <remote/NXProxy.h>
#import "SplatGame.h"
#import "NetworkPlayer.h"

// search for the server in another thread...
void lookForServer (id self)
{
    [self lookForServer];
    cthread_exit(0);
}

@implementation NetworkPlayer

- init
{
    [super init];
    otherPlayer = nil;
    return self;
}

- free
{
    [super free];
    [myServer free];
    [otherPlayer free];
    free(playerName);
    return self;
}

-(BOOL)isConnected
{
    if (otherPlayer)
	return YES;
    else return NO;
}

// This is the method called by the main game when it wants the net client
// to try to connect to the Splat server.  This searching is done in another
// thread so that it can be cancelled by the user.  It also registers its
// own NXConnection so that the server can connect to it.
- connectToServer
{
    char hn[256];
    if (!hostname) {
    	NXRunAlertPanel("Error","You must type in the hostname that the server is running on to be a Splat client.  Aborting game.","OK",NULL,NULL);
	return self;
    }
    gethostname(hn,255);
    sprintf(myServerName,"splatClient%s",hn);
    sprintf(otherServerName,"splatServer%s",hostname);
    myServer = [NXConnection registerRoot:self withName:myServerName];
    [myServer runFromAppKitWithPriority:NX_MODALRESPTHRESHOLD];
    [myServer registerForInvalidationNotification:self];
    cthread_detach(cthread_fork((cthread_fn_t)lookForServer,self));
    return self;
}

// here's where the actual connection is being done
- lookForServer
{
    struct timeval t;
    struct timezone tz;
    long orig;

    // Look for the server for 60 seconds, and then abort with a timeout.
    //! This is really lame.  I should just be using the builtin timeout
    //! support in DO.
    gettimeofday(&t,&tz);
    orig = t.tv_sec;
    while (!otherPlayer && ((t.tv_sec - orig) < 60)) {
	otherPlayer = [NXConnection connectToName:otherServerName 
				    onHost:hostname];
	[[otherPlayer connectionForProxy]
		registerForInvalidationNotification:self];
	gettimeofday(&t,&tz);
    }
    if (!otherPlayer) {
	NXRunAlertPanel("Error","Connection timed out.  Unable to connect to Splat server on host %s","OK",NULL,NULL,hostname);
	return self;
    }
    [otherPlayer lookForClient];
    return self;
}

// same as above, just looking for the client this time
- connectToClient
{
    char hn[256];
    if (!hostname) {
    	NXRunAlertPanel("Error","You must type in the hostname of the Splat client so that I know who to talk to. Aborting game.","OK",NULL,NULL);
	return self;
    }
    gethostname(hn,255);
    sprintf(myServerName,"splatServer%s",hn);
    sprintf(otherServerName,"splatClient%s",hostname);
    myServer = [NXConnection registerRoot:self withName:myServerName];
    [myServer registerForInvalidationNotification:self];
    [myServer runFromAppKitWithPriority:NX_MODALRESPTHRESHOLD];
    return self;
}

// actually connect to the client
- (oneway void)lookForClient
{
    struct timeval t;
    struct timezone tz;
    long orig;
    
    // timeout after 10 seconds.  This is not really needed at all since
    // we got this message from the client, so connecting should be a snap.
    //! Once again, I should be using the builtin timeout support in DO.
    gettimeofday(&t,&tz);
    orig = t.tv_sec;
    while (!otherPlayer && ((t.tv_sec - orig) < 10)) {
	otherPlayer = [NXConnection connectToName:otherServerName 
				    onHost:hostname];
	[[otherPlayer connectionForProxy]
		registerForInvalidationNotification:self];
	gettimeofday(&t,&tz);
    }
    if (!otherPlayer) {
	NXRunAlertPanel("Error","Connection timed out.  Unable to connect to Splat client on host %s","OK",NULL,NULL,hostname);
	return;
    }
    // we're the server, so we'll make the client use our board
    [otherPlayer setClientBoard:[game board]];
    return;
}

// set the game board to be what the server told us...
- (oneway void)setClientBoard:(bycopy Board *)b
{
    [game setBoard:b];
    return;
}

// do the next move (tell the player we represent that they should do their
// nexty move by passing them the last move that was done here)
- doNextMove:(Board *)b
{
    if ((b->currentMove.from.col == 0) && (b->currentMove.from.row == 0) && (b->currentMove.to.col == 0) && (b->currentMove.to.row == 0))
	return self;
    [otherPlayer completedMove:&(b->currentMove)];
    return self;
}

// do the last move that was done by the other side
- (oneway void)completedMove:(in move *)mv
{
    [game startMove:mv];
    return;
}

// clean up
- gameOver:(Board *)b
{
    currentState = PLAYER_INACTIVE;
    [otherPlayer completedMove:&(b->currentMove)];
    return self;
}

- setHostname:(const char *)nm
{
    strncpy(hostname,nm,255);
    return self;
}

- (char *)hostname
{
    return hostname;
}

- (char *)playerName
{
    if (otherPlayer && !playerName) {
    	playerName = malloc(strlen([otherPlayer otherPlayerName]) + 1);
	strcpy(playerName,[otherPlayer otherPlayerName]);
    }
    return playerName;
}

- (char *)otherPlayerName
{
    id player1, player2;
    player1 = [game player1];
    player2 = [game player2];
    if (player1 == self)
	return [player2 playerName];
    else
	return [player1 playerName];
}

// when one side pauses or stops, the other side should too.
- setPlayerState:(PlayerState)state
{
    PlayerState oldState;
    if (state == currentState)
	return self;
    oldState = currentState;
    [super setPlayerState:state];
    switch (currentState) {
	case PLAYER_STOPPED:
	    [otherPlayer stopGame];
	    break;
	case PLAYER_PAUSED:
	    [otherPlayer pauseGame];
	    break;
	case PLAYER_ACTIVE:
	    if (oldState == PLAYER_STOPPED) {
		if (self == (id)[game player2]) {
		    [otherPlayer initNetGame];
		}
	    } else if (oldState == PLAYER_PAUSED) {
		[otherPlayer unpauseGame];
	    }
	    break;
	case PLAYER_INACTIVE:
	    if (oldState == PLAYER_STOPPED) {
		if (self == (id)[game player2]) {
		    [otherPlayer initNetGame];
		}
	    } else if (oldState == PLAYER_PAUSED) {
		[otherPlayer unpauseGame];
	    }
	    break;
    }
    return self;
}

- setActive:(BOOL)state
{
    return self;
}

// start a network game
- (oneway void)initNetGame
{
    if (self == (id)[game player1]) {
	[game showGame];
    } else [game initGame];
    return;
}

- (oneway void)stopGame
{
    [game stopGame:self];
    return;
}

- (oneway void)pauseGame
{
    if ([game gameState] == GAME_RUNNING)
	[game pauseGame:self];
    return;
}

- (oneway void)unpauseGame
{
    if ([game gameState] == GAME_PAUSED)
	[game pauseGame:self];
    return;
}

// abort when the connection has been dropped
//! this should free/remove the game rather than just stopping it
- senderIsInvalid:sender
{
    NXRunAlertPanel("Game Aborted","%s on host %s has quit the game.",
    			"OK",NULL,NULL,playerName,hostname);
    [game stopGame:self];
    return self;
}

@end
