// Project Lab - NHTV Igad

#pragma once
#include <unordered_map>
using std::unordered_map;
#include "AbilityState.h"
#include "ScalingGraph.h"
#include "NetworkPossessable.h"
#include "IngameSkillTree.h"
#include "NetworkCharacter.generated.h"

class ACreatureSpawn;

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCharacterKilled, ANetworkCharacter*, killer, UAbilityInfo*, ability);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDamageTaken, ANetworkCharacter*, dealer, int32, damage, UAbilityInfo*, ability);

UCLASS(config = Game)
class ANetworkCharacter : public ANetworkPossessable
{
	GENERATED_BODY()
public:
	ANetworkCharacter();

	virtual void BeginPlay() override; 
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void Destroyed() override;
	virtual void OnDeath(UAbilityInfo* ability);
	void RegisterHealthBar();
	void UnregisterHealthBar();

	virtual void Tick(float DeltaSeconds) override;

	// This processes the learned abilities on the server and calls LearnSkills on the owning client
	void LearnSkills_Server(const TArray<FIngameSkillTreeSkill>& skills);
	// This creates a list of all learned skills on the client's version of the character so that he gets an updated casting bar, etc.
	UFUNCTION(Client, Reliable)
	void LearnSkills(const TArray<FIngameSkillTreeSkill>& skills);

	// The Buff/Modifier manager, for server side characters
	class ModifierManager* GetModifierManager();
	UFUNCTION(BlueprintCallable, Category = "modifiers")
	TArray<class AModifier*>  GetModifiersOfClass(TSubclassOf<class AModifier> modifierClass);

	virtual void ResetModifiers();
	void CheckStatsOverflow();
	
	// Damage dealing
	UFUNCTION(BlueprintCallable, Category = "Damage", meta = (WorldContext="WorldContextObject"))
	int32 DealDamage(class UObject* WorldContextObject, int32 damage, float armorPercentageIgnore = 0.0f);
	virtual int32 NativeDealDamage(class ANetworkCharacter* dealer, int32 damage, float armorPercentageIgnore = 0.0f, UAbilityInfo* ability = nullptr);

	virtual void NativeOnKilled(class ANetworkCharacter* killer, class UAbilityInfo* ability);

	UFUNCTION(BlueprintCallable, Category = "Damage")
	void OnStandardAttack(ANetworkCharacter* targetCharacter);

	

	// Healing
	UFUNCTION(BlueprintCallable, Category = "Healing")
	void AddHealth(int32 health);
	virtual void NativeAddHealth(int32 health);

	void RemoveHealth(int32 health);
	void AddMana(float mana);
	void RemoveMana(float mana);
	UFUNCTION(BlueprintCallable, Category = "Damage")
	float GetTimeSinceDamage() const;

	UFUNCTION(BlueprintCallable, Category = "Animation")
	void TriggerSwingAnimation_Server();

	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetHealth() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetMana() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetBlockedMana() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetMaxHealth() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetMaxMana() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	int32 GetArmor() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool GetHitable() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	void SetHitable(bool hitable);
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetDamageMultiplier() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetCooldownReduction() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetAttackSpeed() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetAttackDamage() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetMagicDamage() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	float GetTotalDamage() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool IsStunned() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool CanRotate() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool IsSilenced() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool IsChanneling() const;
	UFUNCTION(BlueprintCallable, Category = "State")
	bool IsRanged() const;
	bool CanBeStunned() const;

	ANetworkCharacter* GetLastPlayerDamage() const;

	void CastAbility(int32 abilityIndex);
	void CastAbility(class UAbilityInfo* abilityInfo);

	// Returns the ability that is currently being casted(channeling)
	UFUNCTION(BlueprintCallable, Category = "Ability")
	class UAbilityInfo* GetCastingAbility();

	// Gets the persistent state of an ability between casts
	class AAbilityState* GetAbilityState(class UAbilityInfo* abilityInfo);

	// Gets the toggle state for abilities that have isToggleAbility set
	bool GetAbilityToggleState(class UAbilityInfo* abilityInfo);
	bool m_initialised;

	// Minimap/FoW visibility
	bool IsVisible() const;

	// The collection of abilities this character has, for players.
	// The first ability in this array is always treated the character's basic attack,
	//  and thus is affected by the attack speed variable of the character
	UPROPERTY(EditDefaultsOnly, Category = "Ability")
	TArray<class UAbilityInfo*> abilities;

	UPROPERTY(BlueprintReadOnly, Replicated, Category = "Ability")
	class UAbilityInfo* basicAttack;

	UPROPERTY(EditDefaultsOnly, Category = "Ability")
	TArray<class UAbilityInfo*> passives;

	UPROPERTY(Replicated, BlueprintReadOnly, Category = "Animation")
	int32 swingAnimationSequence;

	UPROPERTY(EditAnywhere, Category = "Experience Settings")
	int32 experienceGainOnKill;

	// Intervals at which damage and healing combat text are spawned
	UPROPERTY(EditAnywhere, Category = "Combat Text")
	float healthAccumTimer;
	UPROPERTY(EditAnywhere, Category = "Combat Text")
	float damageAccumTimer;

	// Duration before a team's damage is no longer valid
	// Gets refreshed when a team deals damage to the creature
	UPROPERTY(EditAnywhere, Category = "Team Damage Table")
	float teamDamageTimer;

	UPROPERTY(BlueprintAssignable, Category = "Character")
	FCharacterKilled onCharacterKilled;
	// SERVER ONLY
	UPROPERTY(BlueprintAssignable, Category = "Character")
	FOnDamageTaken onDamageTaken;

	METRICS_EXPR(Metrics::PlayerHandle* metricsHandle);

protected:
	// Called when the character is spawned or reset to set initial modifiers like fixed health/mana-regen
	// overridable
	virtual void m_SpawnModifiers();

	// This checks stunn state, etc. to allow NetworkPossessable to move
	virtual bool m_AllowedToMove() override;
	virtual bool m_AllowedToRotate() override;
	virtual void m_SetLevelStats();

	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	int32 baseMovementSpeed;
	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	int32 baseArmor;
	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	int32 passiveManaRegen;
	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	int32 passiveHealthRegen;
	
	UPROPERTY(Replicated)
	int32 m_blockedMana;
	UPROPERTY(Replicated)
	float m_damageMultiplier;
	UPROPERTY(Replicated)
	float m_ignoreArmor;
	UPROPERTY(Replicated)
	float m_manaRegenMultiplier;
	UPROPERTY(Replicated)
	float m_manaUsageMultiplier;
	UPROPERTY(Replicated)
	float m_positiveEffectMultiplier;
	UPROPERTY(Replicated)
	float m_negativeEffectMultiplier;
	UPROPERTY(Replicated)
	float m_damageTakenMultiplier;
	UPROPERTY(Replicated)
	int32 m_armor;
	UPROPERTY(Replicated)
	bool m_stunned;
	UPROPERTY(Replicated)
	bool m_silenced;
	//sileneCount is the ammount of silences by modifiers, silence by casting is not taking into account.
	UPROPERTY(Replicated)
	uint32 m_silenceCount;
	UPROPERTY(Replicated)
	bool m_visible;
	UPROPERTY(Replicated)
	bool m_hitable;
	UPROPERTY(Replicated)
	bool m_isRanged;

	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	bool canBeStunned;
	UPROPERTY(EditDefaultsOnly, Category = "Stats")
	bool canBeSilenced;

	UPROPERTY(Replicated)
	int32 m_health;
	UPROPERTY(Replicated)
	int32 m_maxHealth;
	UPROPERTY(Replicated)
	float m_mana;
	UPROPERTY(Replicated)
	int32 m_maxMana;
	UPROPERTY(Replicated)
	float m_cooldownReduction;
	UPROPERTY(Replicated)
	float m_attackSpeed;
	UPROPERTY(Replicated)
	float m_attackDamageMultiplier;
	UPROPERTY(Replicated)
	float m_magicDamageMultiplier;

	UPROPERTY(Replicated)
	bool m_usesMana;

	UPROPERTY(Replicated)
	float m_castingMovementspeedMultiplier;

	friend class ModifierManager;
	friend class APreCastAbilityEventGroup;
	friend class AIllusionCharacter;
	friend class ADefaultPlayerController;
	class ModifierManager* m_modifierManager;

	bool m_shouldBeDestroyed = false;
private:
	// Request to the server to cast an ability
	UFUNCTION(Reliable, Server, WithValidation)
	void m_CastAbility_Server(class UAbilityInfo* ability);
	void m_InitAbilitySequence(class UAbilityInfo* ability, class AAbilityState* state);

	// Updates all the cooldown timers and ticks modifiers
	void m_TickAbilities(float DeltaSeconds);

	void m_SetAbilityCooldown(class UAbilityInfo* ability, class AAbilityState* state);

	// Server only casting interupt
	void m_InteruptSpellcasting();

	// Called if we know an ability is successfully casted or not
	UFUNCTION(Reliable, Client)
		void m_OnAbilityCastConfirm(UAbilityInfo* ability, bool success, bool toggleState, float cooldown, float cooldownTime);

	void m_PreCast(UAbilityInfo* ability);
	// Called if we know an ability is successfully casted or not (server-side)
	virtual void m_OnAbilityCastConfirm_Server(UAbilityInfo* ability, bool success);

	// Callback for when an ability progresses to it's next state
	UFUNCTION()
	void m_OnAbilityEventGroupEnded(class UAbilityInfo* ability, class AAbilityEventGroup* current, class AAbilityEventGroup* next);

	friend class ADefaultPlayerState;

	// Active Stats
	struct DamagePeriod
	{
		DamagePeriod(int32 damage, float timer) : damage(damage), timer(timer) {}
		int32 damage;
		float timer;
	};
	unordered_map<class ADefaultPlayerController*, DamagePeriod> m_damageAccum;
	int32 m_damageAccumValue;
	float m_damageAccumTimer;
	int32 m_healAccumValue;
	float m_healAccumTimer;
	float m_totalDamage;
	unordered_map<int32, DamagePeriod> m_teamDamageDealt;

	UPROPERTY()
	ANetworkCharacter* m_lastPlayerDamage;

	// Keeps ability persistent state between casts
	UPROPERTY()
	TMap<class UAbilityInfo*, class AAbilityState*> m_abilityStates;
	// Keeps ability execution states / active group (server-side)
	UPROPERTY()
	TMap<class UAbilityInfo*, class AAbilityEventGroup*> m_activeAbilities;

	friend class AModifier;
	friend class AVisibilityModifier;
	float m_timeSinceDamage;
	UPROPERTY()
	class AAbilityEventGroup* m_channelGroup;
	UPROPERTY(Replicated)
	class UAbilityInfo* m_castingAbility;
	UPROPERTY(Replicated)
	bool m_channelStun;
	UPROPERTY(Replicated)
	bool m_allowChannelRotation;


	class APreCastAbilityEventGroup* m_currentPreCast;

};

