#include "Monster.h"
#include "DamageNumber.h"
#include "Crawler.h"
#include "Bullet.h"
#include "BulletTypes.h"
#include "DEFINES.h"
#include "safemap.h"
#include "MonsterStrategyHelpers.h"
#include "utils.h"
#include "MonsterAttribute.h"

INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_game
INCLUDE_BULLET_LIST
INCLUDE_DATA
INCLUDE_STRATEGY_DATA
INCLUDE_GFX

safemap<std::string,int>STRATEGY_DATA;
safemap<int,std::string>STRATEGY_ID_DATA;
std::map<int,Renderable*>MonsterData::imgs;

Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
	pos(pos),hp(data.GetHealth()),maxhp(data.GetHealth()),atk(data.GetAttack()),moveSpd(data.GetMoveSpdMult()),size(data.GetSizeMult()),targetSize(data.GetSizeMult()),strategy(data.GetAIStrategy()),id(data.GetID()),upperLevel(upperLevel),isBoss(bossMob){
	bool firstAnimation=true;
	for(std::string&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(std::string state){
	animation.ChangeState(internal_animState,state);
}
void Monster::PerformJumpAnimation(){
	animation.ChangeState(internal_animState,MONSTER_DATA[id].GetJumpAnimation());
}
void Monster::PerformShootAnimation(){
	animation.ChangeState(internal_animState,MONSTER_DATA[id].GetShootAnimation());
}
void Monster::PerformIdleAnimation(){
	animation.ChangeState(internal_animState,MONSTER_DATA[id].GetIdleAnimation());
}
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(geom2d::circle<float>(newPos,12*GetSizeMult()),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(geom2d::circle<float>(newPos,12*GetSizeMult()),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){
	lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime);
	iframe_timer=std::max(0.f,iframe_timer-fElapsedTime);
	if(size!=targetSize){
		if(size>targetSize){
			size=std::max(targetSize,size-Crawler::SIZE_CHANGE_SPEED*fElapsedTime);
		}else{
			size=std::min(targetSize,size+Crawler::SIZE_CHANGE_SPEED*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;
			}
		}
		if(!HasIframes()){
			for(Monster&m:MONSTER_LIST){
				if(&m==this)continue;
				if(!m.HasIframes()&&OnUpperLevel()==m.OnUpperLevel()&&abs(m.GetZ()-GetZ())<=1&&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.SetPos(line.rpoint(dist*1.1));
					if(m.IsAlive()){
						vel=line.vector().norm()*-128;
					}
				}
			}
			if(!game->GetPlayer()->HasIframes()&&abs(game->GetPlayer()->GetZ()-GetZ())<=1&&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();
				SetPos(line.rpoint(-0.1));
				vel=line.vector().norm()*-128;
			}
		}
		if(GetState()==State::NORMAL){
			if(game->GetPlayer()->GetX()>pos.x){
				facingDirection=RIGHT;
			} else {
				facingDirection=LEFT;
			}
		}
		Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime);
		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(!IsAlive()){
		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(GetZ()>0){
		vf2d shadowScale=vf2d{8*GetSizeMult()/3.f,1}/std::max(1.f,GetZ()/24);
		game->view.DrawDecal(GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
	}
	game->view.DrawPartialRotatedDecal(GetPos()-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*(GetFacingDirection()==RIGHT?-1: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);
}
void Monster::DrawReflection(float drawRatioX,float multiplierX){
	game->SetDecalMode(DecalMode::ADDITIVE);
	game->view.DrawPartialRotatedDecal(GetPos()+vf2d{drawRatioX*GetFrame().GetSourceRect().size.x,GetZ()+(GetFrame().GetSourceRect().size.y-16)*GetSizeMult()},GetFrame().GetSourceImage()->Decal(),0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*(GetFacingDirection()==RIGHT?-1:1)*multiplierX,GetSizeMult()*-1),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);
	game->SetDecalMode(DecalMode::NORMAL);
}
void Monster::Collision(Player*p){
	if(MONSTER_DATA[id].GetCollisionDmg()>0&&!hasHitPlayer){
		if(p->Hurt(MONSTER_DATA[id].GetCollisionDmg(),OnUpperLevel(),GetZ())){
			hasHitPlayer=true;
		}
	}
	Collision();
}
void Monster::Collision(Monster&m){
	Collision();
}
void Monster::Collision(){
	if(strategy==0&&GetState()==State::MOVE_TOWARDS&&util::random(Monster::STRATEGY::_GetInt(*this,"BumpStopChance",strategy))<1){//The run towards strategy causes state to return to normal upon a collision.
		SetState(State::NORMAL);
	}
}
void Monster::SetVelocity(vf2d vel){
	this->vel=vel;
}
bool Monster::SetPos(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;
		}
	}
}
std::string Monster::GetDeathAnimationName(){
	return MONSTER_DATA[id].GetDeathAnimation();
} 
bool Monster::Hurt(int damage,bool onUpperLevel,float z){
	if(!IsAlive()||onUpperLevel!=OnUpperLevel()||HasIframes()||abs(GetZ()-z)>1) return false;
	if(game->InBossEncounter()){
		game->StartBossEncounter();
	}
	float mod_dmg=damage;
	for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
		mod_dmg-=damage*b.intensity;
	}
	hp=std::max(0,hp-int(mod_dmg));
	if(lastHitTimer>0){
		damageNumberPtr.get()->damage+=int(mod_dmg);
		damageNumberPtr.get()->pauseTime=0.4;
	} else {
		damageNumberPtr=std::make_shared<DamageNumber>(pos,int(mod_dmg));
		DAMAGENUMBER_LIST.push_back(damageNumberPtr);
	}
	lastHitTimer=0.05;
	if(!IsAlive()){
		animation.ChangeState(internal_animState,GetDeathAnimationName());
		if(isBoss){
			game->ReduceBossEncounterMobCount();
		}
	}else{
		hp=std::max(1,hp); //Make sure it stays alive if it's supposed to be alive...
	}
	if(game->InBossEncounter()){
		game->BossDamageDealt(int(mod_dmg));
	}
	GetInt(Attribute::HITS_UNTIL_DEATH)=std::max(0,GetInt(Attribute::HITS_UNTIL_DEATH)-1);
	iframe_timer=GetFloat(Attribute::IFRAME_TIME_UPON_HIT);
	return true;
}

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

MonsterSpawner::MonsterSpawner(){}
MonsterSpawner::MonsterSpawner(vf2d pos,vf2d range,std::vector<std::pair<int,vf2d>>monsters,bool upperLevel,std::string bossNameDisplay)
	:pos(pos),range(range),monsters(monsters),upperLevel(upperLevel),bossNameDisplay(bossNameDisplay){
}
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<int,vf2d>&monsterInfo:monsters){
			game->SpawnMonster(pos+monsterInfo.second,&MONSTER_DATA[monsterInfo.first],DoesUpperLevelSpawning(),bossNameDisplay!="");
		}
		if(bossNameDisplay!=""){
			game->SetBossNameDisplay(bossNameDisplay);
		}
	}
}

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){
	SetState(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){
			SetPos(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;
}

State::State Monster::GetState(){
	return state;
}

void Monster::SetState(State::State newState){
	state=newState;
}

void Monster::InitializeStrategies(){
	int readCounter=0;
	while(DATA["MonsterStrategy"].HasProperty(std::to_string(readCounter))){
		std::string strategyName=DATA["MonsterStrategy"][std::to_string(readCounter)]["Name"].GetString();
		STRATEGY_DATA[strategyName]=readCounter;
		STRATEGY_ID_DATA[readCounter]=strategyName;
		readCounter++;
	}
	STRATEGY_DATA.SetInitialized();
	STRATEGY_ID_DATA.SetInitialized();
}

bool Monster::HasIframes(){
	return iframe_timer>0;
}

float Monster::GetZ(){
	return z;
}

std::string Monster::GetStrategy(){
	return STRATEGY_ID_DATA[strategy];
}

void Monster::SetSize(float newSize,bool immediate){
	if(immediate){
		size=targetSize=newSize;
	}else{
		targetSize=newSize;
	}	
}

void Monster::SetZ(float z){
	this->z=z;
}

void Monster::SetStrategyDrawFunction(std::function<void(Crawler*)>func){
	strategyDraw=func;
}