/* 
 * 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 <math.h>
#include <limits.h>

#include "utils.h"
#include "population.h"
#include "player.h"
#include "map.h"
#include "senkenconfig.h"
#include "landvalue.h"
#include "connections.h"
#include "game.h"
#include "companies.h"
#include "investment.h"
#include "vars.h"
#include "happy.h"

static short uid = 1;
extern tiles_t *tiles;
extern map_t *map;
extern game_t *game;
extern companies_t *companies;

int g_population_change_month = 0;
int g_left_month = 0;
float g_food_production_month = 0;
extern mapobj_t *entertainment_types_list;
extern mapobj_t *commercial_types_list;
extern int commercial_types_len;

typedef struct agegroup_s {
    popgroup_t group;
} agegroup_t;

struct population_s {
    agegroup_t *age[MAXAGE+1];
    
    int total_population;
};

static int find_school(person_t *person, map_t *map, mapspot_list_t **list);
static int find_house(person_t *person, map_t *map, mapspot_list_t **list);
static int find_job(person_t *person, map_t *map, mapspot_list_t **list);



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

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

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

static int
add_person(population_t *pop, int age, int edlevel, person_t *parent, 
	   person_t **newperson)
{
    popgroup_t *group = &pop->age[age]->group;
    int pnum = group->size;
    person_t *person;

    group->size++;
    pop->total_population++;

    if (group->size > group->allocsize) {
	if (group->allocsize == 0) {
	    group->allocsize = 4;
	} else {
	    group->allocsize *= 2;
	}

	group->data = realloc(group->data, group->allocsize*sizeof(person_t));
    }

    person = &group->data[pnum];

    if (parent) {
	person->uid = parent->uid;
    } else {
	person->uid = uid++;
    }
    person->livex = -1;
    person->livey = -1;
    person->workx = -1;
    person->worky = -1;
    person->happy = 100;
    person->flags = 0;
    person->edlevel = edlevel;


    /* xxx */
    person->savings = 100;
    
    if (newperson) {
	*newperson = person;
    }

    return 0;
}

static int
add_random_person(population_t *pop, int minage, int maxage, int life_expectancy)
{
    int range = maxage - minage;
    float deathrate = 50./life_expectancy * 100;
    int prob_kids;
    person_t *parent = NULL;

    while (1) {
	int age = rand()%range + minage;
	int i;
	int edlevel;

	for (i = 0; i < age; i++) {
	    if (rand()%(100*100) <= deathrate) {
		continue;
	    }
	}

	edlevel = 8 + (rand()%6 + rand()%6);

	add_person(pop, age, edlevel, NULL, &parent);

	if (age >= 18) {
	    find_house(parent, map, &game->avail_houses_list);
	    find_job(parent, map, &game->avail_job_list);
	} else {
	    find_school(parent, map, &game->schools_list);
	}
	break;
    }
    
    prob_kids = config_getint("probability_have_kids", 50);

    /* xxx doesn't handle prob_kids being > 1.0 */
    if (rand()%100 <= prob_kids) {
	int min_working_age = config_getint("min_work_age", 18);
	person_t *child = NULL;
	int age = rand() % min_working_age;

	add_person(pop, age, age - 4, parent, &child);
	parent->flags |= PERSON_FLAG_HASCHILD;

	find_school(child, map, &game->schools_list);
    }
    
    return 0;
}

static void
quit_job(map_t *map, person_t *person)
{
    if (person->workx != -1) {
	companies_quitjob(companies, person->workx, person->worky, person->worklevel);
    }
    person->workx = -1;
    person->worky = -1;
}

static void
moveout(map_t *map, person_t *person, int age)
{
    if (person->livex != -1) {
	if (age < 18) {
	    map_leave_school(map, person->livex, person->livey);
	} else {
	    map_unrent(map, person->livex, person->livey);
	}
    }
    person->livex = -1;
    person->livey = -1;
}

static void
remove_one_person(population_t *pop, map_t *map, int age, popgroup_t *group, int index)
{
    person_t *person = &group->data[index];

    quit_job(map, person);
    moveout(map, person, age);

    memmove(&group->data[index], &group->data[index+1], (group->size - index -1) * sizeof(person_t));

    group->size--;   
    pop->total_population--;    
}

extern void
population_foreach_child(population_t *pop, short uid, 
			 child_iterate_func *func, void *rock)
{
    int i;
    int min_working_age = config_getint("min_work_age", 18);

    for (i = 0; i < min_working_age; i++) {
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *child = &group->data[k];
	    
	    if (child->uid == uid) {
		func(child, rock);
	    }
	}    
    }
}

static person_t *
find_parent(population_t *pop, short uid)
{
    int i;
    
    for (i = 18; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *parent = &group->data[k];

	    if ((parent->uid == uid) && (parent->flags & PERSON_FLAG_HASCHILD)) {
		return parent;
	    }
	}    
    }

    return NULL;
}

static void
remove_children(population_t *pop, map_t *map, short uid)
{
    int i;
    int found = 0;

    for (i = 0; i < 18; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *child = &group->data[k];
	    
	    if (child->uid == uid) {
		remove_one_person(pop, map, i, group, k);
		found++;
	    }
	}    
    }

    if (!found) {
	printf(_("didn't find any children for uid %d\n"), uid);
    }
}

static void
remove_person(population_t *pop, map_t *map, int age, 
	      popgroup_t *group, int index, int andchildren)
{
    person_t *parent = &group->data[index];

    if ((andchildren) && (parent->flags & PERSON_FLAG_HASCHILD)) {
	/* find children and remove them */
	remove_children(pop, map, parent->uid);
    }

    remove_one_person(pop, map, age, group, index);
}

int population_init(population_t **pop)
{
    population_t *ret;
    int i;
    int initial_population = config_getfunction_evaluate("initial_population", 
							 &vars_population_getvariable, NULL, 0);
    int life_expectancy = config_getint("life_expectancy", 50);
    int min_working_age = config_getint("min_work_age", 18);
    int r;

    ret = calloc(1, sizeof(population_t));
    if (!ret) return -1;

    for (i = 0; i <= MAXAGE; i++) {
	agegroup_t *agegroup = calloc(1, sizeof(agegroup_t));

	ret->age[i] = agegroup;
    }

    /*
     * XXX Uniform education level
     */
    for (i = 0; i < initial_population; i++) {
	int r;

	r = add_random_person(ret, min_working_age, MAXAGE, life_expectancy);
	if (r) return r;
    }

    r = investment_init();
    if (r) return r;

    *pop = ret;
    return 0;
}



int population_imigrate(population_t *pop, int days)
{
    int num;
    int life_expectancy = config_getint("life_expectancy", 50);
    int min_working_age = config_getint("min_work_age", 18);
    int i;
    float numf;
    static float immigrate_remainer = 0.0;

    numf = ((float)config_getfunction_evaluate("immigrants_month",
					       &vars_population_getvariable, NULL, 0) * days) / ((float)DAYS_MONTH) + immigrate_remainer;
    if (numf < 0.0) numf = 0.0;
    num = numf;

    immigrate_remainer = numf - num;
	

    /*
     * XXX Uniform education level
     */
    for (i = 0; i < num; i++) {
	int r;

	r = add_random_person(pop, min_working_age, MAXAGE, life_expectancy);
	if (r) {
	    printf("Error adding random person: %d\n", r);
	}
    }    

    /*printf("%d people arrived in the last %d days\n", num, days);*/

    return num;
}

int
population_exigrate(population_t *pop, map_t *map, int days)
{
    int i;
    int min_working_age = config_getint("min_work_age", 18);
    int left = 0;
    int totalhappy = 0;
    int totalpop = 0;

    game->totalkids = 0;

    /* just count the young'ins */
    for (i = 0; i < min_working_age; i++) {
	int k;
	popgroup_t *group = &pop->age[i]->group;

	totalpop += group->size;
	game->totalkids += group->size;

	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    totalhappy += person->happy;
	}	
    }

    for (i = min_working_age; i < MAXAGE; i++) {
	popgroup_t *group = &pop->age[i]->group;
	int k;
	
	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    int remove = 0;
	    if (person->happy <= 50) {
		/*printf("happiness too low (%d). person leaving\n", person->happy);*/
		remove = 1;
	    } else if (person->savings < 0) {
		/*printf("savings too low (%d). person leaving\n", person->savings);*/
		remove = 1;
	    }
	    
	    if (remove) {
		/* xxx doesn't count kids leaving */
		remove_person(pop, map, i, group, k, 1); 
		k--;
		left++;
		continue;
	    }
	    
	    totalhappy += person->happy;
	    totalpop++;
	}
	
    }

    game->totalpop = totalpop;

    g_left_month += left;

    /*printf("%d people left in the last %d days\n", left, days);*/
    return left;
}

int
population_getproduction(population_t *pop)
{
    return g_food_production_month;
}

float
population_homeless_rate(population_t *pop)
{
    if (game->totalpop) {
	return ((float)game->homeless) / ((float)game->totalpop);
    } else {
	return 0.0;
    }
}

float
population_unemployment_rate(population_t *pop)
{
    if (game->totalpop) {
	return ((float)labor_count(&game->needjobs)) / ((float)game->totalpop);
    } else {
	return 0.0;
    }
}

int population_age1year(population_t *pop, map_t *map)
{
    int i;
    int min_working_age = config_getint("min_work_age", 18);

    /*
     * Everyone MAXAGE old dies
     */
    while (pop->age[MAXAGE]->group.size > 0) {
	remove_person(pop, map, MAXAGE, &pop->age[MAXAGE]->group, 0, 0); 
	/* xxx remove last person */
    }

    /*
     * Everyone gets 1 year older
     */
    for (i = MAXAGE; i >= 1; i--) {
	pop->age[i] = pop->age[i-1];

	/*
	 * People going to school get smarter
	 */
	if (i <= min_working_age) {
	    int k;

	    for (k = 0; k < pop->age[i]->group.size; k++) {	    
		person_t *person = &pop->age[i]->group.data[k];

		if (person->livex != -1) {
		    person->edlevel++;
		}
	    }
	}

	/*
	 * when turn 18 no longer dependent on parents
	 *
	 * also no longer go to school
	 */
	if (i == min_working_age) {
	    int k;

	    for (k = 0; k < pop->age[i]->group.size; k++) {
		person_t *child = &pop->age[i]->group.data[k];
		person_t *parent;
		
		/* find parent and unlink them */
		parent = find_parent(pop, child->uid);
		if (parent) {
		    parent->flags &= ~PERSON_FLAG_HASCHILD;
		    parent->uid = 0;
		}
		child->uid = 0;
		
		/* leave school. find house */
		if (child->livex != -1) {
		    map_leave_school(map, child->livex, child->livey);
		}
		
		find_house(child, map, &game->avail_houses_list);
		find_job(child, map, &game->avail_job_list);
		}
	}
    }	    

    pop->age[0] = calloc(1, sizeof(agegroup_t));

    /*
     * XXX nobody dies
     */

    return 0;
}

int population_born(population_t *pop, int days)
{
    int i;
    int birth_rate = config_getint("birth_rate", 0);
    int born = 0;

    /*
     * XXX everyone 20-40 has equal chance of giving birth
     *
     */
    for (i = 20; i < 40; i++) {
	int k;
	
	for (k = 0; k < pop->age[i]->group.size; k++) {
	    if (rand()%(12*1000*DAYS_MONTH/days) <= birth_rate) {
		person_t *parent = &pop->age[i]->group.data[k];
		
		parent->flags |= PERSON_FLAG_HASCHILD;
		add_person(pop, 0, 0, parent, NULL);
		born++;
	    }
	}    
    }

    return born;
}

static int
house_is_suitable(int mapx, int mapy, void *data, float dist, void *rock)
{
    person_t *person = (person_t *)rock;
    int r;
    int owner;

    r = map_rent(map, mapx, mapy,&owner);
    if (r) {
	printf(_("Error renting %d,%d\n"), mapx, mapy);
	return 0;
    }

    person->livex = mapx;
    person->livey = mapy;

    happy_upto(person, 100, 5, REASON_FIND_HOUSING);

    return 1;
}

static int
find_house(person_t *person, map_t *map, mapspot_list_t **list)
{
    int r;

    r = mapspot_within_iterate(*list, 0, 0, 
			       MAXFLOAT, &house_is_suitable, person);    
    if (r) {
	if (!map_isvacant(map, person->livex, person->livey)) {
	    mapspot_list_remove(list, person->livex, person->livey);
	}   
	return 0;
    } else {
	return 1;
    }
}


static int
school_is_suitable(int mapx, int mapy, void *data, float dist, void *rock)
{
    person_t *person = (person_t *)rock;
    int r;

    r = map_attend_school(map, mapx, mapy);
    if (r) {
	return 0;
    }

    person->livex = mapx;
    person->livey = mapy;

    /* xxx make parents happy too */
    happy_upto(person, 100, 5, REASON_FIND_HOUSING);

    /* XXX */
    return 1;
}

static int
find_school(person_t *person, map_t *map, mapspot_list_t **list)
{
    int r;

    r = mapspot_within_iterate(*list, 0, 0, 
			       MAXFLOAT, &school_is_suitable, person);    
    if (r) {
	if (map_is_school_full(map, person->livex, person->livey)) {
	    mapspot_list_remove(list, person->livex, person->livey);
	}
	return 0;
    } else {
	return 1;
    }
}

static int
job_is_suitable(int mapx, int mapy, void *data, float dist, void *rock)
{
    person_t *person = (person_t *)rock;
    int r;
    enum labor_skill skill = (enum labor_skill)data;

    /*
     * Don't take job can't do
     */
    if (person->edlevel < labor_table[skill].edlevel) {
	/*printf("not smart enough to take job (%d < %d)\n", person->edlevel, labor_table[skill].edlevel);*/
	return 0;
    }

    /*
     * If overqualified don't take the job unless very unhappy
     */
    if ((person->edlevel - 4 > labor_table[skill].edlevel) &&
	(person->happy > 80)) {
	/*	printf("won't degrade to work at that level %d vs %d\n", person->edlevel, labor_table[skill].edlevel);*/
	return 0;
    }

    r = companies_takejob(companies, mapx, mapy, skill);
    if (r) {
	printf(_("Error taking job at %d,%d\n"), mapx, mapy);
	return 0;
    }

    person->workx = mapx;
    person->worky = mapy;
    person->worklevel = skill;

    happy_upto(person, 100, 5, REASON_FIND_WORK);

    /* XXX */
    return 1;
}

static int
find_job(person_t *person, map_t *map, mapspot_list_t **list)
{
    int r;

    r = mapspot_within_iterate(*list, 0, 0, 
			       MAXFLOAT, &job_is_suitable, person);    
    if (r) {
	if (!companies_hasopening(companies, person->workx, person->worky, person->worklevel)) {
	    mapspot_list_remove_data(list, person->workx, person->worky, (void *)person->worklevel);
	}
	return 0;
    } else {
	return 1;
    }
}

int population_movehousing(population_t *pop, map_t *map)
{
    int i;
    int min_working_age = config_getint("min_work_age", 18);
    int homeless = 0;

    /*
     * kids try to find schools
     */
    if (game->schools_list) {
	mapspot_list_free(game->schools_list);
	game->schools_list = NULL;
    }
    game->schoolless = 0;
    game->schools_list = map_find_tile_list(map, MAPTYPE_SCHOOL);

    for (i = 0; i < min_working_age; i++) {
	popgroup_t *group = &pop->age[i]->group;
	int k;

	for (k = 0; k < group->size; k++) {
	    if (group->data[k].livex == -1) {
		if (find_school(&group->data[k], map, &game->schools_list) != 0) {	
		    game->schoolless++;
		}
	    }
	}	    
    }
   
    /*
     * First people without homes try to find homes
     *
     */
    if (game->avail_houses_list) {
	mapspot_list_free(game->avail_houses_list);
	game->avail_houses_list = NULL;	
    }
    game->avail_houses_list = map_find_vacant_list(map);

    for (i = min_working_age; i < MAXAGE; i++) {
	popgroup_t *group = &pop->age[i]->group;
	int k;

	for (k = 0; k < group->size; k++) {
	    if (group->data[k].livex == -1) {
		if (find_house(&group->data[k], map, &game->avail_houses_list) != 0) {	
		    /* xxx remove_person(pop, map, group, k);*/
		    homeless++;
		}
	    }	
	
	}
    }

    /* XXX move */

    return 0;
}

int 
population_changejobs(population_t *pop, map_t *map)
{
    int i;
    int min_working_age = config_getint("min_work_age", 18);

    /*
     * First people without jobs try to find them
     *
     */
    game->avail_job_list = companies_find_openings_list(companies);

    for (i = min_working_age; i < MAXAGE; i++) {
	popgroup_t *group = &pop->age[i]->group;
	int k;
	
	for (k = 0; k < group->size; k++) {
	    if (group->data[k].workx == -1) {
		if (find_job(&group->data[k], map, &game->avail_job_list) != 0) {
		    /* xxx remove_person(pop, map, group, k);*/
		}
	    }
	}
	    
    }

    /* XXX change jobs */

    return 0;
}


void
population_calculate_happiness(population_t *pop)
{
    int i;
    happy_t *happy = happy_init();
    int totalhappy = 0;
    int cnt = 0;
    int per = 1000/(MAXAGE-game->min_working_age);

    for (i = game->min_working_age; i < MAXAGE; i++) {
	popgroup_t *group = &pop->age[i]->group;
	int k;
	int numtodo = per;
	
	if (numtodo > group->size) {
	    numtodo = group->size;
	}

	for (k = 0; k < numtodo; k++) {
	    person_t *person = &group->data[rand()%group->size];
	    
	    totalhappy += happy_calc_person(happy, person);
	    cnt++;
	}
    }

    /* calc average happy */
    if (cnt) {
	int newavg = ((float)totalhappy)/((float)cnt);
	game->avghappy = (game->avghappy + newavg + 1)/2;
    } else {
	game->avghappy = 100;
    }


    happy_free(happy);
}


static void
pay_salary(person_t *person, map_t *map)
{
    int owner;
    float salary;
    float tax;
    if (person->workx == -1) {
	return;
    }

    owner = map_get_owner(map, person->workx, person->worky);
    salary = companies_getsalary(companies, person->workx, person->worky, person->worklevel)/12;

    game->month_gdp += salary;

    player_reducemoney_num(owner, COST_SALARY, salary);

    tax = salary * ((float)game->tax_rates[TAX_TYPE_INCOME]/100.0);
    salary -= tax;

    person->savings += (salary - tax)/100;

    /* xxx who is paid tax? */
    player_addmoney_num(game->government_player, INCOME_TAX, tax);
}

int 
population_paysalaries(population_t *pop, map_t *map)
{
    int i;
    
    for (i = 0; i < MAXAGE; i++) {
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    pay_salary(&group->data[k], map);
	}    
    }

    return 0;
}

static void
pay_rent(person_t *person, map_t *map)
{
    int owner;
    float rent;

    if (person->livex == -1) {
	return;
    }

    rent = map_getrent(map, person->livex, person->livey)/12;
    owner = map_getowner(map, person->livex, person->livey);

    /* xxx what if don't have enough? */

    person->savings -= (rent/100);
    player_addmoney_num(owner, INCOME_RENT, rent);
    game->month_gdp += rent;

    /* xxx decide to move? */
}

typedef struct patronige_s {
    int visits;
    person_t *person;
} patronige_t;

static int
patronize_cb(int mapx, int mapy, void *data, float dist, void *rock)
{
    patronige_t *pat = (patronige_t *)rock;

    if (pat->visits > 0) {
	return -1;
    }

    if (map_patronize(map, mapx, mapy) == 0) {
	pat->visits++;
    }

    return 0;
}

static void
patronize(person_t *person, mapspot_list_t **lists)
{
    patronige_t pat;
    int i;
    int nobuy = 0;

    memset(&pat, 0, sizeof(patronige_t));
    pat.person = person;
    
    i = 0;
    while (commercial_types_list[i] != MAPOBJ_INVALID) {
	pat.visits = 0;
	if (rand()%100 <= tiles_getpatronperc(tiles, commercial_types_list[i]) * 100) {
	    mapspot_within_iterate(lists[i], person->livex, person->livey,
				   tiles_getrange(tiles, commercial_types_list[i]),
				   &patronize_cb, &pat);
	    if (pat.visits == 0) {
		nobuy++;
	    }
	}

	i++;
    }

    if (nobuy) {
	happy_down(person, nobuy, REASON_NOBUY);
    }
}

int
population_patronize(population_t *pop, map_t *map)
{
    int i;
    mapspot_list_t **lists;

    map_clear_patronige(map);


    lists = malloc(sizeof(mapspot_list_t *) * commercial_types_len);
    i = 0;
    while (commercial_types_list[i] != MAPOBJ_INVALID) {
	lists[i] = map_find_tile_list_obj(map, commercial_types_list[i]);
	i++;
    }
    
    for (i = 0; i < MAXAGE; i++) {
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    patronize(&group->data[k], lists);
	}    
    }

    return 0;
}

int
population_payrent(population_t *pop, map_t *map)
{
    int i;

    for (i = 0; i < MAXAGE; i++) {
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    pay_rent(&group->data[k], map);
	}
    }

    return 0;
}

popgroup_t *
population_getgroup(population_t *pop, int age)
{
    return &pop->age[age]->group;
}

static void
pay_upkeep(map_t *map, int mapx, int mapy, int owner, int upkeep, void *rock)
{
    upkeep /= 12;

    game->month_gdp += upkeep;

    player_reducemoney_num(owner, COST_UPKEEP, upkeep);
}

void
population_payupkeep(population_t *pop, map_t *map)
{
    map_upkeep_iterate(map, &pay_upkeep, NULL);
}

static void
make_revenue(company_t *company, int owner, int revenue, 
	     int costs, float food_production, void *rock)
{
    int salestax;
    int profit;

    revenue = revenue/12;
    costs = costs/12;
    
    game->month_gdp += revenue;

    salestax = revenue * ((float)game->tax_rates[TAX_TYPE_SALES]/100.0);
    revenue -= salestax;

    profit = revenue - costs;

    if (company) {
	company->lastprofit = profit;
	company->cash += profit/100;
    }

    if (profit > 0) {
	int incometax = profit * ((float)game->tax_rates[TAX_TYPE_BUS_INCOME]/100.0);
	revenue -= incometax;
	player_addmoney_num(game->government_player, INCOME_TAX, incometax);
    }

    player_addmoney_num(owner, INCOME_REVENUE, revenue);

    player_addmoney_num(game->government_player, INCOME_TAX, salestax);

    g_food_production_month += ((float)food_production)/((float)12);
}

static void
is_field_callback(map_t *map, int mapx, int mapy, int owner, 
		  mapobj_t obj, void *rock)
{
    int food_production;
    int revenue;
    int upkeep;
    getvar_struct_t rstruct;
    mapobj_t fieldobj = *(mapobj_t *)rock;

    if (obj != fieldobj) return;

    rstruct.map = map;
    rstruct.x = mapx;
    rstruct.y = mapy;

    revenue = tiles_getrevenue(tiles, obj, &getvariable_cb, &rstruct);
    food_production = tiles_getproduction(tiles, obj,
					  &getvariable_cb, &rstruct);
    upkeep = tiles_getupkeep(tiles, obj, &getvariable_cb, &rstruct);

    make_revenue(NULL,
		 map_get_owner(map, mapx, mapy),
		 revenue,
		 upkeep,
		 food_production,
		 NULL);
}

void
population_revenue(population_t *pop, map_t *map)
{
    mapobj_t fieldobj = map_item_name2obj("Field");
    game->month_gdp = 0;
    g_food_production_month = 0;
    companies_revenue_iterate(companies, &make_revenue, NULL);

    map_iterate(map, MAP_ITERATE_NORMAL,
		0, 0, 0,
		NULL, NULL,
		NULL, NULL,
		&is_field_callback, &fieldobj);
}

extern int
population_calculate_adults(population_t *pop)
{
    int adults = 0;
    int i;

    for (i = 20; i < MAXAGE; i++) { /* xxx */
	popgroup_t *group = &pop->age[i]->group;
	
	adults += group->size;
    }

    return adults;
}

extern int
population_calculate_needhousing(population_t *pop)
{
    int homeless = 0;
    int i;

    for (i = 20; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if (person->livex == -1)
		homeless++;
	}    
    }

    return homeless;	
}

extern void
population_calculate_unemployed(population_t *pop, labor_t *labor)
{
    int i;

    memset(labor, 0, sizeof(labor_t));

    for (i = 18; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;
	
	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if (person->workx == -1) {
		int j;
				
		for (j = 1; j < LABOR_NUMGROUPS; j++) {
		    if (person->edlevel < labor_table[j].edlevel) {
			break;
		    }			
		}

		labor->workers[j-1]++;
	    }
	}    
    }
}


static long total = 0; /* xxx */

int
population_investment_available(population_t *pop)
{
    return total;
}

extern void
population_center(population_t *pop, int *x, int *y)
{
    int i;
    int totx = 0;
    int toty = 0;
    int totpeople = 0;
    
    for (i = 0; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if (person->livex != -1) {
		totx += person->livex;
		toty += person->livey;
		totpeople++;
	    }
	}    
    }

    if (totpeople > 0) {
	*x = totx / totpeople;
	*y = toty / totpeople;
    } else {
	*x = 0;
	*y = 0;
    }
}

static void
corp_poperty_tax_callback(map_t *map, int mapx, int mapy, int owner, 
			  mapobj_t obj, void *rock)
{
    int property_value;
    int tax;

    if (!map_item_gettype(obj) != MAPTYPE_COMMERCIAL) return;

    /* xxx need to pay for whole area */

    property_value = government_calculate_onelandvalue(map, tiles, mapx, mapy) / 12;

    tax = ((float)property_value)*((float)game->tax_rates[TAX_TYPE_PROPERTY]/100.0);
    
    player_addmoney_num(game->government_player, INCOME_TAX, tax);    
}

int
population_property_tax(population_t *pop)
{
    int nominal_tax = config_getint("nominal_property_taxrate", 1);
    float taxrate = ((float)game->tax_rates[TAX_TYPE_PROPERTY]/100.0);
    int i;

    /*
     * People pay property tax
     */
    for (i = game->min_working_age; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;
	
	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if (person->livex != -1) {
		int property_value = government_calculate_onelandvalue(map, tiles, 
								       person->livex, person->livey) / 12;
		int numlive = map_occupancy(map, person->livex, person->livey);
		int tax;
		
		if (!numlive) {
		    printf("Error. person lives at %d,%d but occupancy zero\n",
			   person->livex, person->livey);
		    continue;
		}
		
		tax = ((float)(property_value/numlive))*taxrate;
		
		person->savings -= tax / 100;
		
		if (game->tax_rates[TAX_TYPE_PROPERTY] > nominal_tax) {
		    happy_down(person, (game->tax_rates[TAX_TYPE_PROPERTY] - nominal_tax) * 2, REASON_TAXES);
		}
		
		player_addmoney_num(game->government_player, INCOME_TAX, tax);
	    }
	}    
    }

    /*
     * Now corporations
     */
    map_iterate(map, MAP_ITERATE_NORMAL,
		0, 0, 0, 
		NULL, NULL,
		NULL, NULL,
		&corp_poperty_tax_callback, NULL);

    return 0;
}

int
population_fire_everybody(population_t *pop, int x, int y)
{
    int i;
    
    for (i = 0; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;
	
	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if ((person->workx == x) && (person->worky == y)) {
		person->workx = -1;
		person->worky = -1;
		happy_down(person, 30, REASON_UNEMPLOYED);
	    }
	}    
    }

    return 0;
}

int
population_evict_everybody(population_t *pop, int x, int y)
{
    int i;
    
    for (i = 0; i < MAXAGE; i++) { /* xxx */
	int k;
	popgroup_t *group = &pop->age[i]->group;

	for (k = 0; k < group->size; k++) {
	    person_t *person = &group->data[k];
	    
	    if ((person->livex == x) && (person->livey == y)) {
		person->livex = -1;
		person->livey = -1;
		happy_down(person, 30, REASON_HOMELESS);
	    }
	}
    }    

    return 0;
}

static char *reason_table[] = 
    {"Found housing",
     "Found work",
     "Employed",
     "Unemployed",
     "Taxes",
     "Economy",
     "Homeless",
     "Nature",
     "Police",
     "Fire",
     "Hospital",
     "Housed",
     "Schools",
     "Nowhere to shop",
     "Social",
     "Not enough schools",
     "Underemployed",
     "Not enough power",
     "Not enough water",
     "Garbage not being collected",
     NULL};

char *
population_reason_string(happy_reason_t reason)
{
    return reason_table[reason];
}

void
population_newmonth(void)
{
    int i;

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

    game->left_lastmonth = g_left_month;
    g_left_month = 0;

    game->popchange_lastmonth = g_population_change_month;
    g_population_change_month = 0;
}
