#ifndef BASECOMBATAICOMPONENT_H
#define BASECOMBATAICOMPONENT_H

#include "RakNetTypes.h"
#include "dCommonVars.h"
#include "NiPoint3.h"
#include "Behavior.h"
#include "dpWorld.h"
#include "dpEntity.h"
#include "Component.h"

#include <vector>
#include <map>

class MovementAIComponent;
class Entity;

/**
 * The current state of the AI
 */
enum class AiState : int {
	idle = 0,   // Doing nothing
	aggro,      // Waiting for an enemy to cross / running back to spawn
	tether,     // Chasing an enemy
	spawn,      // Spawning into the world
	dead        // Killed
};

/**
 * Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill
 * may be cast.
 */
struct AiSkillEntry
{
	uint32_t skillId;

	float cooldown;

	float abilityCooldown;

	Behavior* behavior;
};

/**
 * Handles the AI of entities, making them wander, tether and attack their enemies
 */
class BaseCombatAIComponent : public Component {
public:
	static const uint32_t ComponentType = COMPONENT_TYPE_BASE_COMBAT_AI;

	BaseCombatAIComponent(Entity* parentEntity, uint32_t id);
	~BaseCombatAIComponent() override;

	void Update(float deltaTime) override;
	void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);

	/**
	 * Get the current behavioral state of the enemy
	 * @return the current state
	 */
	AiState GetState() const { return m_State; }

	/**
	 * Set the current behavioral state of the enemy
	 * @param state the state to change to
	 */
	void SetState(AiState state) { m_State = state; }

	/**
	 * Checks if the target may be an enemy of this entity
	 * @param target the target to check for
	 * @return whether the target is a valid enemy for this entity or not
	 */
	bool IsEnemy(LWOOBJID target) const;

	/**
	 * Gets the current target ID that this entity will attack
	 * @return the current target ID of this entity
	 */
	LWOOBJID GetTarget() const { return m_Target; }

	/**
	 * Sets the target that this entity will attack
	 * @param target the target to set
	 */
	void SetTarget(LWOOBJID target);

	/**
	 * Gets the current target entity that this entity will attack
	 * @return the current target entity of this entity
	 */
	Entity* GetTargetEntity() const;

	/**
	 * Taunts this entity, making it a higher or lower threat for this entity. Increasing or decreasing the chance to
	 * be attacked.
	 * @param offender the entity that triggered the taunt
	 * @param threat how high to increase the threat for the offender
	 */
	void Taunt(LWOOBJID offender, float threat);

	/**
	 * Gets the current threat level for an offending entity
	 * @param offender the entity to get the threat for
	 * @return the current threat level of the offending entity, 0 if the entity is not a threat
	 */
	float GetThreat(LWOOBJID offender);

	/**
	 * Sets the threat level for an entity
	 * @param offender the entity to set the threat level for
	 * @param threat the threat level to set
	 */
	void SetThreat(LWOOBJID offender, float threat);

	/**
	 * Gets the position where the entity spawned
	 * @return the position where the entity spawned
	 */
	const NiPoint3& GetStartPosition() const;

	/**
	 * Removes all threats for this entities, and thus chances for it attacking other entities
	 */
	void ClearThreat();

	/**
	 * Makes the entity continue to wander to a random point around it's starting position
	 */
	void Wander();

	/**
	 * Continues a step in the aggro state, making sure that the entity is around its start position, if an entity
	 * crosses its aggro range this will set the state to tether.
	 */
	void OnAggro();

	/**
	 * Continues a step in the tether state, making the entity run towards its target, if the target is outside of its
	 * tether range, this will change the state to aggro
	 */
	void OnTether();

	/**
	 * Gets whether or not the entity is currently stunned
	 * @return whether the entity is currently stunned
	 */
	bool GetStunned() const;

	/**
	 * (un)stuns the entity, determining whether it'll be able to attack other entities
	 * @param value whether the enemy is stunned
	 */
	void SetStunned(bool value);

	/**
	 * Gets if this entity may be stunned
	 * @return if this entity may be stunned
	 */
	bool GetStunImmune() const;

	/**
	 * Set the stun immune value, determining if the entity may be stunned
	 * @param value
	 */
	void SetStunImmune(bool value);

	/**
	 * Gets the current speed at which an entity runs when tethering
	 * @return the current speed at which an entity runs when tethering
	 */
	float GetTetherSpeed() const;

	/**
	 * Sets the speed at which an entity will tether
	 * @param value the new tether speed
	 */
	void SetTetherSpeed(float value);

	/**
	 * Stuns the entity for a certain amount of time, will not work if the entity is stun immune
	 * @param time the time to stun the entity, if stunnable
	 */
	void Stun(float time);

	/**
	 * Gets the radius that will cause this entity to get aggro'd, causing a target chase
	 * @return the aggro radius of the entity
	 */
	float GetAggroRadius() const;

	/**
	 * Sets the aggro radius, causing the entity to start chasing enemies in this range
	 * @param value the aggro radius to set
	 */
	void SetAggroRadius(float value);

	/**
	 * Makes the entity look at a certain point in space
	 * @param point the point to look at
	 */
	void LookAt(const NiPoint3& point);

	/**
	 * (dis)ables the AI, causing it to stop/start attacking enemies
	 * @param value
	 */
	void SetDisabled(bool value);

	/**
	 * Gets the current state of the AI, whether or not it's looking for enemies to attack
	 * @return
	 */
	bool GetDistabled() const;

	/**
	 * Turns the entity asleep, stopping updates to its physics volumes
	 */
	void Sleep();

	/**
	 * Wakes the entity, allowing updates to its physics volumes
	 */
	void Wake();

private:
	/**
	 * Returns the current target or the target that currently is the largest threat to this entity
	 * @return the current highest priority enemy of this entity
	 */
	LWOOBJID FindTarget();

	/**
	 * Handles anything attack related for the game loop, e.g.: finding targets, sticking with targets and attacking
	 * them, depending on cooldowns.
	 * @param deltaTime the time since the last game tick
	 */
	void CalculateCombat(float deltaTime);

	/**
	 * Gets all the targets that are in the aggro collision phantom of this entity
	 * @return the targets within the aggro range of this entity
	 */
	std::vector<LWOOBJID> GetTargetWithinAggroRange() const;

	/**
	 * The current state of the AI
	 */
	AiState m_State;

	/**
	 * The target this entity is currently trying to attack
	 */
	LWOOBJID m_Target;

	/**
	 * The aggro physics volumes of this entity
	 */
	dpEntity* m_dpEntity;
	dpEntity* m_dpEntityEnemy;

	/**
	 * The max radius of this entity to an enemy allowing it to be chased
	 */
	float m_HardTetherRadius = 100;

	/**
	 * A soft radius for the tether, currently unused
	 */
	float m_SoftTetherRadius = 25;

	/**
	 * The speed at which this entity chases enemies
	 */
	float m_PursuitSpeed = 2;

	/**
	 * The radius that can cause enemies to aggro this entity
	 */
	float m_AggroRadius = 25;

	/**
	 * The speed at which an enemy wanders around
	 */
	float m_TetherSpeed = 4;

	/**
	 * How close this entity needs to be to an enemy to allow attacks
	 */
	float m_AttackRadius = 5.0f;

	/**
	 * Timer before we start attacking others
	 */
	float m_Timer = 0.0f;

	/**
	 * Timer to serializing this entity
	 */
	float m_SoftTimer = 0.0f;

	/**
	 * The skills this entity can cast on enemies
	 */
	std::vector<AiSkillEntry> m_SkillEntries;

	/**
	 * The current enemies and their respective threats to this entity
	 */
	std::map<LWOOBJID, float> m_ThreatEntries;

	/**
	 * The component that handles movement AI, also owned by this entity
	 */
	MovementAIComponent* m_MovementAI;

	/**
	 * The position at which this entity spawned
	 */
	NiPoint3 m_StartPosition;

	/**
	 * For how long this entity has been stunned
	 */
	float m_StunTime = 0;

	/**
	 * If this entity is stunned
	 */
	bool m_Stunned = false;

	/**
	 * If this entity is immune to stunds
	 */
	bool m_StunImmune = false;

	/**
	 * Time taken between actions
	 */
	float m_Downtime = 0;

	/**
	 * How long this entity needs to execute its skill
	 */
	float m_SkillTime = 0;

	/**
	 * If the entity is currently showing the exclamation mark icon above its head
	 */
	bool m_TetherEffectActive = false;

	/**
	 * How long the tether effect will remain active
	 */
	float m_TetherTime = 0;

	/**
	 * How long until we will consider this entity out of combat, resetting its health and armor
	 */
	float m_OutOfCombatTime = 0;

	/**
	 * If the entity is currently out of combat, resetting its health and armor if it just came out of combat
	 */
	bool m_OutOfCombat = false;

	/**
	 * If the AI is currently disabled
	 */
	bool m_Disabled = false;

	/**
	 * If the threat list should be updated
	 */
	bool m_DirtyThreat = false;

	/**
	 * Whether the current entity is a mech enemy, needed as mechs tether radius works differently
	 * @return whether this entity is a mech
	 */
	bool IsMech();
};

#endif // BASECOMBATAICOMPONENT_H