#include <new>

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

#include <engine/e_server_interface.h>
#include <engine/e_config.h>
#include <game/mapitems.hpp>

#include "player.hpp"
#include "gamecontext.hpp"
#include "gamecontroller.hpp"
#include "statistics.hpp"

#include <string.h>
#include "gamemodes/fb.hpp"

MACRO_ALLOC_POOL_ID_IMPL(PLAYER, MAX_CLIENTS)

PLAYER::PLAYER(int client_id)
{
	respawn_tick = game_tick();
	character = 0;
	this->client_id = client_id;
	this->reset();

	num_owngoals = 0;
	owngoal_warned = false;
}

PLAYER::~PLAYER()
{
	delete character;
	character = 0;
}

void PLAYER::reset()
{
	this->frozen = false;

	this->melt_tick = -1;
	this->automelt_tick = -1;
	this->camp_tick = -1;
	this->spawn_tick = -1;
	this->freeze_tile_tick = -1;

	this->last_input = server_tick();

	this->camp_sent = false;

	this->being_thawed_sent = false;

	this->join_frozen = config.sv_join_as_frozen && game.controller->round_start_tick + server_tickspeed() * 10 <= game_tick();

	this->killstreak = 0;
	this->award = false;

	this->s_shots = 0;
	this->s_kills = 0;
	this->s_deaths = 0;
	this->s_hits = 0;
}

int PLAYER::is_melted()
{
	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(game.players[i])
		{
			if(!game.players[i]->frozen && i != client_id && team == game.players[i]->team && game.players[i]->get_character())
			{
				if(distance(game.players[i]->get_character()->pos, character->pos) <= (float)config.sv_melt_range)
					melters[i] = true;
				else
					melters[i] = false;
			}
			else
				melters[i] = false;
		}
		else
			melters[i] = false;
	}

	for(int i = 0; i < MAX_CLIENTS; i++)
	{
		if(melters[i])
			return i;
	}

	return -1;
}

void PLAYER::tick()
{
	if(get_character())
	{
		// Anticamper
		if(config.sv_anticamper && !frozen && !game.world.paused)
		{
			if(camp_tick == -1)
			{
				camp_pos = character->pos;
				camp_tick = game_tick() + server_tickspeed() * config.sv_anticamper_time;
			}

			if((camp_pos.x - character->pos.x >= (float)config.sv_anticamper_range || camp_pos.x - character->pos.x <= -1.0f * (float)config.sv_anticamper_range)
			|| (camp_pos.y - character->pos.y >= (float)config.sv_anticamper_range || camp_pos.y - character->pos.y <= -1.0f * (float)config.sv_anticamper_range))
			{
				camp_tick = -1;
			}

			if(camp_tick <= game_tick() + server_tickspeed() * 5 && camp_tick != -1 && !camp_sent)
			{
				// Last chance
				game.send_broadcast("ANTICAMPER: Move or die!", client_id);
				camp_sent = true;

			}

			if(camp_tick <= game_tick() && camp_tick != -1)
			{
				// Die camper naab! :D
				character->die(client_id, WEAPON_SELF);
				camp_tick = -1;
				camp_sent = false;
			}
		}
		else
		{
			camp_tick = -1;
			camp_sent = false;
		}

		// Teleport tiles
		int t_index = col_is_tele_start(character->pos.x, character->pos.y);

		if(t_index && config.sv_teleport_usetiles)
		{
			vec2 dest = col_get_dest(t_index);
			character->core.hook_state = HOOK_IDLE;
			character->core.hooked_player = -1;
			character->core.pos = dest;
		}

		// Booster tiles
		int b_index = col_is_booster(character->pos.x, character->pos.y);

		if(b_index && config.sv_booster_usetiles)
		{
			character->core.jumped = 1;

            int factor = 25;
			int dirs[5][2] = {{0, -1}, {1, 0}, {-1, 0}, {1, -0.5}, {-1, -0.5}}; 
			int type = b_index - 87;

			character->core.vel = vec2(dirs[type][0], dirs[type][1]) * factor;
		}

		if(game.controller->is_freeze)
		{
			int melter_id;
	
			// Melt
			if(frozen)
			{
				melter_id = is_melted();	

				if(melter_id >= 0)
				{
					if(melt_tick == -1)
					{
						game.create_sound(character->pos, SOUND_HAMMER_FIRE);
						character->emote_type = EMOTE_HAPPY;
						character->emote_stop = game_tick() + server_tickspeed();
						melt_tick = game_tick() + server_tickspeed() * config.sv_melt_time;
						being_thawed_sent = false;
					}
				}
				else if(!col_is_melt(character->pos.x, character->pos.y))
					melt_tick = -1;

				if(melt_tick <= game_tick() && melt_tick != -1)
					melt(melter_id);

				if(automelt_tick <= game_tick() && automelt_tick != -1 && game.controller->is_ctf)
				{
					if(get_character())
						game.create_sound(character->pos, SOUND_GRENADE_EXPLODE);

					remove_chr(true);
				}
				else if(automelt_tick <= game_tick() && automelt_tick != -1)
					melt(-1);

				if(melt_tick >= 0 && !being_thawed_sent)
				{
					char buf[128];
					str_format(buf, sizeof(buf), "You are being thawed.");
					game.send_broadcast(buf, client_id);
					being_thawed_sent = true;
				}
			}

			// Spawnprotect runout
			if(spawn_tick <= game_tick())
				spawn_tick = -1;

			// Frozen joining
			if(join_frozen)
			{
				game.controller->on_character_death(this, this, WEAPON_SELF);
				score += 1;
			}

			// General tile stuff
			int f_index = col_is_freeze(character->pos.x, character->pos.y);
			int m_index = col_is_melt(character->pos.x, character->pos.y);


			// Freeze tiles
			if(config.sv_freeze_usetiles)
			{
				if(!frozen && f_index && freeze_tile_tick == -1)
				{
					if(f_index != 46)
						game.send_broadcast("You're touching something cold...", client_id);

					freeze_tile_tick = game_tick() + (server_tickspeed() * (f_index - 45));
				}
				else if(!f_index || frozen)
					freeze_tile_tick = -1;

				if(freeze_tile_tick <= game_tick() && freeze_tile_tick != -1 && !frozen)
				{
					game.send_broadcast("You shouldn't touch that...", client_id);
					freeze_tile_tick = -1;
					character->die(client_id, WEAPON_SELF);
				}
			}


			// Melt tiles
			if(m_index && config.sv_melt_usetiles)
			{
				if(frozen && melt_tick == -1)
				{
					game.send_broadcast("It's warm here...", client_id);
					melt_tick = game_tick() + (server_tickspeed() * (m_index - 51));
				}

				if(!frozen && game_tick()%server_tickspeed() == 0 && config.sv_melt_tiles_degeneration)
				{
					vec2 null;
					null.x = 0;
					null.y = 0;
					character->take_damage(null ,config.sv_melt_tiles_degeneration, client_id, WEAPON_SELF);
				}
			}
			else if(melter_id == -1)
				melt_tick = -1;

			// Automelt display
			if(config.sv_automelt_display && frozen)
				character->armor = ((((float)automelt_tick - (float)game_tick()) / (float)server_tickspeed()) / config.sv_automelt_time) * 10;
		}
	}
	else
	{
		camp_tick = -1;
		melt_tick = -1;
		freeze_tile_tick = -1;
		automelt_tick = -1;
		camp_sent = false;
	}

	server_setclientscore(client_id, score);

	// do latency stuff
	{
		CLIENT_INFO info;
		if(server_getclientinfo(client_id, &info))
		{
			latency.accum += info.latency;
			latency.accum_max = max(latency.accum_max, info.latency);
			latency.accum_min = min(latency.accum_min, info.latency);
		}

		if(server_tick()%server_tickspeed() == 0)
		{
			latency.avg = latency.accum/server_tickspeed();
			latency.max = latency.accum_max;
			latency.min = latency.accum_min;
			latency.accum = 0;
			latency.accum_min = 1000;
			latency.accum_max = 0;
		}
	}

	if(!character && die_tick+server_tickspeed()*3 <= game_tick())
		spawning = true;

	if(character)
	{
		if(character->alive)
		{
			view_pos = character->pos;
		}
		else
		{
			delete character;
			character = 0;
		}
	}
	else if(spawning && respawn_tick <= game_tick())
		try_respawn();
}

void PLAYER::snap(int snapping_client)
{
	NETOBJ_CLIENT_INFO *client_info = (NETOBJ_CLIENT_INFO *)snap_new_item(NETOBJTYPE_CLIENT_INFO, client_id, sizeof(NETOBJ_CLIENT_INFO));
	str_to_ints(&client_info->name0, 6, server_clientname(client_id));
	str_to_ints(&client_info->skin0, 6, skin_name);
	client_info->use_custom_color = use_custom_color;
	client_info->color_body = color_body;
	client_info->color_feet = color_feet;

	NETOBJ_PLAYER_INFO *info = (NETOBJ_PLAYER_INFO *)snap_new_item(NETOBJTYPE_PLAYER_INFO, client_id, sizeof(NETOBJ_PLAYER_INFO));

	info->latency = latency.min;
	info->latency_flux = latency.max-latency.min;
	info->local = 0;
	info->cid = client_id;
	info->score = score;
	info->team = team;

	if(client_id == snapping_client)
		info->local = 1;	
}

void PLAYER::on_disconnect()
{
	kill_character(WEAPON_GAME);
	
	//game.controller->on_player_death(&game.players[client_id], 0, -1);
	
	//	update leaving player in gamecontroller (flagball)
	if (strstr (game.controller->gametype, "FB"))
	{
		static_cast<GAMECONTROLLER_FB *> (game.controller)->on_player_disconnect (this);
	}
		
	char buf[512];
	str_format(buf, sizeof(buf),  "%s has left the game", server_clientname(client_id));
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf);

	statistics_leave(client_id);

	dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id));
}

void PLAYER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input)
{
	if(frozen)
		return;

	CHARACTER *chr = get_character();
	if(chr)
		chr->on_predicted_input(new_input);
	is_chatting = new_input->player_state == PLAYERSTATE_CHATTING;
}

void PLAYER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input)
{
	if(frozen)
	{
		view_pos = vec2(new_input->target_x, new_input->target_y);
		return;
	}

	CHARACTER *chr = get_character();
	if(chr)
		chr->on_direct_input(new_input);

	if(!chr && !game.world.paused && team >= 0 && (new_input->fire&1))
		spawning = true;
	
	if(!chr && team == -1)
		view_pos = vec2(new_input->target_x, new_input->target_y);
	is_chatting = new_input->player_state == PLAYERSTATE_CHATTING;
}

CHARACTER *PLAYER::get_character()
{
	if(character && character->alive)
		return character;
	return 0;
}

void PLAYER::kill_character(int weapon)
{
	//CHARACTER *chr = get_character();
	if(character)
	{
		character->die(client_id, weapon);

		if(!game.controller->is_freeze)
		{
			delete character;
			character = 0;
		}
	}
}

void PLAYER::respawn()
{
	if(team > -1)
		spawning = true;
}

void PLAYER::set_team(int new_team)
{
	// clamp the team
	new_team = game.controller->clampteam(new_team);
	if(team == new_team)
		return;
		
	char buf[512];
	str_format(buf, sizeof(buf), "%s joined the %s", server_clientname(client_id), game.controller->get_team_name(new_team));
	game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf); 
	
	if(get_character())
	{
		get_character()->die(client_id, WEAPON_GAME);
		delete character;
		character = 0;
	}
	
	frozen = false;

	team = new_team;
	score = 0;
	dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), team);

	statistics_switch(client_id);
	
	game.controller->on_player_info_change(game.players[client_id]);
}

void PLAYER::try_respawn()
{
	vec2 spawnpos = vec2(100.0f, -60.0f);
	
	if(!game.controller->can_spawn(this, &spawnpos))
		return;

	// check if the position is occupado
	ENTITY *ents[2] = {0};
	int num_ents = game.world.find_entities(spawnpos, 64, ents, 2, NETOBJTYPE_CHARACTER);
	
	if(num_ents == 0)
	{
		spawning = false;
		character = new(client_id) CHARACTER();
		character->spawn(this, spawnpos, team);
		game.create_playerspawn(spawnpos);
	}
}

void PLAYER::melt(int melter_id)
{
	if(melter_id >= 0)
	{
		if(game.players[melter_id])
		{
			if(game.players[melter_id]->get_character())
			{
				game.players[melter_id]->score++;

				char buf[128];
				str_format(buf, sizeof(buf), "You melted %s.", server_clientname(client_id));
				game.send_broadcast(buf, melter_id);

				str_format(buf, sizeof(buf), "%s melted you.", server_clientname(melter_id));
				game.send_broadcast(buf, client_id);

				game.players[melter_id]->get_character()->emote_type = EMOTE_HAPPY;
				game.players[melter_id]->get_character()->emote_stop = game_tick() + server_tickspeed();
			}
		}
	}

	if(get_character())
		game.create_sound(character->pos, SOUND_GRENADE_EXPLODE);

	remove_chr(false);
}

void PLAYER::remove_chr(bool strict)
{
	if(get_character())
	{
		automelt_tick = -1;
		melt_tick = -1;

		if(frozen && config.sv_frozen_tags)
			server_setclientname(client_id, start_name);

		frozen = false;

		if(config.sv_melt_respawn || strict)
		{
			character->alive = false;

			// this is for auto respawn after 3 secs
			die_tick = game_tick();

			game.world.remove_entity(character);
			game.world.core.characters[client_id] = 0;
			game.create_death(character->pos, client_id);
	
			// we got to wait 0.5 secs before respawning
			respawn_tick = game_tick()+server_tickspeed()/2;

			return;
		}

		character->health = config.sv_melt_health;
		character->armor = 0;
		character->set_weapon(WEAPON_RIFLE);
	}
}

void PLAYER::give(int type)
{
	switch(type)
	{
	case 1:
		if(get_character())
		{
			get_character()->weapons[WEAPON_GRENADE].got = true;
			if(config.sv_grenade_split)
				get_character()->weapons[WEAPON_GRENADE].ammo++;
			else
				get_character()->weapons[WEAPON_GRENADE].ammo = min(data->weapons.id[WEAPON_GRENADE].maxammo, get_character()->weapons[WEAPON_GRENADE].ammo + 10);
		}
		break;
		

	case 2:
		award = true;
		break;
	}
}
