/* 
 * 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 "investment.h"
#include "companies.h"
#include "landvalue.h"
#include "senkenconfig.h"
#include "utils.h"
#include "connections.h"
#include "tiles.h"
#include "vars.h"

extern tiles_t *tiles;
extern map_t *map;
extern game_t *game;
extern companies_t *companies;

struct investment_obj {
    mapspot_list_t *houselist;    
    mapspot_list_t *house_upgradelist;    
    mapspot_list_t *officelist;
    mapspot_list_t *commerciallist;
    mapspot_list_t *industriallist;
    mapspot_list_t *farmlist;
    mapspot_list_t *comm_list[100]; /* xxx */

    int needhousing;
    labor_t unemployed;
    int total_adults;
    int farm_maxlandpay;
    int farm_within;
};

mapobj_t *house_types_list;
mapobj_t *commercial_types_list;
int commercial_types_len;
mapobj_t *entertainment_types_list;
mapobj_t *office_types_list;
mapobj_t *industry_types_list;

extern int g_population_change_lastmonth;

static int
labor_avail(labor_t *labor, mapobj_t obj)
{
    labor_t *need = tiles_getemploy(tiles, obj);
    int i;

    for (i = 0; i < LABOR_NUMGROUPS; i++) {
	int numneed = need->workers[i];

	if (numneed) {
	    int have = labor->workers[i];

	    if (i < LABOR_NUMGROUPS-1) {
		have += (labor->workers[i+1] - need->workers[i+1])/2;
	    }
	    if (have < numneed) {
		return 0;
	    }
	}
    }
    
    return 1;
}

static int
max_employ(tiles_t *tiles, mapobj_t obj, int x, int y)
{
    return tiles_getnumemploy(tiles, obj);
}

static int
house_compare(const void *a, const void *b)
{
    int housea = tiles_gethouse(tiles, *(mapobj_t *)a);
    int houseb = tiles_gethouse(tiles, *(mapobj_t *)b);

    if (housea > houseb) {
	return -1;
    } else {
	return 1;
    }
}

static int 
office_compare(const void *a, const void *b)
{
    int employa = max_employ(tiles, *(mapobj_t *)a, 0, 0);
    int employb = max_employ(tiles, *(mapobj_t *)b, 0, 0);

    if (employa > employb) {
	return -1;
    } else {
	return 1;
    }
}

static int 
industry_compare(const void *a, const void *b)
{
    int employa = tiles_getnumemploy(tiles, *(mapobj_t *)a);
    int employb = tiles_getnumemploy(tiles, *(mapobj_t *)b);

    if (employa > employb) {
	return -1;
    } else {
	return 1;
    }
}

mapobj_t ZONE_COMMERCIAL;
mapobj_t ZONE_HOUSE;
mapobj_t ZONE_FARM;
mapobj_t ZONE_OFFICE;
mapobj_t ZONE_INDUSTRIAL;
mapobj_t FARM;
mapobj_t FIELD;

extern int
investment_init(void)
{
    house_types_list = map_item_get_typelist(MAPTYPE_HOUSE,
					     &house_compare, NULL);
    commercial_types_list = map_item_get_typelist(MAPTYPE_COMMERCIAL,
					     NULL, &commercial_types_len);
    entertainment_types_list = map_item_get_typelist(MAPTYPE_ENTERTAINMENT,
						NULL, NULL);
    office_types_list = map_item_get_typelist(MAPTYPE_OFFICE,
					 &office_compare, NULL);
    industry_types_list = map_item_get_typelist(MAPTYPE_INDUSTRIAL,
					   &industry_compare, NULL);

    ZONE_COMMERCIAL = map_item_name2obj("ZoneCommercial");
    ZONE_HOUSE = map_item_name2obj("ZoneHouse");
    ZONE_FARM = map_item_name2obj("ZoneFarm");
    ZONE_OFFICE = map_item_name2obj("ZoneOffice");
    ZONE_INDUSTRIAL = map_item_name2obj("ZoneIndustrial");
    FARM = map_item_name2obj("Farm");
    FIELD = map_item_name2obj("Field");
    return 0;
}

static void
update_buildings(population_t *pop, map_t *map, int money)
{
    /* xxx */
}

static int
enough_room(mapobj_t obj, int x, int y, mapobj_t zone, mapspot_list_t *objlist)
{
    int sizex;
    int sizey;
    int i;
    int j;

    /*
     * Make sure have enough room
     */
    tiles_getsize(tiles, obj, &sizex, &sizey);

    for (i = x; i < x+sizex; i++) {
	for (j = y; j < y+sizey; j++) {
	    if (map_get_type(map, i, j) != zone) {
		return 0;
	    }
	}
    }
    
    for (i = x; i < x+sizex; i++) {
	for (j = y; j < y+sizey; j++) {
	    if ((i != x) && (j != y)) {
		mapspot_list_remove(&objlist, i, j);
	    }
	}
    }

    return 1;
}



static int
is_market_for(struct investment_obj *invobj, map_t *map, 
	      int x, int y, float density, mapobj_t obj)
{
    int marketfor;
    int maxpatrons;
    float patronperc;
    int range;
    int num_customers;
    int num_type;

    range = tiles_getrange(tiles, obj);
    maxpatrons = tiles_get_maxpatrons(tiles, obj);
    patronperc = tiles_getpatronperc(tiles, obj);

    /*
     * figure out number of customers based on density
     */
    num_customers = density * (range*range);

    num_type = mapspot_list_num_within(invobj->comm_list[obj], x, y, range);

    /* xxx */
    marketfor = ((float)num_customers) * patronperc - (maxpatrons * num_type);

    if (marketfor >= maxpatrons/2) { /* xxx */
	return 1;
    } else {
	return 0;
    }
}

static mapobj_t
find_market(struct investment_obj *obj, map_t *map, 
	    int x, int y, mapspot_list_t *list)
{
    int i;
    int start = rand()%commercial_types_len;
    float density = map_density(map, x, y, 10);

    i = start;
    do {
	mapobj_t o = commercial_types_list[i];

	if (enough_room(o, x, y, ZONE_COMMERCIAL,
			list)) {
	    if (is_market_for(obj, map, x, y, density, o)) {		
		return o;
	    }
	}

	i++;
	if (i == commercial_types_len) {
	    i = 0;
	}
    } while (i != start);
    
    return MAPOBJ_INVALID;
}

static mapobj_t
rand_ofsize(int size, mapobj_t def)
{
    int first = -1;
    int last = -1;
    int i;

    for (i = 0; house_types_list[i] != MAPOBJ_INVALID; i++) {    
	int n = tiles_gethouse(tiles, house_types_list[i]);

	if (n == size) {
	    if (first == -1) {
		first = i;
	    }
	} else {
	    if ((first != -1) && (last == -1)) {
		last = i;
	    }
	}
    }
    if (last == -1) {
	last = i-1;
    }

    if ((first != -1) && (last != -1)) {
	int offset = (rand()%(last-first+1))+first;
	return house_types_list[offset];
    } else {
	return def;
    }
}

static mapobj_t
build_housing(struct investment_obj *obj, int *mapx, int *mapy)
{
    if (obj->houselist) {
	int i = 0;
	int spots;

	mapspot_list_extract(&obj->houselist, mapx, mapy, NULL);

	spots = mapspot_list_length(obj->houselist);	

	for (i = 0; house_types_list[i] != MAPOBJ_INVALID; i++) {
	    mapobj_t cur = house_types_list[i];
	    mapobj_t next = house_types_list[i+1];
	    int smallerhouse;
	    
	    smallerhouse = tiles_gethouse(tiles, next);

	    if ((spots * smallerhouse < obj->needhousing) ||
	        (next == MAPOBJ_INVALID)) {

		if (enough_room(cur, *mapx, *mapy, ZONE_HOUSE, 
				obj->houselist)) {
		    return rand_ofsize(tiles_gethouse(tiles, cur), cur);
		}
	    }
	}
    }

    while (obj->house_upgradelist) {
	int i = 0;
	int spots;
	mapobj_t cur_obj;

	mapspot_list_extract(&obj->house_upgradelist, mapx, mapy, NULL);

	spots = mapspot_list_length(obj->houselist);	
	cur_obj = map_get_type(map, *mapx, *mapy);

	for (i = 0; house_types_list[i] != cur_obj; i++) {
	    mapobj_t cur = house_types_list[i];
	    mapobj_t next = house_types_list[i+1];
	    int smallerhouse;
	    
	    smallerhouse = tiles_gethouse(tiles, next);

	    if ((spots * smallerhouse < obj->needhousing) ||
	        (next == cur_obj)) {
		int sizex;
		int sizey;
		int x;
		int y;

		/* xxx use enough_room here */

		/*
		 * Make sure have enough room
		 */
		tiles_getsize(tiles, cur, &sizex, &sizey);

		for (x = *mapx; x < (*mapx)+sizex; x++) {
		    for (y = *mapy; y < (*mapy)+sizey; y++) {
			mapobj_t a = map_get_type(map, x, y);
			if ((map_item_gettype(a) != MAPTYPE_HOUSE) ||
			    (map_is_multi(map, x, y))) {
			    goto next2;
			}
		    }
		}

		for (x = *mapx; x < (*mapx)+sizex-1; x++) {
		    for (y = *mapy; y < (*mapy)+sizey-1; y++) {
			if ((x != *mapx) && (y != *mapy)) {
			    mapspot_list_remove(&obj->house_upgradelist, x, y);
			}
		    }
		}

		return cur;
	    }

	next2:
	    ; /* for compiler reasons xxx */
	}

    }

    return MAPOBJ_INVALID;
}

static mapobj_t
buy_something(struct investment_obj *obj, int *mapx, int *mapy)
{
    float vacancy_rate;
    float unemployment_rate;
    int rndnum;

    if (obj->total_adults) {
	vacancy_rate = ((float)(-1 * obj->needhousing))/((float)obj->total_adults);
	unemployment_rate = ((float)labor_count(&obj->unemployed))/((float)obj->total_adults);
    } else {
	vacancy_rate = 0.0;
	unemployment_rate = 0.0;
    }

    do {
	rndnum = rand() % 5;

	switch (rndnum) {
    case 0:
	/*
	 * Figure out where to invest next
	 */
	while (obj->farmlist) {	   
	    int landvalue;

	    mapspot_list_extract(&obj->farmlist, mapx, mapy, NULL);

	    landvalue = government_calculate_onelandvalue(map, tiles, *mapx, *mapy);

	    if (landvalue < obj->farm_maxlandpay) {
		
		if (map_within(map, *mapx, *mapy, obj->farm_within, FARM, -1)) {
		    return FIELD;
		} else if ((map_within_count(map, *mapx, *mapy, obj->farm_within, FIELD) + 
			   map_within_count(map, *mapx, *mapy, obj->farm_within, ZONE_FARM) >= 
			   (((float)obj->farm_within * obj->farm_within)*0.75)) &&
			   (map_islevel(map, *mapx, *mapy))) {
		    return FARM;
		}
	    }
	}
	/* fallthru */
	case 1:
	    if (vacancy_rate < .05) {
		mapobj_t o = build_housing(obj, mapx, mapy);
		if (o != MAPOBJ_INVALID) return o;
	    }
	    /* fallthru */
	case 2:
	    if (obj->officelist) {
		if (unemployment_rate > -0.03) { /* xxx configurable */
		    int i = 0;
		    int density;

		    mapspot_list_extract(&obj->officelist, mapx, mapy, NULL);

		    density = map_density(map, *mapx, *mapy, 5);
		    while (office_types_list[i] != MAPOBJ_INVALID) {
			int maxemploy = max_employ(tiles, office_types_list[i], 
						   *mapx, *mapy);
			int n;

			/*
			 * Build if rand(density * 2) > maxemploy and
			 * labor is available
			 */
			n = (rand() % (density * 2 + 1)) + density;
			
			if ((office_types_list[i+1] == MAPOBJ_INVALID) ||
			    (n > maxemploy)) {
			    if (labor_avail(&obj->unemployed, office_types_list[i])) {
				return office_types_list[i];
			    }
			}
			
			i++;
		    }
		}
	    }
	    break;

	case 3:
	    while (obj->commerciallist) {
		mapobj_t o;
		
		mapspot_list_extract(&obj->commerciallist, mapx, mapy, NULL);

		o = find_market(obj, map, *mapx, *mapy, obj->commerciallist);
		if (o != MAPOBJ_INVALID) {
		    return o;
		}
	    }
	    break;

	case 4:
	    if (obj->industriallist) {
		if (unemployment_rate > -0.03) { /* xxx configurable */
		    int density;
		    int indcnt;
		    int i = 0;
		    mapspot_list_extract(&obj->industriallist, mapx, mapy, NULL);

		    density = map_density(map, *mapx, *mapy, 5);
		    indcnt = map_within_count_type(map, *mapx, *mapy, 
						   3, MAPTYPE_INDUSTRIAL);

		    while (industry_types_list[i] != MAPOBJ_INVALID) {
			if (enough_room(industry_types_list[i],
					*mapx, *mapy, ZONE_INDUSTRIAL,
					obj->industriallist)) {
			    int maxemploy = max_employ(tiles, industry_types_list[i], 
						       *mapx, *mapy);
			    int n;

			    /*
			     * Build if rand(density * 2) > maxemploy and
			     * labor is available
			     */
			    n = (rand() % (density * 2 + indcnt + 1)) + density;
			
			    if ((industry_types_list[i+1] == MAPOBJ_INVALID) ||
				(n > maxemploy)) {
				if (labor_avail(&obj->unemployed, industry_types_list[i])) {
				    return industry_types_list[i];
				}
			    }
			}
			i++;
		    }
		}
	    }
	    break;
    }
    } while (rndnum != 0);

    return MAPOBJ_INVALID;
}

static void
find_zoned(map_t *map, int mapx, int mapy, int owner, mapobj_t obj, void *rock)
{
    struct investment_obj *invobj = (struct investment_obj *)rock;
    maptype_t kind;

    if (obj == ZONE_HOUSE) {
	/*
	 * If no roads within 3 don't build
	 */
	if (map_within_type(map, mapx, mapy, 3, MAPTYPE_ROAD, -1)) {
	    mapspot_list_add(&invobj->houselist, mapx, mapy, NULL);
	}

    } else if (obj == ZONE_OFFICE) {
	/*
	 * If no roads within 3 don't build
	 */
	if (map_within_type(map, mapx, mapy, 3, MAPTYPE_ROAD, -1)) {
	    mapspot_list_add(&invobj->officelist, mapx, mapy, NULL);	    
	}

    } else if (obj == ZONE_INDUSTRIAL) {
	/*
	 * If no roads within 4 don't build
	 */
	if (map_within_type(map, mapx, mapy, 4, MAPTYPE_ROAD, -1)) {
	    mapspot_list_add(&invobj->industriallist, mapx, mapy, NULL);
	}


    } else if (obj == ZONE_COMMERCIAL) {
	/*
	 * If no roads within 2 don't build
	 */
	if (map_within_type(map, mapx, mapy, 2, MAPTYPE_ROAD, -1)) {
	    mapspot_list_add(&invobj->commerciallist, mapx, mapy, NULL);	    
	}

    } else if (obj == ZONE_FARM) {
	if (map_within_type(map, mapx, mapy, 15, MAPTYPE_ROAD, -1)) { /* xxx */
	    if (map_within(map, mapx, mapy, 3, FARM, -1)) {
		mapspot_list_add(&invobj->farmlist, mapx, mapy, NULL);
	    } else if (map_within_count(map, mapx, mapy, 3, FIELD) + 
		       map_within_count(map, mapx, mapy, 3, ZONE_FARM) >= 8) {
		mapspot_list_add(&invobj->farmlist, mapx, mapy, NULL);
	    }
	}

    } else {
	kind = map_item_gettype(obj);
	switch (kind) 
	    {
	    case MAPTYPE_HOUSE:
		if (owner == NO_OWNER) {
		    mapspot_list_add(&invobj->house_upgradelist, mapx, mapy, NULL);	
		}
		break;
	    case MAPTYPE_COMMERCIAL:
		mapspot_list_add(&invobj->comm_list[obj], mapx, mapy, NULL);	
		break;
	    default:
		break;
	    }
    }
}

extern void
investment_invest(population_t *pop, map_t *map, int days, int initial)
{
    struct investment_obj obj;
    int r;
    long max_investment;
    long investment;
    int centerx;
    int centery;
    int i;

    /*
     * Find zoned spots
     */
    memset(&obj, 0, sizeof(struct investment_obj));    
    population_center(pop, &centerx, &centery);
    map_all_iterate(map, centerx, centery, &find_zoned, &obj);

    /*
     * make investments
     */
    investment = config_getfunction_evaluate("investment_month", 
		    &vars_population_getvariable, NULL, 0) / DAYS_MONTH * days;


    max_investment = config_getfunction_evaluate("max_investment_month", 
			 &vars_population_getvariable, NULL, investment*12) / DAYS_MONTH * days;

    game->total_to_invest += investment;
    if (game->total_to_invest > max_investment) {
	game->total_to_invest = max_investment;
    }

    if (initial) {
	game->total_to_invest = INT_MAX;
    }

    game->vacancies = map_vacancies(map);
    game->homeless = population_calculate_needhousing(pop); /* xxx bad calculation of homeless */
    obj.needhousing =  game->homeless - game->vacancies;
    companies_positions_avail(companies, &game->jobsavail);
    population_calculate_unemployed(pop, &game->needjobs);
    obj.total_adults = population_calculate_adults(pop);
    obj.farm_maxlandpay = config_getint("maxfarm_payforland", 2000);
    obj.farm_within = config_getint("maxfarm_fromfield", 4);
    for (i = 0; i < LABOR_NUMGROUPS; i++) {
	int num = game->needjobs.workers[i] - game->jobsavail.workers[i];

	if ((num <= 0) && (game->jobsavail.workers[i] < 10)) {
	    if (num < game->popchange_lastmonth * .05) {
		num = game->popchange_lastmonth * .05;
	    }
	    if (num < 2) {
		num = rand()%10;
	    }
	}
	obj.unemployed.workers[i] = num;

	/*printf("%s need jobs = %d jobs avail = %d saying %d\n", labor_table[i].name,
	  game->needjobs.workers[i], game->jobsavail.workers[i], num);*/
    }

    if (0) {
	printf("need housing = %d\n", obj.needhousing);
	printf("funds avail = %d\n", game->total_to_invest);
    }

    while (game->total_to_invest > 0) {
	int mapx = -1;
	int mapy = -1;
	mapobj_t mapobj;
	mapobj_t curtype;
	mapspot_t spot;
	int curowner;
	int landvalue = 0;
	int cost;
	int sizex;
	int sizey;
	int x;
	int y;
	int num_houses;
	labor_t *num_employ;
	int poweruse;

	mapobj = buy_something(&obj, &mapx, &mapy);

	if (mapobj == MAPOBJ_INVALID) {
	    break;
	}

	poweruse = tiles_getpoweruse(tiles, mapobj);
	if (game->power_excess < poweruse) {
	    break;
	}
	if ((game->totalpop > 50) && (game->water_excess < 50)) {
	    break;
	}
	game->power_excess -= poweruse;

	/*
	 * build what's requested
	 */ 
	tiles_getsize(tiles, mapobj, &sizex, &sizey);

	for (x = mapx; x < mapx+sizex; x++) {
	    for (y = mapy; y < mapy+sizey; y++) {
		curowner = map_get_owner(map, x, y);
		landvalue += government_calculate_onelandvalue(map, tiles, x, y);
		curtype = map_get_type(map, x, y);
		
		if (!map_item_emptyland(curtype)) {
		    r = map_set_type(map, x, y, MAPOBJ_ACTION_DEMOLISH, 0);
		    if (r) {
			printf(_("Error demolishing at %d,%d\n"), mapx, mapy);
			abort();
		    }

		    memset(&spot, 255, sizeof(mapspot_t));
	
		    spot.mapobj = MAPOBJ_ACTION_DEMOLISH;
		    if (!initial) {
			connections_send_to_all("* BOARDSPOT %d %d %s\r\n",x,y,
						map_spot2string(&spot));
		    }

		    cost = tiles_get_demolish_cost(tiles, curtype);
		    game->total_to_invest -= cost;
		    game->month_gdp += cost;
		}

		player_addmoney_num(curowner, INCOME_LAND, landvalue);
	    }   
	}

	memset(&spot, 255, sizeof(mapspot_t));
	
	spot.owner = 0;
	spot.mapobj = mapobj;
	r = map_set_spot(map, NULL, mapx, mapy, 0, 0, &spot);
	if (r) {
	    printf(_("Error setting %s at %d,%d\n"),map_item_getname(mapobj), mapx, mapy);
	    return;
	}

	if (0) {
	    printf("built %s\n", map_item_getname(mapobj));
	}
	
	if (!initial) {
	    connections_send_to_all("* BOARDSPOT %d %d %s\r\n",mapx,mapy,map_spot2string(&spot));
	}

	cost = landvalue + tiles_cost(tiles, mapobj);
	if (game->total_to_invest < cost) {
	    break;
	}
	game->total_to_invest -= cost;
	game->month_gdp += cost;

	num_houses = tiles_gethouse(tiles, mapobj);	
	if (num_houses) {
	    obj.needhousing -= num_houses;
	    if (!mapspot_is_in_list(game->avail_houses_list, mapx, mapy)) {
		mapspot_list_add(&game->avail_houses_list, mapx, mapy, NULL);
	    }
	}

	num_employ = tiles_getemploy(tiles, mapobj);
	if (labor_count(num_employ) > 0) {
	    int j;

	    for (j = 0; j < LABOR_NUMGROUPS; j++) {
		obj.unemployed.workers[j] -= num_employ->workers[j];
	    }
	    if (!mapspot_is_in_list(game->avail_job_list, mapx, mapy)) {
		mapspot_list_add(&game->avail_job_list, mapx, mapy, NULL);
	    }
	}
	
    }

    if (game->total_to_invest > 0) {
	update_buildings(pop, map, game->total_to_invest);
    }

    mapspot_list_free(obj.houselist);
    mapspot_list_free(obj.house_upgradelist);
    mapspot_list_free(obj.officelist);
    mapspot_list_free(obj.commerciallist);
    mapspot_list_free(obj.industriallist);
    mapspot_list_free(obj.farmlist);

    if (initial) {
	game->total_to_invest = 0;
    }

}
