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

#import "City.h"
#import "Map.h"
#import "EmPlayer.h"
#import "Unit.h"
#import "Orders.subproj/Orders.h"
#import "PathSegment.h"

static int _cityNumber = 0;
#if 1
// Production times for 50 % production efficiency.
int unit_production_times[][2] =
{
    // initial production, subsequent production
    { 0,  0    }, // u_unknown
    { 6,  5    }, // u_army
    { 12, 10   }, // u_fighter
    { 30, 25   }, // u_transport
    { 24, 20   }, // u_submarine
    { 24, 20   }, // u_destroyer
    { 42, 35   }, // u_cruiser
    { 48, 40   }, // u_carrier
    { 60, 50   }, // u_battleship
    { 24, 1000 }  // u_hovercraft
};
#else
// These are more suitable for testing...
int unit_production_times[][2] =
{
    // initial production, subsequent production
    { 0,  0    }, // u_unknown
    { 2,  1    }, // u_army
    { 2,  1000 }, // u_fighter
    { 2,  100  }, // u_transport
    { 2,  200  }, // u_submarine
    { 2,  200  }, // u_destroyer
    { 2,  350  }, // u_cruiser
    { 48, 40   }, // u_carrier
    { 60, 50   }, // u_battleship
    { 1,  1000 }  // u_hovercraft
};
#endif

//======================================================================
// Cities produce units for a player.  They may be sent across the
// wire in distributed games.
//
// The encoding needs to be revised if we want to support saved games.
//======================================================================

#define City_VERSION 1

@implementation City

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

//----------------------------------------------------------------------
// Perhaps a city shouldn't be required to know about maps and tokens...
//----------------------------------------------------------------------

- initAtLocation:(EMMapLocation)theCityLocation ofMap:(Map *)map
{
    MapToken mapTokens[9];
    int l;
    
    [super init];

    cityLocation = theCityLocation;

    // Mutable?
    cityName = [[NSString stringWithFormat:@"City #%d", _cityNumber++] retain];
    NSAssert (cityName != nil, @"City name is nil.");
  
    owner = nil;

    cityUnits = [[NSMutableArray array] retain];

    port = NO;

    // Perhaps the map should be updated first?
    for (l = 0; l < 9; l++)
        mapTokens[l] = -1;

    [map get3x3Tokens:mapTokens aroundLocation:cityLocation];
    for (l = 0; l < 9; l++)
    {
        if (mapTokens[l] != -1 && EMTerrainComponent (mapTokens[l]) == t_water)
            port = YES;
    }

    observers = [[NSMutableArray array] retain];

    flightPath = nil;

    productionType = u_unknown;
    firstUnit = YES;
    [self setProduction:u_army];

    return self;
}

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

- (void) dealloc
{
    SNRelease (cityName);
    SNRelease (cityUnits);
    SNRelease (observers);
    SNRelease (flightPath);

    [super dealloc];
}

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

- (void) encodeWithCoder:(NSCoder *)aCoder
{
    //NSAssert (owner == nil, @"Won't encode owner.");

    [super encodeWithCoder:aCoder];

    [aCoder encodeValuesOfObjCTypes:"iic", &cityLocation.row, &cityLocation.column, &port];

    [aCoder encodeObject:cityName];

    // Cities are only encoded when they've been captured -- production type, and
    // other player specific things need not be encoded.
}

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

- initWithCoder:(NSCoder *)aDecoder
{
    [super initWithCoder:aDecoder];

    [aDecoder decodeValuesOfObjCTypes:"iic", &cityLocation.row, &cityLocation.column, &port];

    cityName = [[aDecoder decodeObject] retain];

    owner = nil;

    cityUnits = [[NSMutableArray array] retain];

    observers = [[NSMutableArray array] retain];

    productionType = u_unknown;
    firstUnit = YES;
    [self setProduction:u_army];

    return self;
}

//----------------------------------------------------------------------
// We really do want cities sent bycopy.
//----------------------------------------------------------------------

- replacementObjectForPortCoder:(NSPortCoder *)encoder
{
    if ([encoder isBycopy] == YES)
        return self;

    return [super replacementObjectForPortCoder:encoder];
}

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

- (NSString *) description
{
    return [NSString stringWithFormat:@"%@: %@ at (%d,%d), owner: %@",
                     NSStringFromClass ([self class]), cityName, cityLocation.row, cityLocation.column, owner];
}

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

- (EMMapLocation) cityLocation
{
    return cityLocation;
}

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

- (BOOL) isAPort
{
    return port;
}

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

- (NSString *) cityName
{
    return cityName;
}

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

- (void) setCityName:(NSString *)aName
{
    SNRelease (cityName);

    cityName = aName;
    [cityName retain];
}

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

- (EmPlayer *) owner
{
    return owner;
}

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

- (void) setOwner:(EmPlayer *)aPlayer
{
    owner = aPlayer;
}

//----------------------------------------------------------------------
// Figures out the production efficiency based on current owner.
//----------------------------------------------------------------------

- (int) productionEfficiency
{
    if (owner == nil)
        return 50;

    return [owner productionEfficiency];
}

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

- (float) productionTimeMultiplier
{
    int pe = [self productionEfficiency];
    float r;

    // Arbitrary value to avoid division by zero.
    if (pe == 0)
        r = 100;
    else
        r = 50.0 / pe;

    return r;
}

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

- (int) turnsToConstruct:(int *)turnArray
{
    float ptm = [self productionTimeMultiplier];
    int l;

    if (turnArray != NULL)
    {
        for (l = u_army; l <= u_hovercraft; l++)
            turnArray[l - u_army] = unit_production_times[l][0] * ptm;

        if (productionType != u_unknown)
            turnArray[productionType - u_army] = turnsUntilConstructed;
    }

    return turnsUntilConstructed;
              
}

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

- (void) defaultTurnsToConstruct:(int *)turnArray
{
    float ptm = [self productionTimeMultiplier];
    int l;

    for (l = u_army; l <= u_hovercraft; l++)
        turnArray[l - u_army] = unit_production_times[l][0] * ptm;
}

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

- (NSArray *) cityUnits
{
    return cityUnits;
}

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

- (UnitType) productionType
{
    return productionType;
}

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

- (void) setProduction:(UnitType)newProductionType
{
    NSAssert (newProductionType >= u_army && newProductionType <= u_hovercraft, @"Invalid unit type.");

    if (productionType == newProductionType)
        return;

    firstUnit = YES;
    productionType = newProductionType;
    if (productionType != u_unknown)
        turnsUntilConstructed = unit_production_times[productionType][0] * [self productionTimeMultiplier];
    else
        turnsUntilConstructed = 0;

    if (owner != nil)
        [owner cityProductionChanged:self];
}

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

- (void) startOfTurn
{
    NSEnumerator *unitEnumerator;
    Unit *unit;

    unitEnumerator = [cityUnits objectEnumerator];

    while (unit = [unitEnumerator nextObject])
    {
        [unit repairDamage];
        //[unit refuel];
    }

    if (productionType == u_unknown)
    {
        [self setProduction:u_army];
    }
    else if (productionType >= u_army && productionType <= u_hovercraft)
    {
        // Produce new units...
        turnsUntilConstructed--;

        if (turnsUntilConstructed == 0)
        {
            Unit *newUnit;

            // Create unit
            newUnit = [[Unit alloc] initWithUnitType:productionType inCity:self];

            //NSLog (@"%@ produced %@", self, newUnit);

            if (productionType == u_fighter && flightPath != nil)
            {
                OMoveTo *tmpOrder = [flightPath copy];
                [tmpOrder setUnit:newUnit];
                [newUnit setOrder:tmpOrder];
                //printf ("Orders(1) for '%s' set to a flight path...\n", [newUnit unitName]);
            }

            // Add to player, city
            [owner addUnit:newUnit];
            //[self unitDidEnter:newUnit]; // done in init

            // reset production
            firstUnit = NO;
            turnsUntilConstructed = unit_production_times[productionType][1] * [self productionTimeMultiplier];
        }
    }
}

//======================================================================
// Observer Pattern
//======================================================================

- (void) attach:observer
{
    [observers addObject:observer];
}

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

- (void) detach:observer
{
    [observers removeObject:observer];
}


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

- (void) unitDidExit:(Unit *)aUnit
{
    NSEnumerator *objectEnumerator;
    id tmp;

    [cityUnits removeObject:aUnit];

    objectEnumerator = [observers objectEnumerator];
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (unitDidExit:)] == YES)
            [tmp unitDidExit:aUnit];
    }
}

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

- (void) unitDidEnter:(Unit *)aUnit
{
    NSEnumerator *objectEnumerator;
    id tmp;

    [cityUnits addObject:aUnit];

    // Send fighters that enter the city along the flight path.
    if ([aUnit unitType] == u_fighter && flightPath != nil)
    {
        OMoveTo *tmpOrder = [flightPath copy];
        [tmpOrder setUnit:aUnit];
        [aUnit setOrder:tmpOrder];
    }

    objectEnumerator = [observers objectEnumerator];
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (unitDidEnter:)] == YES)
            [tmp unitDidEnter:aUnit];
    }
}

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

- (void) cityWasLost
{
    NSEnumerator *objectEnumerator;
    id tmp;
    Unit *unit;

    // Notify all observers.  Observers don't detach -- they are
    // detached here.

    objectEnumerator = [observers objectEnumerator];
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (cityWasLost)] == YES)
            [tmp cityWasLost];
    }

    [observers removeAllObjects];

    // Now, destroy all units within city.
    unit = [cityUnits lastObject];
    while (unit != nil)
    {
        [owner destroyUnit:unit wasDisbanded:NO];
        //[cityUnits removeLastObject];
        unit = [cityUnits lastObject];
    }

    // Reset instance vars to default.

    owner = nil;
    productionType = u_unknown;
    firstUnit = YES;
    [self setProduction:u_army];
}

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

- (void) unitIconHasChanged:(Unit *)aUnit
{
    NSEnumerator *objectEnumerator;
    id tmp;

    objectEnumerator = [observers objectEnumerator];
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (unitIconHasChanged:)] == YES)
            [tmp unitIconHasChanged:aUnit];
    }
}

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

- (PathSegment *) flightPathInMapView:(MapView *)mapView
{
    PathSegment *path = nil;

    if (flightPath != nil)
    {
        path = [[PathSegment alloc] initPathFromLocation:cityLocation
                                    toLocation:[flightPath destination]
                                    in:mapView];
    }

    return path;
}

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

- (void) setFlightPath:(OMoveTo *)newFlightPath
{
    SNRelease (flightPath);

    flightPath = [newFlightPath retain];
}

@end

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

@implementation City (Extensions)

- (NSString *) attributesInspectorClassName
{
    return @"CityAttributesInspector";
}

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

- (NSString *) unitsInspectorClassName
{
    return @"CityUnitsInspector";
}

@end
