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

#include "senkenconfig.h"
#include "map.h"
#include "tiles.h"

#include "happy.h"
#include "population.h"

extern game_t *game;
extern population_t *population;
extern map_t *map;
extern mapobj_t *entertainment_types_list;
extern tiles_t *tiles;

static int count_list_employ(mapspot_list_t *list);

struct happy_s {
    int nominal_income_tax;
    int nominal_leave_perc;
    int need_services_density;
    int unemployment_rate;

    mapspot_list_t *police_list;
    mapspot_list_t *firedept_list;
    mapspot_list_t *hospital_list;
    mapspot_list_t *ent_list;
    int hospital_coverage;
    int police_coverage;
};

extern happy_t *
happy_init(void)
{
    happy_t *ret;
    int p_per_citizen = config_getint("policeofficer_per_citizen", 100);
    int h_per_citizen = config_getint("hospitalworker_per_citizen", 100);

    ret = calloc(1, sizeof(happy_t));

    ret->nominal_income_tax = config_getint("nominal_income_taxrate", 10);
    ret->nominal_leave_perc = config_getint("nominal_leave_percentage", 3);
    ret->need_services_density = config_getint("needservices_density", 50);
    ret->unemployment_rate = population_unemployment_rate(population);
    

    ret->police_list = map_find_tile_list(map, MAPTYPE_POLICE);
    ret->firedept_list = map_find_tile_list(map, MAPTYPE_FIRE);
    ret->hospital_list = map_find_tile_list(map, MAPTYPE_HOSPITAL);
    ret->ent_list = map_find_tile_list_fromlist(map, entertainment_types_list);

    /*
     * See if there are enough police officers and hospital workers
     */    
    ret->police_coverage = p_per_citizen * 
	count_list_employ(ret->police_list);
    if (ret->police_coverage > 2) ret->police_coverage = 2;

    ret->hospital_coverage = h_per_citizen * 
	count_list_employ(ret->hospital_list);
    if (ret->hospital_coverage > 2) ret->hospital_coverage = 2;
    
    return ret;
}

extern void 
happy_free(happy_t *happy)
{
    free(happy);
}

static void attends_school(person_t *child, void *rock)
{
    person_t *parent = (person_t *)rock;

    if (child->livex == -1) {
	happy_down(parent, 10, REASON_NOSCHOOL);
    }
}

static int
entertainment_cb(int x, int y, void *data, float dist, void *rock)
{
    mapobj_t obj = (mapobj_t)data;
    person_t *person = (person_t *)rock;
    int range;

    range = tiles_getrange(tiles, obj);
    
    if (dist <= range) {
	happy_up(person, 1, REASON_NATURE);
    }

    return 0;
}


static int
count_employ_cb(int x, int y, void *data, float dist, void *rock)
{
    int *countp = (int *)rock;

    (*countp) += tiles_getnumemploy(tiles, (mapobj_t)data);

    return 0;
}

static int
count_list_employ(mapspot_list_t *list)
{
    int count = 0;

    mapspot_iterate(list, &count_employ_cb, &count);

    return count;
}

extern int
happy_calc_person(happy_t *happy, person_t *person)
{
    int density; 
	    
    /*
     * If no job become less happy
     */
    if (person->workx == -1) {
	happy_down(person, 5, REASON_UNEMPLOYED);
    } else {
		
	if (labor_table[person->worklevel].edlevel < person->edlevel - 4) {
	    happy_down(person, 3, REASON_UNDEREMPLOYED);
	} else {
	    happy_up(person, 2, REASON_EMPLOYED);
	}
		
	/* worry about income tax */
	happy_up(person, happy->nominal_income_tax - 
		 game->tax_rates[TAX_TYPE_INCOME], REASON_TAXES);
    }
	    
    /*
     * Coverage
     */
    happy_up(person, happy->police_coverage, REASON_POLICE);
    happy_up(person, happy->hospital_coverage, REASON_HOSPITAL);
	    
    /*
     * If unemployment rate high
     */
    if (happy->unemployment_rate > 6) { /* xxx */
	happy_down(person, (happy->unemployment_rate - 6), REASON_ECONOMY);
    }
	    
    if (person->livex == -1) {
	happy_down(person, 10, REASON_HOMELESS);
	density = 0; /* xxx? */
    } else {
	int watercnt;
	
	happy_up(person, 1, REASON_HOUSED);
	density = map_density(map, person->livex, person->livey, 5) * 25;
	
	if (map_isflagset(map, person->livex, person->livey, MAPFLAG_NOPOWER)) {
	    happy_down(person, 5, REASON_NOPOWER);
	}
	
	if (map_isflagset(map, person->livex, person->livey, MAPFLAG_NOWATER)) {
	    happy_down(person, 7, REASON_NOWATER);
	}
	
	/* 
	 * happier if live near water 
	 *
	 * XXX configurable
	 */
	watercnt = map_within_count_type(map, person->livex, person->livey, 
					 15, MAPTYPE_WATER);
	if (watercnt > 5) {
	    happy_up(person, 3, REASON_NATURE);
	} else if (watercnt > 0) {
	    happy_up(person, 1, REASON_NATURE);
	}
    }
    
    /*
     * Garbage not being picked up
     */
    if (game->garbage_excess > 750) {
	happy_down(person, 4, REASON_GARBAGE);
    }
    
    /*
     * Less happy if friends leaving
     */
    if (game->totalpop) {
	float leftperc = ((float)game->left_lastmonth*100.0)/((float)game->totalpop);
	
	leftperc -= happy->nominal_leave_perc;
	
	if (leftperc >= 1.0) {
	    leftperc = leftperc/2 + 1;
	    if (leftperc > 5) leftperc = 5;
	    
	    happy_down(person, leftperc,
		       REASON_SOCIAL);
	}
    }
    
    /*
     * Less happy if children not attending school
     */
    population_foreach_child(population, person->uid, &attends_school, person);
    
    /*
     * Less happy if not enough schools
     *
     * XXX old people shouldn't care
     */
    if (game->totalkids) {
	float noschoolperc = ((float)game->schoolless*100.0)/((float)game->totalkids);
	
	if (noschoolperc > 1.0) {
	    if (noschoolperc > 5) noschoolperc = 5;
	    
	    happy_down(person, noschoolperc,
		       REASON_SCHOOLS);
	}
    }
    
    if (density > happy->need_services_density) {
	/*
	 * Less happy if no nearby police
	 */
	if (!mapspot_within_range(happy->police_list, 
				  person->livex, person->livey,
				  map, tiles)) {
	    happy_downto(person, 95, 8, REASON_POLICE);
	} else {
	    happy_up(person, 1, REASON_POLICE);
	}
	
	/*
	 * Less happy if no nearby fire to live or work
	 */
	if (!mapspot_within_range(happy->firedept_list, 
				  person->livex, person->livey,
				  map, tiles)) {
	    happy_downto(person, 95, 5, REASON_FIRE);
	} else {
	    happy_up(person, 1, REASON_FIRE);
	}
	
	/*
	 * Less happy if no nearby hospital		 
	 */
	if (!mapspot_within_range(happy->hospital_list, 
				  person->livex, person->livey,
				  map, tiles)) {
	    happy_downto(person, 95, 8, REASON_HOSPITAL);
	} else {
		    happy_up(person, 1, REASON_HOSPITAL);
	}
    }
    
    /*
     * Entertainment!
     */
    mapspot_within_iterate(happy->ent_list, person->livex, person->livey,
			   50, &entertainment_cb, person);

    return person->happy;
}

extern void
happy_up(person_t *person, int n, happy_reason_t reason)
{
    if (person->happy + n > 255) { 
	person->happy = 255; 
    } else { 
	person->happy+=n; 
    }

    game->reasons[reason] += n;
}

extern void
happy_down(person_t *person, int n, happy_reason_t reason)
{
    if (person->happy - n < 0) { 
	person->happy = 0; 
    } else { 
	person->happy -= n; 
    }

    game->reasons[reason] -= n;
}

extern void
happy_upto(person_t *person, int to, int n, happy_reason_t reason)
{
    if (person->happy < to) {
	game->reasons[reason] += (to - person->happy);
	person->happy = to; 
    } else { 
	happy_up(person, n, reason);
    }
}

extern void
happy_downto(person_t *person, int to, int n, happy_reason_t reason)
{
    if (person->happy > to) {
	game->reasons[reason] -= (person->happy - to);
	person->happy = to; 
    } else { 
	happy_down(person, n, reason);
    }
}
