/*
Copyright (C) 2015-2019 Parallel Realities

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 "player.h"

static void selectTarget(void);
static void switchGuns(void);
static void cycleRadarZoom(void);
static void selectMissionTarget(void);
static void selectNewPlayer(int dir);
static void initPlayerSelect(void);
static void activateBoost(void);
static void deactivateBoost(void);
static void activateECM(void);
static void handleKeyboard(void);
static void faceMouse(void);
static void handleMouse(void);
static void preFireMissile(void);
static void applyRestrictions(void);
static int isPriorityMissionTarget(Entity *e, int dist, int closest);
static int targetOutOfRange(void);
static void rechargeBoostECM(void);
static void setPilotName(void);
static void updateDeathStats(void);
static void handleSuspicionLevel(void);

static int selectedPlayerIndex;
static int availableGuns[BT_MAX];
static Entity *availablePlayerUnits[MAX_SELECTABLE_PLAYERS];

void initPlayer(void)
{
	int i, n;

	memset(&availableGuns, 0, sizeof(int) * BT_MAX);

	battle.numPlayerGuns = 0;

	player->selectedGunType = -1;

	if (!player->combinedGuns)
	{
		for (i = 0 ; i < MAX_FIGHTER_GUNS ; i++)
		{
			n = player->guns[i].type;

			if (n)
			{
				if (!availableGuns[n])
				{
					battle.numPlayerGuns++;
				}

				availableGuns[n] = 1;

				if (player->selectedGunType == -1)
				{
					player->selectedGunType = n;
				}
			}
		}
	}
	else
	{
		player->selectedGunType = 0;
	}

	setPilotName();

	player->action = NULL;

	battle.boostTimer = BOOST_RECHARGE_TIME;
	battle.ecmTimer = ECM_RECHARGE_TIME;

	player->flags |= EF_NO_HEALTH_BAR;
	player->flags &= ~EF_IMMORTAL;

	player->target = NULL;

	game.stats[STAT_EPIC_KILL_STREAK] = MAX(game.stats[STAT_EPIC_KILL_STREAK], battle.stats[STAT_EPIC_KILL_STREAK]);

	battle.stats[STAT_EPIC_KILL_STREAK] = 0;
}

static void setPilotName(void)
{
	int i, pos;

	pos = -1;

	for (i = 0 ; i < strlen(game.currentMission->pilot) ; i++)
	{
		if (game.currentMission->pilot[i] == ' ')
		{
			pos = i;
		}
	}

	memset(player->name, '\0', MAX_NAME_LENGTH);

	if (pos != -1)
	{
		memcpy(player->name, game.currentMission->pilot + pos + 1, strlen(game.currentMission->pilot) - pos - 1);
	}

	SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Pilot name = '%s'", player->name);
}

void doPlayer(void)
{
	self = player;

	rechargeBoostECM();

	if (game.currentMission->challengeData.isChallenge)
	{
		applyRestrictions();
	}

	if (player->alive == ALIVE_ALIVE && player->systemPower > 0)
	{
		handleKeyboard();

		handleMouse();

		handleSuspicionLevel();

		if (!player->target || player->target->health <= 0 || player->target->systemPower <= 0 || targetOutOfRange())
		{
			selectTarget();
		}
	}

	player->angle = ((int)player->angle) % 360;

	if (player->health <= 0 && battle.status == MS_IN_PROGRESS)
	{
		if (game.currentMission->challengeData.isChallenge)
		{
			if (!game.currentMission->challengeData.allowPlayerDeath)
			{
				updateDeathStats();

				failMission();
			}
		}
		else if (!battle.isEpic)
		{
			updateDeathStats();

			failMission();
		}
		else if (player->health == -FPS)
		{
			updateDeathStats();

			updateCondition("PLAYER", TT_DESTROY);

			if (battle.status == MS_IN_PROGRESS)
			{
				initPlayerSelect();
			}
		}
	}

	if (battle.status == MS_IN_PROGRESS)
	{
		selectMissionTarget();
	}

	if (dev.playerUnlimitedMissiles)
	{
		player->missiles = 999;
	}

	/* really only used in challenge mode */
	if (player->systemPower <= 0 && battle.status == MS_IN_PROGRESS)
	{
		if (game.currentMission->challengeData.isChallenge)
		{
			addHudMessage(colors.red, _("Challenge Failed!"));
			failMission();
		}
	}

	if (battle.boostTimer == (int)BOOST_FINISHED_TIME)
	{
		deactivateBoost();
	}
}

static void updateDeathStats(void)
{
	battle.stats[STAT_PLAYER_KILLED]++;

	/* the player is known as "Player", so we need to check the craft they were assigned to */
	if (strcmp(game.currentMission->craft, "ATAF") == 0)
	{
		awardTrophy("ATAF_DESTROYED");
	}
}

static void rechargeBoostECM(void)
{
	int boostTimer, ecmTimer;

	boostTimer = battle.boostTimer;
	battle.boostTimer = MIN(battle.boostTimer + 1, BOOST_RECHARGE_TIME);
	if (boostTimer < BOOST_RECHARGE_TIME && battle.boostTimer == BOOST_RECHARGE_TIME)
	{
		playSound(SND_RECHARGED);
	}

	ecmTimer = battle.ecmTimer;
	battle.ecmTimer = MIN(battle.ecmTimer + 1, ECM_RECHARGE_TIME);
	if (ecmTimer < ECM_RECHARGE_TIME && battle.ecmTimer == ECM_RECHARGE_TIME)
	{
		playSound(SND_RECHARGED);
	}
}

static int targetOutOfRange(void)
{
	return (app.gameplay.autoSwitchPlayerTarget && getDistance(player->x, player->y, player->target->x, player->target->y) > SCREEN_WIDTH * 2);
}

static void applyRestrictions(void)
{
	if (game.currentMission->challengeData.noMissiles)
	{
		player->missiles = 0;
	}

	if (game.currentMission->challengeData.noBoost)
	{
		battle.boostTimer = 0;
	}

	if (game.currentMission->challengeData.noECM)
	{
		battle.ecmTimer = 0;
	}

	if (game.currentMission->challengeData.noGuns)
	{
		player->reload = 1;
	}
}

static void handleKeyboard(void)
{
	if (battle.status == MS_IN_PROGRESS)
	{
		if (isControl(CONTROL_BOOST) && player->speed > 0)
		{
			if (battle.boostTimer == BOOST_RECHARGE_TIME)
			{
				playSound(SND_BOOST);

				activateBoost();
			}
			else
			{
				playSound(SND_GUI_DENIED);
			}

			clearControl(CONTROL_BOOST);
		}

		if (isControl(CONTROL_TARGET))
		{
			selectTarget();

			clearControl(CONTROL_TARGET);
		}

		if (isControl(CONTROL_ECM))
		{
			if (battle.ecmTimer == ECM_RECHARGE_TIME)
			{
				playSound(SND_ECM);

				activateECM();
			}
			else
			{
				playSound(SND_GUI_DENIED);
			}

			clearControl(CONTROL_ECM);
		}

		if (isControl(CONTROL_BRAKE))
		{
			applyFighterBrakes();
		}

		if (isControl(CONTROL_GUNS))
		{
			switchGuns();

			clearControl(CONTROL_GUNS);
		}

		if (isControl(CONTROL_RADAR))
		{
			cycleRadarZoom();

			clearControl(CONTROL_RADAR);
		}

		if (isControl(CONTROL_MISSILE))
		{
			preFireMissile();

			clearControl(CONTROL_MISSILE);
		}
	}
	else
	{
		applyFighterBrakes();
	}
}

static void handleMouse(void)
{
	faceMouse();

	if (battle.status == MS_IN_PROGRESS)
	{
		if (isControl(CONTROL_FIRE) && !player->reload && player->guns[0].type)
		{
			if (player->selectedGunType != BT_ROCKET)
			{
				fireGuns(player);
			}
			else
			{
				fireRocket(player);
			}

			if (battle.hasSuspicionLevel && !battle.numEnemies && !battle.suspicionCoolOff)
			{
				battle.suspicionLevel += (MAX_SUSPICION_LEVEL * 0.05);
			}
		}

		if (isControl(CONTROL_ACCELERATE))
		{
			if (battle.boostTimer > BOOST_FINISHED_TIME || game.currentMission->challengeData.noBoost)
			{
				applyFighterThrust();
			}
		}

		if (isControl(CONTROL_MISSILE))
		{
			preFireMissile();

			app.mouse.button[SDL_BUTTON_MIDDLE] = 0;
		}

		if (isControl(CONTROL_GUNS))
		{
			switchGuns();

			app.mouse.button[SDL_BUTTON_X1] = 0;
		}

		if (isControl(CONTROL_RADAR))
		{
			cycleRadarZoom();

			app.mouse.button[SDL_BUTTON_X2] = 0;
		}
	}
}

static void faceMouse(void)
{
	int dir;
	int x, y, wantedAngle;

	x = player->x - battle.camera.x;
	y = player->y - battle.camera.y;
	wantedAngle = getAngle(x, y, app.mouse.x, app.mouse.y);

	wantedAngle %= 360;

	if (fabs(wantedAngle - player->angle) > 2)
	{
		dir = ((int)(wantedAngle - player->angle + 360)) % 360 > 180 ? -1 : 1;

		player->angle += dir * 4;

		player->angle = mod(player->angle, 360);
	}
}

static void preFireMissile(void)
{
	if (player->missiles && player->target)
	{
		if (getDistance(player->x, player->y, player->target->x, player->target->y) <= SCREEN_WIDTH)
		{
			fireMissile(player);
		}
		else
		{
			playSound(SND_GUI_DENIED);

			addHudMessage(colors.white, _("Target not in range"));
		}
	}
	else if (!player->missiles)
	{
		addHudMessage(colors.white, _("Out of missiles"));

		playSound(SND_NO_MISSILES);
	}
}

static void initPlayerSelect(void)
{
	Entity *e;

	memset(&availablePlayerUnits, 0, sizeof(Entity*) * MAX_SELECTABLE_PLAYERS);

	selectedPlayerIndex = 0;

	if (battle.epicLives == 0 || (battle.epicLives > 0 && --battle.epicLives > 0))
	{
		for (e = battle.entityHead.next ; e != NULL ; e = e->next)
		{
			if (e->active && e->type == ET_FIGHTER && e->health > 0 && e->side == SIDE_ALLIES && selectedPlayerIndex < MAX_SELECTABLE_PLAYERS)
			{
				availablePlayerUnits[selectedPlayerIndex++] = e;
			}
		}
	}

	if (selectedPlayerIndex > 0)
	{
		battle.playerSelect = 1;
		selectedPlayerIndex = 0;
		memset(&app.keyboard, 0, sizeof(int) * MAX_KEYBOARD_KEYS);
	}
	else
	{
		battle.isEpic = 0;

		if (battle.epicKills > 0 && battle.stats[STAT_ENEMIES_KILLED_PLAYER] < battle.epicKills)
		{
			battle.unwinnable = 0;
		}

		failMission();
	}
}

void doPlayerSelect(void)
{
	if (isControl(CONTROL_PREV_FIGHTER))
	{
		selectNewPlayer(-1);

		clearControl(CONTROL_PREV_FIGHTER);
	}

	if (isControl(CONTROL_NEXT_FIGHTER))
	{
		selectNewPlayer(1);

		clearControl(CONTROL_NEXT_FIGHTER);
	}

	if (player->health > 0 && isAcceptControl())
	{
		battle.playerSelect = 0;

		initPlayer();

		resetAcceptControls();
	}
}

static void selectNewPlayer(int dir)
{
	player = NULL;

	do
	{
		selectedPlayerIndex += dir;

		selectedPlayerIndex = mod(selectedPlayerIndex, MAX_SELECTABLE_PLAYERS);

		player = availablePlayerUnits[selectedPlayerIndex];
	}
	while (player == NULL);

	battle.camera.x = player->x - (app.winWidth / 2);
	battle.camera.y = player->y - (app.winHeight / 2);
}

static void activateBoost(void)
{
	player->dx += sin(TO_RAIDANS(player->angle)) * 10;
	player->dy += -cos(TO_RAIDANS(player->angle)) * 10;
	player->thrust = sqrt((player->dx * player->dx) + (player->dy * player->dy));

	battle.boostTimer = 0;

	battle.stats[STAT_BOOST]++;
}

static void deactivateBoost(void)
{
	float v, thrust;

	thrust = -1;

	while (thrust != player->thrust)
	{
		thrust = player->thrust;

		v = (player->speed / sqrt(player->thrust));
		player->dx = v * player->dx;
		player->dy = v * player->dy;
		player->thrust = sqrt((player->dx * player->dx) + (player->dy * player->dy));
	}
}

static void activateECM(void)
{
	battle.ecmTimer = 0;

	addECMEffect(player);

	battle.stats[STAT_ECM]++;

	if (battle.hasSuspicionLevel && !battle.numEnemies && !battle.suspicionCoolOff)
	{
		battle.suspicionLevel += (MAX_SUSPICION_LEVEL * 0.25);
	}
}

static void switchGuns(void)
{
	int i;

	i = player->selectedGunType;

	if (!player->combinedGuns && battle.numPlayerGuns)
	{
		do
		{
			i = (i + 1) % BT_MAX;
		}
		while (!availableGuns[i]);
	}

	if (player->selectedGunType != i)
	{
		playSound(SND_SELECT_WEAPON);
		player->selectedGunType = i;
	}
}

static void selectTarget(void)
{
	unsigned int closest = MAX_TARGET_RANGE;
	unsigned int dist = MAX_TARGET_RANGE;
	int i, total;
	Entity *e, *near;
	Entity *targets[MAX_SELECTABLE_TARGETS];

	i = 0;
	near = NULL;
	memset(targets, 0, sizeof(Entity*) * MAX_SELECTABLE_TARGETS);

	if (player->target && (!player->target->health || !player->target->systemPower))
	{
		player->target = NULL;
	}

	for (e = battle.entityHead.next ; e != NULL ; e = e->next)
	{
		if (e->active && e != player && (e->flags & EF_TAKES_DAMAGE) && (!(e->flags & EF_NO_PLAYER_TARGET)) && e->side != player->side && e->alive == ALIVE_ALIVE && e->systemPower > 0 && i < MAX_SELECTABLE_TARGETS)
		{
			dist = getDistance(player->x, player->y, e->x, e->y);

			if (dist < closest)
			{
				near = e;
				closest = dist;
			}

			if (collision(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, e->x - battle.camera.x - (e->w / 2), e->y - battle.camera.y - (e->h / 2), e->w, e->h))
			{
				targets[i++] = e;
			}
			else if (e == player->target)
			{
				player->target = NULL;
			}
		}
	}

	total = i;

	for (i = 0 ; i < total ; i++)
	{
		if (targets[i] == player->target)
		{
			if (i + 1 < MAX_SELECTABLE_TARGETS && targets[i + 1])
			{
				player->target = targets[i + 1];
				return;
			}
			else
			{
				player->target = targets[0];
			}
		}
	}

	if (!player->target || !targets[0])
	{
		player->target = near;
	}
}

static void selectMissionTarget(void)
{
	unsigned int closest = MAX_TARGET_RANGE;
	unsigned int dist = MAX_TARGET_RANGE;
	Entity *e;

	battle.missionTarget = NULL;

	for (e = battle.entityHead.next ; e != NULL ; e = e->next)
	{
		if (e->active && e->flags & EF_MISSION_TARGET && e->alive == ALIVE_ALIVE)
		{
			dist = getDistance(player->x, player->y, e->x, e->y);

			if (battle.missionTarget == NULL)
			{
				battle.missionTarget = e;
				closest = dist;
			}
			else if (battle.missionTarget->type == ET_WAYPOINT && e->type != ET_WAYPOINT)
			{
				battle.missionTarget = e;
			}
			else if (battle.missionTarget->type != ET_WAYPOINT)
			{
				if (isPriorityMissionTarget(e, dist, closest))
				{
					battle.missionTarget = e;
					closest = dist;
				}
			}
			else if (battle.missionTarget->type == ET_WAYPOINT && e->type == ET_WAYPOINT && e->id == battle.missionTarget->id)
			{
				battle.missionTarget = e;
			}
		}
	}
}

static int isPriorityMissionTarget(Entity *e, int dist, int closest)
{
	/* battle.missionTarget is secondary, e is not */
	if ((battle.missionTarget->flags & EF_SECONDARY_TARGET) > (e->flags & EF_SECONDARY_TARGET))
	{
		return 1;
	}

	/* battle.missionTarget is not secondary, e is */
	if ((battle.missionTarget->flags & EF_SECONDARY_TARGET) < (e->flags & EF_SECONDARY_TARGET))
	{
		return 0;
	}

	/* normal distance check */
	return dist < closest;
}

void setInitialPlayerAngle(void)
{
	Entity *e;

	selectMissionTarget();

	if (battle.missionTarget)
	{
		player->angle = getAngle(player->x, player->y, battle.missionTarget->x, battle.missionTarget->y);
	}
	else
	{
		selectTarget();

		if (player->target)
		{
			player->angle = getAngle(player->x, player->y, player->target->x, player->target->y);
		}
		else
		{
			if (battle.jumpgate)
			{
				player->angle = getAngle(player->x, player->y, battle.jumpgate->x, battle.jumpgate->y);
			}
		}
	}

	for (e = battle.entityHead.next ; e != NULL ; e = e->next)
	{
		if (e->side == player->side)
		{
			e->angle = player->angle;
		}
	}
}

static void cycleRadarZoom(void)
{
	battle.radarRange = (battle.radarRange + 1) % 3;

	playSound(SND_ZOOM);
}

int playerHasGun(int type)
{
	return availableGuns[type];
}

static void handleSuspicionLevel(void)
{
	if (battle.hasSuspicionLevel)
	{
		/* don't allow the suspicion level to drop too far */
		battle.suspicionLevel = MAX(battle.suspicionLevel, -(MAX_SUSPICION_LEVEL * 0.25));
	}
}

void loadPlayer(cJSON *node)
{
	char *type;
	int side, addFlags;
	long flags;

	type = cJSON_GetObjectItem(node, "type")->valuestring;
	side = lookup(cJSON_GetObjectItem(node, "side")->valuestring);
	flags = -1;

	player = spawnFighter(type, 0, 0, side);
	player->x = BATTLE_AREA_WIDTH / 2;
	player->y = BATTLE_AREA_HEIGHT / 2;

	if (cJSON_GetObjectItem(node, "x"))
	{
		player->x = (cJSON_GetObjectItem(node, "x")->valuedouble / BATTLE_AREA_CELLS) * BATTLE_AREA_WIDTH;
		player->y = (cJSON_GetObjectItem(node, "y")->valuedouble / BATTLE_AREA_CELLS) * BATTLE_AREA_HEIGHT;
	}

	if (cJSON_GetObjectItem(node, "flags"))
	{
		flags = flagsToLong(cJSON_GetObjectItem(node, "flags")->valuestring, &addFlags);
	}

	if (flags != -1)
	{
		if (addFlags)
		{
			player->flags |= flags;
		}
		else
		{
			player->flags = flags;

			SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "Flags for Player replaced");
		}
	}

	if (strcmp(type, "Tug") == 0)
	{
		battle.stats[STAT_TUG]++;
	}

	if (strcmp(type, "Shuttle") == 0)
	{
		battle.stats[STAT_SHUTTLE]++;
	}
}
