#include "Monster.h"
#include "DamageNumber.h"
#include "Crawler.h"
#include "Bullet.h"
#include "BulletTypes.h"
#include "DEFINES.h"

INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_game
INCLUDE_BULLET_LIST

Monster::Monster(vf2d pos,MonsterData data,bool upperLevel):
	pos(pos),hp(data.GetHealth()),maxhp(data.GetHealth()),atk(data.GetAttack()),moveSpd(data.GetMoveSpdMult()),size(data.GetSizeMult()),strategy(data.GetAIStrategy()),type(data.GetType()),upperLevel(upperLevel){
	bool firstAnimation=true;
	for(AnimationState&anim:data.GetAnimations()){
		animation.AddState(anim,ANIMATION_DATA[anim]);
		if(firstAnimation){
			animation.ChangeState(internal_animState,anim);
			firstAnimation=false;
		}
	}
	randomFrameOffset=(rand()%1000)/1000.f;
}
vf2d&Monster::GetPos(){
	return pos;
}
int Monster::GetHealth(){
	return hp;
}
int Monster::GetAttack(){
	float mod_atk=atk;
	for(Buff&b:GetBuffs(ATTACK_UP)){
		mod_atk+=atk*b.intensity;
	}
	return int(mod_atk);
}
float Monster::GetMoveSpdMult(){
	float mod_moveSpd=moveSpd;
	for(Buff&b:GetBuffs(SLOWDOWN)){
		mod_moveSpd-=moveSpd*b.intensity;
	}
	return mod_moveSpd;
}
float Monster::GetSizeMult(){
	return size;
}
Animate2D::Frame Monster::GetFrame(){
	return animation.GetFrame(internal_animState);
}
void Monster::UpdateAnimation(AnimationState state){
	animation.ChangeState(internal_animState,state);
}
void Monster::PerformJumpAnimation(){
	animation.ChangeState(internal_animState,MONSTER_DATA[type].GetJumpAnimation());
}
void Monster::PerformShootAnimation(){
	animation.ChangeState(internal_animState,MONSTER_DATA[type].GetShootAnimation());
}
bool Monster::SetX(float x){
	vf2d newPos={x,pos.y};
	vi2d tilePos=vi2d(newPos/24)*24;
	geom2d::rect<int>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
	if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){
		pos.x=std::clamp(x,12.f*GetSizeMult(),float(game->WORLD_SIZE.x*24-12*GetSizeMult()));
		Moved();
		return true;
	} else {
		geom2d::rect<float>collision={collisionRect.pos,collisionRect.size};
		collision.pos+=tilePos;
		if(!geom2d::overlaps(newPos,collision)){
			pos.x=std::clamp(x,12.f*GetSizeMult(),float(game->WORLD_SIZE.x*24-12*GetSizeMult()));
			Moved();
			return true;
		}
	}
	return false;
}
bool Monster::SetY(float y){
	vf2d newPos={pos.x,y};
	vi2d tilePos=vi2d(newPos/24)*24;
	geom2d::rect<int>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
	if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){
		pos.y=std::clamp(y,12.f*GetSizeMult(),float(game->WORLD_SIZE.y*24-12*GetSizeMult()));
		Moved();
		return true;
	} else {
		geom2d::rect<float>collision={collisionRect.pos,collisionRect.size};
		collision.pos+=tilePos;
		if(!geom2d::overlaps(newPos,collision)){
			pos.y=std::clamp(y,12.f*GetSizeMult(),float(game->WORLD_SIZE.y*24-12*GetSizeMult()));
			Moved();
			return true;
		}
	}
	return false;
}
bool Monster::Update(float fElapsedTime){
	if(IsAlive()){
		for(std::vector<Buff>::iterator it=buffList.begin();it!=buffList.end();++it){
			Buff&b=*it;
			b.duration-=fElapsedTime;
			if(b.duration<=0){
				it=buffList.erase(it);
				if(it==buffList.end())break;
			}
		}
		for(Monster&m:MONSTER_LIST){
			if(&m==this)continue;
			if(OnUpperLevel()==m.OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){
				m.Collision(*this);
				geom2d::line line(pos,m.GetPos());
				float dist = line.length();
				m.SetPosition(line.rpoint(dist*1.1));
				if(m.IsAlive()){
					vel=line.vector().norm()*-128;
				}
			}
		}
		if(!game->GetPlayer()->HasIframes()&&game->GetPlayer()->OnUpperLevel()==OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(game->GetPlayer()->GetPos(),12*game->GetPlayer()->GetSizeMult()/2))){
			geom2d::line line(pos,game->GetPlayer()->GetPos());
			float dist = line.length();
			SetPosition(line.rpoint(-0.1));
			vel=line.vector().norm()*-128;
		}
		if(state==NORMAL){
			if(game->GetPlayer()->GetX()>pos.x){
				facingDirection=RIGHT;
			} else {
				facingDirection=LEFT;
			}
		}
		switch(strategy){
			case RUN_TOWARDS:{
				targetAcquireTimer=std::max(0.f,targetAcquireTimer-fElapsedTime);
				if(targetAcquireTimer==0){
					targetAcquireTimer=3;
					target=geom2d::line(pos,game->GetPlayer()->GetPos()).upoint(1.2);
					state=MOVE_TOWARDS;
					hasHitPlayer=false;
				}
				switch(state){
					case MOVE_TOWARDS:{
						if(geom2d::line(pos,target).length()>100*fElapsedTime*GetMoveSpdMult()){
							vf2d newPos=pos+geom2d::line(pos,target).vector().norm()*100*fElapsedTime*GetMoveSpdMult();
							if(!SetX(newPos.x)||!SetY(newPos.y)){
								StartPathfinding(4);
							}
							PerformJumpAnimation();
						} else {
							state=NORMAL;//Revert state once we've finished moving towards target.
							UpdateAnimation(MONSTER_DATA[type].GetAnimations()[0]);
						}
					}break;
					case PATH_AROUND:{
						PathAroundBehavior(fElapsedTime);
					}break;
					default:{
					}
				}
			}break;
			case SHOOT_AFAR:{
				targetAcquireTimer=std::max(0.f,targetAcquireTimer-fElapsedTime);
				attackCooldownTimer=std::max(0.f,attackCooldownTimer-fElapsedTime);
				if(queueShotTimer>0){
					queueShotTimer-=fElapsedTime;
					if(queueShotTimer<0){
						queueShotTimer=0;
						{
							BULLET_LIST.push_back(std::make_unique<Bullet>(Bullet(pos + vf2d{ 0,-4 }, geom2d::line(pos + vf2d{ 0,-4 }, game->GetPlayer()->GetPos()).vector().norm() * 24 * 3.f, 2, GetAttack(),upperLevel,false, { 75 / 2,162 / 2,225 / 2 })));
						}
					}
				}
				geom2d::line line(pos,game->GetPlayer()->GetPos());
				if(targetAcquireTimer==0&&queueShotTimer==0){
					targetAcquireTimer=1;
					if(line.length()<24*6){
						target=line.upoint(-1.2);
						if(canMove){
							state=MOVE_AWAY;
						} else {
							state=NORMAL;
						}
					} else
					if(line.length()>24*7){
						target=line.upoint(1.2);
						state=MOVE_TOWARDS;
					} else {
						state=NORMAL;
					}
				}
				canMove=true;
				geom2d::line moveTowardsLine=geom2d::line(pos,target);
				bool pathfindingDecision=false;
				switch(state){
					case MOVE_TOWARDS:{
						if(moveTowardsLine.length()>1){
							vf2d newPos=pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult();
							bool movedX=SetX(newPos.x);
							bool movedY=SetY(newPos.y);
							pathfindingDecision=movedX|movedY;
							canMove=movedX&&movedY;
						}
						if(!pathfindingDecision){
							StartPathfinding(2.5);
						}else
						if(line.length()<=24*7){
							state=NORMAL;
						}
						if(moveTowardsLine.vector().x>0){
							facingDirection=RIGHT;
						} else {
							facingDirection=LEFT;
						}
						PerformJumpAnimation();
					}break;
					case MOVE_AWAY:{
						if(moveTowardsLine.length()>1){
							vf2d newPos=pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult();
							bool movedX=SetX(newPos.x);
							bool movedY=SetY(newPos.y);
							pathfindingDecision=movedX|movedY;
							canMove=movedX&&movedY;
						}
						if(!pathfindingDecision){
							StartPathfinding(2.5);
						}else
						if(line.length()>=24*6){
							state=NORMAL;
						}
						if(moveTowardsLine.vector().x>0){
							facingDirection=RIGHT;
						} else {
							facingDirection=LEFT;
						}
						PerformJumpAnimation();
					}break;
					case PATH_AROUND:{
						PathAroundBehavior(fElapsedTime);
					}break;
					default:{
						if(attackCooldownTimer==0){
							attackCooldownTimer=1;
							queueShotTimer=0.7;
							PerformShootAnimation();
						}
					}
				}
			}break;
		}
		if(vel.x>0){
			vel.x=std::max(0.f,vel.x-friction*fElapsedTime);
		} else {
			vel.x=std::min(0.f,vel.x+friction*fElapsedTime);
		}
		if(vel.y>0){
			vel.y=std::max(0.f,vel.y-friction*fElapsedTime);
		} else {
			vel.y=std::min(0.f,vel.y+friction*fElapsedTime);
		}
		if(vel!=vf2d{0,0}){
			SetX(pos.x+vel.x*fElapsedTime);
			SetY(pos.y+vel.y*fElapsedTime);
		}
	}
	if(hp<=0){
		deathTimer+=fElapsedTime;
		if(deathTimer>3){
			return false;
		}
	}
	animation.UpdateState(internal_animState,randomFrameOffset+fElapsedTime);
	randomFrameOffset=0;
	return true;
}
Key Monster::GetFacingDirection(){
	return facingDirection;
}
void Monster::Draw(){
	if(GetFacingDirection()==RIGHT){
		game->view.DrawPartialDecal((GetPos()+vf2d{float(GetFrame().GetSourceRect().size.x),0}*GetSizeMult())-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*-1,GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE);
	} else {
		game->view.DrawPartialDecal(GetPos()-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult(),GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE);
	}
}
void Monster::Collision(Player*p){
	if(MONSTER_DATA[type].GetCollisionDmg()>0&&!hasHitPlayer){
		hasHitPlayer=true;
		p->Hurt(MONSTER_DATA[type].GetCollisionDmg());
	}
	Collision();
}
void Monster::Collision(Monster&m){
	Collision();
}
void Monster::Collision(){
	if(strategy==RUN_TOWARDS&&state==MOVE_TOWARDS){
		state=NORMAL;
	}
}
void Monster::SetVelocity(vf2d vel){
	this->vel=vel;
}
bool Monster::SetPosition(vf2d pos){
	bool resultX=SetX(pos.x);
	bool resultY=SetY(pos.y);
	if(resultY&&!resultX){
		resultX=SetX(pos.x);
	}
	return resultX|resultY;
}
void Monster::Moved(){
	ZoneData&zoneData=game->GetZoneData(game->GetCurrentLevel());
	for(geom2d::rect<int>&upperLevelZone:zoneData["UpperZone"]){
		if(geom2d::overlaps(upperLevelZone,pos)){
			upperLevel=true;
		}
	}
	for(geom2d::rect<int>&lowerLevelZone:zoneData["LowerZone"]){
		if(geom2d::overlaps(lowerLevelZone,pos)){
			upperLevel=false;
		}
	}
}
AnimationState Monster::GetDeathAnimationName(){
	return MONSTER_DATA[type].GetDeathAnimation();
} 
bool Monster::Hurt(int damage){
	if(hp<=0) return false;
	float mod_dmg=damage;
	for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
		mod_dmg-=damage*b.intensity;
	}
	hp=std::max(0,hp-int(mod_dmg));
	DAMAGENUMBER_LIST.push_back(DamageNumber(pos,int(mod_dmg)));
	if(hp<=0){
		animation.ChangeState(internal_animState,GetDeathAnimationName());
	}
	return true;
}

bool Monster::IsAlive(){
	return hp>0;
}
vf2d&Monster::GetTargetPos(){
	return target;
}

MonsterSpawner::MonsterSpawner(){}
MonsterSpawner::MonsterSpawner(vf2d pos,vf2d range,std::vector<std::pair<MonsterName,vf2d>>monsters,bool upperLevel):
	pos(pos),range(range),monsters(monsters),upperLevel(upperLevel){
}
bool MonsterSpawner::SpawnTriggered(){
	return triggered;
}
vf2d MonsterSpawner::GetRange(){
	return range;
}
vf2d MonsterSpawner::GetPos(){
	return pos;
}
void MonsterSpawner::SetTriggered(bool trigger,bool spawnMonsters){
	triggered=trigger;
	if(spawnMonsters){
		for(std::pair<MonsterName,vf2d>&monsterInfo:monsters){
			MONSTER_LIST.push_back(Monster(pos+monsterInfo.second,MONSTER_DATA[monsterInfo.first],DoesUpperLevelSpawning()));
		}
	}
}

bool MonsterSpawner::DoesUpperLevelSpawning(){
	return upperLevel;
}

bool Monster::OnUpperLevel(){
	return upperLevel;
}

void Monster::AddBuff(BuffType type,float duration,float intensity){
	buffList.push_back(Buff{type,duration,intensity});
}

void Monster::StartPathfinding(float pathingTime){
	state=State::PATH_AROUND;
	path=game->pathfinder.Solve_AStar(pos,target,12,OnUpperLevel());
	if(path.size()>0){
		pathIndex=0;
		//We gives this mob 5 seconds to figure out a path to the target.
		targetAcquireTimer=pathingTime;
	}
}

void Monster::PathAroundBehavior(float fElapsedTime){
	if(path.size()>0){
		//Move towards the new path.
		geom2d::line moveTowardsLine=geom2d::line(pos,path[pathIndex]*24);
		if(moveTowardsLine.length()>2){
			SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult());
			if(moveTowardsLine.vector().x>0){
				facingDirection=RIGHT;
			} else {
				facingDirection=LEFT;
			}
		}else{
			if(pathIndex+1>=path.size()){
				//We have reached the end of the path!
				targetAcquireTimer=0;
			}else{
				pathIndex++;
			}
		}
	} else {
		//We actually can't do anything so just quit.
		targetAcquireTimer=0;
	}
}

std::vector<Buff>Monster::GetBuffs(BuffType buff){
	std::vector<Buff>filteredBuffs;
	std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;});
	return filteredBuffs;
}