#include "Class.h"
#include "DEFINES.h"
#include "Crawler.h"
#include "BulletTypes.h"

INCLUDE_game
INCLUDE_MONSTER_LIST
INCLUDE_BULLET_LIST

std::map<Class,std::unique_ptr<ClassData>>CLASS_DATA;

void ClassData::InitializeClassData(){
	CLASS_DATA[WARRIOR]=std::make_unique<Warrior>(Warrior("Warrior",WARRIOR,
		{"Block",15,0,VERY_DARK_BLUE,DARK_BLUE},
		{"Battlecry",12,40},
		{"Ground Slam",15,50},
		{"Sonic Slash",40,60},
		WARRIOR_WALK_N,WARRIOR_WALK_E,WARRIOR_WALK_S,WARRIOR_WALK_W,
		WARRIOR_IDLE_N,WARRIOR_IDLE_E,WARRIOR_IDLE_S,WARRIOR_IDLE_W));
	CLASS_DATA[RANGER]=std::make_unique<Ranger>(Ranger("Ranger",RANGER,
		{"Retreat",7,0,VERY_DARK_BLUE,DARK_BLUE},
		{"Rapid Fire",12,35},
		{"Charged Shot",15,40},
		{"Multishot",25,50},
		RANGER_WALK_N,RANGER_WALK_E,RANGER_WALK_S,RANGER_WALK_W,
		RANGER_IDLE_N,RANGER_IDLE_E,RANGER_IDLE_S,RANGER_IDLE_W));
	CLASS_DATA[BARD]=std::make_unique<Bard>(Bard("Bard",BARD,
		{"???",7,0,VERY_DARK_BLUE,DARK_BLUE},
		{"???",12,0},
		{"???",15,0},
		{"???",25,0},
		WARRIOR_WALK_N,WARRIOR_WALK_E,WARRIOR_WALK_S,WARRIOR_WALK_W,
		WARRIOR_IDLE_N,WARRIOR_IDLE_E,WARRIOR_IDLE_S,WARRIOR_IDLE_W));
	CLASS_DATA[WIZARD]=std::make_unique<Wizard>(Wizard("Wizard",WIZARD,
		{"Teleport",8,5,VERY_DARK_BLUE,DARK_BLUE},
		{"Firebolt",6,30},
		{"Lightning Bolt",6,25},
		{"Meteor",40,75},
		WIZARD_WALK_N,WIZARD_WALK_E,WIZARD_WALK_S,WIZARD_WALK_W,
		WIZARD_IDLE_N,WIZARD_IDLE_E,WIZARD_IDLE_S,WIZARD_IDLE_W));
	CLASS_DATA[WITCH]=std::make_unique<Witch>(Witch("Witch",WITCH,
		{"???",8,0,VERY_DARK_BLUE,DARK_BLUE},
		{"???",6,0},
		{"???",6,0},
		{"???",40,0},
		WARRIOR_WALK_N,WARRIOR_WALK_E,WARRIOR_WALK_S,WARRIOR_WALK_W,
		WARRIOR_IDLE_N,WARRIOR_IDLE_E,WARRIOR_IDLE_S,WARRIOR_IDLE_W));
}

ClassData::ClassData(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:name(name),cl(cl),rightClickAbility(rightClickAbility),ability1(ability1),ability2(ability2),ability3(ability3),
	walk_n(walk_n),walk_e(walk_e),walk_s(walk_s),walk_w(walk_w),idle_n(idle_n),idle_w(idle_w),idle_s(idle_s),idle_e(idle_e)
{}

Warrior::Warrior(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Warrior::Update(float fElapsedTime){
}

bool Warrior::AutoAttack(){
	ACCESS_PLAYER
	if(p.state!=State::SPIN){
		bool attack=false;
		Monster*closest=nullptr;
		float closest_dist=999999;
		for(Monster&m:MONSTER_LIST){
			if(m.IsAlive()
				&&m.OnUpperLevel()==p.OnUpperLevel()
				&&geom2d::overlaps(geom2d::circle<float>(p.pos-vf2d{p.GetSizeMult()*12,p.GetSizeMult()*12},p.attack_range*p.GetSizeMult()*12),geom2d::circle<float>(m.GetPos()-vf2d{m.GetSizeMult()*12,m.GetSizeMult()*12},m.GetSizeMult()*12))
				&&geom2d::line<float>(game->GetWorldMousePos(),m.GetPos()).length()<closest_dist){
				closest_dist=geom2d::line<float>(game->GetWorldMousePos(),m.GetPos()).length();
				closest=&m;
			}
		}
		if(closest!=nullptr&&closest->Hurt(p.GetAttack())){
			p.attack_cooldown_timer=p.ATTACK_COOLDOWN;
			p.swordSwingTimer=0.2;
			p.SetState(State::SWING_SWORD);
			switch(p.facingDirection){
				case DOWN:{
					p.UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_S);
				}break;
				case RIGHT:{
					p.UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_E);
				}break;
				case LEFT:{
					p.UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_W);
				}break;
				case UP:{
					p.UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_N);
				}break;
			}
		}
	}
	return true;
}

bool Warrior::Ability1(){
	ACCESS_PLAYER
	game->AddEffect(Effect(p.pos,0.1,AnimationState::BATTLECRY_EFFECT,1,0.3));
	p.AddBuff(BuffType::ATTACK_UP,10,0.1);
	p.AddBuff(BuffType::DAMAGE_REDUCTION,10,0.1);
	for(Monster&m:MONSTER_LIST){
		if(m.GetSizeMult()<=1&&geom2d::overlaps(geom2d::circle<float>(p.pos,12*3.5),geom2d::circle<float>(m.GetPos(),m.GetSizeMult()*12))){
			m.AddBuff(BuffType::SLOWDOWN,5,0.3);
		}
	}
	return true;
}

bool Warrior::Ability2(){
	ACCESS_PLAYER
	p.Spin(p.GROUND_SLAM_SPIN_TIME,14*PI);
	p.iframe_time=p.GROUND_SLAM_SPIN_TIME+0.1;
	return true;
}

bool Warrior::Ability3(){
	ACCESS_PLAYER
	p.SetState(State::SWING_SONIC_SWORD);
	p.AddBuff(BuffType::SLOWDOWN,0.5,1);
	vf2d bulletVel={};
	switch(p.GetFacingDirection()){
		case UP:{
			p.vel.y=70;
			bulletVel.y=-400;
			p.UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_N);
		}break;
		case LEFT:{
			p.vel.x=70;
			bulletVel.x=-400;
			p.UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_W);
		}break;
		case RIGHT:{
			p.vel.x=-70;
			bulletVel.x=400;
			p.UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_E);
		}break;
		case DOWN:{
			p.vel.y=-70;
			bulletVel.y=400;
			p.UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_S);
		}break;
	}
	BULLET_LIST.push_back(std::make_unique<Bullet>(p.pos,bulletVel,30,p.GetAttack()*8,AnimationState::SONICSLASH,p.upperLevel,true,2.25,true,true,WHITE));
	game->SetupWorldShake(0.5);
	return true;
}

bool Warrior::RightClickAbility(){
	ACCESS_PLAYER
	if(p.GetState()==State::NORMAL){
		rightClickAbility.cooldown=rightClickAbility.COOLDOWN_TIME;
		p.SetState(State::BLOCK);
		p.AddBuff(BuffType::SLOWDOWN,3,0.3);
		return true;
	}
	return false;
}

Thief::Thief(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Thief::Update(float fElapsedTime){
}

bool Thief::AutoAttack(){
	return true;
}

bool Thief::Ability1(){
	return true;
}

bool Thief::Ability2(){
	return true;
}

bool Thief::Ability3(){
	return true;
}

bool Thief::RightClickAbility(){
	return true;
}

Ranger::Ranger(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Ranger::Update(float fElapsedTime){
}

bool Ranger::AutoAttack(){
	return true;
}

bool Ranger::Ability1(){
	return true;
}

bool Ranger::Ability2(){
	return true;
}

bool Ranger::Ability3(){
	return true;
}

bool Ranger::RightClickAbility(){
	return true;
}

Bard::Bard(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Bard::Update(float fElapsedTime){
}

bool Bard::AutoAttack(){
	return true;
}

bool Bard::Ability1(){
	return true;
}

bool Bard::Ability2(){
	return true;
}

bool Bard::Ability3(){
	return true;
}

bool Bard::RightClickAbility(){
	return true;
}

Wizard::Wizard(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Wizard::Update(float fElapsedTime){
	ACCESS_PLAYER
	if(p.attack_cooldown_timer>0){
		CLASS_DATA[cl]->idle_n=AnimationState::WIZARD_IDLE_ATTACK_N;
		CLASS_DATA[cl]->idle_e=AnimationState::WIZARD_IDLE_ATTACK_E;
		CLASS_DATA[cl]->idle_s=AnimationState::WIZARD_IDLE_ATTACK_S;
		CLASS_DATA[cl]->idle_w=AnimationState::WIZARD_IDLE_ATTACK_W;
		CLASS_DATA[cl]->walk_n=AnimationState::WIZARD_ATTACK_N;
		CLASS_DATA[cl]->walk_e=AnimationState::WIZARD_ATTACK_E;
		CLASS_DATA[cl]->walk_s=AnimationState::WIZARD_ATTACK_S;
		CLASS_DATA[cl]->walk_w=AnimationState::WIZARD_ATTACK_W;
	} else {
		CLASS_DATA[cl]->idle_n=AnimationState::WIZARD_IDLE_N;
		CLASS_DATA[cl]->idle_e=AnimationState::WIZARD_IDLE_E;
		CLASS_DATA[cl]->idle_s=AnimationState::WIZARD_IDLE_S;
		CLASS_DATA[cl]->idle_w=AnimationState::WIZARD_IDLE_W;
		CLASS_DATA[cl]->walk_n=AnimationState::WIZARD_WALK_N;
		CLASS_DATA[cl]->walk_e=AnimationState::WIZARD_WALK_E;
		CLASS_DATA[cl]->walk_s=AnimationState::WIZARD_WALK_S;
		CLASS_DATA[cl]->walk_w=AnimationState::WIZARD_WALK_W;
	}
}

bool Wizard::AutoAttack(){
	ACCESS_PLAYER
	p.attack_cooldown_timer=p.MAGIC_ATTACK_COOLDOWN;
	float angleToCursor=atan2(game->GetWorldMousePos().y-p.pos.y,game->GetWorldMousePos().x-p.pos.x);
	BULLET_LIST.push_back(std::make_unique<EnergyBolt>(EnergyBolt(p.pos,{cos(angleToCursor)*200,sin(angleToCursor)*200},12,p.GetAttack(),p.upperLevel,true,WHITE)));
	return true;
}

bool Wizard::Ability1(){
	ACCESS_PLAYER
	float angleToCursor=atan2(game->GetWorldMousePos().y-p.pos.y,game->GetWorldMousePos().x-p.pos.x);
	BULLET_LIST.push_back(std::make_unique<FireBolt>(FireBolt(p.pos,{cos(angleToCursor)*275,sin(angleToCursor)*275},12,p.GetAttack(),p.upperLevel,true,{240,120,60})));
	return true;
}

bool Wizard::Ability2(){
	return true;
}

bool Wizard::Ability3(){
	return true;
}

bool Wizard::RightClickAbility(){
	ACCESS_PLAYER
	float pointMouseDirection=atan2(game->GetWorldMousePos().y-p.GetPos().y,game->GetWorldMousePos().x-p.GetPos().x);
	vf2d pointTowardsMouse={cos(pointMouseDirection),sin(pointMouseDirection)};
	float dist=std::clamp(geom2d::line<float>{p.GetPos(),game->GetWorldMousePos()}.length(),0.f,6.5f*24);
	if(dist<12)return false;
	vf2d teleportPoint=p.GetPos()+pointTowardsMouse*dist;
	while(dist>0&&game->HasTileCollision(game->GetCurrentLevel(),teleportPoint)&&p.CanPathfindTo(p.GetPos(),teleportPoint)){
		dist-=24;
		teleportPoint=p.GetPos()+pointTowardsMouse*dist;
	}
	if(dist>0&&p.CanPathfindTo(p.GetPos(),teleportPoint)){
		p.SetState(State::TELEPORT);
		p.teleportAnimationTimer=0.35;
		p.teleportTarget=teleportPoint;
		p.teleportStartPosition=p.GetPos();
		p.iframe_time=0.35;
		for(int i=0;i<16;i++){
			game->AddEffect(Effect(p.GetPos()+vf2d{(rand()%160-80)/10.f,(rand()%160-80)/10.f},float(rand()%300)/1000,AnimationState::DOT_PARTICLE,p.upperLevel,0.3,0.2,{float(rand()%1000-500)/100,float(rand()%1000-500)/100},BLACK));
		}
		return true;
	} else {
		p.notificationDisplay={"Cannot Teleport to that location!",0.5};
		return false;
	}
}

Witch::Witch(std::string name,Class cl,Ability rightClickAbility,Ability ability1,Ability ability2,Ability ability3,
	AnimationState walk_n,AnimationState walk_e,AnimationState walk_s,AnimationState walk_w,AnimationState idle_n,AnimationState idle_e,AnimationState idle_s,AnimationState idle_w)
	:ClassData(name,cl,rightClickAbility,ability1,ability2,ability3,
		walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w)
{}

void Witch::Update(float fElapsedTime){
}

bool Witch::AutoAttack(){
	return true;
}

bool Witch::Ability1(){
	return true;
}

bool Witch::Ability2(){
	return true;
}

bool Witch::Ability3(){
	return true;
}

bool Witch::RightClickAbility(){
	return true;
}