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

#import "Map.h"

#define MAP_CLASSNAME @"Map"
#define Map_VERSION 0

//======================================================================
// The Map is central to the game.  It represents the terrain, the units
// of the player, and the last known location of the units of other
// players.  (Fog of war.)
//
// Maps are copied across the wire in distributed games, since the
// complete state of the world is derived by combining knowledge from
// each map.
//======================================================================

@implementation Map

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

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

- initMapWithSize:(EMMapSize)size;
{
    int l;

    [super init];

    mapSize = size;

    mapData = (MapToken *)malloc (sizeof (MapToken) * mapSize.width * mapSize.height);
    NSAssert (mapData != NULL, @"Error malloc()'ing map data");

    mapPointers = (MapToken **)malloc (sizeof (MapToken *) * mapSize.height);
    NSAssert (mapPointers != NULL, @"Error malloc()'ing map pointers");

    memset ((char *)mapData, 0, sizeof (MapToken) * mapSize.width * mapSize.height);

    for (l = 0; l < mapSize.height; l++)
        mapPointers[l] = &mapData[l * mapSize.width];

    observers = [[NSMutableArray array] retain];

    //NSLog (@"Map version: %d", [Map version]);

    return self;
}

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

- (void) dealloc
{
    // And notify observers?
    SNRelease (observers);

    if (mapData != NULL)
        free (mapData);

    if (mapPointers != NULL)
        free (mapPointers);

    [super dealloc];
}

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

- copyWithZone:(NSZone *)zone
{
    MapToken *newMapData;
    Map *newMap = [[Map allocWithZone:zone] initMapWithSize:mapSize];
    int l;

    newMapData = [newMap mapData];
    for (l = 0; l < mapSize.width * mapSize.height; l++)
        newMapData[l] = mapData[l];

    return newMap;
}

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

- copyAndStripIconsFromZone:(NSZone *)zone
{
    MapToken *newMapData;
    int l;
    Map *newMap = [[Map allocWithZone:zone] initMapWithSize:mapSize];

    newMapData = [newMap mapData];
    for (l = 0; l < mapSize.width * mapSize.height; l++)
    {
        if (EMIconComponent (mapData[l]) != i_none)
            newMapData[l] = mapData[l] & ~MT_ICON_MASK;
        else
            newMapData[l] = mapData[l];
    }

    return newMap;
}

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

- (void) encodeWithCoder:(NSCoder *)aCoder
{
    [super encodeWithCoder:aCoder];

    [aCoder encodeValuesOfObjCTypes:"ii", &mapSize.width, &mapSize.height];

    [aCoder encodeArrayOfObjCType:"i" count:mapSize.width * mapSize.height at:mapData];
}

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

- initWithCoder:(NSCoder *)aDecoder
{
    int l;

    [super initWithCoder:aDecoder];

    //NSLog (@"version of map in archive: %d", [aDecoder versionForClassName:MAP_CLASSNAME]);

    [aDecoder decodeValuesOfObjCTypes:"ii", &mapSize.width, &mapSize.height];

    mapData = (MapToken *)malloc (sizeof (MapToken) * mapSize.width * mapSize.height);
    NSAssert (mapData != NULL, @"Error malloc()'ing map data");
    [aDecoder decodeArrayOfObjCType:"i" count:mapSize.width * mapSize.height at:mapData];

    mapPointers = (MapToken **)malloc (sizeof (MapToken *) * mapSize.height);
    NSAssert (mapPointers != NULL, @"Error malloc()'ing map pointers");

    for (l = 0; l < mapSize.height; l++)
        mapPointers[l] = &mapData[l * mapSize.width];

    observers = [[NSMutableArray array] retain];

    return self;
}

//----------------------------------------------------------------------
// We want to support actual copying of maps, for distributed game,
// since there is very frequent use of the data.
//----------------------------------------------------------------------

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

    return [super replacementObjectForPortCoder:encoder];
}

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

- (EMMapSize) mapSize
{
    return mapSize;
}

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

- (int) percentExplored
{
    int l;
    int total = mapSize.width * mapSize.height;
    int explored = 0;

    NSAssert (total != 0, @"Total size of map is 0.");
  
    for (l = 0; l < total; l++)
        if ((mapData[l] & MT_EXPLORED_MASK) == 0)
            explored++;

    return 100 * (float)explored / total;
}

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

- (MapToken *) mapData
{
    return mapData;
}

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

- (MapToken **) mapPtrs
{
    return mapPointers;
}

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

- (MapToken) tokenAtLocation:(EMMapLocation)target
{
    if (target.row < 0 || target.column < 0
        || target.row >= mapSize.height || target.column >= mapSize.width)
    {
        return MT_EXPLORED_MASK;
    }
  
    return mapPointers[target.row][target.column];
}

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

- (void) get3x3Tokens:(MapToken *)tokens aroundLocation:(EMMapLocation)target
{
    int count = 0;
    int d_row, d_col;
    int t_row, t_col;

    for (d_row = -1; d_row <= 1; d_row++)
    {
        for (d_col = -1; d_col <= 1; d_col++)
        {
            t_row = target.row + d_row;
            t_col = target.column + d_col;

            if (t_row < 0 || t_col < 0 || t_row >= mapSize.height || t_col >= mapSize.width)
                tokens[count] = 0;
            else
                tokens[count] = mapPointers[t_row][t_col];

            count++;
        }
    }
}

//----------------------------------------------------------------------
// We need this method for when we explore a location.
//----------------------------------------------------------------------

- (void) get5x5Tokens:(MapToken *)tokens aroundLocation:(EMMapLocation)target
{
    int count = 0;
    int d_row, d_col;
    int t_row, t_col;

    for (d_row = -2; d_row <= 2; d_row++)
    {
        for (d_col = -2; d_col <= 2; d_col++)
        {
            t_row = target.row + d_row;
            t_col = target.column + d_col;

            if (t_row < 0 || t_col < 0 || t_row >= mapSize.height || t_col >= mapSize.width)
                tokens[count] = MT_EXPLORED_MASK;
            else
                tokens[count] = mapPointers[t_row][t_col];

            count++;
        }
    }
}

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

- (void) setToken:(MapToken)token atLocation:(EMMapLocation)target
{
    if (target.row >= 0 && target.column >= 0
        && target.row < mapSize.height && target.column < mapSize.width)
    {
        if (mapPointers[target.row][target.column] != token)
        {
            mapPointers[target.row][target.column] = token;
            [self refreshLocation:target];
        }
    }
}

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

- (void) set3x3Tokens:(MapToken *)tokens aroundLocation:(EMMapLocation)target
{
    MapToken original[9];
    int count = 0;
    int d_row, d_col;
    int t_row, t_col;
    BOOL changed = NO;

    [self get3x3Tokens:original aroundLocation:target];
    for (count = 0; count < 9; count++)
    {
        if (original[count] != tokens[count])
        {
            changed = YES;
            break;
        }
    }

    count = 0;
    if (changed == YES)
    {
        for (d_row = -1; d_row <= 1; d_row++)
        {
            for (d_col = -1; d_col <= 1; d_col++)
            {
                t_row = target.row + d_row;
                t_col = target.column + d_col;

                if (t_row >= 0 && t_col >= 0 && t_row < mapSize.height && t_col < mapSize.width)
                    mapPointers[t_row][t_col] = tokens[count];

                count++;
            }
        }

        [self refresh3x3Location:target];
    }
}

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

- (void) set3x3TokensTo:(MapToken)token aroundLocation:(EMMapLocation)target
{
    MapToken original[9];
    int d_row, d_col;
    int t_row, t_col;
    BOOL changed = NO;
    int l;

    [self get3x3Tokens:original aroundLocation:target];

    for (l = 0; l < 9; l++)
    {
        if (original[l] != token)
        {
            changed = YES;
            break;
        }
    }

    if (changed == YES)
    {
        for (d_row = -1; d_row <= 1; d_row++)
        {
            for (d_col = -1; d_col <= 1; d_col++)
            {
                t_row = target.row + d_row;
                t_col = target.column + d_col;

                if (t_row >= 0 && t_col >= 0 && t_row < mapSize.height && t_col < mapSize.width)
                    mapPointers[t_row][t_col] = token;
            }
        }

        [self refresh3x3Location:target];
    }
}

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

- (void) put:(Player)player:(Icon)icon atLocation:(EMMapLocation)target
{
    if (target.row >= 0 && target.column >= 0 && target.row < mapSize.height && target.column < mapSize.width)
    {
        MapToken target_token = mapPointers[target.row][target.column];
        Terrain target_terrain;
        Icon target_icon;
        Player targetPlayer;

        EMMapTokenComponents (target_token, &target_terrain, &targetPlayer, &target_icon, NULL);
    
        // Since we remove destroyed enemies from *thier* map, not ours...
        if ((target_icon == i_none || targetPlayer != player) && target_terrain != t_city)
        {
            [self setToken:EMCreateMapToken (target_terrain, player, icon, YES) atLocation:target];
        }
    }
}

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

- (void) remove:(Icon)icon atLocation:(EMMapLocation)target
{
    if (target.row >= 0 && target.column >= 0 && target.row < mapSize.height && target.column < mapSize.width)
    {
        MapToken target_token = mapPointers[target.row][target.column];
        Terrain target_terrain;
        Icon target_icon;
        UnitType target_unit_type;
        UnitType this_unit_type;

        EMMapTokenComponents (target_token, &target_terrain, NULL, &target_icon, NULL);
        this_unit_type = EMConvertIconToUnitType (icon);
        target_unit_type = EMConvertIconToUnitType (target_icon);
    
        if (this_unit_type == target_unit_type && target_terrain != t_city)
        {
            [self setToken:EMCreateMapToken (target_terrain, p_neutral, i_none, YES) atLocation:target];
        }
    }
}

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

- (void) setCityAtLocation:(EMMapLocation)target toPlayer:(Player)number
{
    if (target.row >= 0 && target.column >= 0 && target.row < mapSize.height && target.column < mapSize.width)
    {
        MapToken target_token = mapPointers[target.row][target.column];
        Terrain target_terrain;
        Icon target_icon;

        EMMapTokenComponents (target_token, &target_terrain, NULL, &target_icon, NULL);
    
        NSAssert (target_terrain == t_city, @"There is not a city at that location.");
        [self setToken:EMCreateMapToken (target_terrain, number, target_icon, YES) atLocation:target];
    }
}

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

- (void) clearMapTo:(MapToken)token
{
    int l;

    for (l = 0; l < mapSize.width * mapSize.height; l++)
        mapData[l] = token;

    [self refreshMap];
}

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

- (void) setMapExplored:(BOOL)isExplored
{
    int l;

    if (isExplored == YES)
    {
        for (l = 0; l < mapSize.width * mapSize.height; l++)
            mapData[l] &= ~MT_EXPLORED_MASK;
    }
    else
    {
        for (l = 0; l < mapSize.width * mapSize.height; l++)
            mapData[l] |= MT_EXPLORED_MASK;
    }
}

//----------------------------------------------------------------------
// Remove icons, leaving only terrain and player cities.
// To be used in preparation for production view.
//----------------------------------------------------------------------

- (void) stripIcons
{
    int l;

    for (l = 0; l < mapSize.width * mapSize.height; l++)
        if (EMIconComponent (mapData[l]) != i_none)
            mapData[l] &= ~MT_ICON_MASK;
}

//----------------------------------------------------------------------
// This is for when a player resigns, or has been destroyed.
//----------------------------------------------------------------------

- (void) stripIconsOfPlayer:(Player)number
{
    Terrain terrain;
    Player player;
    BOOL explored;
    int l;

    for (l = 0; l < mapSize.width * mapSize.height; l++)
    {
        EMMapTokenComponents (mapData[l], &terrain, &player, NULL, &explored);
        if (player == number)
            mapData[l] = EMCreateMapToken (terrain, p_neutral, i_none, explored);
    }

    [self refreshMap];

    [self strippedIconsOfPlayer:number];
}

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

- (int) countTerrainType:(Terrain)terrain
{
    int count = 0;
    int l;
    
    for (l = 0; l < mapSize.width * mapSize.height; l++)
    {
        if (EMTerrainComponent (mapData[l]) == terrain)
            count++;
    }

    return count;
}

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

- (void) explore3x3AroundLocation:(EMMapLocation)target
{
    MapToken newArea[9];
    int l;

    [self get3x3Tokens:newArea aroundLocation:target];
    for (l = 0; l < 9; l++)
        newArea[l] &= ~MT_EXPLORED_MASK;
    [self set3x3Tokens:newArea aroundLocation:target];
}

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

- (void) refreshMap
{
    NSEnumerator *objectEnumerator = [observers objectEnumerator];
    id tmp;
    
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (refreshMap)] == YES)
            [tmp refreshMap];
    }
}

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

- (void) refreshLocation:(EMMapLocation)target
{
    NSEnumerator *objectEnumerator = [observers objectEnumerator];
    id tmp;
    
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (refreshLocation:)] == YES)
            [tmp refreshLocation:target];
    }
}

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

- (void) refresh3x3Location:(EMMapLocation)target
{
    NSEnumerator *objectEnumerator = [observers objectEnumerator];
    id tmp;
    
    while (tmp = [objectEnumerator nextObject])
    {
        if ([tmp respondsToSelector:@selector (refresh3x3Location:)] == YES)
            [tmp refresh3x3Location:target];
    }
}

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

- (void) strippedIconsOfPlayer:(Player)number
{
    NSEnumerator *objectEnumerator = [observers objectEnumerator];
    id tmp;

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

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

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

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

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

@end
