#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~

Copyright 2018 - 2023 OneLoneCoder.com

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following	disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS	"AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT	HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL,	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
#pragma endregion
#pragma once
#include "olcUTIL_Animate2D.h"
#include "Animation.h"
#include "Monster.h"
#include "State.h"
#include "Ability.h"
#include "Buff.h"
#include "Pathfinding.h"
#include "config.h"
#include "Key.h"
#include "Class.h"
#include "Item.h"

#undef GetClassName
struct DamageNumber;

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

struct Player{
	friend class Crawler;
	friend class sig::Animation;
	friend struct Warrior;
	friend struct Thief;
	friend struct Ranger;
	friend struct Trapper;
	friend struct Wizard;
	friend struct Witch;
	friend class State_GameRun;
private:
	int hp="Warrior.BaseHealth"_I,maxhp=hp;
	int mana="Player.BaseMana"_I,maxmana=mana;
	int atk="Warrior.BaseAtk"_I;
	float hpGrowthRate="Warrior.HealthGrowthRate"_F;
	float atkGrowthRate="Warrior.AtkGrowthRate"_F;
	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.f};
	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 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();
	float iframe_time=0;
	float lastCombatTime=0;
	Ability useItem1;
	Ability useItem2;
	Ability useItem3;
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 SetFacingDirection(Key direction);
	void SetLastReleasedMovementKey(Key k);
	void Spin(float duration,float spinSpd);
	float friction="Player.Friction"_F;
	float attack_cooldown_timer=0;
	float teleportAnimationTimer=0;
	vf2d teleportTarget={};
	vf2d teleportStartPosition={};
	std::pair<std::string,float> notificationDisplay={"",0.f};
	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.025f;
	float ghostFrameTimer=0;
	float ghostRemoveTimer=0;
	float blockTimer=0;
	float retreatTimer=0;
	std::vector<vf2d>ghostPositions;
	float rapidFireTimer=0;
	int remainingRapidFireShots=0;
	float endZoneStandTime=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();
	void SetSizeMult(float size);
	float GetAttackRangeMult();
	float GetSpinAngle();
	State::State GetState();
	Key GetFacingDirection();
	vf2d GetVelocity();
	bool HasIframes();
	void Update(float fElapsedTime);
	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();
	bool CanAct(Ability&ability);
	void Knockback(vf2d vel);
	void SetIframes(float duration);
	void RestoreMana(int amt,bool suppressDamageNumber=false);
	void ConsumeMana(int amt);
	//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 SetState(State::State newState);

	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);
	//Return false if healing was not possible.
	bool Heal(int damage);
	//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();
	void ResetLastCombatTime();
	bool IsOutOfCombat();
	float GetEndZoneStandTime();
	//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;

	void CheckEndZoneCollision();

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

	void SetItem1UseFunc(Ability a);
	void SetItem2UseFunc(Ability a);
	void SetItem3UseFunc(Ability a);

	static InputGroup KEY_ABILITY1, KEY_ABILITY2, KEY_ABILITY3, KEY_ABILITY4, KEY_DEFENSIVE, KEY_ITEM1, KEY_ITEM2, KEY_ITEM3;
};

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.Short Name"_S, \
		#class".Right Click Ability.Description"_S, \
		#class".Right Click Ability.Cooldown"_F, \
		#class".Right Click Ability.Mana Cost"_I, \
		&KEY_DEFENSIVE, \
		"Ability Icons/"+#class".Right Click Ability.Icon"_S, \
		{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}, \
		bool( #class".Right Click Ability.CancelCast"_I ) \
	}; \
	class::ability1={ \
		#class".Ability 1.Name"_S, \
		#class".Ability 1.Short Name"_S, \
		#class".Ability 1.Description"_S, \
		#class".Ability 1.Cooldown"_F, \
		#class".Ability 1.Mana Cost"_I, \
		&KEY_ABILITY1, \
		"Ability Icons/"+#class".Ability 1.Icon"_S, \
		{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}, \
		bool(#class".Ability 1.CancelCast"_I) \
	}; \
	class::ability2={ \
		#class".Ability 2.Name"_S, \
		#class".Ability 2.Short Name"_S, \
		#class".Ability 2.Description"_S, \
		#class".Ability 2.Cooldown"_F, \
		#class".Ability 2.Mana Cost"_I, \
		&KEY_ABILITY2, \
		"Ability Icons/"+#class".Ability 2.Icon"_S, \
		{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}, \
		bool(#class".Ability 2.CancelCast"_I) \
	}; \
	class::ability3={ \
		#class".Ability 3.Name"_S, \
		#class".Ability 3.Short Name"_S, \
		#class".Ability 3.Description"_S, \
		#class".Ability 3.Cooldown"_F, \
		#class".Ability 3.Mana Cost"_I, \
		&KEY_ABILITY3, \
		"Ability Icons/"+#class".Ability 3.Icon"_S, \
		{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}, \
		bool(#class".Ability 3.CancelCast"_I) \
	}; \
	class::ability4; 