#include "Monster.h"
#include "MonsterStrategyHelpers.h"
#include "DEFINES.h"
#include "Crawler.h"
#include "utils.h"
#include "safemap.h"
#include "Effect.h"
#include "FallingDebris.h"
#include "MonsterAttribute.h"

INCLUDE_game
INCLUDE_BULLET_LIST
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_NAME_DATA

typedef Attribute A;

void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumber){
	float bulletSpd=ConfigFloat("BulletSpd")/100*24;

	m.F(A::SHOOT_TIMER)=std::max(0.f,m.F(A::SHOOT_TIMER)-fElapsedTime);
	m.F(A::SHOOT_RING_TIMER)=std::max(0.f,m.F(A::SHOOT_RING_TIMER)-fElapsedTime);
	m.F(A::SHOOT_RING_DELAY)=std::max(0.f,m.F(A::SHOOT_RING_DELAY)-fElapsedTime);
	m.F(A::JUMP_LANDING_TIMER)=std::max(0.f,m.F(A::JUMP_LANDING_TIMER)-fElapsedTime);
	m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
	m.F(A::RUN_AWAY_TIMER)=std::max(0.f,m.F(A::RUN_AWAY_TIMER)-fElapsedTime);

	const auto ShootBulletRing=[&](float angleOffset){
		int bulletCount=ConfigInt("Phase1.RingBulletCount");
		for(int i=0;i<bulletCount;i++){
			float angle=((2*PI)/bulletCount)*i+angleOffset;
			BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
		}
	};

	const auto Landed=[&ShootBulletRing,&m](int currentPhase){
		if(currentPhase==1){
			ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
		}
	};

	const auto TransitionPhase=[&](int newPhase){
		const int MAX_ATTEMPTS=100; //Maximum number of tries to find a valid location.
		Player*player=game->GetPlayer();
		m.I(A::PATTERN_REPEAT_COUNT)=0;
		const auto PositionInRangeOfPlayer=[&player](vf2d&pos,float radius){
			return geom2d::line<float>(player->GetPos(),pos).length()<=player->GetSizeMult()*12*2+radius;
		};
		const auto SpawnMonsterFromConfig=[&](int phase){
			std::string spawnMonster=ConfigStringArr("Phase"+std::to_string(phase)+".MonsterSpawnOnChange",0);
			int spawnCount=ConfigIntArr("Phase"+std::to_string(phase)+".MonsterSpawnOnChange",1);
			for(int i=0;i<spawnCount;i++){
				float randomAngle=util::random(2*PI);
				vf2d spawnPos=m.pos+vf2d{cos(randomAngle),sin(randomAngle)}*m.GetSizeMult()*12;
				for(int attempts=0;attempts<MAX_ATTEMPTS&&PositionInRangeOfPlayer(spawnPos,MONSTER_NAME_DATA[spawnMonster]->GetSizeMult()*12);attempts++){
					randomAngle=util::random(2*PI);
					spawnPos=m.pos+vf2d{cos(randomAngle),sin(randomAngle)}*m.GetSizeMult()*12;
				}
				game->SpawnMonster(spawnPos,MONSTER_NAME_DATA[spawnMonster]); 
			}
		};
		switch(newPhase){
			case 2:{
				SpawnMonsterFromConfig(2);
			}break;
			case 3:{
				SpawnMonsterFromConfig(3);
			}break;
			case 4:{
				SpawnMonsterFromConfig(4);
			}break;
			case 5:{

			}break;
		}
	};

	const auto StartJumpTowardsPlayer=[&](float jumpDuration,float recoveryTime,float jumpMoveSpd){
		m.F(A::JUMP_ORIGINAL_LANDING_TIMER)=m.F(A::JUMP_LANDING_TIMER)=jumpDuration;
		m.B(A::JUMP_TOWARDS_PLAYER)=true;
		m.F(A::RECOVERY_TIME)=recoveryTime;
		m.F(A::JUMP_MOVE_SPD)=jumpMoveSpd;
		m.SetState(State::JUMP);
	};

	const auto StartJump=[&](float jumpDuration,vf2d targetPos,float recoveryTime,float jumpMoveSpd){
		m.F(A::JUMP_ORIGINAL_LANDING_TIMER)=m.F(A::JUMP_LANDING_TIMER)=jumpDuration;
		m.V(A::JUMP_TARGET_POS)=targetPos;
		m.B(A::JUMP_TOWARDS_PLAYER)=false;
		m.F(A::RECOVERY_TIME)=recoveryTime;
		m.F(A::JUMP_MOVE_SPD)=jumpMoveSpd;
		m.SetState(State::JUMP);
	};

	const auto Recovered=[&](){
		switch(m.phase){
			case 2:{
				switch(m.I(A::JUMP_COUNT)){
					case 1:
					case 2:{ //After the 1st and 2nd jumps we still have another jump to accomplish.
						m.I(A::JUMP_COUNT)++;
						float jumpTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",0);
						float jumpSpd=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",1);
						StartJumpTowardsPlayer(jumpTime,0.2,jumpSpd);
					}break;
					default:{
						m.PerformIdleAnimation();
						m.SetState(State::NORMAL);
					}
				}
			}break;
		}
	};

	if(m.F(A::RUN_AWAY_TIMER)>0){
		Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,4);
		/*HACK ALERT!! This is kind of a hack. If the Run Away script changes the 4 would be inaccurate, but the run away script doesn't use this value so it's probably fine.*/
		return;
	}

	if(m.GetState()==State::RECOVERY){
		m.F(A::RECOVERY_TIME)=std::max(0.f,m.F(A::RECOVERY_TIME)-fElapsedTime);
		if(m.F(A::RECOVERY_TIME)==0){
			m.SetState(State::NORMAL);
			Recovered();
		}
		return;
	}

	if(m.GetState()==State::JUMP){
		float jumpLandingTimerRatio=m.F(A::JUMP_LANDING_TIMER)/m.F(A::JUMP_ORIGINAL_LANDING_TIMER);
		vf2d jumpTargetPos=m.V(A::JUMP_TARGET_POS);
		if(m.B(A::JUMP_TOWARDS_PLAYER)){
			jumpTargetPos=game->GetPlayer()->GetPos();
		}
		if(m.GetPos().x>jumpTargetPos.x){
			m.SetX(std::max(jumpTargetPos.x,m.GetPos().x-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
		} else
		if(m.GetPos().x<jumpTargetPos.x){
			m.SetX(std::min(jumpTargetPos.x,m.GetPos().x+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
		}
		if(m.GetPos().y>jumpTargetPos.y){
			m.SetY(std::max(jumpTargetPos.y,m.GetPos().y-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
		} else
		if(m.GetPos().y<jumpTargetPos.y){
			m.SetY(std::min(jumpTargetPos.y,m.GetPos().y+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
		}
		if(m.F(A::JUMP_LANDING_TIMER)>=m.F(A::JUMP_ORIGINAL_LANDING_TIMER)/2){
			m.SetZ(util::lerp(0,ConfigInt("JumpHeight"),1-jumpLandingTimerRatio));
		}else{
			m.SetZ(util::lerp(0,ConfigInt("JumpHeight"),jumpLandingTimerRatio*2));
		}
		if(m.F(A::JUMP_LANDING_TIMER)==0){
			m.state=State::RECOVERY;
			game->SetupWorldShake(0.6);
			geom2d::line<float>lineToPlayer(m.GetPos(),game->GetPlayer()->GetPos());
			float dist=lineToPlayer.length();
			for(int i=0;i<200;i++){
				float randomDir=util::random(2*PI);
				game->AddEffect(std::make_unique<FallingDebris>(m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*m.GetSizeMult()*8,util::random(1),"circle.png",m.OnUpperLevel(),vf2d{1,1},0.5,vf2d{cos(randomDir)*util::random(5),sin(randomDir)*-util::random(15)-5}*30,BLACK),true);
			}
			if(dist<12*m.GetSizeMult()){
				game->GetPlayer()->Hurt(ConfigInt("JumpAttackDamage"),m.OnUpperLevel(),m.GetZ());
				if(dist<0.001){
					float randomDir=util::random(2*PI);
					lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1};
				}
				game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*ConfigInt("JumpKnockbackFactor"));
				if(m.phase!=2){ //In phase 2, the player can get slammed multiple times. No iframes for messing up.
					game->GetPlayer()->SetIframes(1);
				}
			}
			m.SetZ(0);
			Landed(m.phase);
			m.SetStrategyDrawFunction([](Crawler*game){});
		} else
		if(m.F(A::JUMP_LANDING_TIMER)<=ConfigFloat("JumpWarningIndicatorTime")){
			m.SetStrategyDrawFunction([&](Crawler*game){
				Decal*dec=ANIMATION_DATA["range_indicator.png"].GetFrame(game->GetElapsedTime()).GetSourceImage()->Decal();
				game->view.DrawRotatedDecal(m.GetPos(),dec,0,dec->sprite->Size()/2,vf2d{m.GetSizeMult(),m.GetSizeMult()},RED);
			});
		}
		return;
	}

	if(m.GetState()==State::CASTING){
		m.UpdateAnimation("monsters/Slime King - Cast.png");
		if(m.F(A::CASTING_TIMER)==0){
			m.SetState(State::NORMAL);
			m.I(A::JUMP_COUNT)++;
			float jumpTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",0);
			float jumpSpd=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",1);
			StartJumpTowardsPlayer(jumpTime,0.2,jumpSpd);
		}
		return;
	}

	switch(m.phase){
		case 0:{
			m.size=ConfigInt("Phase1.Size")/100;
			m.diesNormally=false;
			m.F(A::IFRAME_TIME_UPON_HIT)=0;
			m.iframe_timer=ConfigFloat("Phase5.IframeTimePerHit");
			m.phase=ConfigInt("StartPhase");
		}break;
		case 1:{
			if(m.hp<=m.maxhp*ConfigFloat("Phase2.Change")/100){
				m.phase=2;
				m.SetSize(ConfigFloat("Phase2.Size")/100,false);
				TransitionPhase(m.phase);
				return;
			}
			if(m.F(A::SHOOT_RING_TIMER)==0){
				if(m.I(A::PATTERN_REPEAT_COUNT)>=ConfigInt("Phase1.JumpAfter")){
					StartJumpTowardsPlayer(ConfigFloat("Phase1.AirborneTime"),ConfigFloat("Phase1.LandingRecoveryTime"),ConfigFloat("JumpMoveSpd"));
					m.I(A::PATTERN_REPEAT_COUNT)=0;
					return;
				}
				m.I(A::SHOOT_RING_COUNTER)=ConfigInt("Phase1.ShootRingCount")-1;
				m.F(A::SHOOT_RING_DELAY)=ConfigFloat("Phase1.ShootRingDelay");
				ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
				m.F(A::SHOOT_RING_TIMER)=ConfigFloat("Phase1.ShootRepeatTime");
				m.B(A::SHOOT_RING_RIGHT)=bool(rand()%2);
				m.I(A::PATTERN_REPEAT_COUNT)++;
			}
			if(m.I(A::SHOOT_RING_COUNTER)>0){
				if(m.F(A::SHOOT_RING_DELAY)==0){
					m.I(A::SHOOT_RING_COUNTER)--;
					m.F(A::SHOOT_RING_DELAY)=ConfigFloat("Phase1.ShootRingDelay");
					if(m.B(A::SHOOT_RING_RIGHT)){
						m.F(A::SHOOT_RING_OFFSET)+=util::degToRad(ConfigFloat("Phase1.RingOffset"));
					}else{
						m.F(A::SHOOT_RING_OFFSET)-=util::degToRad(ConfigFloat("Phase1.RingOffset"));
					}
					ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
				}
			}
		}break;
		case 2:{
			if(m.hp<=m.maxhp*ConfigFloat("Phase3.Change")/100){
				m.phase=3;
				m.SetSize(ConfigFloat("Phase3.Size")/100,false);
				TransitionPhase(m.phase);
				return;
			}
			if(m.F(A::SHOOT_TIMER)==0){
				m.F(A::SHOOT_TIMER)=ConfigInt("Phase2.ShootRate");
				m.I(A::PATTERN_REPEAT_COUNT)++;
				int bulletCount=ConfigInt("Phase2.ShootProjectileCount");
				for(int i=0;i<bulletCount;i++){
					float initialAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
					float angle=(i-(bulletCount/2))*util::degToRad(ConfigFloat("Phase2.ShootAngleSpread"))+initialAngle;
					BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
				}
			}
			if(m.I(A::PATTERN_REPEAT_COUNT)>ConfigInt("Phase2.ShootCount")){
				m.I(A::PATTERN_REPEAT_COUNT)=0;
				m.I(A::JUMP_COUNT)=0;
				m.F(A::CASTING_TIMER)=5;
				m.SetState(State::CASTING);
			}
		}break;
		case 3:{
			if(m.hp<=m.maxhp*ConfigFloat("Phase4.Change")/100){
				m.phase=4;
				m.SetSize(ConfigFloat("Phase4.Size")/100,false);
				m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100);
				TransitionPhase(m.phase);
				return;
			}
			if(m.I(A::PATTERN_REPEAT_COUNT)==0){
				StartJumpTowardsPlayer(ConfigFloat("Phase3.JumpDelayTime"),ConfigFloat("Phase3.JumpRecoveryTime"),ConfigFloat("Phase3.JumpMoveSpd"));
				m.I(A::PATTERN_REPEAT_COUNT)++;
			}else
			if(m.I(A::PATTERN_REPEAT_COUNT)<4&&m.F(A::SHOOT_TIMER)==0){
				m.F(A::SHOOT_TIMER)=ConfigFloat("Phase3.ShootRate");
				m.I(A::PATTERN_REPEAT_COUNT)++;
				int bulletCount=ConfigInt("Phase3.ShootProjectileCount");
				for(int i=0;i<bulletCount;i++){
					float initialAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
					float angle=(i-(bulletCount/2))*util::degToRad(ConfigFloat("Phase3.ShootAngleSpread"))+initialAngle;
					BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
				}
			}else
			if(m.I(A::PATTERN_REPEAT_COUNT)>=4){
				m.F(A::RECOVERY_TIME)=ConfigFloat("Phase3.PhaseRecoveryTime");
				m.SetState(State::RECOVERY);
				m.I(A::PATTERN_REPEAT_COUNT)=0;
			}
		}break;
		case 4:{
			if(m.hp<=1){ //HP can't reach 0 when the dies normally flag is on.
				m.phase=5;
				m.F(A::IFRAME_TIME_UPON_HIT)=1;
				m.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-1;
				TransitionPhase(m.phase);
				return;
			}
			if(m.I(A::PHASE_REPEAT_COUNT)>=5){
				m.I(A::PHASE_REPEAT_COUNT)=0;
				float jumpAngle=util::angleTo(m.GetPos(),game->GetCurrentMap().MapSize*game->GetCurrentMap().tilewidth/2); //We jump towards the center to keep the player from constantly dealing with a stuck boss.
				float jumpDistance=ConfigFloat("Phase4.JumpDistance")/100*game->GetCurrentMap().tilewidth;
				float jumpSpd=jumpDistance/ConfigFloat("Phase4.JumpDuration");
				StartJump(ConfigFloat("Phase4.JumpDuration"),m.GetPos()+vf2d{cos(jumpAngle)*jumpDistance,sin(jumpAngle)*jumpDistance},0,jumpSpd);
			}else
			if(m.I(A::PATTERN_REPEAT_COUNT)<5&&m.F(A::SHOOT_TIMER)==0){
				m.I(A::PATTERN_REPEAT_COUNT)++;
				m.F(A::SHOOT_TIMER)=ConfigFloat("Phase4.ShootRate");
				float bulletAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
				float spreadAngle=util::degToRad(ConfigFloat("Phase4.RandomOffsetAngle"));
				bulletAngle+=util::random(spreadAngle*2)-spreadAngle;
				BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(bulletAngle),sin(bulletAngle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
			}else
			if(m.I(A::PATTERN_REPEAT_COUNT)==5){
				m.I(A::PATTERN_REPEAT_COUNT)++;
				m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase4.RunAwayTime");
			}else
			if(m.I(A::PATTERN_REPEAT_COUNT)==6&&m.F(A::RUN_AWAY_TIMER)==0){
				m.F(A::RECOVERY_TIME)=ConfigFloat("Phase4.WaitTime");
				m.SetState(State::RECOVERY);
				m.I(A::PATTERN_REPEAT_COUNT)=0;
				m.I(A::PHASE_REPEAT_COUNT)++;
			}
		}break;
		case 5:{
			float targetSize=ConfigFloat("Phase5.SizeLossPerHit")/100*m.I(A::HITS_UNTIL_DEATH);
			Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,4);
			if(targetSize>0){
				m.SetSize(targetSize,false);
			}else{
				m.diesNormally=true;
			}
		}break;
	}
}