//
// $Id: EmPlayer.m,v 1.11 1997/10/31 04:51:45 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: EmPlayer.m,v 1.11 1997/10/31 04:51:45 nygard Exp $");

#import "City.h"
#import "EmPlayer.h"
#import "DistributedGameManager.h"
#import "Map.h"
#import "Unit.h"

//======================================================================
// This provides default implementations and useful utility methods
// for players.
//
// Currently, this just has what was needed to develop the Human
// player, so it may need work to make it more useful for developing
// computer players.
//======================================================================

@implementation EmPlayer

- initWithName:(NSString *)aName number:(Player)number world:(Map *)aMap
       capital:(City *)capitalCity withEfficiencies:(int)pe:(int)ce
   gameManager:(GameManager *)theGameManager
{
    EMMapLocation capitalLocation;
    int l;
  
    [super init];

    gameManager = [theGameManager retain];

    playerNumber = number;

    map = [aMap copyWithZone:[self zone]];
    [map setMapExplored:NO];

    capitalLocation = [capitalCity cityLocation];

    playerName = [aName retain];

    // update map around city.
    [map explore3x3AroundLocation:capitalLocation];

    productionEfficiency = pe; // 0..100
    combatEfficiency = ce;

    savedCookie = nil;

    cityList = [[NSMutableArray array] retain];
    NSAssert (cityList != nil, @"Could not create city array");

    unitList = [[NSMutableArray array] retain];
    NSAssert (unitList != nil, @"Could not create unit array");

    newUnitList = [[NSMutableArray array] retain];
    NSAssert (newUnitList != nil, @"Could not create new unit array");

    unitsAwaitingMovement = [[NSMutableArray array] retain];
    NSAssert (unitsAwaitingMovement != nil, @"Could not create awaiting units array");

    selectedUnit = nil;

    [self capturedCity:capitalCity];

    for (l = 0; l < UNIT_TYPE_COUNT; l++)
    {
        destroyedUnits[l] = 0;
        lostUnits[l] = 0;
    }

    finalMaps[p_neutral] = nil;
    finalMaps[p_player1] = nil;
    finalMaps[p_player2] = nil;
    finalMaps[p_player3] = nil;

    gameOver = NO;

    return self;
}

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

- (void) dealloc
{
    Player p;

    SNRelease (gameManager);
    SNRelease (map);
    SNRelease (playerName);
    SNRelease (savedCookie);

    // And all it's cities...
    SNRelease (cityList);

    // And all it's units...
    SNRelease (unitList);
    SNRelease (newUnitList);
    SNRelease (unitsAwaitingMovement);

    for (p = p_neutral; p <= p_player3; p++)
        SNRelease (finalMaps[p]);

    [super dealloc];
}

//======================================================================
// General data access
//======================================================================

- (GameManager *) gameManager
{
    return gameManager;
}

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

- (NSString *) playerName
{
    return playerName;
}

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

- (Player) playerNumber
{
    return playerNumber;
}

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

- (Map *) map
{
    return map;
}

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

- (int) productionEfficiency
{
    return productionEfficiency;
}

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

- (int) combatEfficiency
{
    return combatEfficiency;
}

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

- (NSArray *) cityList
{
    return cityList;
}

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

- (City *) cityAtLocation:(EMMapLocation)target
{
    NSEnumerator *cityEnumerator = [cityList objectEnumerator];
    EMMapLocation cityLocation;
    City *city;
    
    while (city = [cityEnumerator nextObject])
    {
        cityLocation = [city cityLocation];
        if (cityLocation.row == target.row && cityLocation.column == target.column)
            return city;
    }

    return nil;
}

//----------------------------------------------------------------------
// This returns the nearest city, ignoring terrain, for the goHome command.
//----------------------------------------------------------------------

- (City *) ourCityNearestToLocation:(EMMapLocation)target
{
    City *nearestCity = nil;
    City *city;
    NSEnumerator *cityEnumerator = [cityList objectEnumerator];
    EMMapLocation cityLocation;
    int nearestDistance = 1000000; // Square of nearest distance
    int distance, dr, dc;

    while (city = [cityEnumerator nextObject])
    {
        cityLocation = [city cityLocation];
        dr = cityLocation.row - target.row;
        dc = cityLocation.column - target.column;
        distance = dr * dr + dc * dc;

        if (distance <= nearestDistance)
        {
            nearestDistance = distance;
            nearestCity = city;
        }
    }

    return nearestCity;
}

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

- (NSArray *) unitList
{
    return unitList;
}

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

- (NSArray *) newUnitList
{
    return newUnitList;
}

//======================================================================
// Turn phases
//======================================================================

- (void) yourTurn:(int)turn withCookie:(NSNumber *)aCookie
{
    NSAssert (savedCookie == nil, @"Cookie is not nil.");
    
    savedCookie = [aCookie retain];
    
    [self yourTurn:turn];
}

//----------------------------------------------------------------------
// This is the one overridden in subclasses.
//----------------------------------------------------------------------

- (void) yourTurn:(int)turn
{
    if (turn != 0)
        [self initialPhase];
}

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

- (void) turnDone
{
    NSAssert (gameOver == NO, @"The game is over.");

    // Hopefully, this will retain the object for us, since we release
    // it ourselves.
    [gameManager performSelector:@selector (turnDone:) withObject:savedCookie afterDelay:0];

    // I found a better way of doing this while working on Risk.  With that, the
    // game manager would first change to the new state, and then execute the
    // current state.  The execution was performed delayed.  This means that
    // the player doesn't have to know about this special requirement.
    // However, at the moment the game manager code is a bit convoluted in this
    // regard.

    //[gameManager turnDone:savedCookie];

    SNRelease (savedCookie);
}

//----------------------------------------------------------------------
// This is not over-ridden in subclasses.
//----------------------------------------------------------------------

- (void) initialPhase
{
    NSEnumerator *cityEnumerator = [cityList objectEnumerator];
    NSEnumerator *unitEnumerator;
    City *city;
    Unit *unit;

    [newUnitList removeAllObjects];

    while (city = [cityEnumerator nextObject])
    {
        // This may create a new unit, and add it to the unit list.
        [city startOfTurn];
    }

    NSAssert ([unitsAwaitingMovement count] == 0, @"There were still units awaiting movement!");

    // refuel anything in a city or on board a ship
    // Reset current range of units

    unitEnumerator = [unitList objectEnumerator];
    
    while (unit = [unitEnumerator nextObject])
    {
        [unit useSkippedFuel];
        [unit resetRange];
        [unit refuel];
        [unitsAwaitingMovement addObject:unit];
    }

    selectedUnit = nil;
}

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

- (void) addUnit:(Unit *)newUnit
{
    [unitList addObject:newUnit];
    [newUnitList addObject:newUnit];
}

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

- (void) capturedCity:(City *)aCity
{
    EMMapLocation location;
    
    //tmp:
    [aCity setOwner:self];

    [cityList addObject:aCity];
    location = [aCity cityLocation];

    // Left for initial (capital) city... otherwise, should be redundant, but harmless.
    [map setCityAtLocation:location toPlayer:playerNumber];
}

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

- (void) lostCity:(City *)aCity
{
    [aCity cityWasLost];

    // Map updating?
    [cityList removeObject:aCity];
}

//----------------------------------------------------------------------
// This is required to handle the degenerate case in a distributed game
// where, for example, all three starting cities are next to each other.
//----------------------------------------------------------------------

- (void) updateMapAroundCities
{
    NSEnumerator *cityEnumerator = [cityList objectEnumerator];
    City *city;
    EMMapLocation location;

    NSAssert (gameOver == NO, @"The game is over.");

    while (city = [cityEnumerator nextObject])
    {
        location = [city cityLocation];
        [gameManager explorePlayer:playerNumber around3x3Location:location updateOtherPlayers:NO];
    }
}

//======================================================================
// Map Updating
//======================================================================

- (void) unitIconHasChanged:(Unit *)aUnit
{
    EMMapLocation unitLocation = [aUnit unitLocation];

    MapToken mapToken = [map tokenAtLocation:unitLocation];
    Icon oldIcon = EMIconComponent (mapToken);

    if (EMConvertIconToUnitType (oldIcon) == [aUnit unitType])
    {
        [map setToken:EMChangeIconComponent (mapToken, [aUnit icon]) atLocation:unitLocation];
    }
}

//----------------------------------------------------------------------
// Used by the Human player when the production is changed while in the
// Show Production state.
//----------------------------------------------------------------------

- (void) cityProductionChanged:(City *)aCity
{
}

//======================================================================
// Statistics
//======================================================================

- (WarStatistics) warStatistics
{
    WarStatistics stats;
    int l;
    int count;
    id city;
    int soonest;
    int productionType;
    UnitType unitType;

    //NSAssert (gameOver == NO, @"The game is over.");

    stats.world_city_count = [gameManager worldCityCount];
    stats.percent_explored = [map percentExplored];

    for (l = 0; l < UNIT_TYPE_COUNT; l++)
    {
        stats.destroyed_units[l] = destroyedUnits[l];
        stats.lost_units[l] = lostUnits[l];
        stats.under_construction[l] = 0;
        stats.in_combat[l] = 0;
        stats.soonest_complete[l] = 999;
    }

    stats.city_count = [cityList count];

    for (l = 0; l < stats.city_count; l++)
    {
        city = [cityList objectAtIndex:l];
        productionType = [city productionType];

        stats.under_construction[productionType]++;

        soonest = [city turnsToConstruct:NULL];
        if (soonest < stats.soonest_complete[productionType])
            stats.soonest_complete[productionType] = soonest;
    }

    count = [unitList count];

    for (l = 0; l < count; l++)
    {
        unitType = [[unitList objectAtIndex:l] unitType];
        stats.in_combat[unitType]++;
    }

    return stats;
}

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

- (int) cityCount
{
    return [cityList count];
}

//======================================================================
// End of player
//======================================================================

- (BOOL) hasPlayerLost
{
    return [cityList count] == 0;
}

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

- (void) gameStopped:(int)activePlayers
{
    //NSLog (@"player: %d", playerNumber);
    [self createFinalMaps:activePlayers];
    gameOver = YES;
}

//----------------------------------------------------------------------
// Returns YES to continue playing, NO to stop game.
//----------------------------------------------------------------------

- (BOOL) playerHasWon:(int)activePlayers
{
    [self gameStopped:activePlayers];

    return NO;
}

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

- (void) playerHasLost:(int)activePlayers
{
    [self gameStopped:activePlayers];
}

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

- (void) playerHasResigned:(int)activePlayers
{
    [self gameStopped:activePlayers];
}

//----------------------------------------------------------------------
// Perhaps final set of maps could just be sent, rather than the active players..
//----------------------------------------------------------------------

- (void) createFinalMaps:(int)activePlayers
{
    Player p;
    EMMapSize mapSize;
    int l;
    MapToken *canonMapData;
    MapToken *mapData;
    
    //NSLog (@"active players: %x", activePlayers);

    NSAssert (finalMaps[p_neutral] == nil
              && finalMaps[p_player1] == nil
              && finalMaps[p_player2] == nil
              && finalMaps[p_player3] == nil, @"Final maps have already been set.");

    NSAssert (gameOver == NO, @"The game is over.");

    // get maps
    finalMaps[playerNumber] = [map retain];

    for (p = p_player1; p <= p_player3; p++)
    {
        if (playerNumber != p)
        {
            //finalMaps[p] = [[gameManager mapForPlayer:p] copyWithZone:[self zone]];
            //finalMaps[p] = [[gameManager fetchMapForPlayer:p] copyWithZone:[self zone]];
            finalMaps[p] = [[gameManager finalMapForPlayer:p] copyWithZone:[self zone]];
            //NSLog (@"finalMaps[%d] retain count: %d", p, [finalMaps[p] retainCount]);
        }
    }

    // Build canon map:
    // 1. get terrain, set explored, and strip icons.
    finalMaps[p_neutral] = [map copyWithZone:[self zone]];
    NSAssert (finalMaps[p_neutral] != nil, @"Could not copy map.");

    [finalMaps[p_neutral] stripIcons];

    // 2. Put icons for each _ACTIVE_ player back
    mapSize = [finalMaps[p_neutral] mapSize];
    canonMapData = [finalMaps[p_neutral] mapData];

    for (p = p_player1; p <= p_player3; p++)
    {
        if (finalMaps[p] != nil && (activePlayers & (1 << (p - p_player1))) != 0)
        {
            //NSLog (@"Combining data from player %d map.", p);
            mapData = [finalMaps[p] mapData];
            for (l = 0; l < mapSize.width * mapSize.height; l++)
            {
                if (EMPlayerComponent (mapData[l]) == p)
                    canonMapData[l] = mapData[l];
            }
        }
    }

    [finalMaps[p_neutral] setMapExplored:YES];
}

//======================================================================
// Movement
//======================================================================

- (Unit *) nextUnitAwaitingMovement
{
    Unit *nextUnit;

    selectedUnit = nil;
    nextUnit = nil;

    while ([unitsAwaitingMovement count] > 0)
    {
        nextUnit = [unitsAwaitingMovement objectAtIndex:0];

        // Need to handle release of destroyed units in a better way.
        if ([nextUnit hasBeenDestroyed] == YES)
        {
            [unitsAwaitingMovement removeObjectAtIndex:0];
            [unitList removeObject:nextUnit]; // Do this in unit...
            //[currentUnit free];
            //continue;
        }
        else if ([nextUnit isFinishedMoving] == YES)
        {
            [unitsAwaitingMovement removeObjectAtIndex:0];
            //continue;
        }
        else
        {
            selectedUnit = nextUnit;
            break;
        }
    }

    return selectedUnit;
}

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

- (Unit *) selectedUnit
{
    //NSLog (@"selected unit is %@", selectedUnit);
    return selectedUnit;
}

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

- (Unit *) unitWithType:(UnitType)unitType atLocation:(EMMapLocation)target
{
    NSEnumerator *unitEnumerator = [unitList objectEnumerator];
    EMMapLocation unitLocation;
    Unit *unit;

    while (unit = [unitEnumerator nextObject])
    {
        unitLocation = [unit unitLocation];
        if (unitLocation.row == target.row && unitLocation.column == target.column && unitType == [unit unitType])
            return unit;
    }

    return nil;
}

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

- (Unit *) primaryUnitAtLocation:(EMMapLocation)target
{
    MapToken token = [map tokenAtLocation:target];
    UnitType unitType;

    unitType = EMConvertIconToUnitType (EMIconComponent (token));

    return [self unitWithType:unitType atLocation:target];
}

//======================================================================
// Commands
//======================================================================

- (MoveResult) moveSelectedUnitInDirection:(Direction)dir
{
    NSAssert (gameOver == NO, @"The game is over.");
    NSAssert (selectedUnit != nil, @"No unit selected.");

    return [gameManager moveUnit:selectedUnit inDirection:dir];
}

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

- (BOOL) moveResultDidMove:(MoveResult)moveResult
{
    return moveResult == mr_moved || moveResult == mr_victory
        || moveResult == mr_destroyed || moveResult == mr_captured_city;
}

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

- (BOOL) tryToMoveSelectedUnitInDirection:(Direction)dir
{
    MoveResult moveResult = [self moveSelectedUnitInDirection:dir];

    [self selectedUnitTriedToMove:moveResult];

    return [self moveResultDidMove:moveResult];
}

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

- (void) activateThisUnit:(Unit *)aUnit
{
    if ([aUnit currentRange] > 0)
    {
        [unitsAwaitingMovement removeObject:aUnit];
        [aUnit setUnitFinishedMoving:NO];
        [unitsAwaitingMovement insertObject:aUnit atIndex:0];
        //currentUnit = aUnit;
    }
}

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

- (void) waitUnit:(Unit *)aUnit
{
    [unitsAwaitingMovement removeObject:aUnit];
    [unitsAwaitingMovement addObject:aUnit];
}

//======================================================================
// Other
//======================================================================

- (void) destroyUnit:(Unit *)aUnit wasDisbanded:(BOOL)disbanded
{
    NSAssert ([aUnit owner] == self, @"We don't own that unit.");

    if (aUnit == selectedUnit)
        selectedUnit = nil;

    [aUnit destroyUnit:disbanded];
    if (disbanded == NO)
        lostUnits[[aUnit unitType]]++;

    [unitsAwaitingMovement removeObject:aUnit];
    [newUnitList removeObject:aUnit];
    [unitList removeObject:aUnit]; // This should be the last reference.
}

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

- (void) showExplosions:(int)count atLocation:(EMMapLocation)target
{
}

//======================================================================
// Game Resignation
//======================================================================

- (NSArray *) remainingCities
{
    NSArray *tmp = [NSArray arrayWithArray:cityList];

    //NSLog (@"remaining cities: %@", tmp);
    [cityList removeAllObjects];

    return tmp;
}

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

- (void) aPlayerHasResigned:(Player)number
{
    [map stripIconsOfPlayer:number];
}

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

- (void) resign
{
    NSAssert (gameOver == NO, @"The game is already over.");

    // This is synchronous, so that when it returns we can safely disconnect.
    [gameManager resignPlayerFromGame:playerNumber];
}

//======================================================================
// Subclass Responsibilities
//======================================================================

- (void) selectedUnitTriedToMove:(MoveResult)moveResult
{
}

@end
