/* 
 * Copyright (C) 2003 Tim Martin
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "map.h"
#include "heightmap.h"
#include "landvalue.h"
#include "mapspot.h"
#include "senkenconfig.h"

#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define MAX(x, y) (((x) > (y)) ? (x) : (y))

#define OWNER_MASK 0x07
#define FLAG_MASK 0xF8

#define MAPPOS(mapstruct, x, y) mapstruct->map[ ((y) * mapstruct->sizex) + (x)]
#define MAPOWNERPOS(mapstruct, x, y) (mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] & OWNER_MASK)
#define SETMAPOWNERPOS(mapstruct, x, y, newowner) mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] = (mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] & FLAG_MASK) | newowner

#define FLAGSPOS(mapstruct, x, y) (mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] & FLAG_MASK)

#define SETFLAGSPOS(mapstruct, x, y, newflags) mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] |= newflags

#define UNSETFLAGSPOS(mapstruct, x, y, newflags) mapstruct->ownermap[ ((y) * mapstruct->sizex) + (x)] &= ~(newflags)

#define OCCUPANCY(mapstruct, x, y) mapstruct->info[ ((y) * mapstruct->sizex) + (x)].occupancy
#define PATRONS(mapstruct, x, y) mapstruct->info[ ((y) * mapstruct->sizex) + (x)].patrons

#define ISWITHINRANGE(mapstruct, x, y) (((x < 0) || (x >= mapstruct->sizex) || (y < 0) || (y >= mapstruct->sizey)) ? 0 : 1)

typedef struct server_info_s {
    unsigned int occupancy : 8;
    unsigned int employment : 8;
    unsigned int patrons : 8;
} server_info_t;

struct map_s {
    int sizex;
    int sizey;

    heightmap_t *heightmap;
    tiles_t *tiles;

    char *special_spots;

    unsigned char *map;
    unsigned char *ownermap;

    server_info_t *info;

    map_obj_changed_callback *obj_changed_cb;
    void *obj_changed_rock;
};

extern int 
map_init(tiles_t *tiles, int sizex, int sizey, mapobj_t defaultobj, map_t **map)
{
    map_t *ret;
    int i;
    int j;
    int r;

    ret = malloc(sizeof(map_t));
    if (!ret) return -1;
    memset(ret, 0, sizeof(map_t));

    r = heightmap_init(sizex, sizey, &ret->heightmap);
    if (r) return r;

    ret->tiles = tiles;

    ret->sizex = sizex;
    ret->sizey = sizey;

    ret->map = malloc(sizeof(unsigned char)*sizex*sizey);
    if (!ret->map) return -1;

    for (i = 0; i < ret->sizex; i++) {
	for (j = 0; j < ret->sizey; j++) {
	    MAPPOS(ret, i, j) = defaultobj;
	}
    }

    ret->ownermap = malloc(sizeof(unsigned char)*sizex*sizey);
    if (!ret->ownermap) return -1;

    for (i = 0; i < ret->sizex; i++) {
	for (j = 0; j < ret->sizey; j++) {
	    SETMAPOWNERPOS(ret, i, j, NO_OWNER);
	}
    }

    ret->info = calloc(1, sizeof(server_info_t)*sizex*sizey);
    if (!ret->info) return -1;

    ret->special_spots = calloc(1, sizeof(char)*sizex*sizey);
    if (!ret->special_spots) return -1;
    
    *map = ret;

    return 0;
}

extern int 
map_getvariable_cb(char *name, map_t *map, int x, int y)
{
    if (!map) return 0;

    if (strcasecmp(name, "patrons") == 0) {
	return PATRONS(map, x, y);

    } else if (strcasecmp(name, "students") == 0) {
	return OCCUPANCY(map, x, y);	

    } else if (strcasecmp(name, "heightdiff") == 0) {
	if (heightmap_islevel_spot(map->heightmap, x, y)) {
	    return 1;
	} else {
	    return 2;
	}

    } else if (strncasecmp(name, "within-",7) == 0) {
	char *end = NULL;
       	int dist;
	mapobj_t of2;

	dist = strtoul(name+7, &end, 10);

	if ((!end) || (*end != '-')) {
	    printf(_("Error parsing within: %s\n"), name);
	    return 0;
	}

	of2 = map_item_name2obj(end+1);
	if (of2 == MAPOBJ_INVALID) {
	    printf(_("Error. Invalid mapobj [%s] in within statement\n"), end+1);
	    return 0;
	}

	return map_within(map, x, y, 
			  dist, of2,
			  MAPOWNERPOS(map, x, y));
    }


    printf(_("variable not understood: %s\n"), name);
    return 0;
}

char *map_spot2string(mapspot_t *spot)
{
    static char ret[5];

    if (spot->mapobj == 255) {
	ret[0] = '-';
    } else {
	ret[0] = spot->mapobj + '0';
    }
    if (spot->height == 255) {
	ret[1] = '-';
    } else {
	ret[1] = spot->height + '0';
    }
    if (spot->owner == 7) {
	ret[2] = '-';
    } else {
	ret[2] = spot->owner + '0';
    }
    if (spot->extra == 255) {
	ret[3] = '-';
    } else {
	ret[3] = spot->extra + '0';
    }
    ret[4] = '\0';

    return ret;
}

int map_string2spot(char *str, mapspot_t *spot)
{
    if (str[0] == '-') {
	spot->mapobj = 255;
    } else {
	spot->mapobj = str[0] - '0';
    }
    if (str[1] == '-') {
	spot->height = 255;
    } else {
	spot->height = str[1] - '0';
    }
    if (str[2] == '-') {
	spot->owner = 255;
    } else {
	spot->owner = str[2] - '0';
    }
    if (str[3] == '-') {
	spot->extra = 255;
    } else {
	spot->extra = str[3] - '0';
    }

    return 0;
}


int map_get_sizex(map_t *map)
{
    return map->sizex;
}

int map_get_sizey(map_t *map)
{
    return map->sizey;
}

static void
get_surrounding(map_t *map, int x, int y, int dirview, surrounding_t *surrounding)
{
    /* xxx should be some negative thingee */
    memset(surrounding, 0, sizeof(surrounding_t));

    if (x-1 >= 0) surrounding->index[(3-dirview+4)%4]         = MAPPOS(map, x-1, y);
    if (x+1 < map->sizex) surrounding->index[(1-dirview+4)%4] = MAPPOS(map, x+1,y);
    if (y-1 >= 0) surrounding->index[(2-dirview+4)%4]         = MAPPOS(map,x,y-1);
    if (y+1 < map->sizey) surrounding->index[(0-dirview+4)%4] = MAPPOS(map,x,y+1);
}

extern int
map_empty_land(map_t *map, int x, int y)
{
    return map_item_emptyland(MAPPOS(map, x, y));
}

static int
map_can_place_one(map_t *map, player_t *player, int x, int y, 
		  int dirview, mapobj_t obj, int *cost)
{
    heightsquare_t square;
    surrounding_t surrounding;
    tile_t *tile;
    int r;
    mapspot_list_t *list = NULL;
    maptype_t type;
    int range;

    /*
     * If not demolishing and spot already contains something can't
     * place there
     */
    switch (obj) 
	{
	case MAPOBJ_ACTION_RAISE:
	case MAPOBJ_ACTION_LOWER:
	    r = map_change_height_square(&list,
					 map, player, x, y, 
					 (obj == MAPOBJ_ACTION_RAISE) ? 1 : -1);
	    if (r) {
		return 0;
	    } else {
		*cost = mapspot_list_length(list) * 1000;
		mapspot_list_free(list);
		return 1;
	    }
	case MAPOBJ_ACTION_FLATTEN:
	    return 1; /* xxx */

	case MAPOBJ_ACTION_BUYLAND:
	    if ((map_item_emptyland(MAPPOS(map, x, y))) &&		
		(map_can_buyland(map, player, x, y))) {
    
		*cost = government_calculate_onelandvalue(map, map->tiles, 
							  x, y);
		return 1;
	    } else {
		return 0;
	    }
	case MAPOBJ_ACTION_BUYALL:
	    if (map_can_buyland(map, player, x, y)) {
		*cost = government_calculate_onelandvalue(map, map->tiles, 
							  x, y) +
		    tiles_cost(map->tiles, MAPPOS(map, x, y));
		return 1;
	    } else {
		return 0;
	    }
	case MAPOBJ_ACTION_SELLLAND:
	    if ((map_item_emptyland(MAPPOS(map, x, y))) &&
		(map_can_sellland(map, player, x, y))) {
		*cost = government_calculate_onelandvalue(map, map->tiles, 
							  x, y) * -1;
		return 1;
	    } else {
		return 0;
	    }
	case MAPOBJ_ACTION_DEMOLISH:
	    *cost = tiles_get_demolish_cost(map->tiles, MAPPOS(map, x, y));
	    return 1;
	default:
	    type = map_item_gettype(obj);
	    
	    switch(type) {
	    case MAPTYPE_FARM:
		if (!map_item_landfarmable(MAPPOS(map, x, y))) {
		    return 0;
		}
		break;
	    case MAPTYPE_WATERUTIL:
		range = tiles_getrange(map->tiles, obj);
		if ((range) && (!map_within_type(map, x, y, range, 
						 MAPTYPE_WATER, -1))) {
		    return 0;
		}
		break;
	    default:
		break;
	    }
	    
	    if (!map_item_emptyland(MAPPOS(map, x, y))) {
		return 0;
	    }
	}

    /* must own the land */
    if (player) {
	int playernum = player_getnum(player);
	
	if (MAPOWNERPOS(map,x,y) != playernum) {
	    return 0;
	}
    }

    get_surrounding(map, x, y, dirview, &surrounding);
    heightmap_getheight(map->heightmap, x, y, dirview, &square);

    /* if we can get a pixmap for it then it we can place there! */
    r = tiles_get(map->tiles, obj, &surrounding, &square, &tile);
	    
    if (r == 0) {
	*cost = tiles_cost(map->tiles, obj);
	return 1;
    } else {
	return 0;    
    }    
}

int map_can_place(map_t *map, player_t *player, int x, int y, 
		  int dirview, mapobj_t obj, int *cost)
{
    int sizex;
    int sizey;
    int i;
    int j;
    
    tiles_getsize(map->tiles, obj, &sizex, &sizey);

    for (i = x; i < x+sizex; i++) {
	for (j = y; j < y+sizey; j++) {
	    if (!map_can_place_one(map, player, i, j, dirview, obj, cost)) {
		*cost = 0;
		return 0;
	    }
	}
    }

    return 1;
}

int
map_get_owner(map_t *map, int x, int y)
{
    if ((x < 0) || (x >= map->sizex)) return -1;
    if ((y < 0) || (y >= map->sizey)) return -1;

    return MAPOWNERPOS(map,x,y);
}

extern void
map_find_tiletop(map_t *map, int *xp, int *yp)
{
    int x = *xp;
    int y = *yp;
    mapobj_t curtype;
    int sizex;
    int sizey;
    int i;
    int j;

    if (!(FLAGSPOS(map, x, y) & MAPFLAG_SUB)) return;
    
    curtype = MAPPOS(map, x, y);
    tiles_getsize(map->tiles, curtype, &sizex, &sizey);

    for (i = 0; i < sizex; i++) {
	for (j = 0; j < sizey; j++) {
	    if ((!(FLAGSPOS(map, x-i, y-j) & MAPFLAG_SUB)) &&
		(MAPPOS(map,x-i,y-j) == curtype)) {
		*xp = x-i;	
		*yp = y-j;
		return;
	    }
	}
    }

    printf(_("Can't find tile top at %d,%d\n"), x, y);
}

int
map_set_type(map_t *map, int x, int y, mapobj_t type, int force)
{
    int sizex;
    int sizey;
    int i;
    int j;
    int subflag;
    int cost;

    if (!force) {
	if (!map_can_place(map, NULL, x, y, 0, type, &cost)) {
	    /*printf("Can't place that there!\n");*/
	    return -1;
	}
    }

    if (type == MAPOBJ_ACTION_DEMOLISH) {
	mapobj_t curtype = MAPPOS(map, x, y);

	tiles_getsize(map->tiles, curtype, &sizex, &sizey);

	if ((sizex > 0) || (sizey > 0)) {
	    map_find_tiletop(map, &x, &y);
	}

	type = map_item_emptytype();
	subflag = 0;
    } else {
	tiles_getsize(map->tiles, type, &sizex, &sizey);
	subflag = MAPFLAG_SUB;
    }

    for (i = x; i < x+sizex; i++) {
	for (j = y; j < y+sizey; j++) {
	    mapobj_t old = MAPPOS(map,i,j);
	    MAPPOS(map,i,j) = type;
	    if ((i > x) || (j > y)) {
		SETFLAGSPOS(map,i,j, subflag);
	    } else {
		UNSETFLAGSPOS(map,i,j, MAPFLAG_SUB);
		if (map->obj_changed_cb) {
		    map->obj_changed_cb(map, i, j, old, type, map->obj_changed_rock);
		}
	    }
	}

    }
    
    return 0;
}

int 
map_can_change_height(map_t *map, player_t *player, int x, int y, int change)
{
    /* 
     * can only raise if all adjacent squares are empty
     */
    if (ISWITHINRANGE(map, x,y)) {
	if (!map_item_emptyland( MAPPOS(map,x  ,y  ) )) return 0;
    }
    if (ISWITHINRANGE(map, x-1,y)) {
	if (!map_item_emptyland( MAPPOS(map,x-1,y  ) )) return 0;
    }
    if (ISWITHINRANGE(map, x, y-1)) {
	if (!map_item_emptyland( MAPPOS(map,x  ,y-1) )) return 0;
    }
    if (ISWITHINRANGE(map, x-1,y-1)) {
	if (!map_item_emptyland( MAPPOS(map,x-1,y-1) )) return 0;
    }

    /*
     * Also must own all the adjacent squares
     */
    if (player) {
	int playernum = player_getnum(player);
	if (ISWITHINRANGE(map, x,y)) {
	    if (MAPOWNERPOS(map,x  ,y  ) != playernum) return 0;
	}
	if (ISWITHINRANGE(map, x-1,y)) {
	    if (MAPOWNERPOS(map,x-1,y  ) != playernum) return 0;
	}
	if (ISWITHINRANGE(map, x,y-1)) {
	    if (MAPOWNERPOS(map,x  ,y-1) != playernum) return 0;
	}
	if (ISWITHINRANGE(map, x-1,y-1)) {
	    if (MAPOWNERPOS(map,x-1,y-1) != playernum) return 0;
	}
    }
    
    /* xxx if (!heightmap_can_changeheight(map->heightmap, x, y, change)) return 0; */

    return 1;
}

static int
opp_exists(int x, int y, void *data, float dist, void *rock)
{
    if ((int)data != (int)rock) return -1;

    return 0;
}

extern int
map_change_height_list(mapspot_list_t **list, map_t *map, 
		       player_t *player,
		       int x, int y, int change)
{
    int newh;
    int i;
    int j;
    int r;

    if (change == 0) return 0;

    if (!map_can_change_height(map, player, x, y, change)) {
	goto bad;
    }

    /*
     * If already contains other direction we know we have a loop
     */
    r = mapspot_within_iterate(*list, x, y, 0.0,
			       opp_exists, (void *)change);
    if (r) return 0;


    /*
     * 8 adjacent nodes must be within MAXHEIGHTDIFFERENCE
     */
    newh = heightmap_get(map->heightmap, x, y) + 
	(mapspot_list_num_within(*list, x, y, 0) * change) + 
	change;

    for (i = -1; i <= 1; i++) {
	for (j = -1; j<= 1; j++) {

	    if ((x + i < 0) || (x + i >= map->sizex)) continue;
	    if ((y + j < 0) || (y + j >= map->sizey)) continue;

	    if ((i != 0) || (j != 0)) {
		int h= heightmap_get(map->heightmap, x + i, y + j) + 
		    (mapspot_list_num_within(*list, x + i, y + j, 0) * change);

		if (newh - h > 1) {
		    r = map_change_height_list(list, map, player, 
					       x + i, y + j, 1);
		    if (r) goto bad;
		} else if (newh - h < -1) {
		    r = map_change_height_list(list, map, player, 
					       x + i, y + j, -1);
		    if (r) goto bad;
		}
	    }
	}
    }

    mapspot_list_add(list, x, y, (void *)change);

    return 0;

 bad:
    mapspot_list_free(*list);
    *list = NULL;
    return -1;
}



void
map_change_height_which(map_t *map, int x, int y, int change, int outx[4], int outy[4])
{
    heightsquare_t square;
    int most;
    int different = 0;
    int i;

    heightmap_getheight(map->heightmap, x, y, 0, &square);

    most = square.index[0];

    /*
     * Find the new desired height
     */
    for (i = 0; i < 4; i++) {
	if (square.index[i] != most) {
	    different = 1;
	}
	if (change > 0) {
	    if (square.index[i] > most) {
		most = square.index[i];
	    }
	} else {
	    if (square.index[i] < most) {
		most = square.index[i];
	    }
	}
    }
    if (different == 0) most+=change;

    for (i = 0; i < 4; i++) {
	outx[i] = -1;
	outy[i] = -1;
    }

    if (square.dir.topleft != most) {
	outx[0] = x;
	outy[0] = y;
    }
    if (square.dir.topright != most) {
	outx[1] = x+1;
	outy[1] = y;
    }
    if (square.dir.botleft != most) {
	outx[2] = x;
	outy[2] = y+1;
    }
    if (square.dir.botright != most) {
	outx[3] = x+1;
	outy[3] = y+1;
    }
    
}

extern int
map_change_height_square(mapspot_list_t **list, map_t *map, 
			 player_t *player,
			 int x, int y, int change)
{
    int outx[4];
    int outy[4];
    int i;
    int r;

    map_change_height_which(map, x, y, change, outx, outy);
    
    for (i = 0; i < 4; i++) {
	if ((outx[i] != -1) && (outy[i] != -1)) {
	    r = map_change_height_list(list, map, player, 
				       outx[i], outy[i], change);
	    if (r) return r;
	}
    }

    return 0;
}

int 
map_can_change_height_quad(map_t *map, player_t *player, int x, int y, int change)
{
    int xc[4];
    int yc[4];
    int i;

    map_change_height_which(map,x,y,change,xc,yc);

    for (i = 0; i < 4; i++) {
	if ((xc[i] != -1) && (yc[i] != -1)) {
	    if (!map_can_change_height(map,player, xc[i],yc[i],change)) {
		return 0;
	    }
	}
    }

    return 1;
}


int 
map_can_buyland(map_t *map, player_t *player, int x, int y)
{
    int current_owner = MAPOWNERPOS(map,x,y);

    if (map_item_gettype(MAPPOS(map, x, y)) == MAPTYPE_WATER) {
	return 0;
    }
    
    return (current_owner == NO_OWNER);    
}

int
map_can_sellland(map_t *map, player_t *player, int x, int y)
{
    int current_owner = MAPOWNERPOS(map,x,y);
    int me = player_getnum(player);

    return (current_owner == me);
}

int
map_is_connected(map_t *map, int x, int y, int new_owner)
{
    if ((map_get_owner(map, x - 1, y    ) != new_owner) &&
	(map_get_owner(map, x + 1, y    ) != new_owner) && 
	(map_get_owner(map, x    , y - 1) != new_owner) &&
	(map_get_owner(map, x    , y + 1) != new_owner)) {
	return 0;
    }

    return 1;
}

int
map_set_owner(map_t *map, int new_owner, player_t *player, int x, int y, int force)
{
    int current_owner = MAPOWNERPOS(map,x,y);

    if (new_owner != current_owner) {


	if (!force) {
	    if ((current_owner != NO_OWNER) && (new_owner != NO_OWNER)) {
		printf(_("Somebody already owns %d,%d\n"),x,y);
		return -1;
	    }	    
	}

	if (map_item_gettype(MAPPOS(map, x, y)) == MAPTYPE_WATER) {
	    return -1;
	}

	if (player) {
	    int landvalue;

	    landvalue = government_calculate_onelandvalue(map, map->tiles, x, y);

	    if (!player_canafford(player, landvalue)) {
		printf(_("Can't afford that!\n"));
		return -1;
	    }
	    
	    if (new_owner == NO_OWNER) {
		player_addmoney(player, COST_LAND, landvalue);
	    } else {
		player_reducemoney(player, COST_LAND, landvalue);
	    }
	}

	SETMAPOWNERPOS(map,x,y, new_owner);
    }

    return 0;
}

int
map_set_spot_cost(map_t *map, int x, int y, mapspot_t *spot)
{
    int cost = 0;

    if (spot->height != 255) {
	cost += config_getint("land_changeheight_cost",1000);
    }

    if (spot->mapobj != 255) {
	if (spot->mapobj == MAPOBJ_ACTION_DEMOLISH) { /* is demolish */
	    cost += tiles_get_demolish_cost(map->tiles, MAPPOS(map,x,y));
	} else {
	    cost += tiles_cost(map->tiles, spot->mapobj);
	}
    }

    if (spot->owner != 7) {
	int lv = government_calculate_onelandvalue(map, map->tiles, x, y);
	if (spot->owner == NO_OWNER) {
	    cost -= lv;
	} else {
	    cost += lv;
	}
    }

    return cost;
}

int map_set_spot(map_t *map, player_t *player, int x, int y, int dirview, int isinit, mapspot_t *spot)
{
    /* first set the height */
    if (spot->height != 255) {
	int change = spot->height - heightmap_get(map->heightmap, x, y);
	int cost;

	if (change != 0) {
	    int r;

	    if (!isinit) {
		if (!map_can_change_height(map, player, x, y, change)) {
		    printf(_("Can't change height! (%d,%d, %d)\n"), x, y, change);
		    return -1;
		}
	    }

	    cost = config_getint("land_changeheight_cost",1000);

	    if (player) {
		if (!player_canafford(player, cost)) {
		    printf("Can't afford that!\n");
		    return -1;
		}
	    }

	    r = heightmap_setheight(map->heightmap, x, y, isinit, spot->height);
	    if (r) {
		printf(_("Error setting height\n"));
		return r;
	    }

	    
	    if (player) {
		/* xxx other type??? */
		player_reducemoney(player, COST_BUILD, cost);
	    }

	}
    }

    /* set the tile */
    if (spot->mapobj != 255) {
	int cost;

	if (!isinit) {
	    if (!map_can_place(map, player, x, y, dirview, spot->mapobj, &cost)) {
		/*printf("Can't place that there!\n");*/
		return -1;
	    }

	    if (player) {
		if (!player_canafford(player, cost)) {
		    printf(_("Can't afford that!\n"));
		    return -1;
		}
		
		player_reducemoney(player, COST_BUILD, cost);
	    }
	}

	map_set_type(map, x, y, spot->mapobj, 1);
    }

    /* set the owner */
    if (spot->owner != 7) {
	int r = map_set_owner(map, spot->owner, player, x, y, 0);
	if (r) return r;
    }

    /* set the extras */
    if (spot->extra != 255) {
	OCCUPANCY(map,x,y) = spot->extra;
    }

    return 0;
}

mapobj_t map_get_info(map_t *map, int x, int y, int dirview, surrounding_t *surrounding, heightsquare_t *heightsquare, int *specials, player_t *me, int *owner)
{
    /* get surrounding tiles */
    if (surrounding) {
	get_surrounding(map, x, y, dirview, surrounding);
    }

    /* get the height square */
    if (heightsquare) {
	heightmap_getheight(map->heightmap, x, y, dirview, heightsquare);
    }

    /* specials */
    if (specials) {
	*specials = 0;
	if (map->special_spots[y*map->sizex + x]) {
	    *specials = map->special_spots[y*map->sizex + x];
	}
    }

    if (owner) {
	*owner = MAPOWNERPOS(map,x,y);
    }

    return MAPPOS(map,x,y);
}

mapobj_t map_get_type(map_t *map, int x, int y)
{
    if ((x < 0) || (x >= map->sizex)) return MAPOBJ_INVALID;
    if ((y < 0) || (y >= map->sizey)) return MAPOBJ_INVALID;

    return MAPPOS(map,x,y);    
}

extern maptype_t
map_get_maptype(map_t *map, int x, int y)
{
    mapobj_t obj = map_get_type(map, x, y);

    return map_item_gettype(obj);
}

int map_get_spot(map_t *map, int x, int y, mapspot_t *spot)
{
    spot->height = heightmap_get(map->heightmap, x, y);
    spot->mapobj = MAPPOS(map,x,y);
    spot->owner = MAPOWNERPOS(map,x,y);
    spot->extra = OCCUPANCY(map,x,y);

    return 0;
}

int map_raise_spot(map_t *map, int x, int y)
{
    if (!map_can_change_height(map, NULL, x, y, 1)) return -1;

    heightmap_raise(map->heightmap, x, y);

    return 0;
}

int map_lower_spot(map_t *map, int x, int y)
{
    if (!map_can_change_height(map, NULL, x, y, -1)) return -1;

    heightmap_lower(map->heightmap, x, y);

    return 0;
}

int map_temporary_reset(map_t *map)
{
    memset(map->special_spots, 0, sizeof(char)*map->sizex*map->sizey);

    return 0;
}

int map_set_temporary(map_t *map, player_t *player, int mapx, int mapy,
		      mapobj_t obj, int special_yes, int special_no)
{
    return map_set_temporary_box(map, player, mapx, mapy, mapx, mapy, 
				 obj, special_yes, special_no);
}

static int
can_place(map_t *map, player_t *player, int x, int y, mapobj_t obj,
	  int *cost)
{
    if (!map_can_place(map, player, x, y, 0, obj, cost)) {
	return 0;
    } else {
	return 1;
    }
}

int map_set_temporary_box(map_t *map, player_t *player,
			  int x1, int y1, int x2, int y2, 
			  mapobj_t obj, 
			  int special_yes, int special_no)
{    
    int mx1, mx2, my1, my2;
    int x, y;
    int rem;
    int sizex;
    int sizey;
    int total_cost = 0;

    map_temporary_reset(map);

    tiles_getsize(map->tiles, obj, &sizex, &sizey);

    /* sort low and hi coordinates */
    mx1 = MIN(x1,x2);
    my1 = MIN(y1,y2);
    mx2 = MAX(x1,x2);
    my2 = MAX(y1,y2);

    /*
     * Make sure is divisible by sizex/sizey
     */
    rem = (mx2 - mx1+1)%sizex;
    if (rem) {
	mx2 += sizex-rem;
    }
    rem = (my2 - my1+1)%sizey;
    if (rem) {
	my2 += sizey-rem;
    }

    for (x = mx1; x <= mx2; x+=sizex) {
	for (y = my1; y <= my2; y+=sizey) {
	    int i;
	    int j;
	    int cost;
	    int can = can_place(map, player, x, y, obj, &cost);

	    total_cost += cost;
	    
	    for (i = 0; i < sizex; i++) {
		for (j = 0; j < sizey; j++) {
		    int s = can ? special_yes : special_no;

		    if ((i > 0) || (j > 0)) {
			s |= MAPSPECIAL_SUB;
		    }

		    map->special_spots[(y+j)*map->sizex + (x+i)] = s;
		}
	    }
	}
    }

    return total_cost;    
}

int map_set_temporary_line(map_t *map, player_t *player, 
			   int x1, int y1, int x2, int y2, mapobj_t obj, 
			   int special_yes, int special_no)
{
    int fromx, tox;
    int fromy, toy;
    int x, y;
    int cost;
    int total_cost = 0;

    map_temporary_reset(map);

    fromx = MIN(x1,x2);
    tox = MAX(x1,x2);

    for (x = fromx; x<= tox; x++) {
	int can = can_place(map, player, x, y1, obj, &cost);
	int s = can ? special_yes : special_no;
	total_cost += cost;

	map->special_spots[y1*map->sizex + x] = s;
    }

    fromy = MIN(y1,y2);
    toy = MAX(y1,y2);

    for (y = fromy+1; y<= toy; y++) {
	int can = can_place(map, player, x2, y, obj, &cost);
	int s = can ? special_yes : special_no;
	total_cost += cost;

	map->special_spots[y*map->sizex + x2] = s;	   
    }

    return total_cost;
}

static void
flood_fill(map_t *map, int x, int y, int special)
{
    heightsquare_t square;

    heightmap_getheight(map->heightmap, x, y, 0, &square);

    if (map->special_spots[y*map->sizex + x] == special) return;

    if (heightmap_islevel(&square) == 0) return;

    if ((x < 0) || (x >= map->sizex)) return;
    if ((y < 0) || (y >= map->sizey)) return;

    /* set this square and continue the fill */
    map->special_spots[y*map->sizex + x] = special;	   

    flood_fill(map, x+1, y  ,special);
    flood_fill(map, x-1, y  ,special);
    flood_fill(map, x  , y+1,special);
    flood_fill(map, x  , y-1,special);
}

int map_set_temporary_fill(map_t *map, int mapx, int mapy, int special)
{
    map_temporary_reset(map);

    flood_fill(map, mapx, mapy, special);        

    return 0;
}

int map_temporary_iterate(map_t *map, int mask,
			  temporary_iterate_callback *callback, void *rock)
{
    int x;
    int y;
    int r;

    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y < map->sizey; y++) {
	    int s = map->special_spots[y*map->sizex + x];
	    if ((s & mask) && (!(s & MAPSPECIAL_SUB))) {
		r = callback(map, x, y, rock);
		if (r) return r;
	    }
	}
    }

    return 0;
}

int map_getowner(map_t *map, int x, int y)
{
    return MAPOWNERPOS(map,x,y);
}

int
map_islevel(map_t *map, int x, int y)
{
    return heightmap_islevel_spot(map->heightmap, x, y);
}

int 
map_getsteepness(map_t *map, int x, int y)
{
    heightsquare_t square;
    int lowest = 99999;
    int highest = -1;
    int i;

    heightmap_getheight(map->heightmap, x, y, 0, &square);

    for (i = 0; i < 4; i++) {
	if (square.index[i] < lowest) {
	    lowest = square.index[i];
	}
	if (square.index[i] > highest) {
	    highest = square.index[i];
	}
    }

    return highest-lowest;
}

int map_is_school_full(map_t *map, int x, int y)
{
    int house = tiles_get_maxstudents(map->tiles, MAPPOS(map, x, y));

    if (!house) return 1;

    if (house - OCCUPANCY(map, x, y) > 0) {
	return 0;
    } else {
	return 1;
    }
}

int map_isvacant(map_t *map, int x, int y)
{
    int house = tiles_gethouse(map->tiles, MAPPOS(map, x, y));

    if (!house) return 0;

    if (house - OCCUPANCY(map, x, y) > 0) {
	return 1;
    }    

    return 0;
}

void 
map_live_info(map_t *map, int x, int y, int *live, int *maxlive)
{
    mapobj_t obj = MAPPOS(map, x, y);
    maptype_t type = map_item_gettype(obj);

    if (type == MAPTYPE_SCHOOL) {
	*maxlive = tiles_get_maxstudents(map->tiles, obj);
    } else {
	*maxlive = tiles_gethouse(map->tiles, obj);
    }
    *live = OCCUPANCY(map, x, y);
}

int
map_occupancy(map_t *map, int x, int y)
{
    return OCCUPANCY(map, x, y);
}

int map_rent(map_t *map, int x, int y, int *owner)
{
    if (!map_isvacant(map, x, y)) {
	printf(_("Can't rent that apt\n"));
	return -1;
    }

    OCCUPANCY(map, x, y) += 1;
    
    if (owner) *owner = MAPOWNERPOS(map, x, y);

    return 0;
}

int map_unrent(map_t *map, int x, int y)
{
    int house = tiles_gethouse(map->tiles, MAPPOS(map, x, y));

    if (!house) {
	printf(_("Can't unrent from place that doesn't rent: %d,%d\n"), x, y);
	return -1;
    }

    OCCUPANCY(map, x, y) -= 1;

    return 0;
}

int map_attend_school(map_t *map, int x, int y)
{
    if (map_is_school_full(map, x, y)) {
	return -1;
    }

    OCCUPANCY(map, x, y) += 1;
    
    return 0;
}

int map_leave_school(map_t *map, int x, int y)
{
    int house = tiles_get_maxstudents(map->tiles, MAPPOS(map, x, y));

    if (!house) {
	printf(_("Can't leave from place that isn't school: %d,%d\n"), x, y);
	return -1;
    }

    OCCUPANCY(map, x, y) -= 1;

    return 0;
}

void map_school_info(map_t *map, int x, int y, int *attend, int *maxattend)
{
    *maxattend = tiles_get_maxstudents(map->tiles, MAPPOS(map, x, y));
    *attend = OCCUPANCY(map, x, y);
}

int map_getrent(map_t *map, int mapx, int mapy)
{
    mapobj_t obj = MAPPOS(map, mapx, mapy);

    return tiles_getrent(map->tiles, obj);
}

typedef struct getvar_struct_s {
    int x;
    int y;
    map_t *map;
} getvar_struct_t;

static int
getvar_cb(char *name, void *rock)
{
    getvar_struct_t *rstruct = (getvar_struct_t *)rock;

    return map_getvariable_cb(name, rstruct->map, rstruct->x, rstruct->y);
}

void
map_upkeep_iterate(map_t *map, upkeep_iterate_callback *callback, void *rock)
{
    int x;
    int y;
    getvar_struct_t rstruct;

    rstruct.map = map;
    
    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y < map->sizey; y++) {
	    int owner = MAPOWNERPOS(map, x, y);
	    int upkeep;

	    rstruct.x = x;
	    rstruct.y = y;

	    upkeep = tiles_getupkeep(map->tiles, MAPPOS(map, x, y), 
				     &getvar_cb, &rstruct);
		
	    if (upkeep) {
		callback(map, x, y, owner, upkeep, rock);
	    }	    
	}
    }
}

int
map_get_upkeep(map_t *map, int x, int y)
{
    getvar_struct_t rstruct;
    rstruct.map = map;
    rstruct.x = x;
    rstruct.y = y;

    return tiles_getupkeep(map->tiles, MAPPOS(map, x, y),
			   &getvar_cb, &rstruct);
}

int
map_get_patrons(map_t *map, int x, int y)
{
    return PATRONS(map, x, y);
}

#if 0
void
map_revenue_iterate(map_t *map, revenue_iterate_callback *callback, void *rock)
{
    int x;
    int y;
    
    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y <= map->sizey; y++) {
	    int owner = MAPOWNERPOS(map, x, y);
	    {
		getvar_struct_t rstruct;
		int revenue;
		float food_production;
		int costs;

		rstruct.x = x;
		rstruct.y = y;
		rstruct.map = map;

		revenue = tiles_getrevenue(map->tiles, MAPPOS(map, x, y), 
					       &map_getvariable_cb, &rstruct);

		food_production = tiles_getproduction(map->tiles, MAPPOS(map, x, y), 
						      &getvariable_cb, &rstruct);

		costs = /* xxx  EMPLOYMENT(map, x, y) * SALARY + */
		    tiles_getupkeep(map->tiles, MAPPOS(map, x, y));

		if (revenue) {
		    int maxemploy = max_employ(map, x, y);
		    int employ = 0; /* xxx EMPLOYMENT(map, x, y); */
		    
		    if (maxemploy > 0) {
			revenue *= employ/maxemploy;
			food_production *= employ/maxemploy;
		    }
		    
		    callback(map, x, y, owner, revenue, costs, food_production, rock);
		}
	    }
	}
    }
}

int
map_get_revenue(map_t *map, int x, int y)
{
    getvar_struct_t rstruct;

    rstruct.x = x;
    rstruct.y = y;
    rstruct.map = map;

    return tiles_getrevenue(map->tiles, MAPPOS(map, x, y), 
			    &getvariable_cb, &rstruct);
}

#endif /* 0 */

static int
is_zoning(mapobj_t obj, void *rock)
{
    return (map_item_gettype(obj) == MAPTYPE_ZONING);
}

void
map_zoning_iterate(map_t *map, map_iterate_callback *callback, void *rock)
{
    int x = rand()%map->sizex;
    int y = rand()%map->sizey;

    map_iterate(map, MAP_ITERATE_RANDOM, x, y, 0, &is_zoning, NULL, NULL, NULL, callback, rock);
}

void
map_all_iterate(map_t *map, int startx, int starty, map_iterate_callback *callback, void *rock)
{
    map_iterate(map, MAP_ITERATE_RADIAL, startx, starty, 0, 
		NULL, NULL, NULL, NULL, callback, rock);
}

void map_player_quit(map_t *map, player_t *player)
{
    int x;
    int y;
    int num = player_getnum(player);
    
    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y <= map->sizey; y++) {
	    int owner = MAPOWNERPOS(map, x, y);
	    if (owner == num) {

		SETMAPOWNERPOS(map, x, y,NO_OWNER);
	    }
	}
    }    
}

static inline void
iter(map_t *map, int x, int y,
     map_iterate_criteria_land *land_crit, void *land_rock,
     map_iterate_criteria_owner *owner_crit, void *owner_rock,
     map_iterate_callback *cb, void *rock)
{
    mapobj_t obj = MAPPOS(map, x, y);
    int owner = MAPOWNERPOS(map, x, y);

    if ((x < 0) || (x >= map->sizex)) return;
    if ((y < 0) || (y >= map->sizey)) return;

    if (FLAGSPOS(map, x, y) & MAPFLAG_SUB) {
	return;
    }
    
    if (land_crit) {
	if (!land_crit(obj, land_rock)) {
	    return;
	}
    }

    if (owner_crit) {
	if (!owner_crit(owner, owner_rock)) {
	    return;
	}
    }

    cb(map, x, y, owner, obj, rock);
}

int 
map_iterate(map_t *map, iterate_type type, int startx, int starty, 
	    int within,
	    map_iterate_criteria_land *land_crit, void *land_rock,
	    map_iterate_criteria_owner *owner_crit, void *owner_rock,
	    map_iterate_callback *cb, void *rock)
{
    int x;
    int y;
    int radius;
    int i;

    if (type == MAP_ITERATE_WITHIN) {
	radius = within;
    } else {
	radius = map->sizex;
    }

    switch (type) {
    case MAP_ITERATE_WITHIN: /* fallthru */
    case MAP_ITERATE_RADIAL: /* fallthru */
    case MAP_ITERATE_RANDOM:
	for (i = 0; i < radius; i++) {

	    /* top + bottom */
	    for (x = startx - i; x <= startx + i; x++) {
		iter(map, x, starty - i, land_crit, land_rock, owner_crit, owner_rock, cb, rock);
		if (i > 0) 
		    iter(map, x, starty + i, land_crit, land_rock, owner_crit, owner_rock, cb, rock);
	    }

	    /* left + right */
	    if (i > 0) {
		for (y = starty - i+1; y < starty + i; y++) {
		    iter(map, startx - i, y, land_crit, land_rock, owner_crit, owner_rock, cb, rock);
		    iter(map, startx + i, y, land_crit, land_rock, owner_crit, owner_rock, cb, rock);
		}
	    }
	}
	break;

    case MAP_ITERATE_NORMAL:
	for (x = 0; x < map->sizex; x++) {
	    for (y = 0; y <= map->sizey; y++) {

		iter(map, x, y, land_crit, land_rock, owner_crit, owner_rock, cb, rock);
	    }
	}
    
	break;
    }

    return 0;
}

static void
count_vacancies(map_t *map, int mapx, int mapy, int owner, 
		mapobj_t obj, void *rock)
{
    int *count = (int *)rock;
    int house = tiles_gethouse(map->tiles, obj);
    int vacant;

    if (!house) return;

    vacant = house - OCCUPANCY(map, mapx, mapy);

    (*count) += vacant;
}

int map_vacancies(map_t *map)
{
    int count = 0;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0, NULL, NULL, NULL, NULL, &count_vacancies, &count);

    return count;
}



int
map_within(map_t *map, int sx, int sy, int dist, mapobj_t obj, int owner)
{
    int x;
    int y;

    for (x = sx-dist; x <= sx+dist; x++) {
	for (y = sy-dist; y <= sy+dist; y++) {

	    if ((x >= 0) && (x < map->sizex) &&
		(y >= 0) && (y < map->sizey)) {
		if (MAPPOS(map, x, y) == obj) {
		    if ((owner < 0) || (MAPOWNERPOS(map, x, y) == owner)) {
			return 1;
		    }
		}
	    }
	}
    }

    return 0;
}

int 
map_within_type(map_t *map, int sx, int sy, int dist, maptype_t type, int owner)
{
    int x;
    int y;

    for (x = sx-dist; x <= sx+dist; x++) {
	for (y = sy-dist; y <= sy+dist; y++) {

	    if ((x >= 0) && (x < map->sizex) &&
		(y >= 0) && (y < map->sizey)) {
		if (map_item_gettype(MAPPOS(map, x, y)) == type) {
		    if ((owner < 0) || (MAPOWNERPOS(map, x, y) == owner)) {
			return 1;
		    }
		}
	    }
	}
    }

    return 0;
}

extern float
map_density(map_t *map, int sx, int sy, int dist)
{
    int x;
    int y;
    int num = 0;
    getvar_struct_t rstruct;

    rstruct.map = map;

    for (x = sx-dist; x <= sx+dist; x++) {
	for (y = sy-dist; y <= sy+dist; y++) {

	    if ((x >= 0) && (x < map->sizex) &&
		(y >= 0) && (y < map->sizey)) {

		rstruct.x = x;
		rstruct.y = y;

		num += OCCUPANCY(map,x,y);
		num += tiles_getnumemploy(map->tiles, MAPPOS(map, x, y));
	    }

	}
    }

    return ((float)num)/((float)(dist*dist));
}

int
map_within_count(map_t *map, int sx, int sy, int dist, mapobj_t obj)
{
    int x;
    int y;
    int count = 0;

    for (x = sx-dist-1; x <= sx+dist; x++) {
	for (y = sy-dist-1; y <= sy+dist; y++) {

	    if ((x >= 0) && (x < map->sizex) &&
		(y >= 0) && (y < map->sizey)) {
		if (MAPPOS(map, x, y) == obj) {
		    count++;
		}
	    }

	}
    }

    return count;
}

int
map_within_count_type(map_t *map, int sx, int sy, int dist, maptype_t type)
{
    int x;
    int y;
    int count = 0;

    for (x = sx-dist-1; x <= sx+dist; x++) {
	for (y = sy-dist-1; y <= sy+dist; y++) {

	    if ((x >= 0) && (x < map->sizex) &&
		(y >= 0) && (y < map->sizey)) {

		if (map_item_gettype(MAPPOS(map, x, y)) == type) {
		    count++;
		}
	    }

	}
    }

    return count;
}


static void
find_vacant_list(map_t *map, int mapx, int mapy, int owner, 
		 mapobj_t obj, void *rock)
{
    mapspot_list_t **list = (mapspot_list_t **)rock;
    int house = tiles_gethouse(map->tiles, obj);
    int vacant;

    if (!house) return;

    vacant = house - OCCUPANCY(map, mapx, mapy);

    if (vacant > 0) {
	mapspot_list_add(list, mapx, mapy, NULL);
    }
}

mapspot_list_t *
map_find_vacant_list(map_t *map)
{
    mapspot_list_t *list = NULL;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0, NULL, NULL, NULL, NULL, &find_vacant_list, &list);

    return list;
}

static void
find_tile_list(map_t *map, int mapx, int mapy, int owner, 
		   mapobj_t obj, void *rock)
{
    mapspot_list_t **list = (mapspot_list_t **)rock;

    if ((FLAGSPOS(map, mapx, mapy) & MAPFLAG_SUB)) return;

    mapspot_list_add(list, mapx, mapy, (void *)obj);
}

extern int
map_is_sub(map_t *map, int mapx, int mapy)
{
    if ((FLAGSPOS(map, mapx, mapy) & MAPFLAG_SUB)) {
	return 1;
    } else {
	return 0;
    }
}

extern int
map_is_multi(map_t *map, int mapx, int mapy)
{
    int sizex;
    int sizey;
    mapobj_t obj = MAPPOS(map, mapx, mapy);
    
    tiles_getsize(map->tiles, obj, &sizex, &sizey);

    if ((sizex > 1) || (sizey > 1)) {
	return 1;
    } else {
	return 0;
    }
}

static int
is_tile(mapobj_t obj, void *rock)
{
    maptype_t lookingfor = (maptype_t)rock;

    return (map_item_gettype(obj) == lookingfor);
}

mapspot_list_t *
map_find_tile_list(map_t *map, maptype_t type)
{
    mapspot_list_t *list = NULL;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0, &is_tile, (void *)type, NULL, NULL, 
		&find_tile_list, &list);

    return list;
}

static int
is_obj(mapobj_t obj, void *rock)
{
    mapobj_t lookingfor = (mapobj_t)rock;

    return (obj == lookingfor);
}

mapspot_list_t *
map_find_tile_list_obj(map_t *map, mapobj_t obj)
{
    mapspot_list_t *list = NULL;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0, &is_obj, (void *)obj, NULL, NULL, 
		&find_tile_list, &list);

    return list;
}

static int
is_in_list(mapobj_t obj, void *rock)
{
    mapobj_t *list = (mapobj_t *)rock;
    int i;

    for (i = 0; list[i] != MAPOBJ_INVALID; i++) {
	if (list[i] == obj) return 1;
    }

    return 0;
}

mapspot_list_t *
map_find_tile_list_fromlist(map_t *map, mapobj_t *objlist)
{
    mapspot_list_t *list = NULL;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0, &is_in_list, objlist, NULL, NULL, 
		&find_tile_list, &list);

    return list;
}

int
map_patronize(map_t *map, int x, int y)
{
    int cur = PATRONS(map, x, y);
    int max = tiles_get_maxpatrons(map->tiles, MAPPOS(map, x, y));
    
    if (cur >= max) {
	return -1;
    }

    PATRONS(map, x, y) += 1;

    return 0;
}

int
map_clear_patronige(map_t *map)
{
    int x;
    int y;

    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y < map->sizey; y++) {
	    PATRONS(map, x, y) = 0;
	}
    }

    return 0;
}

extern void
map_set_obj_changed_cb(map_t *map, map_obj_changed_callback *cb, 
		       void *rock)
{
    map->obj_changed_cb = cb;
    map->obj_changed_rock = rock;
}

extern int
map_get_height(map_t *map, int x, int y)
{
    return heightmap_get(map->heightmap, x, y);
}

extern void
map_setflag(map_t *map, int x, int y, int flag)
{
    SETFLAGSPOS(map, x, y, flag);    
}

extern int
map_isflagset(map_t *map, int x, int y, int flag)
{
    if (FLAGSPOS(map, x, y) & flag) {
	return 1;
    } else {
	return 0;
    }        
}

extern void
map_clearflag_wholemap(map_t *map, int flag)
{
    int x;
    int y;

    for (x = 0; x < map->sizex; x++) {
	for (y = 0; y < map->sizey; y++) {
	    UNSETFLAGSPOS(map, x, y, flag);
	}
    }
}
