/* 
 * 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 "companies.h"
#include "population.h"
#include "connections.h"
#include "senkenconfig.h"
#include "vars.h"

#define HASH_SIZE 107

extern tiles_t *tiles;
extern map_t *map;
extern population_t *population;

struct companies_s {
    company_t *list[HASH_SIZE];
};

static int
hashit(int x, int y)
{
    return ((x * 63) + (y * 43)) % HASH_SIZE;
}

/*
 * Get company starting margin
 */
static float
margin_init(company_t *c)
{
    vars_t vars;
    int margin;

    memset(&vars, 0, sizeof(vars_t));
    vars.c = c;

    margin = config_getfunction_evaluate("company_startmargin",
					 &vars_getvariable, &vars, 125);

    return ((float)margin)/((float)100.0);
}

static void
margin_update(company_t *c)
{
    c->margin -= .01;

    if (c->lastprofit < 0) {
	if (c->cash < abs(3 * c->lastprofit)) {
	    /*
	     * chance of turning things around
	     */
	    if (rand() % 3 == 0) {
		c->margin += (float)(rand() % 12)/((float)100);
	    }
	}
    }
}

int
companies_init(companies_t **companies)
{
    companies_t *ret;
    ret = calloc(1, sizeof(companies_t));
    if (!ret) return -1;

    *companies = ret;

    return 0;
}

int
companies_add(companies_t *companies, int x, int y, int obj)
{
    int hash = hashit(x, y);
    company_t *c;
    int startcash;

    c = calloc(1, sizeof(company_t));
    if (!c) return -1;
    
    c->obj = obj;
    c->x = x;
    c->y = y;

    startcash = config_getfunction_evaluate("company_startcash", 
					    &vars_company_getvariable, c, -1);
    if (startcash == -1) {
	int upkeep;
	int i;
	labor_t *maxlabor = tiles_getemploy(tiles, c->obj);

	/*
	 * Comapnies start with enough cash to hire employees and
	 * operate for 3 months (salaries and upkeep)
	 */
	upkeep = tiles_getupkeep(tiles, obj, &vars_company_getvariable, c);
	startcash = 3 * upkeep;

	for (i = 0; i < LABOR_NUMGROUPS; i++) {
	    startcash += (maxlabor->workers[i] * labor_table[i].salary/12) * 3;
	    startcash += maxlabor->workers[i] * HIRECOST;
	}
    }
    c->cash = startcash / 100;
    c->margin = margin_init(c);    

    c->next = companies->list[hash];
    companies->list[hash] = c;

    return 0;
}

int
companies_remove(companies_t *companies, int x, int y)
{
    int hash = hashit(x, y);
    company_t *c;
    company_t *last = NULL;

    c = companies->list[hash];

    while (c) {
	if ((c->x == x) && (c->y == y)) {
	    if (last) {
		last->next = c->next;
	    } else {
		companies->list[hash] = c->next;
	    }
	    free(c);
	    return 0;
	}

	last = c;	
	c = c->next;
    } 

    return -1;
}

company_t *
companies_find(companies_t *companies, int x, int y)
{
    int hash = hashit(x, y);
    company_t *c;

    c = companies->list[hash];

    while (c) {
	if ((c->x == x) && (c->y == y)) {
	    return c;
	}
	
	c = c->next;
    } 

    return NULL;
}

static int
numemploy(company_t *c)
{
    int i;
    int cnt = 0;
    
    for (i = 0; i < LABOR_NUMGROUPS; i++) {
	cnt += c->labor.workers[i];
    }
    return cnt;
}

extern int
company_numemploy(company_t *c)
{
    return numemploy(c);
}

labor_t *
companies_maxemploy(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);

    if (!c) return 0;

    return tiles_getemploy(tiles, c->obj);
}

int
companies_openings(companies_t *companies, int x, int y,
		   enum labor_skill skill)
{
    company_t *c = companies_find(companies, x, y);
    labor_t *maxlabor;

    if (!c) return 0;

    maxlabor = tiles_getemploy(tiles, c->obj);

    return maxlabor->workers[skill] - c->labor.workers[skill];
}

int
companies_hasopening(companies_t *companies, int x, int y, 
		     enum labor_skill skill)
{
    int openings = companies_openings(companies, x, y, skill);

    return (openings > 0);
}

int 
companies_takejob(companies_t *companies, int x, int y, enum labor_skill skill)
{
    company_t *c = companies_find(companies, x, y);

    if (!c) return -1;    

    if (!companies_hasopening(companies, x, y, skill)) {
	printf(_("Can't work there\n"));
	return -1;
    }

    c->labor.workers[skill]++;
    c->cash -= HIRECOST/100;

    return 0;
}

int
companies_quitjob(companies_t *companies, int x, int y,
		  enum labor_skill skill)
{
    company_t *c = companies_find(companies, x, y);

    if (!c) {
	printf(_("No company there to quit job from\n"));
	return -1;    
    }

    if (c->labor.workers[skill] <= 0) {
	printf(_("Nobody works there\n"));
	return -1;
    }

    c->labor.workers[skill]--;

    return 0;    
}

labor_t *
companies_employ(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);
    if (!c) return 0;

    return &c->labor;
}

mapspot_list_t *
companies_find_openings_list(companies_t *companies)
{
    int i;
    mapspot_list_t *ret = NULL;

    for (i = 0; i < HASH_SIZE; i++) {
	company_t *c = companies->list[i];

	while (c) {
	    labor_t *maxlabor = tiles_getemploy(tiles, c->obj);
	    int j;

	    for (j = 0; j < LABOR_NUMGROUPS; j++) {

		if (maxlabor->workers[j] > c->labor.workers[j]) {
		    mapspot_list_add(&ret, c->x, c->y, (void *)j);
		}
	    }

	    c = c->next;
	}
    }

    return ret;
}

int
companies_gettotalsalary(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);
    int total = 0;
    int i;

    if (!c) return 0;    

    for (i = 0; i < LABOR_NUMGROUPS; i++) {
	total += c->labor.workers[i] * labor_table[i].salary;
    }

    return total;
}

void
companies_positions_avail(companies_t *companies, labor_t *labor)
{
    int i;

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

    for (i = 0; i < HASH_SIZE; i++) {
	company_t *c = companies->list[i];

	while (c) {
	    int j;

	    for (j = 0; j < LABOR_NUMGROUPS; j++) {
		labor->workers[j] += companies_openings(companies, c->x, c->y, j);
	    }

	    c = c->next;
	}
    }
}

int
companies_total_positions(companies_t *companies)
{
    int i;
    int cnt = 0;

    for (i = 0; i < HASH_SIZE; i++) {
	company_t *c = companies->list[i];

	while (c) {
	    cnt += numemploy(c);

	    c = c->next;
	}
    }

    return cnt;
}

void
company_revenue(company_t *company,
		revenue_iterate_callback *callback, 
		void *rock)
{
    int revenue = 0;
    float food_production = 0;
    int costs = 0;
    int i;

    /*
     * Revenue
     */
    if ((!map_isflagset(map, company->x, company->y, MAPFLAG_NOPOWER)) &&
	(!map_isflagset(map, company->x, company->y, MAPFLAG_NOWATER))) {
	revenue = tiles_getrevenue(tiles, company->obj,
				   &vars_company_getvariable, 
				   company) * company->margin;
	
	food_production = tiles_getproduction(tiles, company->obj,
					      &vars_company_getvariable, 
					      company) * company->margin;
    }

    /*
     * Update margin
     */
    margin_update(company);

    /*
     * Building upkeep
     */
    costs += tiles_getupkeep(tiles, company->obj, &vars_company_getvariable,
			     company);

    /*
     * Salaries
     */
    for (i = 0; i < LABOR_NUMGROUPS; i++) {
	costs += company->labor.workers[i] * labor_table[i].salary;
    }
	
    
    {
	int maxemploy = tiles_getnumemploy(tiles, company->obj);
	int employ = numemploy(company);
	int owner = map_get_owner(map, company->x, company->y);
	
	if (maxemploy > 0) {
	    revenue *= employ/maxemploy;
	    food_production *= employ/maxemploy;
	}
	
	callback(company,
		 owner, revenue, costs, food_production, rock);
    }
}

int
companies_get_revenue(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);    
    if (!c) return 0;

    return tiles_getrevenue(tiles, c->obj,
			    &vars_company_getvariable, c);
}

void 
companies_revenue_iterate(companies_t *companies, 
			  revenue_iterate_callback *callback, 
			  void *rock)
{
    int i;

    for (i = 0; i < HASH_SIZE; i++) {
	company_t *c = companies->list[i];

	while (c) {
	    company_revenue(c, callback, rock);

	    c = c->next;
	}
    }
}

int
companies_get_cash(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);    
    if (!c) return -1;

    return c->cash*100;
}

int
companies_get_lastprofit(companies_t *companies, int x, int y)
{
    company_t *c = companies_find(companies, x, y);    
    if (!c) return -1;

    return c->lastprofit;
}


int
companies_out_of_business(companies_t *companies)
{
    int i;

    for (i = 0; i < HASH_SIZE; i++) {
	company_t *c = companies->list[i];
	company_t *last = NULL;

	while (c) {
	    company_t *next = c->next;

	    if (map_item_gettype(c->obj) == MAPTYPE_FARM) {
		/* farms don't go out of business */
		c = next;
		continue;
	    }

	    if ((c->cash < 0) && (map_get_owner(map, c->x, c->y) == 0)) {
		mapspot_t spot;
		mapobj_t newobj = MAPOBJ_ACTION_DEMOLISH;
		int sx;
		int sy;
		int j;
		int k;

		population_fire_everybody(population, c->x, c->y);
		
#if 0
		switch (map_get_kind(c->obj))
		    {
		    case MAPTYPE_ENTERTAINMENT:
		    case MAPTYPE_GOVERNMENT:
		    case MAPTYPE_HOUSE:
		    case MAPTYPE_EMPTY:
			newobj = MAPOBJ_DEMOLISH;
			break;
		    case MAPTYPE_COMMERCIAL:
			newobj = MAPOBJ_ZONE_COMMERCIAL;
			break;
		    case MAPTYPE_OFFICE:
			newobj = MAPOBJ_ZONE_OFFICE;
			break;
		    case MAPTYPE_INDUSTRIAL:
			newobj = MAPOBJ_ZONE_INDUSTRIAL;
			break;
		    case MAPTYPE_FARM:
			newobj = MAPOBJ_ZONE_FARM;
			break;
		    }
#endif /* 0 */
		newobj = MAPOBJ_ACTION_DEMOLISH;


		tiles_getsize(tiles, c->obj, &sx, &sy);

		for (j = 0; j < sx; j++) {
		    for (k = 0; k < sy; k++) {

			map_set_type(map, c->x + j, c->y + k, newobj, 1);
			memset(&spot, 255, sizeof(mapspot_t));
		    
			spot.mapobj = newobj;
			connections_send_to_all("* BOARDSPOT %d %d %s\r\n",
						c->x + j,c->y + k,
						map_spot2string(&spot));
		    }
		}

		free(c);

		if (last) {
		    last->next = next;
		} else {
		    companies->list[i] = next;
		}
	    } else {
		last = c;
	    }

	    c = next;
	}
    }    

    return 0;
}

int
companies_getsalary(companies_t *companies, int x, int y, enum labor_skill skill)
{
    return labor_table[skill].salary;
}
