/*
 * SplatGame
 * description: implementation of the SplatGame class
 * 		a class to keep track of the state of a splat game instance
 * history:
 *	5/1/93 [Erik Kay] - converted much of the SplatController to here
 *	6/8/93 [Erik Kay] - better network support
 */

#import "SplatView.h"
#import "SplatGame.h"
#import "SplatApp.h"
#import "SplatDefaults.h"
#import "HumanPlayer.h"
#import "ComputerPlayer.h"
#import "NetworkPlayer.h"
#import "PieceView.h"

@implementation SplatGame

- init
{
    [super init];

    // load the nib
    [NXApp loadNibSection:"Board.nib" owner:self withNames:NO];

    // set up the server to talk to the other threads....
    myConnection = [NXConnection registerRoot:self];
    [myConnection runFromAppKit];
    threadPort = [myConnection inPort];

    return self;
}

- free
{
    move_list *list;
    [super free];
    [player1 free];
    [player2 free];
    [myConnection free];
    [rules free];
    [board free];
    [boardWindow free];
    [origBoard free];
    list = &moves;
    while (list->next) {
	list = list->next;
	free(list->prev);
    }
    return self;
}

- (NXPort *)inPort
{
    return threadPort;
}

- awakeFromNib
{
    [player1ColorView setImage:[NXImage findImageNamed:"smallone"]];
    [player2ColorView setImage:[NXImage findImageNamed:"smalltwo"]];
    return self;
}

- showPlayerNames
{
    char name[64];

    if ([player1 isKindOf:[HumanPlayer class]]) {
	sprintf(name,"%s",[player1 playerName]);
    	[player1Name setStringValue:name];
    } else if ([player1 isKindOf:[ComputerPlayer class]]) {
	sprintf(name,"Computer\n%s",[player1 playerName]);
	[player1Name setStringValue:name];
    } else {
    	/* network player */
	sprintf(name,"%s\n@%s",[player1 playerName],[player1 hostname]);
	[player1Name setStringValue:name];
    }
    if ([player2 isKindOf:[HumanPlayer class]]) {
	sprintf(name,"%s",[player2 playerName]);
    	[player2Name setStringValue:name];
    } else if ([player2 isKindOf:[ComputerPlayer class]]) {
	sprintf(name,"Computer\n%s",[player2 playerName]);
	[player2Name setStringValue:name];
    } else {
    	/* network player */
	sprintf(name,"%s\n@%s",[player2 playerName],[player2 hostname]);
	[player2Name setStringValue:name];
    }
    return self;
}

- showGame
{
    [self showPlayerNames];
    [self initGame];
    if (board) {
	[boardWindow display];
	[boardView redrawBoard]; // need to do this manually here
	[boardWindow flushWindow];
	[boardWindow center];
	[boardWindow makeKeyAndOrderFront:self];
    }
    if ([player1 isKindOf:[ComputerPlayer class]])
	[self pauseGame:nil];
    else
	[self startGame:nil];
    return self;
}

- initNetPlayers
{
    int res;
    NXModalSession session;
    id alert;
    
    if ([player1 isKindOf:[NetworkPlayer class]]) {
	alert = NXGetAlertPanel("Network Splat",
		"Trying to connect to remote Splat server...",
		"Cancel",NULL,NULL);
	[alert center];
	[alert makeKeyAndOrderFront:self];
	[player1 connectToServer];
	[NXApp beginModalSession:&session for:alert];
	for (;;) {
	    if ((res = [NXApp runModalSession:&session]) != NX_RUNCONTINUES)
		return nil;
	    if ([player1 isConnected])
		break;
	}
	[NXApp endModalSession:&session];
	[alert close];
    } else if ([player2 isKindOf:[NetworkPlayer class]]) {
	alert = NXGetAlertPanel("Network Splat",
		"Waiting for connection from Splat client...",
		"Cancel",NULL,NULL);
	[alert center];
	[alert makeKeyAndOrderFront:self];
	[player2 connectToClient];
	[NXApp beginModalSession:&session for:alert];
	for (;;) {
	    if ((res = [NXApp runModalSession:&session]) != NX_RUNCONTINUES)
		break;
	    if ([player2 isConnected])
		break;
	}
	[NXApp endModalSession:&session];
	[alert close];
	if (res != NX_RUNCONTINUES)
	    return nil;
    }
    return self;
}

- initGame
{
    [self resetBoard];
    
    currentPlayer = nil; // this indicates that the game is just starting
    otherPlayer = player2;
    winner = NULL;
    last_move = &moves;
    [player1 setPlayerState:PLAYER_INACTIVE];
    [player2 setPlayerState:PLAYER_INACTIVE];
    return self;
}

- startGame:sender
{
    if (gameState == GAME_RUNNING)
	return self;
    if ((gameState == GAME_OVER) || (gameState == NO_GAME)) {
	[self initGame];
	[self setGameState:GAME_RUNNING];
	[currentPlayer setPlayerState:PLAYER_ACTIVE];
	[otherPlayer setPlayerState:PLAYER_INACTIVE];
	[currentPlayer doNextMove:board];
    } else if (gameState == GAME_PAUSED) {
	[self pauseGame:sender];
    }
    return self;
}

- pauseGame:sender
{
    if (gameState == GAME_PAUSED) {
	[self setGameState:GAME_RUNNING];
	[currentPlayer setPlayerState:PLAYER_ACTIVE];
	[otherPlayer setPlayerState:PLAYER_INACTIVE];
	if ([currentPlayer isKindOf:[ComputerPlayer class]])
	    [currentPlayer doNextMove:board];
    } else {
	[self setGameState:GAME_PAUSED];
	[currentPlayer setPlayerState:PLAYER_PAUSED];
	[currentPlayer setPlayerState:PLAYER_PAUSED];
    }
    return self;
}

- stopGame:sender
{
    [self resetGame];
    return self;
}

- gameOver
{
    char *tmp,title[256];

    [self setGameState:GAME_OVER];
    [board fillEmptyWith:[currentPlayer pieceType]];
    [self updateCount];
    winner = [rules winner:self];
    [otherPlayer gameOver:board];
    if (winner) {
    	if (winner == player1)
	    tmp = "Player One Wins";
	else
	    tmp = "Player Two Wins";
    } else
	tmp = "Tie Game";

    sprintf(title,"Game Over - %s",tmp);
    [boardWindow setTitle:title];
    [boardView display];
    [boardView gameOver];
    if ([[NXApp defaultsManager] playSounds]) {
	if ([winner isKindOf:[HumanPlayer class]])
	    [[NXApp soundManager] playSound:WIN_SOUND];
	else
	    [[NXApp soundManager] playSound:GAMEOVER_SOUND];
    }
    return self;
}

- resetGame
{
    [player1 setPlayerState:PLAYER_STOPPED];
    [player2 setPlayerState:PLAYER_STOPPED];
    [self setGameState:NO_GAME];
    winner = NULL;
    return self;
}

- (Player *)winner
{
    return winner;
}

- resetBoard
{
    [board free];
    board = [origBoard trueCopy];
    [[boardView setBoard:board] display];
    [boardWindow flushWindow];
    return self;
}

- setBoard:(Board *)b
{
    Board *newb;
    if (board)
	[board free];
    if (origBoard)
	[origBoard free];
    newb = [b trueCopy];
    origBoard = [b trueCopy];
    [boardView setScalable:NO];
    [[boardView setBoard:newb] display];
    [boardView setScalable:YES];
    [rules setInitialBoard:newb];
    board = newb;
    return self;
}

- updateBoard:(Board *)b
{
    Board *newb;
    if (board)
	[board free];
    newb = [b trueCopy];
    [boardView setScalable:NO];
    [boardView setBoard:newb];
    [boardView setScalable:YES];
    board = newb;
    return self;
}

- (Board *)board
{
    return board;
}

- setRules:(SplatRules *)r
{
    rules = r;
    return self;
}

- (SplatRules *)rules
{
    return rules;
}

- (Player *)currentPlayer
{
    return currentPlayer;
}

- (Player *)otherPlayer
{
    return otherPlayer;
}

- setPlayer1:(Player *)p
{
    if (player1) {
	[player1 free];
    }
    player1 = p;
    [player1 setGame:self];
    return self;
}

- (Player *)player1
{
    return player1;
}

- setPlayer2:(Player *)p
{
    if (player2) {
	[player2 free];
    }
    player2 = p;
    [player2 setGame:self];
    return self;
}

- (Player *)player2
{
    return player2;
}

- updateStatus
{
    char title[256], *tmp;

    switch (gameState) {
	case GAME_RUNNING: 
	    if (!currentPlayer)
		currentPlayer = player1;
	    if ([player1 isKindOf:[HumanPlayer class]] ||
	    	[player2 isKindOf:[HumanPlayer class]]) {
		if ([currentPlayer isKindOf:[HumanPlayer class]])
		    tmp = "Splat - %s's Turn";
		else
		    tmp = "Splat - %s's Turn (Thinking)";
		sprintf(title,tmp,[currentPlayer playerName]);
	    } else {
		if ([currentPlayer isKindOf:[HumanPlayer class]])
		    tmp = "Splat - Player %s's Turn";
		else
		    tmp = "Splat - Player %s's Turn (Thinking)";
		if (currentPlayer == player1)
		    sprintf(title,tmp,"One");
		else
		    sprintf(title,tmp,"Two");
	    }
	    [boardWindow setTitle:title];
	    [self showPlayerNames];
	    [pauseButton setEnabled:1];
	    [playButton setEnabled:0];
	    [stopButton setEnabled:1];
	    [undoButton setEnabled:1];
	    [pauseButton setState:0];
	    [playButton setState:1];
	    break;
	case GAME_PAUSED:
	    [boardWindow setTitle:"Splat - Paused"];
	    [pauseButton setEnabled:1];
	    [playButton setEnabled:1];
	    [stopButton setEnabled:1];
	    [undoButton setEnabled:0];
	    [pauseButton setState:1];
	    [playButton setState:1];
	    break;
	case GAME_OVER:
	    [pauseButton setEnabled:0];
	    [playButton setEnabled:1];
	    [stopButton setEnabled:0];
	    [undoButton setEnabled:0];
	    [pauseButton setState:0];
	    [playButton setState:0];
	    break;
	case NO_GAME:
	    [boardWindow setTitle:"Splat - Game Aborted"];
	    [pauseButton setEnabled:0];
	    [playButton setEnabled:1];
	    [stopButton setEnabled:0];
	    [undoButton setEnabled:0];
	    [pauseButton setState:0];
	    [playButton setState:0];
	    break;
    }
    [[pauseButton controlView] display];
    [[playButton controlView] display];
    [[stopButton controlView] display];
    [[undoButton controlView] display];
    [boardWindow flushWindow];
    return self;
}

- setGameState:(enum game_state)state
{
    if (gameState == state)
	return self;
    gameState = state;
    switch (gameState) {
	case NO_GAME:
	    [player1 setPlayerState:PLAYER_STOPPED];
	    [player2 setPlayerState:PLAYER_STOPPED];
	    break;
	case GAME_PAUSED:
	    [player1 setPlayerState:PLAYER_PAUSED];
	    [player2 setPlayerState:PLAYER_PAUSED];
	    break;
	case GAME_RUNNING:
	    if (currentPlayer == player1) {
		[player1 setPlayerState:PLAYER_ACTIVE];
		[player2 setPlayerState:PLAYER_INACTIVE];
	    } else if (currentPlayer == player2) {
		[player1 setPlayerState:PLAYER_INACTIVE];
		[player2 setPlayerState:PLAYER_ACTIVE];
	    } else {
		[player1 setPlayerState:PLAYER_INACTIVE];
		[player2 setPlayerState:PLAYER_INACTIVE];
	    }
	    break;
	default:
	    break;
    }
    [self updateStatus];
    return self;
}

- (enum game_state)gameState
{
    return gameState;
}

- updateCount
{
    int num;

    num = [board numberOfPiece:SQUARE_ONE];
    [player1PiecesField setIntValue:num];
    num = [board numberOfPiece:SQUARE_TWO];
    [player2PiecesField setIntValue:num];
    return self;
}

- doNextMove
{
    id player;
    
    if (gameState != GAME_RUNNING)
	return self;
    if ([rules gameOver:board forPlayer:[otherPlayer pieceType]]) {
	[self gameOver];
	return self;
    }
    if (!currentPlayer)
	currentPlayer = player1;
    player = currentPlayer;
    currentPlayer = otherPlayer;
    otherPlayer = player;
    [self updateStatus];
    [currentPlayer setPlayerState:PLAYER_ACTIVE];
    [otherPlayer setPlayerState:PLAYER_INACTIVE];
    [currentPlayer doNextMove:board];
    return self;
}

- doMove:(move *)mv
{
    location loc = {0,0};
    move_list *newmove;
    last_move->mv = *mv;
    newmove = malloc(sizeof(move_list));
    newmove->prev = last_move;
    newmove->next = NULL;
    newmove->mv.from = loc;
    newmove->mv.to = loc;
    last_move->next = newmove;
    last_move = newmove;
    [rules doMove:mv onBoard:board];
    [self updateCount];
    return self;
}

- doUndoMove:sender
{
    Board *newb;
    move_list *last;
    move nomove = {0,0};
    id tmpPlayer;
    
    if (!moves.next)
	return self;
    [boardWindow setTitle:"Undoing Move..."];
    if (([otherPlayer isKindOf:[ComputerPlayer class]]) ||
    	([currentPlayer isKindOf:[ComputerPlayer class]])) {
	newb = [rules undoMove:&moves];
	[self updateBoard:newb];
	last = last_move->prev;
	if (!last->prev)
	    last->mv = nomove;
	else {
	    last = last->prev;
	    free(last->next);
	    last->next = last_move;
	    last_move->prev = last;
	}
    } else {
	tmpPlayer = currentPlayer;
	currentPlayer = otherPlayer;
	otherPlayer = tmpPlayer;
    }
    newb = [rules undoMove:&moves];
    [self updateBoard:newb];
    last = last_move->prev;
    if (!last->prev) {
	last_move->mv = nomove;
    } else {
	last = last->prev;
	free(last->next);
	last->next = last_move;
	last_move->prev = last;
    }
    [self updateCount];
    [self updateStatus];
    return self;
}

/*
 * BoardView callbacks
 */
 

- squareHit:(int)col :(int)row
{
    move mv;
    
    if (![currentPlayer isKindOf:[HumanPlayer class]])
    	return self;
    if (gameState != GAME_RUNNING)
    	return self;
    mv.from.col = [boardView selectedCol];
    mv.from.row = [boardView selectedRow];
    mv.to.col = col;
    mv.to.row = row;
    if ((mv.from.col == -1) || (![rules validMove:&mv onBoard:board])) {
        if ([board pieceAt:col :row] == [currentPlayer pieceType])
	    [[boardView selectSquare:col :row] display];
    } else {
	[self startMove:&mv];
    }
    return self;
}

- startMove:(move *)mv
{
    if ([[NXApp defaultsManager] doMoveAnimation])
	[boardView doMoveAnimation:mv];
    else
	[self moveAnimationFinished:mv];
    return self;
}

- moveAnimationFinished:(move *)mv
{
    Board *copy;

    copy = [board trueCopy];
    [self doMove:mv];
    [self updateCount];

    if ([[NXApp defaultsManager] doMoveAnimation])
	[boardView doChangeAnimation:copy];
    else
	[self changeAnimationFinished];
    [copy free]; //? we assume that boardView has made a copy here
    return self;
}

- changeAnimationFinished
{
    if (![[NXApp defaultsManager] doMoveAnimation])
    	[boardView display];
    [self doNextMove];
    return self;
}

- windowWillClose:sender
{
    int res;
    
    switch (gameState) {
	case NO_GAME:
	case GAME_OVER:
	    return self;
	    break;
	default:
	    res = NXRunAlertPanel("Whoops!","There's a game in progress.  Are you sure you want to kill it?\n","OK","Cancel",NULL);
	    if (res == NX_ALERTDEFAULT)
		return self;
	    else
		return nil;
	    break;
    }
    return self;
}

/*
 * for saving and restoring games
 * (extension == ".splat")
 */

- write
{
    return self;
}

- read
{
    return self;
}


@end
