/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <string.h>
#include <stdio.h>
#include <engine/e_config.h>
#include <engine/e_server_interface.h>
#include <game/mapitems.hpp>

#include <game/generated/g_protocol.hpp>

#include "entities/pickup.hpp"
#include "gamecontroller.hpp"
#include "gamecontext.hpp"
#include "statistics.hpp"

#include <engine/e_perl.h>


GAMECONTROLLER::GAMECONTROLLER()
{
	gametype = "unknown";
	
	//
	do_warmup(config.sv_warmup);
	game_over_tick = -1;
	sudden_death = 0;
	round_start_tick = game_tick();
	paused_ticks = 0;
	round_count = 0;
	game_flags = 0;
	teamscore[0] = 0;
	teamscore[1] = 0;
	map_wish[0] = 0;
	
	unbalanced_tick = -1;
	force_balanced = false;
	instagib = false;
        flagball = false;
	
	num_spawn_points[0] = 0;
	num_spawn_points[1] = 0;
	num_spawn_points[2] = 0;

	locked[0] = 0;
	locked[1] = 0;
}

GAMECONTROLLER::~GAMECONTROLLER()
{
}

float GAMECONTROLLER::evaluate_spawn_pos(SPAWNEVAL *eval, vec2 pos)
{
	float score = 0.0f;
	CHARACTER *c = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER);
	for(; c; c = (CHARACTER *)c->typenext())
	{
		// team mates are not as dangerous as enemies
		float scoremod = 1.0f;
		if(eval->friendly_team != -1 && c->team == eval->friendly_team)
			scoremod = 0.5f;
			
		float d = distance(pos, c->pos);
		if(d == 0)
			score += 1000000000.0f;
		else
			score += 1.0f/d;
	}
	
	return score;
}

void GAMECONTROLLER::evaluate_spawn_type(SPAWNEVAL *eval, int t)
{
	// get spawn point
	for(int i  = 0; i < num_spawn_points[t]; i++)
	{
		vec2 p = spawn_points[t][i];
		float s = evaluate_spawn_pos(eval, p);
		if(!eval->got || eval->score > s)
		{
			eval->got = true;
			eval->score = s;
			eval->pos = p;
		}
	}
}

bool GAMECONTROLLER::can_spawn(PLAYER *player, vec2 *out_pos)
{
	SPAWNEVAL eval;
	
	// spectators can't spawn
	if(player->team == -1)
		return false;
	
	if(is_teamplay())
	{
		eval.friendly_team = player->team;
		
		// try first try own team spawn, then normal spawn and then enemy
		evaluate_spawn_type(&eval, 1+(player->team&1));
		if(!eval.got)
		{
			evaluate_spawn_type(&eval, 0);
			if(!eval.got)
				evaluate_spawn_type(&eval, 1+((player->team+1)&1));
		}
	}
	else
	{
		evaluate_spawn_type(&eval, 0);
		evaluate_spawn_type(&eval, 1);
		evaluate_spawn_type(&eval, 2);
	}
	
	*out_pos = eval.pos;
	return eval.got;
}


bool GAMECONTROLLER::on_entity(int index, vec2 pos)
{
	int type = -1;
	int subtype = 0;
	
	if(index == ENTITY_SPAWN)
		spawn_points[0][num_spawn_points[0]++] = pos;
	else if(index == ENTITY_SPAWN_RED)
		spawn_points[1][num_spawn_points[1]++] = pos;
	else if(index == ENTITY_SPAWN_BLUE)
		spawn_points[2][num_spawn_points[2]++] = pos;
	else if(index == ENTITY_ARMOR_1)
		type = POWERUP_ARMOR;
	else if(index == ENTITY_HEALTH_1)
		type = POWERUP_HEALTH;
	else if(index == ENTITY_WEAPON_SHOTGUN)
	{
		type = POWERUP_WEAPON;
		subtype = WEAPON_SHOTGUN;
	}
	else if(index == ENTITY_WEAPON_GRENADE)
	{
		type = POWERUP_WEAPON;
		subtype = WEAPON_GRENADE;
	}
	else if(index == ENTITY_WEAPON_RIFLE)
	{
		type = POWERUP_WEAPON;
		subtype = WEAPON_RIFLE;
	}
	else if(index == ENTITY_POWERUP_NINJA && config.sv_powerups)
	{
		type = POWERUP_NINJA;
		subtype = WEAPON_NINJA;
	}
	
	if(type != -1 && (!is_instagib() || (config.sv_grenade_split && subtype == WEAPON_GRENADE)))
	{
		PICKUP *pickup = new PICKUP(type, subtype);
		pickup->pos = pos;
		return true;
	}

	return false;
}

void GAMECONTROLLER::endround()
{
	if(warmup) // game can't end when we are running warmup
		return;
		
	game.world.paused = server_tick();
	game_over_tick = game_tick();
	sudden_death = 0;
	statistics_end_round();
        perl_server_end_round();
}

void GAMECONTROLLER::resetgame()
{
	game.world.reset_requested = true;
}

const char *GAMECONTROLLER::get_team_name(int team)
{
	if(is_teamplay())
	{
		if(team == 0)
			return "red team";
		else if(team == 1)
			return "blue team";
	}
	else
	{
		if(team == 0)
			return "game";
	}
	
	return "spectators";
}

static bool is_separator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; }

void GAMECONTROLLER::startround()
{
	warmup = 0;
	resetgame();
	
	round_start_tick = game_tick();
	sudden_death = 0;
	game_over_tick = -1;
	game.world.paused = 0;
	paused_ticks = 0;
	teamscore[0] = 0;
	teamscore[1] = 0;
	unbalanced_tick = -1;
	force_balanced = false;
	statistics_start_round();
        perl_server_start_round();
	dbg_msg("game","start round type='%s' teamplay='%d'", gametype, game_flags&GAMEFLAG_TEAMS);
}

void GAMECONTROLLER::change_map(const char *to_map)
{
	str_copy(map_wish, to_map, sizeof(map_wish));
	endround();
}

void GAMECONTROLLER::cyclemap()
{
	if(perl_cyclemap() == 0)
		return;
	if(map_wish[0] != 0)
	{
		dbg_msg("game", "rotating map to %s", map_wish);
		str_copy(config.sv_map, map_wish, sizeof(config.sv_map));
		map_wish[0] = 0;
		round_count = 0;
		return;
	}
	if(!strlen(config.sv_maprotation))
		return;

	if(round_count < config.sv_rounds_per_map-1)
		return;
		
	// handle maprotation
	const char *map_rotation = config.sv_maprotation;
	const char *current_map = config.sv_map;
	
	int current_map_len = strlen(current_map);
	const char *next_map = map_rotation;
	while(*next_map)
	{
		int wordlen = 0;
		while(next_map[wordlen] && !is_separator(next_map[wordlen]))
			wordlen++;
		
		if(wordlen == current_map_len && strncmp(next_map, current_map, current_map_len) == 0)
		{
			// map found
			next_map += current_map_len;
			while(*next_map && is_separator(*next_map))
				next_map++;
				
			break;
		}
		
		next_map++;
	}
	
	// restart rotation
	if(next_map[0] == 0)
		next_map = map_rotation;

	// cut out the next map	
	char buf[512];
	for(int i = 0; i < 512; i++)
	{
		buf[i] = next_map[i];
		if(is_separator(next_map[i]) || next_map[i] == 0)
		{
			buf[i] = 0;
			break;
		}
	}
	
	// skip spaces
	int i = 0;
	while(is_separator(buf[i]))
		i++;
	
	round_count = 0;
	
	dbg_msg("game", "rotating map to %s", &buf[i]);
	str_copy(config.sv_map, &buf[i], sizeof(config.sv_map));
}

void GAMECONTROLLER::post_reset()
{
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			game.players[i]->respawn();
			game.players[i]->score = 0;
		}
	}
}
	
void GAMECONTROLLER::on_player_info_change(class PLAYER *p)
{
	const int team_colors[2] = {65387, 10223467};

	if(is_teamplay())
	{
		if(p->team >= 0 || p->team <= 1)
		{
			p->use_custom_color = 1;
			p->color_body = team_colors[p->team];
			p->color_feet = team_colors[p->team];
		}
	}
}


int GAMECONTROLLER::on_character_death(class PLAYER *victim, class PLAYER *killer, int weapon)
{
	victim->s_deaths++;

	// do scoreing
	if(config.sv_killingspree && (victim->award || config.sv_killingspree_kills <= victim->killstreak))
	{
		char buf[128];

		str_format(buf, sizeof(buf), "%s's killing spree\n(%d kills)\nhas ended by %s", server_clientname(victim->client_id), victim->killstreak, server_clientname(killer->client_id));
		game.send_broadcast(buf, -1);

		server_setclientname(victim->client_id, victim->start_name);
		victim->award = false;
	}

	victim->killstreak = 0;

	if(killer == victim)
	{
		if(!victim->frozen) // Not frozen
			victim->score--; // suicide
	}
	else
	{
		if(is_teamplay() && victim->team == killer->team)
			killer->score--; // teamkill
		else
		{
			killer->s_kills++;
			killer->score++; // normal kill
			killer->killstreak++;

			statistics_kill(killer->client_id, victim->client_id);

			if(config.sv_killingspree && killer->killstreak % config.sv_killingspree_kills == 0)
			{
				char buf[128];
	
				char spree_note[5][32] = { "on a killing spree", "on a rampage", "dominating", "unstoppable", "godlike" };
				int kspreemsgtype = (killer->killstreak / config.sv_killingspree_kills) - 1;
				if(kspreemsgtype > 4) kspreemsgtype = 4;

				str_format(buf, sizeof(buf), "%s is %s (%d kills)", server_clientname(killer->client_id), spree_note[kspreemsgtype], killer->killstreak);
				game.send_broadcast(buf, -1);

				char tmp[MAX_NAME_LENGTH];

				if(config.sv_killingspree_tag)
				{
					str_format(tmp, sizeof(tmp),"[SPREE] ");
					strncat(tmp, killer->start_name,MAX_NAME_LENGTH-9);
					tmp[MAX_NAME_LENGTH]=0;
					server_setclientname(killer->client_id, tmp);
				}

				if(config.sv_killingspree_award)
					killer->award = true;
			}

			return 0;
		}
	}

	return 0;
}

void GAMECONTROLLER::on_character_spawn(class CHARACTER *chr)
{
	// default health
	chr->health = 10;
	chr->player->spawn_tick = game_tick() + (float)server_tickspeed() * ((float)config.sv_spawnprotect_time / 1000.0f);
	
	// give default weapons
	if(is_instagib())
	{
		chr->weapons[WEAPON_RIFLE].got = 1;
		chr->weapons[WEAPON_RIFLE].ammo = -1;
	}
	else
	{
		chr->weapons[WEAPON_HAMMER].got = 1;
		chr->weapons[WEAPON_HAMMER].ammo = -1;
		chr->weapons[WEAPON_GUN].got = 1;
		chr->weapons[WEAPON_GUN].ammo = 10;
	}
}

void GAMECONTROLLER::do_warmup(int seconds, void (GAMECONTROLLER::*action)())
{
	warmup = seconds*server_tickspeed();
	warmup_action = action;
}

bool GAMECONTROLLER::is_friendly_fire(int cid1, int cid2)
{
	if(cid1 == cid2)
		return false;
	
	if(is_teamplay())
	{
		if(!game.players[cid1] || !game.players[cid2])
			return false;
			
		if(game.players[cid1]->team == game.players[cid2]->team)
			return true;
	}
	
	return false;
}

bool GAMECONTROLLER::is_force_balanced()
{
	if(force_balanced)
	{
		force_balanced = false;
		return true;
	}
	else
		return false;
}

void GAMECONTROLLER::tick()
{
	// Inactivity kick
	if(!game.world.paused && server_tick()%server_tickspeed() == 0 && config.sv_inactivity_kick)
	{
		int delay = config.sv_inactivity_kick * server_tickspeed();
		int now = server_tick();
		if(now - last_paused > delay)
			for(int i = 0; i < MAX_CLIENTS; i++)
				if(game.players[i] && game.players[i]->team != -1 && game.players[i]->last_input != -1 && now - game.players[i]->last_input >= delay)
					if(!config.sv_inactivity_kick_spect)
						server_kick(i, "Inactivity kick. Please don't go away in games.");
					else 
						game.players[i]->set_team(-1);
	}

	// Chatloop
	if(!game.world.paused)
		if(config.sv_chatloop_interval && server_tick() % config.sv_chatloop_interval == 0 && server_tick() > server_tickspeed() * 5)
			game.send_chat(-1, -2, config.sv_chatloop_message);

	// do warmup
	if(warmup)
	{
		warmup--;
		if(!warmup && warmup_action)
                	(this->*warmup_action)();
	}
	
	if(game_over_tick != -1)
	{
		// game over.. wait for restart
		if(game_tick() > game_over_tick+server_tickspeed()*10)
		{
			cyclemap();
			startround();
			round_count++;
		}
	}
	
	// do team-balancing
	if (is_teamplay() && unbalanced_tick != -1 && game_tick() > unbalanced_tick+config.sv_teambalance_time*server_tickspeed()*60 && config.sv_teambalance)
	{
		dbg_msg("game", "Balancing teams");
		
		int t[2] = {0,0};
		int tscore[2] = {0,0};
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(game.players[i] && game.players[i]->team != -1)
			{
				t[game.players[i]->team]++;
				tscore[game.players[i]->team]+=game.players[i]->score;
			}
		}
		
		// are teams unbalanced?
		if(abs(t[0]-t[1]) >= 2)
		{
			int m = (t[0] > t[1]) ? 0 : 1;
			int num_balance = abs(t[0]-t[1]) / 2;
			
			do
			{
				PLAYER *p = 0;
				int pd = tscore[m];
				for(int i = 0; i < MAX_CLIENTS; i++)
				{
					if(!game.players[i])
						continue;
					
					// remember the player who would cause lowest score-difference
					if(game.players[i]->team == m && (!p || abs((tscore[m^1]+game.players[i]->score) - (tscore[m]-game.players[i]->score)) < pd))
					{
						p = game.players[i];
						pd = abs((tscore[m^1]+p->score) - (tscore[m]-p->score));
					}
				}
				
				// move the player to other team without losing his score
				// TODO: change in player::set_team needed: player won't lose score on team-change
				int score_before = p->score;
				p->set_team(m^1);
				p->score = score_before;
				
				p->respawn();
				p->force_balanced = true;
			} while (--num_balance);
			
			force_balanced = true;
		}
		unbalanced_tick = -1;
	}
	
	// update browse info
	int prog = -1;
	if(config.sv_timelimit > 0)
		prog = max(prog, (game_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60));

	if(config.sv_scorelimit)
	{
		if(is_teamplay())
		{
			prog = max(prog, (teamscore[0]*100)/config.sv_scorelimit);
			prog = max(prog, (teamscore[1]*100)/config.sv_scorelimit);
		}
		else
		{
			for(int i = 0; i < MAX_CLIENTS; i++)
			{
				if(game.players[i])
					prog = max(prog, (game.players[i]->score*100)/config.sv_scorelimit);
			}
		}
	}

	if(warmup)
		prog = -1;
		
	server_setbrowseinfo(gametype, prog);
}

void GAMECONTROLLER::make_instagib(char *new_gametype)
{
	instagib = true;
	gametype = new_gametype;
}

void GAMECONTROLLER::make_vanilla(char *new_gametype)
{
	instagib = false;
	gametype = new_gametype;
}

bool GAMECONTROLLER::is_instagib() const
{
	return instagib;
}

bool GAMECONTROLLER::is_flagball() const
{
	return flagball;
}

bool GAMECONTROLLER::is_teamplay() const
{
	return game_flags&GAMEFLAG_TEAMS;
}

void GAMECONTROLLER::snap(int snapping_client)
{
	NETOBJ_GAME *gameobj = (NETOBJ_GAME *)snap_new_item(NETOBJTYPE_GAME, 0, sizeof(NETOBJ_GAME));
	gameobj->paused = game.world.paused;
	gameobj->game_over = game_over_tick==-1?0:1;
	gameobj->sudden_death = sudden_death;
	
	gameobj->score_limit = config.sv_scorelimit;
	gameobj->time_limit = config.sv_timelimit;
	gameobj->round_start_tick = round_start_tick + paused_ticks + (!game.world.paused ? 0 : server_tick() - game.world.paused);
	gameobj->flags = game_flags;
	
	gameobj->warmup = warmup;
	
	gameobj->round_num = (strlen(config.sv_maprotation) && config.sv_rounds_per_map) ? config.sv_rounds_per_map : 0;
	gameobj->round_current = round_count+1;
	
	
	if(snapping_client == -1)
	{
		// we are recording a demo, just set the scores
		gameobj->teamscore_red = teamscore[0];
		gameobj->teamscore_blue = teamscore[1];
	}
	else
	{
		// TODO: this little hack should be removed
		gameobj->teamscore_red = is_teamplay() ? teamscore[0] : game.players[snapping_client]->score;
		gameobj->teamscore_blue = teamscore[1];
	}
}

int GAMECONTROLLER::get_auto_team(int notthisid)
{
	// this will force the auto balancer to work overtime aswell
	if(config.dbg_stress)
		return 0;

	if(config.sv_tournament_mode)
		return -1;
	
	int numplayers[2] = {0,0};
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i] && i != notthisid)
		{
			if(game.players[i]->team == 0 || game.players[i]->team == 1)
				numplayers[game.players[i]->team]++;
		}
	}

	int team = 0;
	if(is_teamplay())
		team = numplayers[0] > numplayers[1] ? 1 : 0;
		
	if(can_join_team(team, notthisid))
		return team;
	return -1;
}

bool GAMECONTROLLER::can_join_team(int team, int notthisid)
{
	(void)team;
	int numplayers[2] = {0,0};
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i] && i != notthisid)
		{
			if(game.players[i]->team >= 0 || game.players[i]->team == 1)
				numplayers[game.players[i]->team]++;
		}
	}
	
	return (numplayers[0] + numplayers[1]) < config.sv_max_clients-config.sv_spectator_slots;
}

bool GAMECONTROLLER::check_team_balance()
{
	if(!is_teamplay() || !config.sv_teambalance_time || !config.sv_teambalance)
		return true;
	
	int t[2] = {0, 0};
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		PLAYER *p = game.players[i];
		if(p && p->team != -1)
			t[p->team]++;
	}
	
	if(abs(t[0]-t[1]) >= 2)
	{
		dbg_msg("game", "Team is NOT balanced (red=%d blue=%d)", t[0], t[1]);
		if (game.controller->unbalanced_tick == -1)
			game.controller->unbalanced_tick = game_tick();
		return false;
	}
	else
	{
		dbg_msg("game", "Team is balanced (red=%d blue=%d)", t[0], t[1]);
		game.controller->unbalanced_tick = -1;
		return true;
	}
}

bool GAMECONTROLLER::can_change_team(PLAYER *pplayer, int jointeam)
{
	int t[2] = {0, 0};
	
	if (!is_teamplay() || jointeam == -1 || !config.sv_teambalance_time)
		return true;
	
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		PLAYER *p = game.players[i];
		if(p && p->team != -1)
			t[p->team]++;
	}
	
	// simulate what would happen if changed team
	t[jointeam]++;
	if (pplayer->team != -1)
		t[jointeam^1]--;
	
	// there is a player-difference of at least 2
	if(abs(t[0]-t[1]) >= 2 && config.sv_teambalance)
	{
		// player wants to join team with less players
		if ((t[0] < t[1] && jointeam == 0) || (t[0] > t[1] && jointeam == 1))
			return true;
		else
			return false;
	}
	else
		return true;
}

void GAMECONTROLLER::do_player_score_wincheck()
{
	if(game_over_tick == -1  && !warmup)
	{
		// gather some stats
		int topscore = 0;
		int topscore_count = 0;
		for(int i = 0; i < MAX_CLIENTS; i++)
		{
			if(game.players[i])
			{
				if(game.players[i]->score > topscore)
				{
					topscore = game.players[i]->score;
					topscore_count = 1;
				}
				else if(game.players[i]->score == topscore)
					topscore_count++;
			}
		}
		
		// check score win condition
		if((config.sv_scorelimit > 0 && topscore >= config.sv_scorelimit) ||
			(config.sv_timelimit > 0 && (game_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60))
		{
			if(topscore_count == 1)
				endround();
			else
				sudden_death = 1;
		}
	}
}

void GAMECONTROLLER::do_team_score_wincheck()
{
	if(game_over_tick == -1 && !warmup)
	{
		// check score win condition
		if((config.sv_scorelimit > 0 && (teamscore[0] >= config.sv_scorelimit || teamscore[1] >= config.sv_scorelimit)) ||
			(config.sv_timelimit > 0 && (game_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60))
		{
			if(teamscore[0] != teamscore[1])
				endround();
			else
				sudden_death = 1;
		}
	}
}

int GAMECONTROLLER::clampteam(int team)
{
	if(team < 0) // spectator
		return -1;
	if(is_teamplay())
		return team&1;
	return  0;
}

void GAMECONTROLLER::unpause() 
{
	last_paused = server_tick();
	paused_ticks += last_paused - game.world.paused;
	game.world.paused = 0;
	server_pause(0);
}

void GAMECONTROLLER::pause(int state)
{
	if(game.world.paused)
	{
		if(state > 0 && warmup && warmup_action == &GAMECONTROLLER::unpause)
			warmup = 0;
		else if(state <= 0)
			if(config.sv_pause_warmup)
				do_warmup(config.sv_pause_warmup, &GAMECONTROLLER::unpause);
			else
				unpause();
	}
	else if(state != 0)
	{
		game.world.paused = server_tick();
		server_pause(1);
	}
}

GAMECONTROLLER *gamecontroller = 0;
