#ifndef DESTROYABLECOMPONENT_H
#define DESTROYABLECOMPONENT_H

#include "RakNetTypes.h"
#include <vector>
#include "tinyxml2.h"
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"

namespace CppScripts {
	class Script;
}; //! namespace CppScripts
enum class eStateChangeType : uint32_t;

/**
 * Represents the stats of an entity, for example its health, imagination and armor. Also handles factions, which
 * indicate which enemies this entity has.
 */
class DestroyableComponent final : public Component {
public:
	static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE;

	DestroyableComponent(Entity* parentEntity);
	~DestroyableComponent() override;

	void Update(float deltaTime) override;
	void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
	void LoadFromXml(tinyxml2::XMLDocument* doc) override;
	void UpdateXml(tinyxml2::XMLDocument* doc) override;

	/**
	 * Initializes the component using a different LOT
	 * @param templateID the ID to use for initialization
	 */
	void Reinitialize(LOT templateID);

	/**
	 * Sets the health of this entity. Makes sure this is serialized on the next tick and if this is a character its
	 * stats will also update.
	 * @param value the new health value
	 */
	void SetHealth(int32_t value);

	/**
	 * Heals the entity by some delta amount
	 * @param health the delta amount to heal
	 */
	void Heal(uint32_t health);

	/**
	 * Returns the current health of this entity
	 * @return the current health of this entity
	 */
	int32_t GetHealth() const { return m_iHealth; }

	/**
	 * Updates the max health this entity has (e.g. what it can heal to), and optionally displays a UI animation indicating that
	 * @param value the max health value to set
	 * @param playAnim whether or not to play a UI animation indicating the change in max health
	 */
	void SetMaxHealth(float value, bool playAnim = false);

	/**
	 * Returns the curent max health of this entity
	 * @return the current max health of this entity
	 */
	float GetMaxHealth() const { return m_fMaxHealth; }

	/**
	 * Sets the armor for this entity. This also makes sure this change is serialized and if this is a character it also
	 * updates their stats.
	 * @param value the armor value to set
	 */
	void SetArmor(int32_t value);

	/**
	 * Repairs armor of this entity, updating it by a delta amount
	 * @param armor the amount of armor to repair
	 */
	void Repair(uint32_t armor);

	/**
	 * Returns the current armor value for the entity
	 * @return the current armor value for the entity
	 */
	int32_t GetArmor() const { return m_iArmor; }

	/**
	 * Updates the max armor this entity has (e.g. what it can heal to), and optionally displays a UI animation indicating that
	 * @param value the max armor value to set
	 * @param playAnim whether or not to play a UI animation indicating the change in max armor
	 */
	void SetMaxArmor(float value, bool playAnim = false);

	/**
	 * Returns the current maximum armor this entity can have
	 * @return the current maximum armor this entity can have
	 */
	float GetMaxArmor() const { return m_fMaxArmor; }

	/**
	 * Sets the imagination value for this entity. Ensures that the change is serialized and if this is a character
	 * their stats will be updated. Can also trigger the assembly passive ability to restore on 0 imag.
	 * @param value
	 */
	void SetImagination(int32_t value);

	/**
	 * Updates the imagination of this entity by a delta amount
	 * @param deltaImagination the imagination to update
	 */
	void Imagine(int32_t deltaImagination);

	/**
	 * Returns the current imagination value of this entity
	 * @return the current imagination value of this entity
	 */
	int32_t GetImagination() const { return m_iImagination; }

	/**
	 * Updates the max imagination this entity has (e.g. what it can heal to), and optionally displays a UI animation indicating that
	 * @param value the max imagination value to set
	 * @param playAnim whether or not to play a UI animation indicating the change in max imagination
	 */
	void SetMaxImagination(float value, bool playAnim = false);

	/**
	 * Returns the current max imagination value
	 * @return the current max imagination value
	 */
	float GetMaxImagination() const { return m_fMaxImagination; }

	/**
	 * Sets the damage this entity can absorb before getting hurt, also serializes this change.
	 * @param value the damage to absorb
	 */
	void SetDamageToAbsorb(int32_t value);

	/**
	 * Returns the current damage to absorb
	 * @return the current damage to absorb
	 */
	int32_t GetDamageToAbsorb()  const { return m_DamageToAbsorb; }

	/**
	 * Sets the reduced damage value for each attack for this entity, also serializes that change.
	 * @param value the damage to reduce for each attack
	 */
	void SetDamageReduction(int32_t value);

	/**
	 * Returns the current damage reduction value
	 * @return the current damage reduction value
	 */
	int32_t GetDamageReduction() const { return m_DamageReduction; }

	/**
	 * Sets whether or not this entity is immune to attacks
	 * @param value whether or not this entity is immune to attacks
	 */
	void SetIsImmune(bool value);

	/**
	 * Returns whether or not this entity is immune to attacks
	 * @return whether or not this entity is immune to attacks
	 */
	bool IsImmune() const;

	/**
	 * @return whether this entity is currently immune to attacks due to a damage cooldown period
	*/
	bool IsCooldownImmune() const;

	/**
	 * Sets if this entity has GM immunity, making it not killable
	 * @param value the GM immunity of this entity
	 */
	void SetIsGMImmune(bool value);

	/**
	 * Returns whether or not this entity has GM immunity
	 * @return whether or not this entity has GM immunity
	 */
	bool GetIsGMImmune() const { return m_IsGMImmune; }

	/**
	 * Sets whether or not this entity is shielded for a certain amount of damage
	 * @param value whether or not this entity is shielded for a certain amount of damage
	 */
	void SetIsShielded(bool value);

	/**
	 * Returns if this entity is currently shielded from damage
	 * @return if this entity is currently shielded from damage
	 */
	bool GetIsShielded() const { return m_IsShielded; }

	/**
	 * Adds a faction to the faction list of this entity, potentially making more factions friendly. Fetches the info
	 * from the CDClient.
	 * @param factionID the faction ID to add
	 * @param ignoreChecks whether or not to allow factionID -1
	 */
	void AddFaction(int32_t factionID, bool ignoreChecks = false);

	/**
	 * Adds a faction ID to the enemy list
	 * @param factionID the faction ID to make an enemy
	 */
	void AddEnemyFaction(int32_t factionID);

	/**
	 * Sets whether or not this entity can be smashed, does not indicate the smashable glow, which is indicated by
	 * faction ids
	 * @param value whether or not this entity is smashable
	 */
	void SetIsSmashable(bool value);

	/**
	 * Returns whether or not this entity is smashable
	 * @return whether or not this entity is smashable
	 */
	bool GetIsSmashable() const { return m_IsSmashable; }

	/**
	 * Returns the current is-dead value, this is mostly unused
	 * @return the current is-dead value, this is mostly unused
	 */
	bool GetIsDead() const { return m_IsDead; }

	/**
	 * Returns the current is-smashed value, this is mostly unused
	 * @return the current is-smashed value, this is mostly unused
	 */
	bool GetIsSmashed() const { return m_IsSmashed; }

	/**
	 * Sets whether or not this entity has bricks flying out when smashed
	 * @param value whether or not this entity has bricks flying out when smashed
	 */
	void SetHasBricks(bool value);

	/**
	 * Returns whether or not this entity has bricks flying out when smashed
	 * @return whether or not this entity has bricks flying out when smashed
	 */
	bool GetHasBricks() const { return m_IsModuleAssembly; }

	/**
	 * Sets the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed
	 * @param value the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed
	 */
	void SetExplodeFactor(float value) { m_ExplodeFactor = value; };

	/**
	 * Returns the current multiplier for explosions
	 * @return the current multiplier for explosions
	 */
	float GetExplodeFactor() const { return m_ExplodeFactor; }

	/**
	 * Sets the amount of attacks this entity can block before being able to be damaged again, useful for example for
	 * shields.
	 * @param value the amount of attacks this entity can block before being able to be damaged again
	 */
	void SetAttacksToBlock(uint32_t value);

	/**
	 * Returns the current amount of attacks this entity can block
	 * @return the current amount of attacks this entity can block
	 */
	uint32_t GetAttacksToBlock() const { return m_AttacksToBlock; }

	/**
	 * Sets whether or not this enemy currently has threats, NOTE: only here for serialization, has no use internally
	 * @param value whether or not this enemy currently has threats
	 */
	void SetHasThreats(bool value);

	/**
	 * Returns whether or not this entity currently has threats, NOTE: unused internally
	 * @return whether or not this entity currently has threats
	 */
	bool GetHasThreats() const { return m_HasThreats; }

	/**
	 * Returns whether or not this entity is knockback immune, based on whether it's quickbuilding or has assembly gear
	 * @return whether or not this entity is knockback immune
	 */
	bool IsKnockbackImmune() const;

	/**
	 * Sets the faction ID of this entity, overriding all previously set entries
	 * @param factionID the faction ID to set
	 */
	void SetFaction(int32_t factionID, bool ignoreChecks = false);

	/**
	 * Returns whether or not the provided entity is an enemy of this entity
	 * @param other the entity to check
	 * @return whether the provided entity is an enemy of this entity or not
	 */
	bool IsEnemy(const Entity* other) const;

	/**
	 * Returns whether or not the provided entity is a friend of this entity
	 * @param other the entity to check
	 * @return whether or not the provided entity is a friend of this entity
	 */
	bool IsFriend(const Entity* other) const;

	/**
	 * Returns all the faction IDs that this entity considers a friend
	 * @return all the faction IDs that this entity considers a friend
	 */
	const std::vector<int32_t>& GetFactionIDs() const { return m_FactionIDs; }

	/**
	 * Returns all the faction IDs that this entity considers an enemy
	 * @return all the faction IDs that this entity considers an enemy
	 */
	const std::vector<int32_t>& GetEnemyFactionsIDs() const { return m_EnemyFactionIDs; }

	/**
	 * Returns whether the provided faction is a friendly faction
	 * @param factionID the faction ID to check
	 * @return whether the provided faction is a friendly faction
	 */
	bool HasFaction(int32_t factionID) const;

	/**
	 * Sets the minimum amount of coins this entity drops when smashed
	 * @param minCoins the minimum amount of coins this entity drops when smashed
	 */
	void SetMinCoins(uint32_t minCoins) { m_MinCoins = minCoins; }

	/**
	 * Returns the minimum amount of coins this entity drops when smashed
	 * @return the minimum amount of coins this entity drops when smashed
	 */
	uint32_t GetMinCoins() const { return m_MinCoins; }

	/**
	 * Sets the maximum amount of coins this entity drops when smashed
	 * @param maxCoins the maximum amount of coins this entity drops when smashed
	 */
	void SetMaxCoins(uint32_t maxCoins) { m_MaxCoins = maxCoins; }

	/**
	 * Returns the maximum amount of coins this entity drops when smashed
	 * @return the maximum amount of coins this entity drops when smashed
	 */
	uint32_t GetMaxCoins() const { return m_MaxCoins; }

	/**
	 * Sets the loot matrix ID that will be used to determine what items to drop when this entity is smashed
	 * @param lootMatrixID the loot matrix ID to set
	 */
	void SetLootMatrixID(uint32_t lootMatrixID) { m_LootMatrixID = lootMatrixID; }

	/**
	 * Returns the current loot matrix ID that will be used to determine loot drops when this entity is smashed
	 * @return the current loot matrix ID
	 */
	uint32_t GetLootMatrixID() const { return m_LootMatrixID; }

	/**
	 * Returns the ID of the entity that killed this entity, if any
	 * @return the ID of the entity that killed this entity, if any
	 */
	LWOOBJID GetKillerID() const;

	/**
	 * Returns the entity that killed this entity, if any
	 * @return the entity that killed this entity, if any
	 */
	Entity* GetKiller() const;

	/**
	 * Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks.
	 * @param damage the damage to attempt to apply
	 * @param source the attacker that caused this damage
	 * @param skillID the skill that damaged this entity
	 * @param echo whether or not to serialize the damage
	 */
	void Damage(uint32_t damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true);

	/**
	 * Smashes this entity, notifying all clients
	 * @param source the source that smashed this entity
	 * @param skillID the skill that killed this entity
	 * @param killType the way this entity was killed, determines if a client animation is played
	 * @param deathType the animation to play when killed
	 */
	void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u"", uint32_t skillID = 0);

	/**
	 * Push or Pop a layer of status immunity to this entity
	 */
	void SetStatusImmunity(
		const eStateChangeType state,
		const bool bImmuneToBasicAttack = false,
		const bool bImmuneToDamageOverTime = false,
		const bool bImmuneToKnockback = false,
		const bool bImmuneToInterrupt = false,
		const bool bImmuneToSpeed = false,
		const bool bImmuneToImaginationGain = false,
		const bool bImmuneToImaginationLoss = false,
		const bool bImmuneToQuickbuildInterrupt = false,
		const bool bImmuneToPullToPoint = false
	);

	// Getters for status immunities
	const bool GetImmuneToBasicAttack() { return m_ImmuneToBasicAttackCount > 0; };
	const bool GetImmuneToDamageOverTime() { return m_ImmuneToDamageOverTimeCount > 0; };
	const bool GetImmuneToKnockback() { return m_ImmuneToKnockbackCount > 0; };
	const bool GetImmuneToInterrupt() { return m_ImmuneToInterruptCount > 0; };
	const bool GetImmuneToSpeed() { return m_ImmuneToSpeedCount > 0; };
	const bool GetImmuneToImaginationGain() { return m_ImmuneToImaginationGainCount > 0; };
	const bool GetImmuneToImaginationLoss() { return m_ImmuneToImaginationLossCount > 0; };
	const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 0; };
	const bool GetImmuneToPullToPoint() { return m_ImmuneToPullToPointCount > 0; };

	// Damage cooldown setters/getters
	void SetDamageCooldownTimer(float value) { m_DamageCooldownTimer = value; }
	float GetDamageCooldownTimer() { return m_DamageCooldownTimer; }

	// Death behavior setters/getters
	void SetDeathBehavior(int32_t value) { m_DeathBehavior = value; }
	int32_t GetDeathBehavior() const { return m_DeathBehavior; }

	/**
	 * Utility to reset all stats to the default stats based on items and completed missions
	 */
	void FixStats();

	/**
	 * Adds a callback that is called when this entity is hit by some other entity
	 * @param callback the callback to add
	 */
	void AddOnHitCallback(const std::function<void(Entity*)>& callback);

	/**
	 * Pushes a faction back to the list of factions.
	 * @param value Faction to add to list.
	 *
	 * This method should only be used for testing.  Use AddFaction(int32_t, bool) for adding a faction properly.
	 */
	void AddFactionNoLookup(int32_t faction) { m_FactionIDs.push_back(faction); };

	/**
	 * Notify subscribed scripts of Damage actions.
	 *
	 * @param attacker The attacking Entity
	 * @param damage The amount of damage that was done
	 */
	void NotifySubscribers(Entity* attacker, uint32_t damage);

	void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd);
	void Unsubscribe(LWOOBJID scriptObjId);

	// handle hardcode mode drops
	void DoHardcoreModeDrops(const LWOOBJID source);

private:
	/**
	 * Whether or not the health should be serialized
	 */
	bool m_DirtyHealth;

	/**
	 * The health of the entity
	 */
	int32_t m_iHealth;

	/**
	 * The max health of the entity
	 */
	float m_fMaxHealth;

	/**
	 * The armor of the entity
	 */
	int32_t m_iArmor;

	/**
	 * The max armor of the entity
	 */
	float m_fMaxArmor;

	/**
	 * The imagination of the entity
	 */
	int32_t m_iImagination;

	/**
	 * The max imagination of the entity
	 */
	float m_fMaxImagination;

	/**
	 * The damage this entity can absord before being able to be damaged again
	 */
	int32_t m_DamageToAbsorb;

	/**
	 * Whether this entity currently has GM immunity, making it unsmashable
	 */
	bool m_IsGMImmune;

	/**
	 * Whether this entity is currently shielded from other attacks
	 */
	bool m_IsShielded;

	/**
	 * The number of attacks this entity can block before being able to be attacked again
	 */
	uint32_t m_AttacksToBlock;

	/**
	 * The amount of damage that should be reduced from every attack
	 */
	int32_t m_DamageReduction;

	/**
	 * The faction IDs this entity considers friendly
	 */
	std::vector<int32_t> m_FactionIDs;

	/**
	 * The faction IDs this entity considers hostile
	 */
	std::vector<int32_t> m_EnemyFactionIDs;

	/**
	 * Whether this entity is smasahble, mostly unused
	 */
	bool m_IsSmashable;

	/**
	 * Whether this entity is dead. Unused, here for serialization
	 */
	bool m_IsDead;

	/**
	 * Whether this entity is smashed. Unused, here for serialization
	 */
	bool m_IsSmashed;

	/**
	 * Whether this entity has bricks flying out when smashed (causes the client to look up the files)
	 */
	bool m_IsModuleAssembly;

	/**
	 * The rate at which bricks fly out when smashed
	 */
	float m_ExplodeFactor;

	/**
	 * Whether the list of potential enemies has changed
	 */
	bool m_DirtyThreatList;

	/**
	 * Whether the entity has threats. Unused: here for serialization
	 */
	bool m_HasThreats;

	/**
	 * The loot matrix that will be used to drop items when the entity is smashed
	 */
	uint32_t m_LootMatrixID;

	/**
	 * The min amount of coins that will drop when this entity is smashed
	 */
	uint32_t m_MinCoins;

	/**
	 * The max amount of coins that will drop when this entity is smashed
	 */
	uint32_t m_MaxCoins;

	/**
	 * The ID of the entity that smashed this entity, if any
	 */
	LWOOBJID m_KillerID;

	/**
	 * The list of callbacks that will be called when this entity gets hit
	 */
	std::vector<std::function<void(Entity*)>> m_OnHitCallbacks;

	/**
	 * The list of scripts subscribed to this components actions
	 */
	std::map<LWOOBJID, CppScripts::Script*> m_SubscribedScripts;

	/**
	 * status immunity counters
	 */
	uint32_t m_ImmuneToBasicAttackCount;
	uint32_t m_ImmuneToDamageOverTimeCount;
	uint32_t m_ImmuneToKnockbackCount;
	uint32_t m_ImmuneToInterruptCount;
	uint32_t m_ImmuneToSpeedCount;
	uint32_t m_ImmuneToImaginationGainCount;
	uint32_t m_ImmuneToImaginationLossCount;
	uint32_t m_ImmuneToQuickbuildInterruptCount;
	uint32_t m_ImmuneToPullToPointCount;

	/**
	 * Death behavior type.  If 0, the client plays a death animation as opposed to a smash animation.
	 */
	int32_t m_DeathBehavior;

	/**
	 * Damage immunity cooldown timer. Set to a value that then counts down to create a damage cooldown for players
	 */
	float 	m_DamageCooldownTimer;
};

#endif // DESTROYABLECOMPONENT_H