/* 
 * 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>

/* so strptime gets picked up */
#define _XOPEN_SOURCE
#include <time.h>

#include "main.h"
#include "protocol.h"
#include "map.h"
#include "landvalue.h"
#include "government.h"
#include "population.h"
#include "player.h"
#include "game.h"
#include "senkenconfig.h"
#include "utils.h"
#include "game.h"
#include "companies.h"
#include "investment.h"
#include "utilities.h"
#include "traffic.h"

#define MAX_ARGS 10
#define DEFAULT_MAPSIZE 100

game_t *game = NULL;

map_t *map = NULL;
population_t *population = NULL;
companies_t *companies = NULL;
extern struct timeval game_speed;
tiles_t *tiles = NULL;
extern int g_population_change_month;
extern time_t CurrentGameTime;

static int cmd_populationstats(connection_t *conn);
static int cmd_finances(connection_t *conn);
static int cmd_setspeed(connection_t *conn, char *speedstr);
static int cmd_spotinfo(connection_t *conn, char **argv, int argc);
static int cmd_borrow(connection_t *conn, char **argv, int argc);
static int cmd_listgames(connection_t *conn);
static int cmd_stats(connection_t *conn);
static int cmd_selectgame(connection_t *conn, char **argv, int argc);
static int cmd_getgoal(connection_t *conn);
static int cmd_txn_start(connection_t *conn);
static int cmd_txn_commit(connection_t *conn);
static int cmd_txn_abort(connection_t *conn);
static int cmd_listdebts(connection_t *conn);
static int cmd_payback(connection_t *conn, char **argv, int argc);
static int cmd_settax(connection_t *conn, char **argv, int argc);
static int cmd_gettax(connection_t *conn, char **argv, int argc);
static int cmd_gethappiness(connection_t *conn);

static void listdebts_cb(player_t *player, int num, int amount, int term, int payments_made, 
			 float rate, void *rock);

typedef struct set_op_s {
    int x;
    int y;
    int connected;
    mapspot_t spot;
} set_op_t;

static void
obj_changed(map_t *map, int x, int y, 
	    mapobj_t old, mapobj_t newobj, 
	    void *rock)
{
    maptype_t type = map_item_gettype(newobj);
    switch (type) {

    case MAPTYPE_HOUSE:
    case MAPTYPE_EMPTY:
    case MAPTYPE_WATER:
    case MAPTYPE_ZONING:
    case MAPTYPE_ENTERTAINMENT:
	/* xxx */
	break;
    default:
	if (type == MAPTYPE_POWER) {
	    if (game) {
		game->power_excess += (-1 * tiles_getpoweruse(tiles, newobj));
	    }
	}
	if (type == MAPTYPE_WATERUTIL) {
	    if (game) {
		int prod = -1 * tiles_getwateruse(tiles, newobj);
		if (prod > 0) {
		    game->water_excess += prod;
		}
	    }
	}
	if ((companies) && (tiles_getnumemploy(tiles, newobj) > 0)) {
	    companies_add(companies, x, y, newobj);
	}
	break;
    }
}

static map_t *
make_map(tiles_t *tiles, player_t *player, int sizex, int sizey, int *startx, int *starty)
{
    map_t *ret;
    int r;
    const char *land = config_getstring("initial_land_type", "grassland");
    mapobj_t landobj;

    landobj = map_item_name2obj((char *)land);
    if (landobj == MAPOBJ_INVALID) {
	printf("Invalid initial land type: %s\n", land);
	landobj = map_item_emptytype();
    }

    map_init(tiles, sizex, sizey, landobj, &ret);
    map_set_obj_changed_cb(ret, &obj_changed, NULL);

    government_randomize(ret, tiles, player, startx, starty);

    r = government_initial_player_land(ret, tiles, player, startx, starty);
    if (r) {
	return NULL;
    }

    return ret;
}


/*
 * Utility function. Make into a args structure
 */
static int
make_args(char *str, char **argv, int maxargs, int *argc)
{
    int numargs = 0;

    while (str) {
	char *end;

	end = strchr(str,' ');
	if (end) {
	    *end = '\0';
	    end++;
	}

	argv[numargs++] = str;
	
	str = end;
    }

    *argc = numargs;
    argv[numargs] = NULL;

    return 0;
}

/*
 * A connection wishes to leave
 */
static int
cmd_quit(connection_t *conn)
{
    connection_write(conn, 1, "* BYE\r\n");

    connection_close(conn);

    return 0;
}

static int
showboard(connection_t *conn)
{
    int i;
    int sizex;
    int sizey;
    char *data;

    sizex = map_get_sizex(map);
    sizey = map_get_sizey(map);

    connection_write(conn, 1, "* BOARDSIZE %d %d\r\n",sizex, sizey);

    data = malloc(4 * sizex + 1);
    if (!data) return -1;

    /* give the layout of the board */
    for (i = 0; i < sizey; i++) {
	int j;

	for (j = 0; j < sizex; j++) {
	    mapspot_t spot;
	    map_get_spot(map, j, i, &spot);
	    
	    memcpy(&data[j*4],map_spot2string(&spot), 4);
	}
	data[j*4] = '\0';
	
	connection_write(conn, 0, "* BOARDLINE %d %s\r\n",i,data);
    }

    connection_write(conn, 0, "* REPOSITION %d %d\r\n", 
		     game->map_startx, game->map_starty);

    free(data);

    return 0;
}

static void *
load_tile(char *filename, int request_width, int h, int *eh, void *rock)
{
    /* return non-NULL */
    return (void *)1;
}

static int
cmd_refreshboard(connection_t *conn, char **argv, int argc)
{

    if (argc != 0) return -1;

    showboard(conn);

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static int
set_spot(connection_t *conn, int x, int y, mapspot_t *spot, int give_okno)
{
    player_t *player = connection_getplayer(conn);
    int r;

    r = map_set_spot(map, player, x, y, 0, 0, spot);
    if (r) {
	if (give_okno)
	    connection_write(conn, 1, "NO Setting map spot failed\r\n");
    } else {
	connections_send_to_all("* BOARDSPOT %d %d %s\r\n",x,y,map_spot2string(spot));
	connection_write(conn, 0, "* PLAYERINFO CASH %d\r\n",player_getmoney(player));
	if (give_okno)
	    connection_write(conn, 1, "OK\r\n");	

	if (spot->mapobj == MAPOBJ_ACTION_DEMOLISH) {
	    companies_remove(companies, x, y);
	    population_fire_everybody(population, x, y);
	    population_evict_everybody(population, x, y);
	}
    }

    return 0;
}

static int
cmd_set(connection_t *conn, char **argv, int argc)
{
    int x;
    int y;
    mapspot_t spot;

    if (argc != 3) return -1;

    x = atoi(argv[0]);
    y = atoi(argv[1]);

    map_string2spot(argv[2], &spot);

    /*
     * If transaction active just save this for later
     */
    if (connection_txn_active) {
	set_op_t *op = calloc(1, sizeof(set_op_t));
	int r;

	op->x = x;
	op->y = y;
	op->spot = spot;
	r = connection_txn_add(conn, op);

	if (r) {
	    connection_write(conn, 1, "NO Error adding operation to transaction list\r\n");
	} else {
	    connection_write(conn, 1, "OK\r\n");	
	}
	return r;

    } else {
	return set_spot(conn, x, y, &spot, 1);
    }
}

static int
cmd_login(connection_t *conn, char **argv, int argc)
{
    int playernum;
    player_t *player;
    
    if (connection_setplayer(conn, argv[0])) {
	connection_write(conn, 1, "NO Error creating player\r\n");
	return -1;
    }

    player = connection_getplayer(conn);
    playernum = player_getnum(player);

    connection_write(conn, 0, "* PLAYERINFO NUMBER %d\r\n",playernum);
    connection_write(conn, 0, "* PLAYERINFO CASH %d\r\n",player_getmoney(player));
    connection_write(conn, 1, "OK\r\n");	

    return 0;
}

/*
 * Handle a line of protocol
 */
int
protocol_handle_line(connection_t *conn, char *line)
{
    int len;
    char *argv[MAX_ARGS+1];
    int argc;
    int r = 0;

    /* remove any newline */
    len = strlen(line);
    if (line[len-1] == '\n') {
	len--;
    }
    if (line[len-1] == '\r') {
	len--;
    }
    line[len] = '\0';

    make_args(line, argv, MAX_ARGS, &argc);
   
    if (strcasecmp(argv[0],"quit")==0) {
	r = cmd_quit(conn);

    } else if (strcasecmp(argv[0],"refreshboard")==0) {
	r = cmd_refreshboard(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0],"set")==0) {
	r = cmd_set(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "login")==0) {
	r = cmd_login(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "populationstats") == 0) {
	r = cmd_populationstats(conn);

    } else if (strcasecmp(argv[0], "finances") == 0) {
	r = cmd_finances(conn);

    } else if (strcasecmp(argv[0], "setspeed") == 0) {
	r = cmd_setspeed(conn, argv[1]);

    } else if (strcasecmp(argv[0], "spotinfo") == 0) {
	r = cmd_spotinfo(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "borrow") == 0) {
	r = cmd_borrow(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "listdebts") == 0) {
	r = cmd_listdebts(conn);

    } else if (strcasecmp(argv[0], "payback") == 0) {
	r = cmd_payback(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "listgames") == 0) {
	r = cmd_listgames(conn);

    } else if (strcasecmp(argv[0], "selectgame") == 0) {
	r = cmd_selectgame(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "getgoal") == 0) {
	r = cmd_getgoal(conn);

    } else if (strcasecmp(argv[0], "gethappiness") == 0) {
	r = cmd_gethappiness(conn);

    } else if (strcasecmp(argv[0], "txn_start") == 0) {
	r = cmd_txn_start(conn);

    } else if (strcasecmp(argv[0], "txn_commit") == 0) {
	r = cmd_txn_commit(conn);

    } else if (strcasecmp(argv[0], "txn_abort") == 0) {
	r = cmd_txn_abort(conn);

    } else if (strcasecmp(argv[0], "stats") == 0) {
	r = cmd_stats(conn);

    } else if (strcasecmp(argv[0], "gettax") == 0) {
	r = cmd_gettax(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "settax") == 0) {
	r = cmd_settax(conn, &argv[1], argc-1);

    } else if (strcasecmp(argv[0], "noop") == 0) {
	connection_write(conn, 1, "OK\r\n");

    } else if (strcasecmp(argv[0], "exit") == 0) {
	exit(0); /* xxx only in debugging */
    
    } else {
	connection_write(conn, 1,"Command '%s' not understood\r\n",line);
	r = -1;
    }

    return r;
}

static int
interest_income_day(player_t *player)
{
    int cash;
    float interest;

    cash = player_getmoney(player);

    /* XXX 5% yearly interest rate */
    interest = (((float)cash) * .05)/365.;

    player_addmoney(player, INCOME_INTEREST, interest);

    return interest;
}

static void 
update_money(connection_t *conn, player_t *player, void *rock)
{
    connection_write(conn, 0, "* PLAYERINFO CASH %d\r\n",player_getmoney(player));    
}

static void
update_monies(void)
{
    connections_enumerate(&update_money, NULL);
}

static void
goal_succeeded(void)
{
    connections_send_to_all("* GAMEOVER \"Goal met!\"\r\n");
    game->active = 0;
}

static void
goal_failed(void)
{
    connections_send_to_all("* GAMEOVER \"You failed!\"\r\n");
    game->active = 0;
}

static int
all_goals_met(player_t *player)
{
    int goal_population;
    int goal_production;
    int goal_profit;
    int goal_gdp;

    if (config_getswitch("goal_justplay", 0)) {
	return 0;
    }

    /*
     * Goal population
     */ 
    goal_population = config_getint("goal_population", -1);
    if (goal_population > -1) {
	if (game->totalpop < goal_population) {
	    return 0;
	}
    }

    /*
     * Goal production
     */
    goal_production = config_getint("goal_production", -1);
    if (goal_production > -1) {
	if (population_getproduction(population) < goal_production) {
	    return 0;
	}
    }

    /*
     * Goal profit
     */
    goal_profit = config_getint("goal_profit", -1);
    if (goal_profit > -1) {
	if (player_lastmonth_profit(player) < goal_profit) {
	    return 0;
	}
    }

    /*
     * Goal GDP
     */
    goal_gdp = config_getint("goal_gdp", -1);
    if (goal_gdp > -1) {
	if (game->month_gdp < goal_gdp) {
	    return 0;
	}
    }

    return 1;
}

/*
 * Check to see if any player has reached the goals
 */
static void
check_goals(time_t curtime, player_t *player)
{
    char *datestr;
    struct tm tm;
    time_t goaltime = 0;

    if (all_goals_met(player)) {
	goal_succeeded();
	return;
    }

    /*
     * See if time has run out
     */    
    datestr = (char *)config_getstring("goal_deadline",NULL);

    if (datestr) {
	memset(&tm, 0, sizeof(struct tm));
	strptime(datestr,"%m/%d/%Y", &tm);

	goaltime = mktime(&tm);
    }

    if ((goaltime > 0) && (curtime >= goaltime)) {
	goal_failed();
	return;
    }

}

static void
year_elapsed(int initial)
{
    if (!initial) {
	players_clearout_year();
    }

    population_age1year(population, map);
}

static void
update_population(int days, int initial)
{
    g_population_change_month += population_imigrate(population, days);
    g_population_change_month -= population_exigrate(population, map, days);

    g_population_change_month += population_born(population, days);

    /*
     * New building
     */
    investment_invest(population, map, days, initial);
}

static void
week_elapsed(int initial)
{
    /*
     * Now people change their positions in life
     */

    population_movehousing(population, map);
    population_changejobs(population, map);

    if (!initial) {
	population_calculate_happiness(population);
    }
}

static void
month_elapsed(time_t curtime, int initial)
{
    int i;

    if (initial) return;

    players_clearout_month();

    /*
     * Service debt
     */
    for (i = 1; i < MAX_NUM_PLAYERS; i++) {
	if (player_isactive(i)) {
	    player_servicedebt(player_get(i));
	}
    }

    /*
     * Salaries
     */
    population_paysalaries(population, map);
    
    /* 
     * Rent income 
     */
    population_payrent(population, map);

    /*
     * Everyone has to pay upkeep
     */
    population_payupkeep(population, map);

    population_property_tax(population);

    /*
     * Business revenue
     */
    population_revenue(population, map);

    /*
     * Utilities
     */
    utilities_calculate(map, population);

    /*
     * The amount of money each player has probably changed
     */
    update_monies();

    population_patronize(population, map);

    companies_out_of_business(companies);

    for (i = 1; i < MAX_NUM_PLAYERS; i++) {
	if (player_isactive(i)) {
	    check_goals(curtime, player_get(i));
	}
    }

    population_newmonth();

    /*
     * Calculate traffic
     */
    traffic_calculate();
}

static void
day_elapsed(int initial)
{
    int i;

    /* 
     * Interest income 
     */
    if (!initial) {
	for (i = 1; i < MAX_NUM_PLAYERS; i++) {
	    if (player_isactive(i)) {
		interest_income_day(player_get(i));
	    }
	}
    }

    update_population(1, initial);
}

struct season_item_s {
    char *name;
    int def_avgtemp;
    int def_variance;
};

struct season_item_s season_table[] = {
    {"winter",   40, 20},
    {"spring",   60, 20},
    {"summer",   80, 20},
    {"fall",     60, 20},
};

struct weather_chance_s {
    char *name;
    int def_chance;
};

struct weather_chance_s weather_types[] = {
    {"rain",         15},
    {"cloudy",       15},
    {"partcloudy",   15},
    {"sunny",        55},
    {NULL, 0},
};

static void
give_weather(int mon)
{
    int season = mon % 3;
    char key[256];        
    int avgtemp;
    int variance;
    int temp;
    int i;
    char *weather;
    
    /*
     * Get the temperature
     */
    snprintf(key, sizeof(key)-1,"%s_avgtemp", season_table[season].name);
    avgtemp = config_getint(key, season_table[season].def_avgtemp);
    snprintf(key, sizeof(key)-1,"%s_tempvar", season_table[season].name);
    variance = config_getint(key, season_table[season].def_variance);

    temp = avgtemp - variance + rand()%(variance*2);

    /*
     * Find weather type
     */
    for (i = 0; ; i++) {
	int chance;

	if (weather_types[i+1].name == NULL) {
	    weather = weather_types[i].name;
	    break;	    
	}

	snprintf(key, sizeof(key)-1,"%s_chance", weather_types[i].name);
	chance = config_getint(key, weather_types[i].def_chance);

	if (rand()%100 < chance) {
	    weather = weather_types[i].name;
	    break;
	}

    }
    
    connections_send_to_all("* WEATHER %d %s\r\n", temp, weather);
}

void
pass_time(time_t curtime, int initial)
{
    struct tm *tm;

    tm = gmtime(&curtime);

    give_weather(tm->tm_mon);

    if (tm->tm_wday == 1)
	week_elapsed(initial);

    if (tm->tm_mday == 1)
	month_elapsed(curtime, initial);
    if (tm->tm_yday == 1)
	year_elapsed(initial);

    day_elapsed(initial);
}

static void
run_days(int days)
{
    time_t t = CurrentGameTime - (days * SECS_IN_DAY);
    int i;

    utilities_calculate(map, population);

    for (i = 0; i < days; i++) {
	pass_time(t, 1);

	t += SECS_IN_DAY;
    }
}

	
static int
cmd_populationstats(connection_t *conn)
{
    int i;

    if (!population) {
	connection_write(conn, 0, "NO Game hasn't started yet\r\n");
	return -1;
    }

    for (i = 0; i < MAXAGE; i++) {
	popgroup_t *group;
	int housed = 0;
	int working = 0;
	int k;
	long long tot_income = 0;
	long long tot_happy = 0;
	int avgincome = 0;
	int avghappy = 0;
	
	group = population_getgroup(population, i);
	
	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if (person->livex >= 0)
		housed++;
	    if (person->workx >= 0) {
		tot_income += companies_getsalary(companies, 
						  person->workx, person->worky, 
						  person->worklevel);
		working++;
	    }
	    
	    tot_happy += person->happy;
	}
	
	if (group->size) {
	    avgincome = (int)(tot_income/group->size);
	    avghappy =  (int)(tot_happy/group->size);
	}
	
	connection_write(conn, 0, "* POPULATION %d %d SIZE=%d INCOME=%d HOUSED=%d WORKING=%d HAPPINESS=%d\r\n",
			 i, 0, group->size, avgincome, housed, working, avghappy);
    }

    connection_write(conn, 1, "OK\r\n");	

    return 0;
}

static void
write_finance_type(connection_t *conn, player_t *player, char *name, income_types type)
{
    connection_write(conn, 0, " %s=%d,%d,%d,%d,%d",
		     name,
		     player_getincometype(player, RANGE_CURRENT_MONTH, type),
		     player_getincometype(player, RANGE_LAST_MONTH, type),
		     player_getincometype(player, RANGE_CURRENT_YEAR, type),
		     player_getincometype(player, RANGE_LAST_YEAR, type),
		     player_getincometype(player, RANGE_ALLTIME, type));
}

static int
cmd_finances(connection_t *conn)
{
    player_t *player = connection_getplayer(conn);
    int i;

    connection_write(conn, 0, "* FINANCES");

    for (i = 0; i < INCOME_NUMTYPES; i++) {

	struct balance_item *item = &balance_sheet_items[i];

	write_finance_type(conn, player, item->string, item->type);
    }
    connection_write(conn, 0, "\r\n");

    player_loans_enumerate(player, &listdebts_cb, conn);

    connection_write(conn, 1, "OK\r\n");	

    return 0;
}

static void
set_speed(int speed)
{
    switch (speed) 
	{
	case SPEED_FASTEST:
	    game_speed.tv_sec = 0;
	    game_speed.tv_usec = 500000;
	    break;
	case SPEED_FAST:
	    game_speed.tv_sec = 1;
	    game_speed.tv_usec = 0;
	    break;
	case SPEED_NORMAL:
	    game_speed.tv_sec = 1;
	    game_speed.tv_usec = 500000;
	    break;
	case SPEED_SLOW:
	    game_speed.tv_sec = 3;
	    game_speed.tv_usec = 0;
	    break;
	default:
	case SPEED_PAUSED:
	    game_speed.tv_sec = -1;
	    game_speed.tv_usec = -1;
	    break;
	    
	}
}

static int
cmd_setspeed(connection_t *conn, char *speedstr)
{
    int speed = atoi(speedstr);

    set_speed(speed);

    connection_write(conn, 1, "OK\r\n");	

    return 0;
}

static int
cmd_spotinfo(connection_t *conn, char **argv, int argc)
{
    int x;
    int y;
    int live;
    int maxlive;
    labor_t *work;
    labor_t *maxwork;
    mapobj_t obj;
    maptype_t kind;
    int owner;
    char moneystr[20];
    int power;
    int water;
    int capacity;

    if (argc != 2) return -1;

    x = atoi(argv[0]);
    y = atoi(argv[1]);

    connection_write(conn, 0, "* SPOTINFO");

    obj = map_get_type(map, x, y);
    kind = map_item_gettype(obj);

    connection_write(conn, 0, " NAME=%s", map_item_getname(obj));
    connection_write(conn, 0, " WHERE=%d,%d", x, y);

    owner = map_get_owner(map, x, y);
    connection_write(conn, 0, " OWNER=\"%s\"", player_getname(owner));
		     
    connection_write(conn, 0, " LANDVALUE=%s",
		     prettyprint_money(government_calculate_onelandvalue(map, tiles, x, y),
				       moneystr, sizeof(moneystr)-1));
    connection_write(conn, 0, " UPKEEP=%s", 
		     prettyprint_money(map_get_upkeep(map, x, y)/12,
				       moneystr, sizeof(moneystr)-1));


    maxwork = companies_maxemploy(companies, x, y);
    work = companies_employ(companies, x, y);
    if (maxwork > 0) {
	int i;

	for (i = 0; i < LABOR_NUMGROUPS; i++) {
	    if (maxwork->workers[i] > 0) {
		connection_write(conn, 0, " WORK=\"%d of %d %s\"",
				 work->workers[i], maxwork->workers[i],
				 labor_table[i].name);
	    }
	}

	connection_write(conn, 0, " LABORCOST=%s", 
			 prettyprint_money(companies_gettotalsalary(companies, x, y)/12,
					   moneystr, sizeof(moneystr)-1));
    }
    power = tiles_getpoweruse(tiles, obj);

    if (map_isflagset(map, x, y, MAPFLAG_NOPOWER)) {
	connection_write(conn, 0, " NOPOWER=yes");
    } else if (power > 0) {
	connection_write(conn, 0, " POWERUSE=%d", power);
    } else if (power < 0) {

	connection_write(conn, 0, " POWER_PRODUCTION=%d", 
			 utilities_one_power_production(x, y, obj));
    }

    water = tiles_getwateruse(tiles, obj);

    if (map_isflagset(map, x, y, MAPFLAG_NOWATER)) {
	connection_write(conn, 0, " NOWATER=yes");
    } else if (water > 0) {
	connection_write(conn, 0, " WATERUSE=%d", water);
    } else if (water < 0) {
	connection_write(conn, 0, " WATER_PRODUCTION=%d", -1 * water);
    }
    
    capacity = tiles_getcapacity(tiles, obj);
    if (capacity > 0) {
	connection_write(conn, 0, " CAPACITY=%d", capacity);
    }
	    
    switch (kind) 
	{
	case MAPTYPE_SCHOOL:
		map_live_info(map, x, y, &live, &maxlive);
		connection_write(conn, 0, " ATTENDANCE=\"%d (max %d)\"", live, maxlive);
	    break;

	case MAPTYPE_HOUSE:
	    map_live_info(map, x, y, &live, &maxlive);
	    connection_write(conn, 0, " LIVE=\"%d (max %d)\"", live, maxlive);
	    connection_write(conn, 0, " RENT=%s",
			     prettyprint_money(map_getrent(map, x, y)/12,
					       moneystr, sizeof(moneystr)-1));
	    break;

	case MAPTYPE_POWER:
	case MAPTYPE_WATERUTIL:
	case MAPTYPE_POLICE:
	case MAPTYPE_FIRE:
	case MAPTYPE_HOSPITAL:
	case MAPTYPE_ROAD:
	case MAPTYPE_WATER:
	case MAPTYPE_ZONING:
	case MAPTYPE_EMPTY:
	case MAPTYPE_LANDFILL:
	    break;

	case MAPTYPE_ENTERTAINMENT:
	case MAPTYPE_FARM:
	case MAPTYPE_COMMERCIAL:
	case MAPTYPE_OFFICE:
	case MAPTYPE_INDUSTRIAL:
	    connection_write(conn, 0, " REVENUE=%s", 
			     prettyprint_money(companies_get_revenue(companies, x, y)/12,
					       moneystr, sizeof(moneystr)-1));
	    connection_write(conn, 0, " PATRONS=%d", map_get_patrons(map, x, y));
	    connection_write(conn, 0, " LAST_PROFIT=%s", 
			     prettyprint_money(companies_get_lastprofit(companies, x, y),
					       moneystr, sizeof(moneystr)-1));
			     
	    connection_write(conn, 0, " CASH_RESERVES=%s", 
			     prettyprint_money(companies_get_cash(companies, x, y),
			     moneystr, sizeof(moneystr)-1));

	    break;
	}

    /*xxx height */

    connection_write(conn, 0, "\r\n");
    connection_write(conn, 1, "OK\r\n");	

    return 0;
}

static int
asset_owner(int owner, void *rock)
{
    int *playernump = (int *)rock;

    if (*playernump == owner) return 1;

    return 0;
}

static void
asset_count(map_t *map, int mapx, int mapy, int owner, 
		     mapobj_t obj, void *rock)
{
    int *countp = (int *)rock;

    int landvalue = government_calculate_onelandvalue(map, tiles, mapx, mapy);
    int cost = tiles_cost(tiles, obj);

    (*countp) += landvalue + cost;
}



static int
asset_value(player_t *player)
{
    int num = player_getnum(player);
    int count = 0;

    map_iterate(map, MAP_ITERATE_NORMAL, 0, 0, 0,
		NULL, NULL,
		&asset_owner, &num,
		&asset_count, &count);
    
    return count;
}

static int
player_variable_cb(char *name, void *rock)
{
    player_t *player = (player_t *)rock;

    if (strcasecmp(name, "networth") == 0) {
	return player_getmoney(player) + /* cash */
	    asset_value(player) -
	    player_total_borrowed(player);

    } else {
	printf("player variable unknown: %s\n", name);
	return 0;
    }
}

static int
cmd_borrow(connection_t *conn, char **argv, int argc)
{
    int amount;
    int debt_ceiling;
    player_t *player = connection_getplayer(conn);

    if (argc != 1) return -1;

    amount = atoi(argv[0]);

    debt_ceiling = config_getfunction_evaluate("debt_ceiling", &player_variable_cb, 
					      player, 0);

    if (player_total_borrowed(player) + amount > debt_ceiling) {
	connection_write(conn, 1, "NO Borrowing limit exceeded\r\n");
    } else {
	player_addloan(player, amount, 12*30, .07); /* xxx */
	connection_write(conn, 0, "* PLAYERINFO CASH %d\r\n",player_getmoney(player));
	connection_write(conn, 1, "OK\r\n");	
    }

    return 0;
}

static void
listdebts_cb(player_t *player, int num, 
	     int amount, int term, int payments_made, float rate, void *rock)
{
    connection_t *conn = (connection_t *)rock;

    connection_write(conn, 0, "* LOAN %d %d %d %d %f\r\n", num, amount, term, payments_made, rate);
}

static int
cmd_listdebts(connection_t *conn)
{
    player_t *player = connection_getplayer(conn);

    player_loans_enumerate(player, &listdebts_cb, conn);

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static int
cmd_payback(connection_t *conn, char **argv, int argc)
{
    player_t *player = connection_getplayer(conn);
    int loan_num;
    int r;

    if (argc != 1) {
	connection_write(conn, 1, "NO Wrong number of arguments\r\n");
	return 0;
    }

    loan_num = atoi(argv[0]);

    r = player_payoff_loan(player, loan_num);
    if (r) {
	connection_write(conn, 1, "NO Error paying off loan\r\n");    
    } else {
	connection_write(conn, 1, "OK\r\n");    
    }

    return 0;
}

static int
listgames(connection_t *conn, char *dir)
{
    DIR *dirp;
    struct dirent *dp;
    int cnt = 0;

    dirp = opendir(dir);
    if (!dirp) return 0;

    while ((dp = readdir(dirp)) != NULL) {
	int len = strlen(dp->d_name);

	if (len < 5) continue;

	if (strcasecmp(dp->d_name + len - 5, ".game") != 0) continue;

	connection_write(conn, 0, "* GAME %s\r\n", dp->d_name);
	cnt++;
    }
    (void)closedir(dirp);

    return cnt;
}

static int
cmd_listgames(connection_t *conn)
{
    if (!listgames(conn, "../games/.")) {
	if (!listgames(conn, GAMES_DIRECTORY "/.")) {
	    listgames(conn, ".");
	}
    }

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static int
cmd_selectgame(connection_t *conn, char **argv, int argc)
{
    char *name;
    int mapsizex;
    int mapsizey;
    int r;
    char *datestr;
    player_t *player = connection_getplayer(conn);
    int cash;
    char path[MAXPATHLEN+1];

    if (argc != 1) {
	connection_write(conn, 1, "NO Wrong number of arguments\r\n");
	return 0;
    }

    name = argv[0];

    snprintf(path, sizeof(path)-1,"../games/%s", name);	
    r = config_init(path);
    if (r == ENOENT) {
	snprintf(path, sizeof(path)-1,GAMES_DIRECTORY "/%s", name);
	r = config_init(path);
	if (r == ENOENT) {
	    snprintf(path, sizeof(path)-1,"%s", name);
	    r = config_init(path);
	}
    }
    if (r) {
	if (r == ENOENT) {
	    connection_write(conn, 1, "NO No game by name '%s' doesn't exist\r\n", name);
	} else {
	    connection_write(conn, 1, "NO Error loading game\r\n");
	}
	return 0;
    }

    game = calloc(1, sizeof(game_t));

    mapsizex = config_getint("mapsizex", DEFAULT_MAPSIZE);
    mapsizey = config_getint("mapsizey", DEFAULT_MAPSIZE);

    r = tiles_init(&tiles, "../img/tiles.data", &load_tile, NULL);   
    if (r) {
	r = tiles_init(&tiles, IMG_DIRECTORY "tiles.data", &load_tile, NULL);
    }
    if (r) {
	connection_write(conn, 1, "NO Error loading tiles\r\n");
	return r;
    }

    companies_init(&companies);

    map = make_map(tiles, player, mapsizex, mapsizey, 
		   &game->map_startx, &game->map_starty);
    if (!map) {
	connection_write(conn, 1, "NO Error creating map\r\n");
	return r;
    }

    /*
     * Default tax rates are natural ones
     */
    game->tax_rates[TAX_TYPE_INCOME] = config_getint("nominal_income_taxrate", 10);
    game->tax_rates[TAX_TYPE_PROPERTY] = config_getint("nominal_property_taxrate", 1);
    game->tax_rates[TAX_TYPE_BUS_INCOME] = config_getint("nominal_bus_income_taxrate", 10);
    game->tax_rates[TAX_TYPE_SALES] = config_getint("nominal_sales_taxrate", 5);

    game->min_working_age = config_getint("min_work_age", 18);

    population_init(&population);

    datestr = (char *)config_getstring("start_date","1/1/2000");
    set_time_str(datestr);

    game->active = 1;

    run_days(config_getint("initial_run_days", 0));

    game->government_player = player_getnum(player); /* xxx */
    game->month_gdp = 0;

    set_speed(SPEED_NORMAL);
    connection_write(conn, 0, "* SPEED %d\r\n", SPEED_NORMAL);

    cash = config_getint("initial_player_money", 1000000);
    player_setmoney(player, cash);
    connection_write(conn, 0, "* PLAYERINFO CASH %d\r\n",player_getmoney(player));

    connection_write(conn, 1, "OK\r\n");

    return 0;    
}

static int
cmd_getgoal(connection_t *conn)
{
    char *goal;

    goal = (char *)config_getstring("goal_description", NULL);

    if (goal) {
	connection_write(conn, 0, "* VALUE \"%s\"\r\n", goal);
	connection_write(conn, 1, "OK\r\n");
    } else {
	connection_write(conn, 1, "NO No goal description found\r\n");
    }
    
    return 0;
}

static int
cmd_gethappiness(connection_t *conn)
{
    int i;
    char buf[8096];
    char *p = buf;
    char *eob = buf + sizeof(buf) - 1;
    int total = game->totalpop;

    for (i = 0; i < NUM_REASONS; i++) {
	int reason = game->reasons_last[i];
	float percentage = ((float)(abs(reason)*100))/((float)total);
	
	if (percentage > 100.0) {
	    percentage = 100.0;
	}   

	if (reason >= 0) {
	    p += snprintf(p, eob - p, "%s - %.2f%% cool with that\n",
			  population_reason_string(i),
			  percentage);
	} else {
	    p += snprintf(p, eob - p, "%s - %.2f%% totally pissed\n",
			  population_reason_string(i), 
			  percentage);
	}
    }
        
    connection_write(conn, 0, "* VALUE {%d+}\r\n%s\r\n", strlen(buf),buf);
    connection_write(conn, 1, "OK\r\n");
    
    return 0;
}

static char*
biggest_complaint(void)
{
    int i;
    int minreason = -1;
    int minreason_num = 0;

    for (i = 0; i < NUM_REASONS; i++) {
	int reason = game->reasons_last[i];

	if (reason < minreason_num) {
	    minreason = i;
	    minreason_num = reason;
	}
    }
    
    if (minreason == -1) {
	return "No complaints";
    } else {
	return population_reason_string(minreason);
    }
}

static int
cmd_txn_start(connection_t *conn)
{
    int r;

    r = connection_txn_start(conn);
    if (r) {
	connection_write(conn, 1, "NO Failed starting transaction\r\n");
    } else {
	connection_write(conn, 1, "OK\r\n");
    }

    return 0;
}

struct can_s {
    int failed;
    int cost;

    int connect_added;
    int connect_failed;
    mapspot_list_t *connect_list;
};

static int
is_connected(int x, int y, int owner, mapspot_list_t *connect_list)
{
    if (map_is_connected(map, x, y, owner)) return 1;

    if (mapspot_is_in_list(connect_list, x - 1, y    )) return 1;
    if (mapspot_is_in_list(connect_list, x + 1, y    )) return 1;
    if (mapspot_is_in_list(connect_list, x    , y - 1)) return 1;
    if (mapspot_is_in_list(connect_list, x    , y + 1)) return 1;

    return 0;
}

static int
connects(int newowner, int x, int y, mapspot_list_t *connect_list)
{
    if (newowner == 7) return 1;

    if (newowner != 0) {
	return is_connected(x, y, newowner, connect_list);
    } else {
	return 1; /* xxx */
    }
}

static void
can_connect_op(void *opp, void *rock)
{
    struct can_s *can = (struct can_s *)rock;
    set_op_t *op = (set_op_t *)opp;

    if (op->connected) return;

    if (connects(op->spot.owner, op->x, op->y, can->connect_list)) {
	can->connect_added++;
	mapspot_list_add(&can->connect_list, op->x, op->y, NULL);
	op->connected = 1;	    
    } else {
	can->connect_failed++;
    }
}

static void
calc_cost_op(void *opp, void *rock)
{
    int *cost = (int *)rock;
    set_op_t *op = (set_op_t *)opp;

    (*cost) += map_set_spot_cost(map, op->x, op->y, &op->spot);
}

static void
do_op(void *opp, void *rock)
{
    set_op_t *op = (set_op_t *)opp;
    connection_t *conn = (connection_t *)rock;

    set_spot(conn, op->x, op->y, &op->spot, 0);
}

static int
cmd_txn_commit(connection_t *conn)
{
    struct can_s can;
    int r = 0;
    int cost = 0;
    player_t *player = connection_getplayer(conn);

    memset(&can, 0, sizeof(struct can_s));

    /*
     * Check if connecting works
     */
    if (config_getswitch("land_mustconnect", 0)) {

	do {
	    can.connect_added = 0;
	    can.connect_failed = 0;

	    connection_txn_enumerate(conn, &can_connect_op, &can);

	} while (can.connect_added > 0);

	if (can.connect_failed > 0) {
	    printf("Something doesn't connect\n");
	    goto done;
	}
    }

    /*
     * See if have enough money
     */
    connection_txn_enumerate(conn, &calc_cost_op, &cost);    
    if (!player_canafford(player, cost)) {
	printf("Can't afford %d!\n", cost);
	r = -1;
	goto done;
    }


    connection_txn_enumerate(conn, &do_op, conn);

 done:
    connection_txn_clear(conn);
    if (r) {
	connection_write(conn, 1, "NO Failed starting transaction\r\n");
    } else {
	connection_write(conn, 1, "OK\r\n");
    }
    
    return r;
}

static int
cmd_txn_abort(connection_t *conn)
{
    connection_txn_clear(conn);

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static float
getpower_percent(game_t *game)
{
    int tot = game->power_numyes + game->power_numno;

    if (tot == 0) return 100.0;

    return ((float)(game->power_numyes*100))/((float)(tot));
}

static float
getwater_percent(game_t *game)
{
    int tot = game->water_numyes + game->water_numno;

    if (tot == 0) return 100.0;

    return ((float)(game->water_numyes*100))/((float)(tot));
}

static int
cmd_stats(connection_t *conn)
{   
    char moneystr[20];

    connection_write(conn, 0, "* STAT POPULATION %d\r\n", game->totalpop);
    connection_write(conn, 0, "* STAT POP_CHANGE %d\r\n", 
		     g_population_change_month);

    connection_write(conn, 0, "* STAT AVG_HAPPY %.2f\r\n", game->avghappy);

    connection_write(conn, 0, "* STAT HOMELESS %.2f%%\r\n", 
		     population_homeless_rate(population) * 100.0);
    connection_write(conn, 0, "* STAT HOMES_AVAIL %d\r\n", 
		     game->vacancies);

    connection_write(conn, 0, "* STAT UNEMPLOYED %.2f%%\r\n", 
		     population_unemployment_rate(population) * 100.0);
    connection_write(conn, 0, "* STAT JOBS_AVAIL %d\r\n", 
		     labor_count(&game->jobsavail));

    connection_write(conn, 0, "* STAT FOOD_PRODUCTION %d\r\n", population_getproduction(population));
    connection_write(conn, 0, "* STAT GDP %s\r\n", 
		     prettyprint_money(game->month_gdp,
				       moneystr, sizeof(moneystr)-1));
    /*    connection_write(conn, 0, "* STAT INVESTMENT_FUNDS_AVAILABLE %s\r\n", 
		     prettyprint_money(game->total_to_invest,
		     moneystr, sizeof(moneystr)-1));*/
    connection_write(conn, 0, "* STAT BIGGEST_COMPLAINT \"%s\"\r\n", 
		     biggest_complaint());    
    connection_write(conn, 0, "* STAT POWERED %.1f%%\r\n",
		     getpower_percent(game));
    connection_write(conn, 0, "* STAT EXCESS_POWER %d\r\n",
		     game->power_excess);
    connection_write(conn, 0, "* STAT WATER %.1f%%\r\n",
		     getwater_percent(game));
    connection_write(conn, 0, "* STAT STORED_WATER %d\r\n",
		     game->water_excess);
    if (game->garbage_excess > 500) {
	connection_write(conn, 0, "* STAT EXCESS_GARBAGE %d\r\n",
			 game->garbage_excess);
    }

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static int
cmd_settax(connection_t *conn, char **argv, int argc)
{        
    char *name;
    int rate;
    int i;

    if (argc != 2) {
	connection_write(conn, 1, "NO Wrong number of arguments\r\n");
	return 0;
    }

    name = argv[0];    
    rate = atoi(argv[1]);

    for (i = 0; i < NUM_TAX_TYPES; i++) {
	if (strcasecmp(name, tax_types[i]) == 0) {
	    game->tax_rates[i] = rate;
	}
    }    

    connection_write(conn, 1, "OK\r\n");

    return 0;
}

static int
cmd_gettax(connection_t *conn, char **argv, int argc)
{
    int i;
    char *name;

    if (argc != 1) {
	connection_write(conn, 1, "NO Wrong number of arguments\r\n");
	return 0;
    }

    name = argv[0];    

    for (i = 0; i < NUM_TAX_TYPES; i++) {
	if (strcasecmp(name, tax_types[i]) == 0) {
	    connection_write(conn, 0, "* TAXRATE %d\r\n", game->tax_rates[i]);
	}
    }
   
    connection_write(conn, 1, "OK\r\n");

    return 0;
}
