//
// $Id: DistributedGameManager.m,v 1.12 1997/10/31 05:44:20 nygard Exp $
//

//
//  This file is a part of Empire, a game of exploration and conquest.
//  Copyright (C) 1996  Steve Nygard
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  You may contact the author by:
//     e-mail:  nygard@telusplanet.net
//

#import "Empire.h"

RCSID ("$Id: DistributedGameManager.m,v 1.12 1997/10/31 05:44:20 nygard Exp $");

#import "DistributedGameManager.h"
#import "GameManager.h"
#import "City.h"
#import "EmPlayer.h"
#import "EmpireProtocols.h"
#import "Map.h"
#import "SNRandom.h"
#import "Unit.h"
#import "World.h"

//======================================================================
// This class is somewhat disorganized.  It adds functionality to
// handle distributed games.
//
// I've annotated some of the methods:
//   Client -> Server: The message is initiated from the client, and
//                     goes to the server.
//   Server -> Client: The message always originates in the server,
//                     and queries the client.
//   Client -> Server -> Client: The message is for a particular client,
//                     and is forwarded to the propre client by the
//                     server.
//
// Generally, when the server gets a message for a client it manages,
// it calls the implementation in its superclass.
//
// You have to be really careful, otherwise you can get unlimited
// recursion happening between separate processes.  However, the
// methods tend to fall into the above three patterns.
//======================================================================

#define DistributedGameManager_VERSION 1

@implementation DistributedGameManager

+ (void) initialize
{
    if (self == [DistributedGameManager class])
    {
        [self setVersion:DistributedGameManager_VERSION];
    }
}

//----------------------------------------------------------------------

- init
{
    [super init];

    master = nil;
    masterConnection = nil;
    clientPlayer = p_neutral;
    
    playerManagers[0] = nil;
    playerManagers[1] = nil;
    playerManagers[2] = nil;
    playerManagers[3] = nil;

    clientConnections[0] = nil;
    clientConnections[1] = nil;
    clientConnections[2] = nil;
    clientConnections[3] = nil;

    unreadyRemotePlayers = 0;

    cachedMaps[0] = nil;
    cachedMaps[1] = nil;
    cachedMaps[2] = nil;
    cachedMaps[3] = nil;

    return self;
}

//----------------------------------------------------------------------

- (void) dealloc
{
    Player p;

    for (p = p_neutral; p <= p_player3; p++)
    {
        SNRelease (cachedMaps[p]);
    }
    
    [super dealloc];
}

//======================================================================
// EmpireGameManagerProtocol
//======================================================================

- (void) ping
{
}

//----------------------------------------------------------------------

- (void) aboutToDisconnect:sender
{
}

//----------------------------------------------------------------------

- (void) peer:clientGameManager forPlayer:(Player)number
{
    NSAssert (playerManagers[number] == nil, @"Player manager already set.");
    NSAssert (clientConnections[number] == nil, @"Client connection already set.");
    
    playerManagers[number] = [clientGameManager retain];
    clientConnections[number] = [clientGameManager connectionForProxy];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector (connectionDidDie:)
                                          name:NSConnectionDidDieNotification
                                          object:clientConnections[number]];

    //[playerManagers[number] setProtocolForProxy:@protocol (EmpireGameManagerProtocol)];

    // Accessed by different threads?
    unreadyRemotePlayers--;
    playersActive[number] = YES;
    activePlayerCount++;

    [[NSNotificationCenter defaultCenter] postNotificationName:EMPlayerStateChangedNotification
                                          object:self];

    [self tryToStart];
}

//----------------------------------------------------------------------

- (Map *) remoteFetchMapForPlayer:(Player)number
{
    return [self fetchMapForPlayer:number];
}

//----------------------------------------------------------------------

// Run by client, so this client hasn't run -distribute maps...
// Server -> Client
- (void) otherMaps:(Map *)map1:(Map *)map2 forPlayer:(Player)number
{
    Player p1, p2;

    //NSLog (@"map1: %@, map2: %@", map1, map2);

    // ? constantly increasing retain count?
    cachedMaps[number] = [[players[number] map] retain];

    p1 = (number == p_player1) ? p_player2 : p_player1;
    p2 = (number == p_player3) ? p_player2 : p_player3;

    SNRelease (cachedMaps[p1]);
    cachedMaps[p1] = [map1 retain];

    SNRelease (cachedMaps[p2]);
    cachedMaps[p2] = [map2 retain];

    //NSLog (@"cachedMaps[%d]: %@, cachedMaps[%d]: %@", p1, cachedMaps[p1], p2, cachedMaps[p2]);
}

//----------------------------------------------------------------------

- (void) remoteTurn:(int)turn withCookie:(NSNumber *)aCookie forPlayer:(Player)number
{
    NSAssert1 (players[number] != nil, @"Player %d is nil.", number);

    [players[number] yourTurn:turn withCookie:aCookie];
}

//----------------------------------------------------------------------

- (void) remoteTurnDone:(NSNumber *)aCookie forPlayer:(Player)number updatedMap:(Map *)newMap
{
    SNRelease (cachedMaps[number]);

    cachedMaps[number] = [newMap retain];

    [self turnDone:aCookie];
}

//======================================================================
// EstablishGame
//======================================================================

- (void) startGameWithMapNamed:(NSString *)mapName
{
    [super startGameWithMapNamed:mapName];

    unreadyRemotePlayers = 0;
}

//----------------------------------------------------------------------

- (void) tryToStart
{
    AssertGameState (gs_establishing_game);

    if (unreadyRemotePlayers == 0)
    {
        [self distributeMaps];
        [super tryToStart];
    }
}

//----------------------------------------------------------------------
#if 0
- (void) stopGame
{
    [super stopGame];
    
    SNRelease (master);
}
#endif
//----------------------------------------------------------------------

//- (void) startGameWithMap:(Map *)worldMap master:(GameManager *)masterGameManager
- (void) startGameWithMap:(Map *)worldMap master:masterGameManager
{
    // Check whether there is a game in progress
    if (gameState != gs_no_game)
    {
        if (NSRunAlertPanel (@"New Client", @"There is already a game in progress or starting.", @"Cancel", @"Start new game", nil) == NSAlertDefaultReturn)
            return;
        
        [self stopGame];
    }

    AssertGameState (gs_no_game);
    NSAssert (world == nil, @"There is already an active world.");

    NSAssert (master == nil, @"Master game manager already set.");
    NSAssert (masterConnection == nil, @"Master connection alread set.");

    world = [[World alloc] initWithMap:worldMap];
    NSAssert (world != nil, @"Could not create world.");

    [self setGameState:gs_establishing_game];
    unreadyRemotePlayers = 0;
    master = [masterGameManager retain];
    masterConnection = [masterGameManager connectionForProxy];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector (connectionDidDie:)
                                          name:NSConnectionDidDieNotification
                                          object:masterConnection];
}

//----------------------------------------------------------------------

- (void) addRemotePlayer:(Player)number forClient:remoteClient
{
    NSProtocolChecker *protocolChecker;

    NSAssert (world != nil, @"No active world.");

    unreadyRemotePlayers++;

    protocolChecker = [NSProtocolChecker protocolCheckerWithTarget:self
                                         protocol:@protocol (EmpireGameManagerProtocol)];

    NSAssert (protocolChecker != nil, @"A protocol checker could not be allocated.");

    NS_DURING
        {
            [remoteClient ping];
            [remoteClient choosePlayer:number forMap:[world worldMap] master:protocolChecker];
        }
    NS_HANDLER
        {
            EHAND;
        }
    NS_ENDHANDLER;
}

//----------------------------------------------------------------------

- (void) notifyMasterForPlayer:(Player)number
{
    NSProtocolChecker *protocolChecker;

    AssertGameState (gs_establishing_game);
    NSAssert (master != nil, @"No master game manager.");

    protocolChecker = [NSProtocolChecker protocolCheckerWithTarget:self
                                         protocol:@protocol (EmpireGameManagerProtocol)];

    NSAssert (protocolChecker != nil, @"A protocol checker could not be allocated.");

    clientPlayer = number;
    [self setGameState:gs_client_active];

    NS_DURING
        {
            // Pass protocol checker.
            [master peer:protocolChecker forPlayer:number];
        }
    NS_HANDLER
        {
            EHAND;
        }
    NS_ENDHANDLER;
}

//----------------------------------------------------------------------

// This initializes the server map cache, which then fills each client
// map cache.  Each client then synchronizes their initial map by
// updating around all of their cities.  This is only needed at startup.
//
// At this time, all non-nil players/player managers should be active.
//
- (void) distributeMaps
{
    Player p;

    for (p = p_player1; p <= p_player3; p++)
    {
        if (playersActive[p] == YES)
        {
            // Problem retaining nil here?
            cachedMaps[p] = [[self fetchMapForPlayer:p] retain];
        }
        else
        {
            cachedMaps[p] = nil;
        }
    }

    // Distribute maps to active remote players.
    for (p = p_player1; p <= p_player3; p++)
    {
        if (playerManagers[p] != nil)
        {
            NSAssert1 (playersActive[p] == YES, @"Player %d should be active.", p);
            [self distributeMapsToRemotePlayer:p];
        }
    }

    for (p = p_player1; p <= p_player3; p++)
    {
        if (playersActive[p] == YES)
        {
            [self updateMapAroundCitiesForPlayer:p];
        }
    }
}

//======================================================================
// TurnHandling
//======================================================================

// Client -> Server

// I can't figure out a way of calling the super's implementation to
// accomplish the default behaviour...
- (void) turnDone:(NSNumber *)aCookie
{
    Player nextPlayer;
    int limit = 3;
    
    if (master != nil)
    {
        NS_DURING
            {
                [master remoteTurnDone:aCookie forPlayer:clientPlayer updatedMap:cachedMaps[clientPlayer]];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
        
        return;
    }

    if ([awaitingCookie isEqual:aCookie] == NO)
    {
        //NSLog (@"awaiting: %@, got: %@", awaitingCookie, aCookie);
        NSLog (@"[2] Invalid cookie!");
        return;
    }

    if (activePlayerCount <= 0)
    {
        NSLog (@"No active players.");
        return;
    }

    SNRelease (awaitingCookie);
    awaitingCookie = [[NSNumber numberWithUnsignedLong:[[SNRandom instance] randomNumber]] retain];

    do
    {
        nextPlayer = [self nextPlayerTurn];
    }
    while (limit-- > 0 && (playersActive[nextPlayer] == NO
                           || (players[nextPlayer] == nil && playerManagers[nextPlayer] == nil)));

    if (playersActive[nextPlayer] == YES && players[nextPlayer] != nil)
    {
        [players[nextPlayer] yourTurn:currentTurn withCookie:awaitingCookie];
    }
    else if (playersActive[nextPlayer] == YES && playerManagers[nextPlayer] != nil)
    {
        [self distributeMapsToRemotePlayer:nextPlayer];
        NS_DURING
            {
                [playerManagers[nextPlayer] remoteTurn:currentTurn withCookie:awaitingCookie forPlayer:nextPlayer];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        NSLog (@"No active players.");
    }
}

//----------------------------------------------------------------------

// This distributes the server's current map cache to a client.  It
// should have no idea of active players.  When a player becomes
// inactive, the map in the cache should become nil.?

// Server -> Client
- (void) distributeMapsToRemotePlayer:(Player)number
{
    Player p1, p2;
    Map *map1, *map2;
        
    NSAssert1 (playerManagers[number] != nil, @"No remote manager for player: %d", number);

    p1 = (number == p_player1) ? p_player2 : p_player1;
    p2 = (number == p_player3) ? p_player2 : p_player3;
    //NSLog (@"Distributing maps(%d,%d) to player manager: %d", p1, p2, number);

    NS_DURING
        {
            map1 = playersActive[p1] == YES ? cachedMaps[p1] : nil;
            map2 = playersActive[p2] == YES ? cachedMaps[p2] : nil;
#if 0
            NSLog (@"Player %d active? %@, Player %d active? %@",
                   p1, playersActive[p1] ? @"Yes" : @"No",
                   p2, playersActive[p2] ? @"Yes" : @"No");
            NSLog (@"Maps are %@, %@", map1, map2);
#endif
            [playerManagers[number] otherMaps:map1:map2 forPlayer:number];
            //[playerManagers[number] otherMaps:cachedMaps[p1]:cachedMaps[p2] forPlayer:number];
        }
    NS_HANDLER
        {
            EHAND;
        }
    NS_ENDHANDLER;
}

//======================================================================
// Empire Game Manager Protocol:
//======================================================================

//----------------------------------------------------------------------
// The -remote* methods may be a bit redundant, but they helped me
// distinguish between remote and local methods.
//----------------------------------------------------------------------

//----------------------------------------------------------------------
// Game Initialization / Termination
//----------------------------------------------------------------------

- (City *) remoteRandomNeutralCity
{
    return [self randomNeutralCity];
}

//----------------------------------------------------------------------

- (void) remoteGameHasStopped:(Player)number activePlayers:(int)activePlayers
{
    [self gameHasStopped:number activePlayers:activePlayers];
}

//----------------------------------------------------------------------
// Combat Support
//----------------------------------------------------------------------

- (void) remoteSet3x3Tokens:(struct NineTokens)tokens aroundLocation:(EMMapLocation)target forPlayer:(Player)number
{
    [self set3x3Tokens:tokens.tokens aroundLocation:target forPlayer:number];
}

//----------------------------------------------------------------------

- (void) remoteSetToken:(MapToken)token atLocation:(EMMapLocation)target forPlayer:(Player)number;
{
    [self setToken:token atLocation:target forPlayer:number];
}

//----------------------------------------------------------------------
// I trying very hard with these methods to avoid referencing remote
// units and cities for combat.
//----------------------------------------------------------------------

- (MoveResult) remoteUnitWithCombatProfile:(CombatProfile)attackerProfile
                             attacksPlayer:(Player)cityPlayer
                            cityAtLocation:(EMMapLocation)target
                 playersAdjacentToDefender:(int)adjacentPlayers
{
    //NSLog (@"master: %@, cityPlayer: %d, cityLocation: %d,%d patd: %d", master, cityPlayer, target.row, target.column, adjacentPlayers);

    return [self unitWithCombatProfile:attackerProfile
                 attacksPlayer:cityPlayer
                 cityAtLocation:target
                 playersAdjacentToDefender:adjacentPlayers];
}

//----------------------------------------------------------------------

- (MoveResult) remoteUnitWithCombatProfile:(CombatProfile)attackerProfile
                             attacksPlayer:(Player)defender
                            unitAtLocation:(EMMapLocation)target
                           withBombardment:(BOOL)bombarding
                 playersAdjacentToDefender:(int)adjacentPlayers
{
    //NSLog (@"master: %@, defender: %d, cityLocation: %d,%d patd: %d", master, defender, target.row, target.column, adjacentPlayers);

    return [self unitWithCombatProfile:attackerProfile
                 attacksPlayer:defender
                 unitAtLocation:target
                 withBombardment:bombarding
                 playersAdjacentToDefender:adjacentPlayers];
}

//----------------------------------------------------------------------

- (CombatProfile) remoteReadyDefendingCityAtLocation:(EMMapLocation)target forPlayer:(Player)number
{
    return [self readyDefendingCityAtLocation:target forPlayer:number];
}

//----------------------------------------------------------------------

- (CombatProfile) remoteReadyDefendingUnitAtLocation:(EMMapLocation)target
                                           forPlayer:(Player)number
                                  againstBombardment:(BOOL)bombarding
{
    return [self readyDefendingUnitAtLocation:target forPlayer:number againstBombardment:bombarding];
}

//----------------------------------------------------------------------

- (void) remoteShowExplosions:(int)count forPlayer:(Player)number atLocation:(EMMapLocation)target
{
    [self showExplosions:count forPlayer:number atLocation:target];
}

//----------------------------------------------------------------------

- (void) remoteHitDefendingUnit:(Player)number withDamage:(int)damage
{
    [self hitDefendingUnit:number withDamage:damage];
}

//----------------------------------------------------------------------

- (City *) remoteLostDefendingCityOfPlayer:(Player)number
{
    return [self lostDefendingCityOfPlayer:number];
}

//----------------------------------------------------------------------

- (void) remoteHitAttacker:(Player)number withDamage:(int)damage
{
    [self hitAttacker:number withDamage:damage];
}

//----------------------------------------------------------------------

- (void) remotePlayer:(Player)number hasCapturedCity:(City *)capturedCity
{
    [self player:number hasCapturedCity:capturedCity];
}

//----------------------------------------------------------------------

- (void) remoteFinishedCombatForPlayer:(Player)number
{
    [self finishedCombatForPlayer:number];
}

//----------------------------------------------------------------------

// Client -> Server
- (BOOL) remoteCheckForEndOfPlayer:(Player)number
{
    return [self checkForEndOfPlayer:number];
}

//----------------------------------------------------------------------

- (BOOL) remoteHasPlayerLost:(Player)number
{
    return [self hasPlayerLost:number];
}

//----------------------------------------------------------------------

- (void) remotePlayerHasLost:(Player)number activePlayers:(int)activePlayers
{
    [self playerHasLost:number activePlayers:activePlayers];
}

//----------------------------------------------------------------------

- (BOOL) remotePlayerHasWon:(Player)number activePlayers:(int)activePlayers
{
    return [self playerHasWon:number activePlayers:activePlayers];
}

//----------------------------------------------------------------------
// Methods that may communicate with remote game managers:
//----------------------------------------------------------------------

//----------------------------------------------------------------------
// Server -> Client...
- (void) gameHasStopped:(Player)number activePlayers:(int)activePlayers
{
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteGameHasStopped:number activePlayers:activePlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;

        [self deactivatePlayer:number];
    }
    else
    {
        [super gameHasStopped:number activePlayers:activePlayers];
    }
}    

//----------------------------------------------------------------------
// The central game manager maintains the list of neutral cities.
//----------------------------------------------------------------------

// Client -> Server
- (City *) randomNeutralCity
{
    City *city = nil;
    
    if (master != nil)
    {
        NS_DURING
            {
                city = [master remoteRandomNeutralCity];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        city = [world randomNeutralCity];
    }

    return city;
}

//----------------------------------------------------------------------

// Client -> Server
- (MoveResult) unitWithCombatProfile:(CombatProfile)attackerProfile
                       attacksPlayer:(Player)cityPlayer
                      cityAtLocation:(EMMapLocation)target
           playersAdjacentToDefender:(int)adjacentPlayers
{
    MoveResult moveResult;
    
    if (master != nil)
    {
        NS_DURING
            {
                moveResult = [master remoteUnitWithCombatProfile:attackerProfile
                                     attacksPlayer:cityPlayer
                                     cityAtLocation:target
                                     playersAdjacentToDefender:adjacentPlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        moveResult = [super unitWithCombatProfile:attackerProfile
                            attacksPlayer:cityPlayer
                            cityAtLocation:target
                            playersAdjacentToDefender:adjacentPlayers];
    }

    return moveResult;
}

//----------------------------------------------------------------------

- (MoveResult) unitWithCombatProfile:(CombatProfile)attackerProfile
                       attacksPlayer:(Player)defender
                      unitAtLocation:(EMMapLocation)target
                     withBombardment:(BOOL)bombarding
           playersAdjacentToDefender:(int)adjacentPlayers
{
    MoveResult moveResult;
    
    if (master != nil)
    {
        NS_DURING
            {
                moveResult = [master remoteUnitWithCombatProfile:attackerProfile
                                     attacksPlayer:defender
                                     unitAtLocation:target
                                     withBombardment:bombarding
                                     playersAdjacentToDefender:adjacentPlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        moveResult = [super unitWithCombatProfile:attackerProfile
                            attacksPlayer:defender
                            unitAtLocation:target
                            withBombardment:bombarding
                            playersAdjacentToDefender:adjacentPlayers];
    }

    return moveResult;
}

//----------------------------------------------------------------------

// Server -> Client
- (CombatProfile) readyDefendingCityAtLocation:(EMMapLocation)target forPlayer:(Player)number
{
    CombatProfile cityProfile;

    NSAssert1 (number == p_neutral || playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                cityProfile = [playerManagers[number] remoteReadyDefendingCityAtLocation:target forPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        cityProfile = [super readyDefendingCityAtLocation:target forPlayer:number];
    }

    return cityProfile;
}

//----------------------------------------------------------------------

// Server -> Client
- (CombatProfile) readyDefendingUnitAtLocation:(EMMapLocation)target forPlayer:(Player)number againstBombardment:(BOOL)bombarding
{
    CombatProfile unitProfile;

    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                unitProfile = [playerManagers[number] remoteReadyDefendingUnitAtLocation:target
                                              forPlayer:number
                                              againstBombardment:bombarding];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        unitProfile = [super readyDefendingUnitAtLocation:target forPlayer:number againstBombardment:bombarding];
    }

    return unitProfile;
}

//----------------------------------------------------------------------

// Server -> Client
- (void) showExplosions:(int)count forPlayer:(Player)number atLocation:(EMMapLocation)target
{
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteShowExplosions:count forPlayer:number atLocation:target];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super showExplosions:count forPlayer:number atLocation:target];
    }
}

//----------------------------------------------------------------------

// Server -> Client
- (void) hitDefendingUnit:(Player)number withDamage:(int)damage
{
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteHitDefendingUnit:number withDamage:damage];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super hitDefendingUnit:number withDamage:damage];
    }
}

//----------------------------------------------------------------------

// Server -> Client
//- (void) hitDefendingCity:(Player)number withDamage:(int)damage
- (City *) lostDefendingCityOfPlayer:(Player)number
{
    City *city = nil;
    
    NSAssert1 (number == p_neutral || playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                city = [playerManagers[number] remoteLostDefendingCityOfPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        city = [super lostDefendingCityOfPlayer:number];
    }

    return city;
}

//----------------------------------------------------------------------

// Server -> Client
- (void) hitAttacker:(Player)number withDamage:(int)damage
{
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteHitAttacker:number withDamage:damage];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super hitAttacker:number withDamage:damage];
    }
}

//----------------------------------------------------------------------

// Server -> Client
- (void) player:(Player)number hasCapturedCity:(City *)capturedCity
{
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remotePlayer:number hasCapturedCity:capturedCity];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super player:number hasCapturedCity:capturedCity];
    }
}

//----------------------------------------------------------------------

// Server -> Client
- (void) finishedCombatForPlayer:(Player)number
{
    NSAssert1 (number == p_neutral || playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteFinishedCombatForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super finishedCombatForPlayer:number];
    }
}

//----------------------------------------------------------------------

// Client -> Server
- (BOOL) checkForEndOfPlayer:(Player)number
{
    BOOL result;

    if (master != nil)
    {
        NS_DURING
            {
                result = [master remoteCheckForEndOfPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        NSAssert1 (number == p_neutral || playersActive[number] == YES, @"Player %d is not active", number);
        result = [super checkForEndOfPlayer:number];
    }

    //[self logStatus];

    return result;
}

//----------------------------------------------------------------------

// Server -> Client
- (BOOL) hasPlayerLost:(Player)number
{
    BOOL result;
    
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                result = [playerManagers[number] remoteHasPlayerLost:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        result = [super hasPlayerLost:number];
    }

    return result;
}

//----------------------------------------------------------------------

// Server -> Client
- (void) playerHasLost:(Player)number activePlayers:(int)activePlayers
{
    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);

    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remotePlayerHasLost:number activePlayers:activePlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;

        [self deactivatePlayer:number]; //?
    }
    else
    {
        //NSAssert (gameState == gs_client_active, @"Expected to be in client active state.");
        
        [super playerHasLost:number activePlayers:activePlayers];
    }
}

//----------------------------------------------------------------------

// Server -> Client
- (BOOL) playerHasWon:(Player)number activePlayers:(int)activePlayers
{
    BOOL keepPlaying;

    NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);

    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                keepPlaying = [playerManagers[number] remotePlayerHasWon:number activePlayers:activePlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;

        if (keepPlaying == NO)
        {
            [self deactivatePlayer:number];
        }
    }
    else
    {
        // Only if master != nil...
        //NSAssert (gameState == gs_client_active, @"Expected to be in client active state."); 
        
        keepPlaying = [super playerHasWon:number activePlayers:activePlayers];
    }

    //[self logStatus];

    return keepPlaying;
}

//======================================================================

// Get's our cached version.
- (Map *) mapForPlayer:(Player)number
{
    Map *map = nil;
    
    //NSLog (@"player %d active? %@", playersActive[number] == YES ? @"Yes" : @"No");

    if (master == nil)
    {
        map = playersActive[number] == YES ? cachedMaps[number] : nil;
    }
    else
    {
        map = cachedMaps[number];
    }

    //NSLog (@"DGM - mapForPlayer:%d is %@", number, map);

    return map;
}

//----------------------------------------------------------------------

// Get's the original, current version from player.
// Client -> Server -> Client
- (Map *) fetchMapForPlayer:(Player)number
{
    Map *map = nil;
    
    //NSLog (@"DGM-Player: %d", number);

    if (players[number] != nil)
    {
        //NSLog (@"Calling super.");
        //NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number); // only server knows...
        map = [super mapForPlayer:number];
    }
    else if (playerManagers[number] != nil && playersActive[number] == YES)
    {
        //NSLog (@"Server -> Client");
        //NSLog (@"player %d active? %@", number, playersActive[number] ? @"Yes" : @"No");
        //NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number); // only server knows...
        NS_DURING
            {
                map = [playerManagers[number] remoteFetchMapForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else if (master != nil)
    {
        //NSLog (@"Client -> Server");
        NS_DURING
            {
                map = [master remoteFetchMapForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
#if 0
    else
    {
        // playerManagers[number] != nil, playersActive[number] == NO, cachedMaps[number]...
        map = cachedMaps[number];
        //NSLog (@"Returning final map for player %d: %@", number, map);
    }
#endif
    return map;
}

//----------------------------------------------------------------------
// Client -> Server -> Client?
- (void) set3x3Tokens:(MapToken *)tokens aroundLocation:(EMMapLocation)target forPlayer:(Player)number
{
    struct NineTokens nineTokens;
    int l;
    
    //NSLog (@"target: %d,%d, player: %d", target.row, target.column, number);

    // Modify our local map
    [super set3x3Tokens:tokens aroundLocation:target forPlayer:number];

    if (players[number] == nil)
    {
        if (master != nil)
        {
            for (l = 0; l < 9; l++)
                nineTokens.tokens[l] = tokens[l];

            NS_DURING
                {
                    [master remoteSet3x3Tokens:nineTokens aroundLocation:target forPlayer:number];
                }
            NS_HANDLER
                {
                    EHAND;
                }
            NS_ENDHANDLER;
        }
        else if (playerManagers[number] != nil)
        {
            NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
            for (l = 0; l < 9; l++)
                nineTokens.tokens[l] = tokens[l];

            NS_DURING
                {
                    [playerManagers[number] remoteSet3x3Tokens:nineTokens aroundLocation:target forPlayer:number];
                }
            NS_HANDLER
                {
                    EHAND;
                }
            NS_ENDHANDLER;
        }
    }
}

//----------------------------------------------------------------------

// setToken: works differently from set3x3Tokens... It doens't set the local
// map, since we're cheating and taking the updated token from the local map...

- (void) setToken:(MapToken)token atLocation:(EMMapLocation)target forPlayer:(Player)number
{
    //NSLog (@"(%d,%d): %@", target.row, target.column, EMFormatComponents (token));

    if (players[number] == nil)
    {
        //NSLog (@"players[%d] not nil", number);
        
        if (master != nil)
        {
            //NSLog (@"master not nil");
            NS_DURING
                {
                    [master remoteSetToken:token atLocation:target forPlayer:number];
                }
            NS_HANDLER
                {
                    EHAND;
                }
            NS_ENDHANDLER;
        }
        else if (playerManagers[number] != nil && playersActive[number] == YES)
        {
            NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
            //NSLog (@"playerManagers[%d] not nil", number);
            NS_DURING
                {
                    [playerManagers[number] remoteSetToken:token atLocation:target forPlayer:number];
                }
            NS_HANDLER
                {
                    EHAND;
                }
            NS_ENDHANDLER;
        }
    }
    else
    {
        NSAssert1 (playersActive[number] == YES, @"Player %d is not active", number);
        [[players[number] map] setToken:token atLocation:target];
    }
}

//----------------------------------------------------------------------

- (void) remove:(Icon)icon atLocation:(EMMapLocation)target forPlayer:(Player)number
{
    [super remove:icon atLocation:target forPlayer:number];
    [self setToken:[[self mapForPlayer:number] tokenAtLocation:target] atLocation:target forPlayer:number];
}

//----------------------------------------------------------------------

- (void) put:(Icon)icon atLocation:(EMMapLocation)target forPlayer:(Player)number
{
    [super put:icon atLocation:target forPlayer:number];
    [self setToken:[[self mapForPlayer:number] tokenAtLocation:target] atLocation:target forPlayer:number];
}

//----------------------------------------------------------------------

- (void) setCityAtLocation:(EMMapLocation)target toPlayer:(Player)newCityPlayer forPlayer:(Player)number
{
    //NSLog (@"target: (%d,%d), toPlayer: %d, forPlayer: %d", target.row, target.column, newCityPlayer, number);
    [super setCityAtLocation:target toPlayer:newCityPlayer forPlayer:number];
    [self setToken:[[self mapForPlayer:number] tokenAtLocation:target] atLocation:target forPlayer:number];
}

//======================================================================
//======================================================================

- (void) remoteUpdateMapAroundCitiesForPlayer:(Player)number
{
    [self updateMapAroundCitiesForPlayer:number];
}

//----------------------------------------------------------------------

// Server -> Client
- (void) updateMapAroundCitiesForPlayer:(Player)number
{
    //NSLog (@"Player %d active? %@", number, playersActive[number] ? @"Yes" : @"No");

    if (players[number] != nil)
    {
        [players[number] updateMapAroundCities];
    }
    else if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteUpdateMapAroundCitiesForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
}

//----------------------------------------------------------------------

- (void) connectionDidDie:(NSNotification *)notification
{
    id object = [notification object];
    Player p;
    
    //NSLog (@"notification: %@", notification);

    //[self logStatus];

    NSLog (@"master connection: %@", masterConnection);
    for (p = p_player1; p <= p_player3; p++)
        NSLog (@"clientConnections[%d]: %@", p, clientConnections[p]);
    
    if (object == nil)
    {
    }
    else if (object == masterConnection)
    {
        NSRunAlertPanel (@"Game Interrupted", @"The connection to the server was lost.", nil, nil, nil);
    }
    else
    {
        for (p = p_player1; p <= p_player3; p++)
        {
            if (object == clientConnections[p])
            {
                NSRunAlertPanel (@"Game Interrupted", @"The connection to client %d was lost.", nil, nil, nil, p);
            }
        }
    }
}

//----------------------------------------------------------------------

- (void) remoteResignPlayerFromGame:(Player)number
{
    [self resignPlayerFromGame:number];
}

//----------------------------------------------------------------------

// Client -> Server
- (void) resignPlayerFromGame:(Player)number
{
    if (master != nil)
    {
        NS_DURING
            {
                [master remoteResignPlayerFromGame:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;

        [self deactivatePlayer:number];
    }
    else
    {
        [super resignPlayerFromGame:number];
    }
}

//----------------------------------------------------------------------

- (NSArray *) remoteRemainingCitiesForPlayer:(Player)number
{
    return [self remainingCitiesForPlayer:number];
}

//----------------------------------------------------------------------

// Server -> Client
- (NSArray *) remainingCitiesForPlayer:(Player)number
{
    NSArray *remainingCities = nil;

    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                remainingCities = [playerManagers[number] remoteRemainingCitiesForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        remainingCities = [super remainingCitiesForPlayer:number];
    }

    return remainingCities;
}

//----------------------------------------------------------------------
// Frees up either client or server game manager for another game...
//----------------------------------------------------------------------

- (void) stopGame
{
    [super stopGame];

    //[self logStatus];
}

//----------------------------------------------------------------------

- (void) deactivatePlayer:(Player)number
{
    [super deactivatePlayer:number];

    SNRelease (playerManagers[number]);
    SNRelease (cachedMaps[number]);

    if (clientConnections[number] != nil)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                              name:nil
                                              object:clientConnections[number]];
        clientConnections[number] = nil;
    }

    // Maybe this should be the thing to terminate when activePlayerCount == 0?
    //NSLog (@"active player count: %d", activePlayerCount);
}

//----------------------------------------------------------------------

- (void) remotePlayerHasResigned:(Player)number activePlayers:(int)activePlayers
{
    [self playerHasResigned:number activePlayers:activePlayers];
}

//----------------------------------------------------------------------

// Server -> Client
- (void) playerHasResigned:(Player)number activePlayers:(int)activePlayers
{
    NSAssert1 (number == p_neutral || playersActive[number] == YES, @"Player %d is not active", number);

    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remotePlayerHasResigned:number activePlayers:activePlayers];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super playerHasResigned:number activePlayers:activePlayers];
    }

    //[self logStatus];
}

//----------------------------------------------------------------------

- (Map *) remoteFinalMapForPlayer:(Player)number
{
    return [self finalMapForPlayer:number];
}

//----------------------------------------------------------------------

// Client -> Server
- (Map *) finalMapForPlayer:(Player)number
{
    Map *map;

    //NSLog (@"DGM-player: %d", number);

    if (master != nil)
    {
        NS_DURING
            {
                map = [master remoteFinalMapForPlayer:number];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        map = [super finalMapForPlayer:number];
    }

    return map;
}

//----------------------------------------------------------------------

- (void) remoteNotifyPlayer:(Player)number aPlayerHasResigned:(Player)resignedPlayer
{
    [self notifyPlayer:number aPlayerHasResigned:resignedPlayer];
}

//----------------------------------------------------------------------

// Server -> Client
- (void) notifyPlayer:(Player)number aPlayerHasResigned:(Player)resignedPlayer
{
    if (playerManagers[number] != nil)
    {
        NS_DURING
            {
                [playerManagers[number] remoteNotifyPlayer:number aPlayerHasResigned:resignedPlayer];
            }
        NS_HANDLER
            {
                EHAND;
            }
        NS_ENDHANDLER;
    }
    else
    {
        [super notifyPlayer:number aPlayerHasResigned:resignedPlayer];
    }
}

//----------------------------------------------------------------------

- (void) serverGameOver
{
    Player p;

    NSAssert (gameState == gs_player1_turn
              || gameState == gs_player2_turn
              || gameState == gs_player3_turn, @"Expected to be in a player turn state.");
    NSAssert (activePlayerCount == 0, @"Expected no active players.");

    SNRelease (awaitingCookie);

    attackingUnit = nil;
    defendingUnit = nil;
    defendingCity = nil;

    for (p = p_player1; p <= p_player3; p++)
    {
        SNRelease (cachedMaps[p]);
    }

    [self setGameState:gs_game_over];
}

//----------------------------------------------------------------------

- (void) clientGameOver
{
    Player p;

    AssertGameState (gs_client_active);
    NSAssert (activePlayerCount == 0, @"Expected no active players.");
    NSAssert (awaitingCookie == nil, @"Expected awaiting cookie to be nil.");
    NSAssert (master != nil, @"Expected master game manager to be set.");
    NSAssert (masterConnection != nil, @"Expected master game manager connection to be set.");

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                          name:nil
                                          object:masterConnection];
    SNRelease (master);
    masterConnection = nil;

    attackingUnit = nil;
    defendingUnit = nil;
    defendingCity = nil;

    for (p = p_player1; p <= p_player3; p++)
    {
        SNRelease (cachedMaps[p]);
    }

    [self setGameState:gs_game_over];
}

//----------------------------------------------------------------------

- (void) theGameIsOver
{
    NSAssert (gameState == gs_client_active
              || gameState == gs_player1_turn
              || gameState == gs_player2_turn
              || gameState == gs_player3_turn, @"Invalid game state.");

    [super theGameIsOver];

    if (gameState == gs_client_active)
    {
        [self clientGameOver];
    }
    else
    {
        [self serverGameOver];
    }
    
    // The game manager sticks around until the last player releases it.  Then, *poof*.
}

//----------------------------------------------------------------------

- (void) logStatus
{
    NSLog (@"----------------------------------------");
    NSLog (@"Players active:     %@ %@ %@",
           playersActive[p_player1] == YES ? @"Yes" : @"No ",
           playersActive[p_player2] == YES ? @"Yes" : @"No ",
           playersActive[p_player3] == YES ? @"Yes" : @"No "
           );
    NSLog (@"Players:            %@ %@ %@",
           players[p_player1] == nil ? @"nil" : @"set",
           players[p_player2] == nil ? @"nil" : @"set",
           players[p_player3] == nil ? @"nil" : @"set"
           );
    NSLog (@"Player managers:    %@ %@ %@",
           playerManagers[p_player1] == nil ? @"nil" : @"set",
           playerManagers[p_player2] == nil ? @"nil" : @"set",
           playerManagers[p_player3] == nil ? @"nil" : @"set"
           );
    NSLog (@"Cached maps:        %@ %@ %@",
           cachedMaps[p_player1] == nil ? @"nil" : @"set",
           cachedMaps[p_player2] == nil ? @"nil" : @"set",
           cachedMaps[p_player3] == nil ? @"nil" : @"set"
           );
    NSLog (@"Final maps:         %@ %@ %@",
           finalMaps[p_player1] == nil ? @"nil" : @"set",
           finalMaps[p_player2] == nil ? @"nil" : @"set",
           finalMaps[p_player3] == nil ? @"nil" : @"set"
           );
    NSLog (@"active player count: %d", activePlayerCount);
    NSLog (@"awaiting cookie: %@", awaitingCookie);
    NSLog (@"attacking unit: %@", attackingUnit);
    NSLog (@"defending unit: %@", defendingUnit);
    NSLog (@"defending city: %@", defendingCity);
    NSLog (@"master: %@", master == nil ? @"nil" : @"set");
    NSLog (@"master connection: %@", masterConnection == nil ? @"nil" : @"set");
    NSLog (@"Client connections: %@ %@ %@",
           clientConnections[p_player1] == nil ? @"nil" : @"set",
           clientConnections[p_player2] == nil ? @"nil" : @"set",
           clientConnections[p_player3] == nil ? @"nil" : @"set"
           );
    NSLog (@"----------------------------------------");
}

@end

// Note: Client game manager ends up with a retain count of 1 when everything has
//       been resigned and closed.  Does the extra retain have anything to do with
//       the connection not being invalidated/released?

// Hmm. Third remote player's game manager is released when the server is quit.
