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

INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_ANIMATION_DATA
INCLUDE_SPAWNER_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_CLASS_DATA
INCLUDE_PLAYER_BULLET_LIST
INCLUDE_game

const float Player::GROUND_SLAM_SPIN_TIME=0.6f;

Player::Player():
	state(State::NORMAL),lastReleasedMovementKey(DOWN),facingDirection(DOWN){}

void Player::SetClass(Class cl){
	this->cl=CLASS_DATA[cl].cl;
	rightClickAbility=CLASS_DATA[cl].rightClickAbility;
	ability1=CLASS_DATA[cl].ability1;
	ability2=CLASS_DATA[cl].ability2;
	ability3=CLASS_DATA[cl].ability3;
	UpdateIdleAnimation(DOWN);
}

void Player::SetX(float x){
	pos.x=x;
};

void Player::SetY(float y){
	pos.y=y;
}

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

void Player::SetPos(vf2d pos){
	this->pos=pos;
}

vf2d&Player::GetPos(){
	return pos;
}

float Player::GetX(){
	return pos.x;
}

float Player::GetY(){
	return pos.y;
}

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

int Player::GetHealth(){
	return hp;
}

int Player::GetMaxHealth(){
	return maxhp;
}

int Player::GetMana(){
	return mana;
}

int Player::GetMaxMana() {
	return maxmana;
}

int Player::GetAttack(){
	float mod_atk=atk;
	for(Buff&b:GetBuffs(BuffType::ATTACK_UP)){
		mod_atk+=atk*b.intensity;
	}
	return int(mod_atk);
}

float Player::GetMoveSpdMult(){
	float mod_moveSpd=moveSpd;
	for(Buff&b:GetBuffs(BuffType::SLOWDOWN)){
		mod_moveSpd-=moveSpd*b.intensity;
	}
	return mod_moveSpd;
}

float Player::GetSizeMult(){
	return size;
}

float Player::GetAttackRangeMult(){
	return attack_range;
}

float Player::GetSpinAngle(){
	return spin_angle;
}

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

void Player::Update(float fElapsedTime){
	attack_cooldown_timer=std::max(0.f,attack_cooldown_timer-fElapsedTime);
	iframe_time=std::max(0.f,iframe_time-fElapsedTime);
	notEnoughManaDisplay.second=std::max(0.f,notEnoughManaDisplay.second-fElapsedTime);
	manaTickTimer-=fElapsedTime;
	while(manaTickTimer<=0){
		manaTickTimer+=0.2;
		mana=std::min(maxmana,mana+1);
	}
	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;
		}
	}
	switch(state){
		case SPIN:{
			switch(facingDirection){
				case UP:{
					if(lastAnimationFlip==0){
						lastAnimationFlip=0.03;
						facingDirection=DOWN;
						animation.ChangeState(internal_animState,AnimationState::WARRIOR_WALK_S);
					}
				}break;
				case DOWN:{
					if(lastAnimationFlip==0){
						lastAnimationFlip=0.03;
						facingDirection=UP;
						animation.ChangeState(internal_animState,AnimationState::WARRIOR_WALK_N);
					}
				}break;
			}
			if(facingDirection==RIGHT){
				spin_angle+=spin_spd*fElapsedTime;
			} else {
				spin_angle-=spin_spd*fElapsedTime;
			}
			if(spin_attack_timer>0){
				z=50*sin(3.3*(GROUND_SLAM_SPIN_TIME-spin_attack_timer)/GROUND_SLAM_SPIN_TIME);
				spin_attack_timer=std::max(0.f,spin_attack_timer-fElapsedTime);
			} else {
				SetState(NORMAL);
				spin_angle=0;
				z=0;
				game->HurtEnemies(pos,3*12,GetAttack()*2.5);
				game->AddEffect(Effect{GetPos(),0.5,AnimationState::GROUND_SLAM_ATTACK_FRONT,1.33f,0.6f},Effect{GetPos(),0.5,AnimationState::GROUND_SLAM_ATTACK_BACK,1.33f,0.6f});
			}
			if(lastAnimationFlip>0){
				lastAnimationFlip=std::max(0.f,lastAnimationFlip-fElapsedTime);
			}
			animation.UpdateState(internal_animState,fElapsedTime);
		}break;
		case BLOCK:{
			if(rightClickAbility.COOLDOWN_TIME-rightClickAbility.cooldown>3){
				SetState(NORMAL);
			}
		}break;
		case SWING_SONIC_SWORD:{
			if(ability3.COOLDOWN_TIME-ability3.cooldown>0.5){
				SetState(NORMAL);
				switch(facingDirection){
					case DOWN:{
						UpdateAnimation(AnimationState::WARRIOR_IDLE_S);
					}break;
					case RIGHT:{
						UpdateAnimation(AnimationState::WARRIOR_IDLE_E);
					}break;
					case LEFT:{
						UpdateAnimation(AnimationState::WARRIOR_IDLE_W);
					}break;
					case UP:{
						UpdateAnimation(AnimationState::WARRIOR_IDLE_N);
					}break;
				}
			}
			animation.UpdateState(internal_animState,fElapsedTime);
		}break;
		default:{
			//Update animations normally.
			animation.UpdateState(internal_animState,fElapsedTime);
		}
	}
	rightClickAbility.cooldown=std::max(0.f,rightClickAbility.cooldown-fElapsedTime);
	ability1.cooldown=std::max(0.f,ability1.cooldown-fElapsedTime);
	ability2.cooldown=std::max(0.f,ability2.cooldown-fElapsedTime);
	ability3.cooldown=std::max(0.f,ability3.cooldown-fElapsedTime);
	for(Monster&m:MONSTER_LIST){
		if(iframe_time==0&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){
			if(m.IsAlive()){
				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(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);
	}
	float newX=pos.x+vel.x*fElapsedTime;
	if(newX-12*size>0&&newX+12*size<game->WORLD_SIZE.x*24){
		pos.x=newX;
	}
	float newY=pos.y+vel.y*fElapsedTime;
	if(newY-12*size>0&&newY+12*size<game->WORLD_SIZE.y*24){
		pos.y=newY;
	}
	if(attack_cooldown_timer==0&&game->GetMouse(0).bHeld){
		switch (cl) {
			case WARRIOR:{
				if(state!=State::SPIN){
					bool attack=false;
					Monster*closest=nullptr;
					float closest_dist=999999;
					for(Monster&m:MONSTER_LIST){
						if(m.IsAlive()
							&&geom2d::overlaps(geom2d::circle<float>(pos-vf2d{size*12,size*12},attack_range*size*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(GetAttack())){
						attack_cooldown_timer=ATTACK_COOLDOWN;
						swordSwingTimer=0.2;
						SetState(State::SWING_SWORD);
						switch(facingDirection){
							case DOWN:{
								UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_S);
							}break;
							case RIGHT:{
								UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_E);
							}break;
							case LEFT:{
								UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_W);
							}break;
							case UP:{
								UpdateAnimation(AnimationState::WARRIOR_SWINGSWORD_N);
							}break;
						}
					}
				}
			}break;
			case THIEF: {
			}break;
			case RANGER: {
			}break;
			case BARD: {
			}break;
			case WIZARD: {
			}break;
			case WITCH: {
			}break;
		}
	}
	if(ability1.cooldown==0&&GetMana()>=ability1.manaCost&&game->GetKey(SHIFT).bHeld){
		switch (cl) {
			case WARRIOR: {
				game->AddEffect(Effect(pos,0.1,AnimationState::BATTLECRY_EFFECT,1,0.3));
				ability1.cooldown=ability1.COOLDOWN_TIME;
				AddBuff(BuffType::ATTACK_UP,10,0.1);
				AddBuff(BuffType::DAMAGE_REDUCTION,10,0.1);
				for(Monster&m:MONSTER_LIST){
					if(m.GetSizeMult()<=1&&geom2d::overlaps(geom2d::circle<float>(pos,12*3.5),geom2d::circle<float>(m.GetPos(),m.GetSizeMult()*12))){
						m.AddBuff(BuffType::SLOWDOWN,5,0.3);
					}
				}
			}break;
			case THIEF: {
			}break;
			case RANGER: {
			}break;
			case BARD: {
			}break;
			case WIZARD: {
			}break;
			case WITCH: {
			}break;
		}
		mana-=ability1.manaCost;
	} else 
	if(ability1.cooldown==0&&GetMana()<ability1.manaCost&&game->GetKey(SHIFT).bPressed){
		notEnoughManaDisplay={ability1.name,1};
	}
	if(ability2.cooldown==0&&GetMana()>=ability2.manaCost&&game->GetKey(SPACE).bPressed){
		switch(cl){
			case WARRIOR:{
				Spin(GROUND_SLAM_SPIN_TIME,14*PI);
				iframe_time=GROUND_SLAM_SPIN_TIME+0.1;
			}break;
			case THIEF:{
			}break;
			case RANGER:{
			}break;
			case BARD:{
			}break;
			case WIZARD:{
			}break;
			case WITCH:{
			}break;
		}
		mana-=ability2.manaCost;
	} else 
	if(ability2.cooldown==0&&GetMana()<ability2.manaCost&&game->GetKey(SPACE).bPressed){
		notEnoughManaDisplay={ability2.name,1};
	}
	if(ability3.cooldown==0&&GetMana()>=ability3.manaCost&&game->GetKey(CTRL).bPressed){
		switch(cl){
			case WARRIOR:{
				ability3.cooldown=ability3.COOLDOWN_TIME;
				SetState(State::SWING_SONIC_SWORD);
				AddBuff(BuffType::SLOWDOWN,0.5,1);
				vf2d bulletVel={};
				switch(GetFacingDirection()){
					case UP:{
						vel.y=70;
						bulletVel.y=-400;
						UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_N);
					}break;
					case LEFT:{
						vel.x=70;
						bulletVel.x=-400;
						UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_W);
					}break;
					case RIGHT:{
						vel.x=-70;
						bulletVel.x=400;
						UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_E);
					}break;
					case DOWN:{
						vel.y=-70;
						bulletVel.y=400;
						UpdateAnimation(AnimationState::WARRIOR_SWINGSONICSWORD_S);
					}break;
				}
				PLAYER_BULLET_LIST.push_back(Bullet(pos,bulletVel,30,GetAttack()*8,AnimationState::SONICSLASH,true,2.25,true));
				game->SetupWorldShake(0.5);
			}break;
			case THIEF:{
			}break;
			case RANGER:{
			}break;
			case BARD:{
			}break;
			case WIZARD:{
			}break;
			case WITCH:{
			}break;
		}
		mana-=ability3.manaCost;
	} else 
	if(ability3.cooldown==0&&GetMana()<ability3.manaCost&&game->GetKey(CTRL).bPressed){
		notEnoughManaDisplay={ability3.name,1};
	}
	if(rightClickAbility.cooldown==0&&GetMana()>=rightClickAbility.manaCost&&game->GetMouse(1).bHeld){
		switch (cl) {
			case WARRIOR: {
				if(GetState()==State::NORMAL){
					rightClickAbility.cooldown=rightClickAbility.COOLDOWN_TIME;
					SetState(State::BLOCK);
					AddBuff(BuffType::SLOWDOWN,3,0.3);
				}
			}break;
			case THIEF: {
			}break;
			case RANGER: {
			}break;
			case BARD: {
			}break;
			case WIZARD: {
			}break;
			case WITCH: {
			}break;
		}
		mana-=rightClickAbility.manaCost;
	} else 
	if(rightClickAbility.cooldown==0&&GetMana()<rightClickAbility.manaCost&&game->GetMouse(1).bPressed){
		notEnoughManaDisplay={rightClickAbility.name,1};
	}
}

float Player::GetSwordSwingTimer(){
	return swordSwingTimer;
}

void Player::SetSwordSwingTimer(float val){
	swordSwingTimer=val;
}

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

vf2d Player::GetVelocity(){
	return vel;
}

bool Player::HasIframes(){
	return iframe_time>0;
}

bool Player::Hurt(int damage){
	if(hp<=0||iframe_time!=0) return false;
	if(state==State::BLOCK)damage=0;
	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)));
	return true;
}

void Player::AddAnimation(AnimationState state){
	animation.AddState(state,ANIMATION_DATA[state]);
}

void Player::UpdateAnimation(AnimationState animState){
	animation.ChangeState(internal_animState,animState);
}

Animate2D::Frame Player::GetFrame(){
	return animation.GetFrame(internal_animState);
}

void Player::SetLastReleasedMovementKey(Key k){
	lastReleasedMovementKey=k;
}

Key Player::GetLastReleasedMovementKey(){
	return lastReleasedMovementKey;
}

void Player::SetFacingDirection(Key direction){
	facingDirection=direction;
}

Key Player::GetFacingDirection(){
	return facingDirection;
}

void Player::Moved(){
	for(MonsterSpawner&spawner:SPAWNER_LIST){
		if(!spawner.SpawnTriggered()&&geom2d::overlaps(geom2d::circle<float>(pos-vf2d{size*12,size*12},size*12),geom2d::circle<float>(spawner.GetPos(),spawner.GetRange()))){
			spawner.SetTriggered(true);
		}
	}
}

float Player::GetAbility2Cooldown(){
	return ability2.cooldown;
}
float Player::GetRightClickCooldown(){
	return rightClickAbility.cooldown;
}

void Player::Spin(float duration,float spinSpd){
	state=State::SPIN;
	spin_attack_timer=duration;
	spin_spd=spinSpd;
	spin_angle=0;
	ability2.cooldown=ability2.COOLDOWN_TIME;
}

void Player::UpdateWalkingAnimation(Key direction){
	AnimationState anim;
	switch(direction){
		case UP:anim=CLASS_DATA[cl].walk_n;break;
		case RIGHT:anim=CLASS_DATA[cl].walk_e;break;
		case DOWN:anim=CLASS_DATA[cl].walk_s;break;
		case LEFT:anim=CLASS_DATA[cl].walk_w;break;
	}
	UpdateAnimation(anim);
}

void Player::UpdateIdleAnimation(Key direction){
	AnimationState anim;
	switch(direction){
		case UP:anim=CLASS_DATA[cl].idle_n;break;
		case RIGHT:anim=CLASS_DATA[cl].idle_e;break;
		case DOWN:anim=CLASS_DATA[cl].idle_s;break;
		case LEFT:anim=CLASS_DATA[cl].idle_w;break;
	}
	UpdateAnimation(anim);
}

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

std::vector<Buff>Player::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;
}