#pragma once
#include "olcUTIL_Animate2D.h"
#include "Animation.h"
#include "Monster.h"
#include "State.h"
#include "Ability.h"
#include "Class.h"
#include "Buff.h"
#include "Pathfinding.h"
#include "DamageNumber.h"
#include "config.h"

struct CastInfo{
	std::string name;
	float castTimer;
	float castTotalTime;
	vf2d castPos;
};

struct Player{
	friend class Crawler;
	friend class sig::Animation;
	friend class Warrior;
	friend class Thief;
	friend class Ranger;
	friend class Trapper;
	friend class Wizard;
	friend class Witch;
private:
	int hp="Player.BaseHealth"_I,maxhp=hp;
	int mana="Player.BaseMana"_I,maxmana=mana;
	int atk="Player.BaseAtk"_I;
	vf2d pos;
	float z=0;
	float moveSpd=1.0f;
	float size=1.0f;
	float spin_attack_timer=0;
	float spin_spd=0;
	float spin_angle=0;
	float lastAnimationFlip=0;
	float manaTickTimer=0;
	std::pair<std::string,float> notEnoughManaDisplay={"",0};
	float teleportAttemptWaitTime=0; //If a teleport fails, we wait awhile before trying again, it's expensive.
	State::State state=State::NORMAL;
	Animate2D::Animation<std::string>animation;
	Animate2D::AnimationState internal_animState;
	Key lastReleasedMovementKey;
	void Update(float fElapsedTime);
	void AddAnimation(std::string state);
	std::vector<Buff>buffList;
	CastInfo castInfo={"",0};
	vf2d movementVelocity={};//This tells us if the player is moving (mostly controlled by user input) since their velocity is not used for regular movement.
	float lastHitTimer=0; //When this is greater than zero, if we get hit again it adds to our displayed combo number.
	std::shared_ptr<DamageNumber>damageNumberPtr;
	void Initialize();
protected:
	const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
	const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;
	float ARROW_ATTACK_COOLDOWN="Ranger.Auto Attack.Cooldown"_F;
	void SetSwordSwingTimer(float val);
	void SetState(State::State newState);
	void SetFacingDirection(Key direction);
	void SetLastReleasedMovementKey(Key k);
	void Spin(float duration,float spinSpd);
	//Returns true if the move was valid and successful.
	bool SetX(float x);
	//Returns true if the move was valid and successful.
	bool SetY(float y);
	void SetZ(float z);
	//Returns true if the move was valid and successful.
	bool SetPos(vf2d pos);
	void Knockback(vf2d vel);
	float friction="Player.Friction"_F;
	float attack_cooldown_timer=0;
	float iframe_time=0;
	float teleportAnimationTimer=0;
	vf2d teleportTarget={};
	vf2d teleportStartPosition={};
	std::pair<std::string,float> notificationDisplay={"",0};
	bool upperLevel=false;
	vf2d vel={0,0};
	float attack_range="Warrior.Auto Attack.Range"_F/100.f;
	Key facingDirection=DOWN;
	float swordSwingTimer=0;
	void CastSpell(Ability&ability);
	Ability*castPrepAbility;
	void PrepareCast(Ability&ability);
	vf2d precastLocation={};
	void SetVelocity(vf2d vel);
	const float RETREAT_DISTANCE=24*"Ranger.Right Click Ability.RetreatDistance"_F/100;
	float RETREAT_TIME="Ranger.Right Click Ability.RetreatTime"_F; //How long the Retreat ability takes.
	const int RETREAT_GHOST_FRAMES=8;
	const float RETREAT_GHOST_FRAME_DELAY=0.025;
	float ghostFrameTimer=0;
	float ghostRemoveTimer=0;
	float blockTimer=0;
	float retreatTimer=0;
	std::vector<vf2d>ghostPositions;
	float rapidFireTimer=0;
	int remainingRapidFireShots=0;
	const float RAPID_FIRE_SHOOT_DELAY="Ranger.Ability 1.ArrowDelay"_F;
	const int RAPID_FIRE_SHOOT_AMOUNT="Ranger.Ability 1.ArrowCount"_I;
public:
	Player();
	//So this is rather fascinating and only exists because we have the ability to change classes which means we need to initialize a class
	//using a new object type... Because of that we'll take the pointer reference to the old object and copy some of its properties to this new
	//one. It's hackish but it means we can reduce the amount of extra boilerplate when class changing...I don't know how to feel about this.
	Player(Player*player);
	static float GROUND_SLAM_SPIN_TIME;
	vf2d&GetPos();
	float GetX();
	float GetY();
	float GetZ();
	int GetHealth();
	int GetMaxHealth();
	int GetMana();
	int GetMaxMana();
	int GetAttack();
	float GetMoveSpdMult();
	float GetSizeMult();
	float GetAttackRangeMult();
	float GetSpinAngle();
	State::State GetState();
	Key GetFacingDirection();
	vf2d GetVelocity();
	bool HasIframes();
	void UpdateWalkingAnimation(Key direction);
	void UpdateIdleAnimation(Key direction);
	//The range is the search range in tiles.
	bool CanPathfindTo(vf2d pos,vf2d targetPos,float range=8);
	bool CanMove();
	bool CanAct();

	void AddBuff(BuffType type,float duration,float intensity);
	std::vector<Buff>GetBuffs(BuffType buff);
	void RemoveBuff(BuffType type); //Removes the first buff found.
	void RemoveAllBuffs(BuffType type); //Removes all buffs of a certain type.
	void RemoveAllBuffs(); //Remove every buff.

	bool Hurt(int damage,bool onUpperLevel,float z);
	//specificClass is a bitwise-combination of classes from the Class enum. It makes sure certain animations only play if you are a certain class.
	void UpdateAnimation(std::string animState,int specificClass=ANY);
	Animate2D::Frame GetFrame();
	Key GetLastReleasedMovementKey();
	float GetSwordSwingTimer();
	bool OnUpperLevel();
	//Triggers when the player has moved.
	void Moved();
	virtual ~Player()=default;
	virtual Class GetClass()=0;
	virtual bool AutoAttack()=0;
	virtual void OnUpdate(float fElapsedTime)=0;
	virtual std::string GetClassName()=0;
	virtual Ability&GetRightClickAbility()=0;
	virtual Ability&GetAbility1()=0;
	virtual Ability&GetAbility2()=0;
	virtual Ability&GetAbility3()=0;
	virtual Ability&GetAbility4()=0;
	virtual std::string&GetWalkNAnimation()=0;
	virtual std::string&GetWalkEAnimation()=0;
	virtual std::string&GetWalkSAnimation()=0;
	virtual std::string&GetWalkWAnimation()=0;
	virtual std::string&GetIdleNAnimation()=0;
	virtual std::string&GetIdleEAnimation()=0;
	virtual std::string&GetIdleSAnimation()=0;
	virtual std::string&GetIdleWAnimation()=0;

	CastInfo&GetCastInfo();
	void SetAnimationBasedOnTargetingDirection(float targetDirection);
};

struct Warrior:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Warrior();
	Warrior(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only WARRIOR-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

struct Thief:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Thief();
	Thief(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only THIEF-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

struct Ranger:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Ranger();
	Ranger(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only RANGER-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

struct Trapper:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Trapper();
	Trapper(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only TRAPPER-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

struct Wizard:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Wizard();
	Wizard(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only WIZARD-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

struct Witch:Player{
	static std::string name;
	static Class cl;
	static Ability rightClickAbility,ability1,ability2,ability3,ability4;
	static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
	static void Initialize();
	Witch();
	Witch(Player*player);
	Class GetClass()override;
	bool AutoAttack()override;
	//Include only WITCHs-specific implementations!
	void OnUpdate(float fElapsedTime)override;
	static void InitializeClassAbilities();
	std::string GetClassName()override;
	Ability&GetRightClickAbility()override;
	Ability&GetAbility1()override;
	Ability&GetAbility2()override;
	Ability&GetAbility3()override;
	Ability&GetAbility4()override;
	std::string&GetWalkNAnimation()override;
	std::string&GetWalkEAnimation()override;
	std::string&GetWalkSAnimation()override;
	std::string&GetWalkWAnimation()override;
	std::string&GetIdleNAnimation()override;
	std::string&GetIdleEAnimation()override;
	std::string&GetIdleSAnimation()override;
	std::string&GetIdleWAnimation()override;
};

#define READFROMCONFIG(class,enum) \
	class::name=#class".ClassName"_S; \
	class::cl=enum; \
	class::rightClickAbility={ \
		#class".Right Click Ability.Name"_S, \
		#class".Right Click Ability.Cooldown"_F, \
		#class".Right Click Ability.Mana Cost"_I, \
		{uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[3]==0?255:#class".Right Click Ability.Cooldown Bar Color 1"_f[3])}, \
		{uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[3]==0?255:#class".Right Click Ability.Cooldown Bar Color 2"_f[3])}, \
		{#class".Right Click Ability.Precast Time"_F,#class".Right Click Ability.Casting Range"_I/100.f*24,#class".Right Click Ability.Casting Size"_I/100.f*24} \
	}; \
	class::ability1={ \
		#class".Ability 1.Name"_S, \
		#class".Ability 1.Cooldown"_F, \
		#class".Ability 1.Mana Cost"_I, \
		{uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 1.Cooldown Bar Color 1"_f[3])}, \
		{uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 1.Cooldown Bar Color 2"_f[3])}, \
		{#class".Ability 1.Precast Time"_F,#class".Ability 1.Casting Range"_I/100.f*24,#class".Ability 1.Casting Size"_I/100.f*24} \
	}; \
	class::ability2={ \
		#class".Ability 2.Name"_S, \
		#class".Ability 2.Cooldown"_F, \
		#class".Ability 2.Mana Cost"_I, \
		{uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 2.Cooldown Bar Color 1"_f[3])}, \
		{uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 2.Cooldown Bar Color 2"_f[3])}, \
		{#class".Ability 2.Precast Time"_F,#class".Ability 2.Casting Range"_I/100.f*24,#class".Ability 2.Casting Size"_I/100.f*24} \
	}; \
	class::ability3={ \
		#class".Ability 3.Name"_S, \
		#class".Ability 3.Cooldown"_F, \
		#class".Ability 3.Mana Cost"_I, \
		{uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 3.Cooldown Bar Color 1"_f[3])}, \
		{uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 3.Cooldown Bar Color 2"_f[3])}, \
		{#class".Ability 3.Precast Time"_F,#class".Ability 3.Casting Range"_I/100.f*24,#class".Ability 3.Casting Size"_I/100.f*24} \
	}; \
	class::ability4={"???",0,0};