/*
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 "bullets.h"

static void huntTarget(Bullet *b);
static void checkCollisions(Bullet *b);
static void resizeDrawList(void);
static void selectNewTarget(Bullet *b);
static void doBulletHitEffect(Bullet *b);

static Bullet bulletDef[BT_MAX];
static Bullet **bulletsToDraw;
static int drawCapacity;

void initBullets(void)
{
	drawCapacity = INITIAL_BULLET_DRAW_CAPACITY;

	bulletsToDraw = malloc(sizeof(Bullet*) * drawCapacity);
	memset(bulletsToDraw, 0, sizeof(Bullet*) * drawCapacity);
}

void initBulletDefs(void)
{
	cJSON *root, *node;
	char *text;
	int type;
	Bullet *def;

	memset(&bulletDef, 0, sizeof(Bullet) * BT_MAX);

	text = readFile("data/battle/bullets.json");

	root = cJSON_Parse(text);

	for (node = root->child ; node != NULL ; node = node->next)
	{
		type = lookup(cJSON_GetObjectItem(node, "type")->valuestring);

		def = &bulletDef[type];
		def->type = type;
		def->damage = cJSON_GetObjectItem(node, "damage")->valueint;
		def->texture = getAtlasImage(cJSON_GetObjectItem(node, "texture")->valuestring);
		def->sound = lookup(cJSON_GetObjectItem(node, "sound")->valuestring);
		def->flags = flagsToLong(cJSON_GetObjectItem(node, "flags")->valuestring, NULL);

		def->w = def->texture->rect.w;
		def->h = def->texture->rect.h;
	}

	cJSON_Delete(root);
	free(text);
}

void doBullets(void)
{
	int i = 0;
	Bullet *b;
	Bullet *prev = &battle.bulletHead;

	battle.incomingMissile = 0;

	memset(bulletsToDraw, 0, sizeof(Bullet*) * drawCapacity);

	for (b = battle.bulletHead.next ; b != NULL ; b = b->next)
	{
		b->x += b->dx;
		b->y += b->dy;

		if (b->type == BT_ROCKET)
		{
			addMissileEngineEffect(b);
		}
		else if (b->type == BT_MISSILE)
		{
			addMissileEngineEffect(b);

			if (b->life < MISSILE_LIFE - (FPS / 4))
			{
				huntTarget(b);
			}

			if (b->target == player && player->alive == ALIVE_ALIVE && player->health > 0)
			{
				battle.incomingMissile = 1;
			}
		}

		checkCollisions(b);

		if (--b->life <= 0)
		{
			if (b == battle.bulletTail)
			{
				battle.bulletTail = prev;
			}

			prev->next = b->next;
			free(b);
			b = prev;
		}
		else
		{
			if (isOnBattleScreen(b->x, b->y, b->w, b->h))
			{
				bulletsToDraw[i++] = b;

				if (i == drawCapacity)
				{
					resizeDrawList();
				}
			}
		}

		prev = b;
	}
}

static void resizeDrawList(void)
{
	int n;

	n = drawCapacity + INITIAL_BULLET_DRAW_CAPACITY;

	SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG, "Resizing bullet draw capacity: %d -> %d", drawCapacity, n);

	bulletsToDraw = resize(bulletsToDraw, sizeof(Bullet*) * drawCapacity, sizeof(Bullet*) * n);

	drawCapacity = n;
}

static void checkCollisions(Bullet *b)
{
	Entity *e, **candidates;
	int i;

	candidates = getAllEntsWithin(b->x - (b->w / 2), b->y - (b->h / 2), b->w, b->h, NULL);

	for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
	{
		if (e->flags & EF_TAKES_DAMAGE)
		{
			if (b->owner->owner != NULL && b->owner->owner == e->owner)
			{
				continue;
			}

			if (b->owner != e && e->health > 0 && collision(b->x - b->w / 2, b->y - b->h / 2, b->w, b->h, e->x - e->w / 2, e->y - e->h / 2, e->w, e->h))
			{
				if (b->owner->side == e->side && !app.gameplay.friendlyFire && (!(e->flags & EF_DISABLED)) && e->type != ET_MINE)
				{
					b->damage = 0;
				}
				else if (b->owner == player)
				{
					if (b->type != BT_ROCKET)
					{
						battle.stats[STAT_SHOTS_HIT]++;
					}
					else
					{
						battle.stats[STAT_ROCKETS_HIT]++;
					}

					if (battle.hasSuspicionLevel)
					{
						if (e->aiFlags & (AIF_AVOIDS_COMBAT|AIF_DEFENSIVE))
						{
							battle.suspicionLevel -= (MAX_SUSPICION_LEVEL * 0.1);
						}
						else
						{
							battle.suspicionLevel -= (MAX_SUSPICION_LEVEL * 0.001);
						}
					}
				}

				if (e->flags & EF_IMMORTAL)
				{
					b->damage = 0;
				}

				damageFighter(e, b->damage, b->flags);

				doBulletHitEffect(b);

				b->life = 0;
				b->damage = 0;

				if (b->flags & BF_EXPLODES)
				{
					addMissileExplosion(b);
					playBattleSound(SND_EXPLOSION_1, b->x, b->y);

					if (e == player)
					{
						battle.stats[STAT_MISSILES_STRUCK]++;
					}
				}

				/* missile was targetting player, but hit something else */
				if (b->type == BT_MISSILE && b->target == player && e != player)
				{
					battle.stats[STAT_MISSILES_EVADED]++;
				}

				if (b->type == BT_MISSILE && b->target != e)
				{
					if (e == player)
					{
						awardTrophy("TEAM_PLAYER");
					}
					else if (b->owner == player && (e->aiFlags & AIF_MOVES_TO_LEADER) && (b->target->flags & EF_AI_LEADER))
					{
						awardTrophy("BODYGUARD");
					}
				}

				/* assuming that health <= 0 will always mean killed */
				if (e->health <= 0)
				{
					e->killedBy = b->owner;

					if (e == player)
					{
						battle.lastKilledPlayer = b->owner;
					}

					if (battle.isEpic && b->owner == player && e == battle.lastKilledPlayer)
					{
						awardTrophy("REVENGE");
					}
				}

				if (b->owner == player && b->type == BT_MISSILE)
				{
					battle.stats[STAT_MISSILES_HIT]++;
				}

				return;
			}
		}
	}
}

void doBulletHitEffect(Bullet *b)
{
	switch (b->type)
	{
		case BT_PARTICLE:
			addBulletHitEffect(b->x, b->y, 255, 0, 255);
			break;

		case BT_PLASMA:
			addBulletHitEffect(b->x, b->y, 0, 255, 0);
			break;

		case BT_LASER:
			addBulletHitEffect(b->x, b->y, 255, 0, 0);
			break;

		case BT_MAG:
			addBulletHitEffect(b->x, b->y, 196, 196, 255);
			break;

		default:
			addBulletHitEffect(b->x, b->y, 255, 255, 255);
			break;
	}
}

void drawBullets(void)
{
	int i;
	Bullet *b;

	setAtlasColor(255, 255, 255, 255);

	for (i = 0, b = bulletsToDraw[i] ; b != NULL ; b = bulletsToDraw[++i])
	{
		blitRotated(b->texture, b->x - battle.camera.x, b->y - battle.camera.y, b->angle);
	}
}

static void faceTarget(Bullet *b)
{
	int dir, wantedAngle, dist;

	wantedAngle = (int)getAngle(b->x, b->y, b->target->x, b->target->y) % 360;

	if (abs(wantedAngle - b->angle) > TURN_THRESHOLD)
	{
		dir = (wantedAngle - b->angle + 360) % 360 > 180 ? -1 : 1;

		b->angle += dir * TURN_SPEED;

		dist = getDistance(b->x, b->y, b->target->x, b->target->y);

		if (dist < 250)
		{
			dist = 250 - dist;

			while (dist > 0)
			{
				b->angle += dir;

				dist -= 50;
			}
		}

		b->angle = mod(b->angle, 360);

		b->dx *= 0.5;
		b->dy *= 0.5;
	}
}

static void applyMissileThrust(Bullet *b)
{
	int maxSpeed;
	float v, thrust;

	b->dx += sin(TO_RAIDANS(b->angle));
	b->dy += -cos(TO_RAIDANS(b->angle));

	maxSpeed = MAX(MIN(b->target->speed + 1, 999), 3);

	thrust = sqrt((b->dx * b->dx) + (b->dy * b->dy));

	if (thrust > maxSpeed)
	{
		v = (maxSpeed / sqrt(thrust));
		b->dx = v * b->dx;
		b->dy = v * b->dy;
	}
}

static void huntTarget(Bullet *b)
{
	if (b->target != NULL && b->target->health > 0)
	{
		faceTarget(b);

		applyMissileThrust(b);

		if (b->target == player && battle.ecmTimer < FPS)
		{
			b->life = 0;
			addMissileExplosion(b);
			playBattleSound(SND_EXPLOSION_1, b->x, b->y);
		}
	}
	else
	{
		selectNewTarget(b);
	}
}

static void selectNewTarget(Bullet *b)
{
	int i;
	Entity *e, **candidates;

	if (app.gameplay.missileReTarget)
	{
		b->target = NULL;

		candidates = getAllEntsInRadius(b->x, b->y, SCREEN_HEIGHT, NULL);

		for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i])
		{
			if (e->type == ET_FIGHTER && e->side != b->owner->side && e->health > 0)
			{
				b->target = e;

				if (b->target == player)
				{
					playSound(SND_INCOMING);
				}

				return;
			}
		}
	}

	/* no target, just explode */
	b->life = 0;
	addMissileExplosion(b);
	playBattleSound(SND_EXPLOSION_1, b->x, b->y);
}

static Bullet *createBullet(int type, int x, int y, Entity *owner)
{
	Bullet *b;

	b = malloc(sizeof(Bullet));
	memset(b, 0, sizeof(Bullet));
	battle.bulletTail->next = b;
	battle.bulletTail = b;

	memcpy(b, &bulletDef[type], sizeof(Bullet));

	b->next = NULL;

	b->x = x;
	b->y = y;
	b->dx += sin(TO_RAIDANS(owner->angle)) * 16;
	b->dy += -cos(TO_RAIDANS(owner->angle)) * 16;
	b->life = FPS * 2;
	b->angle = owner->angle;
	b->owner = owner;
	b->target = owner->target;

	return b;
}

void fireGuns(Entity *owner)
{
	Bullet *b;
	int i;
	float x, y;
	float c, s;

	b = NULL;

	for (i = 0 ; i < MAX_FIGHTER_GUNS ; i++)
	{
		if (owner->guns[i].type != BT_NONE && (owner->guns[i].type == owner->selectedGunType || owner->combinedGuns))
		{
			s = sin(TO_RAIDANS(owner->angle));
			c = cos(TO_RAIDANS(owner->angle));

			x = (owner->guns[i].x * c) - (owner->guns[i].y * s);
			y = (owner->guns[i].x * s) + (owner->guns[i].y * c);

			x += owner->x;
			y += owner->y;

			b = createBullet(owner->guns[i].type, x, y, owner);

			if (owner == player)
			{
				battle.stats[STAT_SHOTS_FIRED]++;
			}
		}
	}

	owner->reload = owner->reloadTime;

	if (b)
	{
		playBattleSound(b->sound, owner->x, owner->y);
	}
}

void fireRocket(Entity *owner)
{
	Bullet *b;

	b = createBullet(BT_ROCKET, owner->x, owner->y, owner);

	playBattleSound(b->sound, owner->x, owner->y);

	owner->reload = FPS;

	if (owner == player)
	{
		battle.stats[STAT_ROCKETS_FIRED]++;
	}
}

void fireMissile(Entity *owner)
{
	Bullet *b;

	b = createBullet(BT_MISSILE, owner->x, owner->y, owner);

	b->dx *= 0.5;
	b->dy *= 0.5;

	b->life = MISSILE_LIFE;

	owner->missiles--;

	if (owner == player)
	{
		battle.stats[STAT_MISSILES_FIRED]++;
	}

	playBattleSound(b->sound, owner->x, owner->y);

	if (b->target == player)
	{
		playSound(SND_INCOMING);
	}
}

void destroyBulletDefs(void)
{
}

void destroyBullets(void)
{
	free(bulletsToDraw);

	bulletsToDraw = NULL;
}
