#include "Player.h"

#include <SDL.h>

#include "Audio.h"
#include "ExMath.h"
#include "Game.h"
#include "Level.h"
#include "Log.h"
#include "VM.h"
#include "World.h"

#include "rovdefs.h"

int Player_EntityTypeId;
int Player_StateBaseOffset;

#define DEFAULT_STR 2
#define DEFAULT_DEF 1
#define DEFAULT_SPD 2
#define DEFAULT_VIT 5
#define DEFAULT_INT 0

void Player_Reset(player_t *player)
{
	Inv_Clear(&player->inventory);
	player->entity = NULL;
	player->sprite = NULL;
	player->savedHealth = -1;
	player->savedMaxHealth = -1;
	player->deathStatistics.kills = 0;
	player->deathStatistics.items = 0;
	player->deathStatistics.dmgTaken = 0;
	player->deathStatistics.dmgDealt = 0;
	player->itemCooldown = 0;
	player->manaTimer = 0.0f;
	player->str = DEFAULT_STR;
	player->def = DEFAULT_DEF;
	player->spd = DEFAULT_SPD;
	player->vit = DEFAULT_VIT;
	player->itl = DEFAULT_INT;
	player->mana = 0;
	player->maxMana = 0;
	player->handSlot = INV_HAND_SLOT;
	player->statPoints = 0;
	player->level = 0;
	player->xp = 0;
}

static float bobPhase = 0.0f, bobAmt = 0.0f;

void Player_DoMoveTick(player_t *player, float deltaTime)
{
	float moveSpeed = player->spd / 8.0f + 2.8f;
	const float rotSpeed = deltaTime * 3.6f;

	float xd, yd, dlen;

	bool isSlipping = Level.tilemap[(int)player->entity->x + (int)player->entity->y * LEVEL_SIZE] == TILE_ICE;

	moveSpeed += Obj_GetModifierValue(player->entity, STATMOD_SPEED);
	if (moveSpeed <= 0.0f)
		moveSpeed = 0.0f;

	xd = Math_Sin(player->entity->rot) * player->input.impulseForward;
	yd = Math_Cos(player->entity->rot) * player->input.impulseForward;

	xd += Math_Sin(player->entity->rot + MATH_HALFPI) * player->input.impulseLateral;
	yd += Math_Cos(player->entity->rot + MATH_HALFPI) * player->input.impulseLateral;

	dlen = Math_Sqrt(xd * xd + yd * yd);

	if (isSlipping)
	{
		if (dlen > 0)
		{
			player->entity->accel = 1.0f;
			player->entity->xd += xd / dlen * moveSpeed * 0.02f;
			player->entity->yd += yd / dlen * moveSpeed * 0.02f;
		}
		player->entity->xd *= 0.985f;
		player->entity->yd *= 0.985f;
	}
	else
	{
		if (dlen > 0)
		{
			player->entity->accel = 1.0f;
			player->entity->xd = xd / dlen * moveSpeed;
			player->entity->yd = yd / dlen * moveSpeed;
		}
		else
		{
			player->entity->xd *= 0.8f;
			player->entity->yd *= 0.8f;
		}
	}

	bobAmt *= 0.75f;
	bobAmt += Math_Sqrt(player->input.impulseForward * player->input.impulseForward + player->input.impulseLateral * player->input.impulseLateral);

	if (player == &World_Players[0])
	{
		bobPhase += deltaTime;
		player->viewBob = (Math_Sin(bobPhase * 16.0f) * 0.01f) * bobAmt + 0.05f + player->entity->z;
	}

	player->entity->rot -= player->input.rotDelta * deltaTime;

	player->entity->rot = player->entity->rot;

	player->manaTimer += deltaTime;
	if (player->manaTimer >= PLAYER_MANA_PER_SECOND)
	{
		player->manaTimer = 0.0f;
		if (++player->mana > player->maxMana)
			player->mana = player->maxMana;
	}
}

#define XP_LIMIT 999999999

const unsigned int LEVELXPS[] = {
	0, 1000, 2500, 5000, 10000, 15000, 20000, 40000, 80000, XP_LIMIT
};

void Player_AwardXP(player_t *player, int amount)
{
	player->xp += amount;

	if (player->xp >= XP_LIMIT)
	{
		player->xp = XP_LIMIT;
		return;
	}

	while (player->xp >= LEVELXPS[player->level + 1])
	{
		Game_ShowStatusMsg("Level up, spend points in inventory.");
		player->level++;
		player->statPoints += 5;
	}
}

bool Player_CanUpgradeStat(player_t *player, enum PlayerStatType stat)
{
	if (player->statPoints == 0)
		return false;
	switch (stat)
	{
	case PLAYER_STAT_STR:
		return player->str < PLAYER_STAT_CAP;
	case PLAYER_STAT_DEF:
		return player->def < PLAYER_STAT_CAP;
	case PLAYER_STAT_SPD:
		return player->spd < PLAYER_STAT_CAP;
	case PLAYER_STAT_VIT:
		return player->vit < PLAYER_STAT_CAP;
	case PLAYER_STAT_INT:
		return player->itl < PLAYER_STAT_CAP;
	}
	return false;
}

void Player_UpgradeStat(player_t *player, enum PlayerStatType stat)
{
	if (!Player_CanUpgradeStat(player, stat))
		return;

	switch (stat)
	{
	case PLAYER_STAT_STR:
		player->str++;
		break;
	case PLAYER_STAT_DEF:
		player->def++;
		player->entity->defense = player->def;
		break;
	case PLAYER_STAT_SPD:
		player->spd++;
		break;
	case PLAYER_STAT_VIT:
		player->vit++;

		break;
	case PLAYER_STAT_INT:
		player->itl++;
		player->maxMana = player->itl * PLAYER_MANA_PER_ITL;
		break;
	}
	player->statPoints--;
	Player_ApplyStatsToEntity(player);
}

void Player_ApplyStatsToEntity(player_t *player)
{
	float healthRatio = (float)player->entity->health / (float)player->entity->maxHealth;
	player->entity->maxHealth = player->vit * 10;
	player->entity->health = (int)(healthRatio * player->entity->maxHealth);
	player->entity->defense = player->def;
}

void Player_BreakItem(player_t *player, Item_t *item)
{
	Inv_ClearItem(&player->inventory, player->handSlot);
	player->itemCooldown = 0;
	if (ItemTypes[item->type].maxUses > 1)
		Audio_PlaySound(Audio_GetIDForName("SfxItemBreak"));
	if (player->handSlot >= INV_BELT_SLOT)
		player->handSlot = INV_HAND_SLOT;
}

void Player_UseItem(player_t *player)
{
	Item_t *handItem = Inv_GetItem(&player->inventory, player->handSlot, NULL);
	if (handItem != NULL && player->itemCooldown <= 0.0f && ItemTypes[handItem->type].useAction != 0)
	{
		float cooldownTime = Item_GetStat(handItem, ISTAT_SPEED) - (float)player->spd * 0.3;
		if (cooldownTime < 0)
			cooldownTime = 0;

		struct VMContext ctx;
		ctx.obj = player->entity;
		ctx.other = NULL;
		ctx.item = handItem;
		ctx.player = player;
		handItem->remainingUses -= VM_RunBytecode(ItemTypes[handItem->type].useAction, &ctx);

		player->itemCooldown = (int)cooldownTime;
		if (handItem->remainingUses <= 0 /* && handItem->typeId != ITEM_POTION_HEALTH */)
			Player_BreakItem(player, handItem);
	}
}

static void CheckEntitiesInDoor(Obj_t *obj, void *canToggle)
{
	*(bool *)(canToggle) = false;
}

static int doorOpenSound = -1, doorCloseSound = -1;
int Player_UseDoors(float x, float y, float rot)
{
	float xd = Math_Sin(rot);
	float yd = Math_Cos(rot);

	float distToTarget = Math_Sqrt(xd * xd + yd * yd);
	if (distToTarget == 0.0f)
	{
		return 0;
	}

	float dx = (xd / distToTarget) * 0.1f;
	float dy = (yd / distToTarget) * 0.1f;

	float xp = x;
	float yp = y;

	bool hitWall = false;
	bool hitDoor = false;
	while (!hitWall && !hitDoor)
	{
		xp += dx;
		yp += dy;

		if (Lvl_IsWall((int)xp, (int)yp))
		{
			hitWall = Lvl_IsWall((int)xp, (int)yp);
		}

		if (Lvl_IsDoor((int)xp, (int)yp))
		{
			hitDoor = Lvl_IsDoor((int)xp, (int)yp);
		}
	}

	float xx = xp - x;
	float yy = yp - y;
	float distanceWithoutWall = Math_Sqrt(xx * xx + yy * yy);

	if (hitDoor && Math_Abs(distanceWithoutWall) < 2.0f)
	{
		char type = Level.tilemap[(int)xp + (int)yp * LEVEL_SIZE];
		bool canToggle = true;

		if (type == 'd') /* If player is trying to close the door */
		{
			struct Rect doorBounds;
			doorBounds.x0 = Math_Floor(xp);
			doorBounds.y0 = Math_Floor(yp);
			doorBounds.x1 = Math_Floor(xp) + 1;
			doorBounds.y1 = Math_Floor(yp) + 1;

			World_IterateObjects(OF_BLOCKMOVEMENT, CheckEntitiesInDoor, &doorBounds, &canToggle);
		}

		if (canToggle)
		{
			int tx = (int)xp;
			int ty = (int)yp;
			int tile;
			if (type == TILE_DOOR_OPEN)
			{
				tile = TILE_DOOR_CLOSED;
				if (doorCloseSound == -1)
					doorCloseSound = Audio_GetIDForName("SfxDoorClose");
				World_PlayTileSound(tx, ty, doorCloseSound);
			}
			else
			{
				tile = TILE_DOOR_OPEN;
				if (doorOpenSound == -1)
					doorOpenSound = Audio_GetIDForName("SfxDoorOpen");
				World_PlayTileSound(tx, ty, doorOpenSound);
			}
			World_SetTile(tx, ty, tile);
			Lvl_ComputeLightmap();
			return 1;
		}
	}
	return 0;
}
