#include <string.h>
#include <engine/e_config.h>
#include <engine/e_server_interface.h>
#include <game/mapitems.hpp>
#include <game/server/entities/character.hpp>
#include <game/server/player.hpp>
#include <game/server/gamecontext.hpp>
#include "fb.hpp"

GAMECONTROLLER_FB::GAMECONTROLLER_FB ()
{
	balls[0] = 0;
	balls[1] = 0;
	goal_pos[0] = vec2 (0, 0);
	goal_pos[1] = vec2 (0, 0);
	num_goals = 0;
        flagball = true;
}

bool GAMECONTROLLER_FB::on_entity (int index, vec2 pos)
{
	//	don't add pickups in instagib
	if (strcmp (gametype, "iFB") == 0 && index > ENTITY_FLAGSTAND_BLUE)
		return false;
		
	if (GAMECONTROLLER::on_entity (index, pos))
		return true;
	
	//	add goal positions and balls
	switch (index)
	{
	case ENTITY_SPAWN: if (num_goals < FB_MAX_GOALS) goal_pos[num_goals++] = pos; break;
	case ENTITY_FLAGSTAND_RED: if (!balls[0]) game.world.insert_entity (balls[0] = new BALL (0, pos)); break;
	case ENTITY_FLAGSTAND_BLUE: if (!balls[1]) game.world.insert_entity (balls[1] = new BALL (1, pos)); break;
	default: return false;
	}

	return true;
}

int GAMECONTROLLER_FB::on_character_death(class PLAYER *victim, class PLAYER *killer, int weaponid)
{
	GAMECONTROLLER::on_character_death(victim, killer, weaponid);

	//	add fb-score and killing message flags
	int had_flag = 0;
	for (int i = 0; i < FB_MAX_BALLS; i++)
	{
		BALL *b = balls[i];
		if (!b || !b->carrier)
			continue;

		if (killer && b->carrier == killer->get_character())
			had_flag |= 2;
		if (b->carrier->player == victim)
		{
			//	drop ball
			game.create_sound_global (SOUND_CTF_DROP);
			b->drop_tick = game_tick ();
			b->last_carrier = victim;
			b->last_carrier_team = victim->team;
                        b->carrier->ball = NULL;
			b->carrier = 0;
			b->vel = vec2 (0, 0);

			if (killer && killer->team != victim->team)
				killer->score++;
				
			had_flag |= 1;
		}
	}

	return had_flag;
}

bool GAMECONTROLLER_FB::handle_goal (BALL *b, int goal)
{
	PLAYER *const p = b->carrier ? b->carrier->player : b->last_carrier;
	const int team = b->last_carrier_team;
	const bool own_goal = team == goal;
	const bool tee_goal = b->carrier ? true : false;
	
	//	update scores
	if (own_goal)
	{
		if (!config.sv_fb_owngoal)
			return false;
		
		if (p)
		{
			p->score -= config.sv_fb_owngoal_penalty;
			p->num_owngoals++;
		
			// send warning to user
			char chat_msg[256];
			str_format (chat_msg, sizeof (chat_msg), "!!! Hey %s, you scored an own-goal! Put the ball into the OTHER goal !!!",
				server_clientname (p->client_id));
			game.send_chat_target (p->client_id, chat_msg);
		}
		// there's no sound like "wah...wah" ;)
		game.create_sound_global (SOUND_NINJA_HIT);  //SOUND_PLAYER_PAIN_LONG
	}
	else
	{
		if (p)
			p->score += tee_goal ? config.sv_fb_score_player_tee : config.sv_fb_score_player_ball;
		game.create_sound_global (SOUND_CTF_CAPTURE);
	}
	teamscore[goal ^ 1] += tee_goal ? config.sv_fb_score_team_tee : config.sv_fb_score_team_ball;
		
	// let the tee die if it jumped into goal... or when dumb own-goal
	if (p && (tee_goal || own_goal))
		p->kill_character (WEAPON_GAME);
	b->reset();
	
	// Cut off long nicknames... the message would jut out on screen!
	const int nick_length = 18;
	char nick[64] = {0};
	str_format (nick, sizeof (nick), "%s", p ? server_clientname (p->client_id) : "player left");
	if (str_length (nick) > nick_length)
	{
		nick[nick_length] = 0;
		str_append (nick, "...", sizeof (nick));
	}

	// Build broadcast-message
	char bc_msg[512] = {0};
	str_format (bc_msg, sizeof (bc_msg), "%s team (%s) scored with %s!", (goal ^ 1) == 0 ? "Red" : "Blue",
		nick, tee_goal ? "tee": "ball");

	// wired workaround for multiline broadcast-message center alignment
	// FIXME: can be done smarter!
	if (own_goal)
	{
		char line2[] = "\nPwned... that was an own-goal!";
		const int fillup = 2 * (str_length (bc_msg) - (str_length (line2) - 1));
		str_append (bc_msg, line2, sizeof (bc_msg));

		if (fillup > 0)
		{
			char *cur_pos = bc_msg + str_length (bc_msg);
			for (int l = 0; l < fillup; l++)
				*cur_pos++ = ' ';
			*cur_pos = 0;
		}
	}
	// send everyone the scoring-message
	game.send_broadcast(bc_msg, -1);

	// kick unteachable player from server
	if (config.sv_fb_owngoal_kick && p && p->num_owngoals >= config.sv_fb_owngoal_kick)
	{
		char chat_msg[256];
		str_format (chat_msg, sizeof (chat_msg), "%s was automatically kicked. Cause: Scored too many own-goals (%d).",
			server_clientname (p->client_id), config.sv_fb_owngoal_kick);
			
		server_kick (p->client_id,
			"You scored too many own-goals! Please play the "
			"game as it is supposed to be. Thank you!");
		
		game.send_chat (-1, GAMECONTEXT::CHAT_ALL, chat_msg);
	}

	return true;
}

void GAMECONTROLLER_FB::on_player_disconnect (const PLAYER *const p)
{
	for (int i = 0; i < FB_MAX_BALLS; ++i)
	{
		if (balls[i] && !balls[i]->at_stand)
		{
			if (balls[i]->carrier && p == balls[i]->carrier->player)
				balls[i]->carrier = NULL;
			if (p == balls[i]->last_carrier)
				balls[i]->last_carrier = NULL;
		}
	}
}

void GAMECONTROLLER_FB::tick()
{
	GAMECONTROLLER::tick();

	do_team_score_wincheck();

	if (game_over_tick == -1 && round_start_tick != game_tick ())
	{
		
		for (int i = 0; i < MAX_CLIENTS; ++i)
		{
			if (!game.players[i]) continue;

			CHARACTER *ch = game.players[i]->get_character ();
			if (!ch || !ch->alive)
				continue;

			// Check for a goal-camper
			if (config.sv_fb_goalcamp)
			{
				bool in_range = false;
				for (int goal = 0; goal < num_goals; ++goal)
				{
					if (distance (ch->pos, goal_pos[goal]) <
						config.sv_fb_goalsize * static_cast<float> (config.sv_fb_goalcamp_factor / 10.0f))  //	1.5f
					{
						// if in range, count up 2 ticks
						ch->goal_camp_tick += 2;
						in_range = true;
						break;
					}
				}
		
				if (!in_range)
				{
					// if out of range, count down just 1 tick again (slower)
					if (ch->goal_camp_tick)
						ch->goal_camp_tick--;
				}
				else
				{	
					//	kill goal camping player
					if ((ch->goal_camp_tick >> 1) >= server_tickspeed () * config.sv_fb_goalcamp_time)
					{
						game.players[i]->kill_character (WEAPON_GAME);
				
						char chat_msg[128];
						str_format (chat_msg, sizeof (chat_msg), "%s was caught camping near a goal...",
							server_clientname(game.players[i]->client_id));
						game.send_chat(-1, -2, chat_msg);
						continue;
					}
				}
			}

		}

		//	update balls
		for (int bi = 0; bi < FB_MAX_BALLS; ++bi)
		{
			BALL *b = balls[bi];

			if (!b)
				continue;

			//	update ball position
			if (b->carrier)
				b->pos = b->carrier->pos;
			else if (!b->at_stand)
				{	
					//	ball hits death tile
					if (b->idle_tick == -1 && col_get (static_cast<int> (b->pos.x), static_cast<int> (b->pos.y)) & COLFLAG_DEATH)
						b->idle_tick = config.sv_fb_ball_deathtile_idle * server_tickspeed ();

					//	flag reset delay (after hitting a death tile)
					if (b->idle_tick > 0)
					{
						b->idle_tick--;
						continue;
					}

					if (game_tick () >= b->drop_tick + server_tickspeed () * config.sv_fb_ball_reset_time || !b->idle_tick)
					{
						//	reset ball
						game.create_sound_global (SOUND_CTF_RETURN);
						b->reset ();
					}
					else if(!game.world.paused)
					{
						// do ball physics
						b->vel.y += game.world.core.tuning.gravity;

						if (b->vel.x > -0.1f && b->vel.x < 0.1f) 
							b->vel.x = 0.0f;
						else if (b->vel.x < 0)
							b->vel.x += 0.055f;
						else
							b->vel.x -= 0.055f;

						move_box (&b->pos, &b->vel, vec2 (b->phys_size, b->phys_size), 0.5f);
					}

				}

			//	check/handle goal
			bool is_goal = false;
			for (int g = 0; g < num_goals; g++)
			{
				if (distance(b->pos, goal_pos[g]) < config.sv_fb_goalsize)
				{
					is_goal = handle_goal(b, g);
					break;
				}
			}
			if (is_goal)
				continue;

			if (b->carrier)
			{	//	ball is carried by player

				//	warn ball-carrying player once when he is near his own goal
				if (config.sv_fb_owngoal_warn && !b->carrier->player->owngoal_warned && b->carrier->team < num_goals &&
					(distance(b->carrier->pos, goal_pos[b->carrier->team]) < config.sv_fb_goalsize * 5))
				{
					game.send_broadcast(
						"Attention! This is your team's goal\n"
						"you are approaching. You probably\n"
						"want to go to the other side!",
						b->carrier->player->client_id);

					// play the server-chat sound
					game.create_sound(b->carrier->pos, SOUND_CHAT_SERVER, cmask_one(b->carrier->player->client_id));

					b->carrier->player->owngoal_warned = true;
				}
			}
			else
			{	//	ball is carried by none

				//	small delay for throwing ball a bit away before fetching again
				if (b->at_stand || game_tick () >= b->drop_tick + server_tickspeed () * 0.2f)
				{
					//	find all players that can grab the ball
					CHARACTER *close_characters[MAX_CLIENTS];
					const int max_num = game.world.find_entities (b->pos, 32.0f, reinterpret_cast<ENTITY**> (close_characters), MAX_CLIENTS, NETOBJTYPE_CHARACTER);
					int curr_num = 0;
					for (int i = 0; i < max_num; ++i)
					{
						if (!close_characters[i]->alive || close_characters[i]->team == -1 || col_intersect_line (b->pos, close_characters[i]->pos, NULL, NULL) ||
							balls[bi ^ 1]->carrier == close_characters[i]) // don't take flag if already have one (might be useful with two flags)
							continue;
						close_characters[curr_num++] = close_characters[i];
					}
					
					if (!curr_num)
						continue;

					//	grab the ball
					CHARACTER *ch = close_characters[game_tick () % curr_num];
					if (b->at_stand)
					{
						ch->player->score += config.sv_fb_score_player_stand;
						b->at_stand = 0;
					}				

					ch->capture_ball(b);

					for (int c = 0; c < MAX_CLIENTS; c++)
					{
						if (!game.players[c])
							continue;
							
						if (game.players[c]->team == bi)
							game.create_sound_global (SOUND_CTF_GRAB_EN, game.players[c]->client_id);
						else
							game.create_sound_global (SOUND_CTF_GRAB_PL, game.players[c]->client_id);
					}
				}
			}
		}
	}
}

//	Ball
BALL::BALL (int _team, vec2 _pos)
:	ENTITY (NETOBJTYPE_FLAG),
	stand_pos (_pos),
	team (_team)
{
	proximity_radius = phys_size;
	
	reset();
}

void BALL::reset ()
{
	carrier = 0;
	last_carrier = 0;
	last_carrier_team = team;
	at_stand = 1;
	pos = stand_pos;
	vel = vec2 (0, 0);
	grab_tick = 0;
	drop_tick = game_tick ();
	idle_tick = -1;
}

void BALL::snap (int snapping_client)
{
	if (idle_tick != -1)
		return;

	NETOBJ_FLAG *ball = static_cast<NETOBJ_FLAG *> (snap_new_item (NETOBJTYPE_FLAG, team, sizeof (NETOBJ_FLAG)));
	ball->x = static_cast<int> (pos.x);
	ball->y = static_cast<int> (pos.y);
	if (config.sv_fb_laser_momentum) ball->team = last_carrier_team;
	else ball->team = team;
	ball->carried_by = -1;
	
	if (at_stand)
		ball->carried_by = -2;
	else if (carrier && carrier->player)
		ball->carried_by = carrier->player->client_id;
}

