#include "Unit.h"
#include "olcUTIL_Geometry2D.h"
#include "TileManager.h"
#include "util.h"
#include "DebuffIcon.h"
#include "olcPGEX_QuickGUI.h"

std::string BasicUnit::unitName="";
std::string BasicUnit::unitDescription="";
std::vector<Memory> BasicUnit::resourceCost={{HEALTH,4},{RANGE,2},{ATKSPD,2},{MOVESPD,3},{PROCEDURE,1}};
BasicUnit::BasicUnit(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,BasicUnit::resourceCost,pos,12,*IMAGES[VIRUS_IMG1],WHITE,WHITE,friendly,moveable){}


void BasicUnit::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	victim<<=1;
}

std::string BasicUnit2::unitName="";
std::string BasicUnit2::unitDescription="";
std::vector<Memory> BasicUnit2::resourceCost={{RANGE,2},{ATKSPD,2},{MOVESPD,3},{PROCEDURE,1},{HEALTH,4}};
BasicUnit2::BasicUnit2(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,BasicUnit2::resourceCost,pos,12,*IMAGES[VIRUS_IMG1],WHITE,WHITE,friendly,moveable){}

void BasicUnit2::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	victim>>=1;
}

std::string LeftShifter::unitName="Left Shifter";
std::string LeftShifter::unitDescription="Memory Shifts target memory 1 bit to the left.";
std::vector<Memory> LeftShifter::resourceCost={{RANGE,2},{ATKSPD,2},{MOVESPD,3},{PROCEDURE,1},{HEALTH,4}};
LeftShifter::LeftShifter(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,LeftShifter::resourceCost,pos,12,*IMAGES[LEFT_SHIFTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){}

void LeftShifter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	victim<<=1;
}

std::string RightShifter::unitName="Right Shifter";
std::string RightShifter::unitDescription="Memory Shifts target memory 1 bit to the right.";
std::vector<Memory> RightShifter::resourceCost={{HEALTH,4},{RANGE,2},{ATKSPD,2},{MOVESPD,3},{PROCEDURE,1}};
RightShifter::RightShifter(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,RightShifter::resourceCost,pos,12,*IMAGES[RIGHT_SHIFTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){}

void RightShifter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	victim>>=1;
}

std::string BitRestorer::unitName="Bit Restorer";
std::string BitRestorer::unitDescription="Randomly restores 1 missing bit to a target.";
std::vector<Memory> BitRestorer::resourceCost={{PROCEDURE,6},{RANGE,1},{ATKSPD,1},{MOVESPD,1},{HEALTH,2}};
BitRestorer::BitRestorer(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,BitRestorer::resourceCost,pos,12,*IMAGES[BIT_RESTORER],CONSTANT::HEALER_TARGET_COL,CONSTANT::HEALER_ATTACK_COL,friendly,moveable,true,false){}

void BitRestorer::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	std::vector<int>emptyMemoryPositions;
	for(int i=0;i<victim.GetMemorySize();i++){
		if(!victim.memory[i]){
			emptyMemoryPositions.emplace_back(i);
		}
	}
	if(emptyMemoryPositions.size()==0){
		//First see if we can find another damaged target, if we can, then we try healing them. Otherwise we exit.
		appliedTarget.reset();
		AttemptToHealOtherAllies(otherUnits);
		return;
	}
	int randomBit=emptyMemoryPositions[rand()%emptyMemoryPositions.size()];
	victim.memory[randomBit]=victim.ghostMemory[randomBit]=true;
}

void BitRestorer::AttemptToHealOtherAllies(std::vector<std::shared_ptr<Unit>>&otherUnits){
	std::vector<int>emptyMemoryPositions;
	for(auto&u:otherUnits){
		if(u.get()!=this&&u->IsFriendly()&&InRange(u)){
			for(int i=0;i<u->GetMemorySize();i++){
				if(!u->memory[i]){
					emptyMemoryPositions.emplace_back(i);
				}
			}
			if(emptyMemoryPositions.size()!=0){
				int randomBit=emptyMemoryPositions[rand()%emptyMemoryPositions.size()];
				u->memory[randomBit]=u->ghostMemory[randomBit]=true;
				appliedTarget=u;
				return;
			}
		}
	}
}

std::string MemorySwapper::unitName="Memory Swapper";
std::string MemorySwapper::unitDescription="Flips the orientation of all bits of a target around.";
std::vector<Memory> MemorySwapper::resourceCost={{RANGE,3},{ATKSPD,1},{HEALTH,3},{PROCEDURE,3},{MOVESPD,2}};
MemorySwapper::MemorySwapper(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,MemorySwapper::resourceCost,pos,12,*IMAGES[MEMORY_SWAPPER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable,true){
	autoAcquireFriendlyTarget=false;
}

void MemorySwapper::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){

}

std::string Corrupter::unitName="Corrupter";
std::string Corrupter::unitDescription="Chooses a random bit and negates it on a target.";
std::vector<Memory> Corrupter::resourceCost={{ATKSPD,3},{RANGE,1},{PROCEDURE,8},{MOVESPD,4},{HEALTH,4}};
Corrupter::Corrupter(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,Corrupter::resourceCost,pos,12,*IMAGES[CORRUPTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){}

void Corrupter::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	//Chooses a bit at random and corrupts it.
	int randomBit=rand()%victim.memory.size();
	victim.memory[randomBit]=victim.ghostMemory[randomBit]=false;
}

std::string MemoryAllocator::unitName="Memory Allocator";
std::string MemoryAllocator::unitDescription="A unit that builds other units.";
std::vector<Memory> MemoryAllocator::resourceCost={{RANGE,1},{ATKSPD,1},{MOVESPD,1},{PROCEDURE,1},{HEALTH,1}};
MemoryAllocator::MemoryAllocator(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly,bool moveable)
	:Unit(pge,MemoryAllocator::resourceCost,pos,12,*IMAGES[UNIT_ALLOCATOR],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,true,false){
	isAllocator=true;
}

void MemoryAllocator::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){
	
}

void MemoryAllocator::Update(PixelGameEngine*pge,std::map<Sound,std::unique_ptr<Audio>>&SOUNDS,std::vector<std::unique_ptr<Unit>>&queuedUnits){
	if(IsBuilding()){
		SetTargetLocation(CONSTANT::UNSELECTED);
		target.reset();
		buildTime-=pge->GetElapsedTime();
		if(buildTime<=0){
			for(int i=0;i<GetMemorySize();i++){
				memory[i]=false; //Kill the unit.
			}
			queuedUnits.push_back(std::move(buildTransformUnit));
		}
	}
}

void MemoryAllocator::Draw(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(IsBuilding()){
		game.GetPGE()->SetDrawTarget(img.Sprite());
		game.GetPGE()->Clear(BLANK);
		Renderable&targetImg=buildTransformUnit->GetImage();
		game.GetPGE()->SetDrawTarget(nullptr);
		game.DrawPartialRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{0,0},{float(img.Sprite()->width),float((buildTime/CONSTANT::UNIT_BUILD_TIME)*img.Sprite()->height)},{1,1},GetUnitColor()/3);
		game.DrawPartialRotatedDecal(GetGhostPos()+vf2d{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},buildTransformUnit->GetImage().Decal(),0,buildTransformUnit->GetImage().Sprite()->Size()/2,{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},{float(buildTransformUnit->GetImage().Sprite()->width),float(buildTransformUnit->GetImage().Sprite()->height-(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height)},{1,1},GetUnitColor()/1.5f);
		if(fmod(buildTime,1)>0.5){
			game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),{0, 202, 217});
		}
	} else {
		game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor());
		if(IsSelected()){
			game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE);
		}
	}
}

std::string RAMBank::unitName="RAM Bank";
std::string RAMBank::unitDescription="Allows for the construction of Memory Allocators.";
std::vector<Memory> RAMBank::resourceCost={{RANGE,0},{ATKSPD,0},{MOVESPD,0},{PROCEDURE,25},{HEALTH,16}};
RAMBank::RAMBank(PixelGameEngine*pge,vf2d pos,std::map<Image,std::unique_ptr<Renderable>>&IMAGES,bool friendly)
	:Unit(pge,RAMBank::resourceCost,pos,41,*IMAGES[RAM_BANK],WHITE,WHITE,friendly,false
		,false,false
		),randomOffset({util::random(128),util::random(128)}),matrixImg(*IMAGES[MATRIX]),
		originalImg(*IMAGES[RAM_BANK]){
	img.Create(IMAGES[RAM_BANK]->Sprite()->width,IMAGES[RAM_BANK]->Sprite()->height);
	pge->SetDrawTarget(img.Sprite());
	pge->Clear(BLANK);
	pge->DrawSprite({0,0},IMAGES[RAM_BANK]->Sprite());
	pge->SetDrawTarget(nullptr);
	allocatorManager.colNormal = olc::VERY_DARK_GREEN;
	allocatorManager.colHover = olc::GREEN;
	allocatorManager.colClick = olc::YELLOW;
	allocatorManager.colDisable = olc::DARK_GREY;
	allocatorManager.colBorder = olc::YELLOW;
	allocatorManager.colText = olc::WHITE;
	allocatorButton = new QuickGUI::ImageButton(allocatorManager,*IMAGES[UNIT_ALLOCATOR],{0.5f,0.5f},pos-vf2d{8,48},{20,20});
}

void RAMBank::Attack(Unit&victim,std::vector<std::shared_ptr<Unit>>&otherUnits){

}

void RAMBank::Update(PixelGameEngine*pge,std::map<Sound,std::unique_ptr<Audio>>&SOUNDS,std::vector<std::unique_ptr<Unit>>&queuedUnits){
	pge->SetDrawTarget(img.Sprite());
	for(int y=0;y<img.Sprite()->height;y++){
		for(int x=0;x<img.Sprite()->width;x++){
			Pixel col = originalImg.Sprite()->GetPixel(x,y);
			if(col==WHITE){
				pge->Draw(x,y,matrixImg.Sprite()->GetPixel(int(x+randomOffset.x),int(y+randomOffset.y)));
			}
		}
	}
	if(!soundStarted){
		soundStarted=true;
		soundHandle=SOUNDS[Sound::HUM]->Play(GetPos(),0.4,0.4,true);
	}
	img.Decal()->Update();
	pge->SetDrawTarget(nullptr);
}

void RAMBank::DrawHud(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(IsSelected()){
		allocatorManager.DrawDecal(game);
	}
}

void RAMBank::Draw(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor());
	if(IsSelected()){
		game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE);
	}
}

void RAMBank::OnDeath(std::map<Sound,std::unique_ptr<Audio>>&SOUNDS){
	SOUNDS[Sound::HUM]->Stop(soundHandle);
}

void RAMBank::UpdateGUIState(TileTransformedView&game,Resources&player_resources){
	allocatorManager.Update(game);

	auto TotalResources=[&](){
		return player_resources.atkSpd+player_resources.health+player_resources.moveSpd+player_resources.procedure+player_resources.range;
	};

	allocatorButton->Enable(IsSelected()&&TotalResources()>=5);
}

bool RAMBank::ClickHandled(TileTransformedView&game,Resources&player_resources,std::vector<std::shared_ptr<Unit>>&units,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(allocatorButton->bPressed){
		//First try to take one of each.
		if(player_resources.atkSpd>0&&player_resources.health>0&&player_resources.moveSpd>0&&player_resources.procedure>0&&player_resources.range>0){
			player_resources.atkSpd--;
			player_resources.health--;
			player_resources.moveSpd--;
			player_resources.procedure--;
			player_resources.range--;
		} else {
			//Remove from the highest resource amount until 5 are removed.
			for(int i=0;i<5;i++){
				int*maxAmt=&player_resources.atkSpd;
				if(player_resources.health>*maxAmt){
					maxAmt=&player_resources.health;
				}
				if(player_resources.moveSpd>*maxAmt){
					maxAmt=&player_resources.moveSpd;
				}
				if(player_resources.procedure>*maxAmt){
					maxAmt=&player_resources.procedure;
				}
				if(player_resources.range>*maxAmt){
					maxAmt=&player_resources.range;
				}
				(*maxAmt)--;
			}
		}
		units.push_back(std::make_shared<MemoryAllocator>(game.GetPGE(),GetPos()+vf2d{0,24},IMAGES,friendly));
		return true;
	}
	return false;
}

Unit::Unit(PixelGameEngine*pge,std::vector<Memory>memory,vf2d pos,float radius,Renderable&img,Pixel targetLineColor,Pixel attackingLineColor,bool friendly,bool moveable,bool friendlyInteractable,bool enemyInteractable)
:pos(pos),radius(radius),ghostPos(pos),img(img),targetLineCol(targetLineColor),attackingLineCol(attackingLineColor),friendly(friendly),moveable(moveable),friendlyInteractable(friendlyInteractable),enemyInteractable(enemyInteractable){
	int marker=0;
	for(Memory&mem:memory){
		for(int i=0;i<mem.size;i++){
			this->memory.push_back(true);
			this->ghostMemory.push_back(true);
		}
		switch(mem.type){
		case HEALTH:{
			health.index=marker;
			health.size=mem.size;
		}break;	
		case RANGE:{
			range.index=marker;
			range.size=mem.size;
		}break;	
		case ATKSPD:{
			atkSpd.index=marker;
			atkSpd.size=mem.size;
		}break;	
		case MOVESPD:{
			moveSpd.index=marker;
			moveSpd.size=mem.size;
		}break;	
		case PROCEDURE:{
			procedure.index=marker;
			procedure.size=mem.size;
		}break;	
		}
		marker+=mem.size;
	}

	attackingLine.Create(25,24,false,false);
	targetingLine.Create(25,24,false,false);
}

void Unit::DrawRangeIndicator(PixelGameEngine*pge,TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(!CanInteractWithAllies()&&!CanInteractWithEnemies())return;
	float dist=geom2d::line<float>(game.ScreenToWorld(pge->GetMousePos()),GetGhostPos()).length();
	float range=12*(GetRange()+1);
	float totalRange=GetUnitSize().x/2+range;

	if(IsSelected()){
		Pixel col;
		if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
			col={40,127,173};
		} else {
			col={0,196,0};
		}
		game.DrawRotatedDecal(GetGhostPos(),IMAGES[RANGE_INDICATOR]->Decal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},col);

	}else
	if(dist<range*2){
		Pixel col;
		if(IsFriendly()){
			if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
				col={40,127,173};
			} else {
				col={0,196,0};
			}
		}else{
			if(CanInteractWithAllies()&&!CanInteractWithEnemies()){
				col={194, 37, 168};
			} else {
				col={196,130,0};
			}
		}
		uint8_t transparency=uint8_t((1.f-(dist/(range*2)))*255);
		game.DrawRotatedDecal(GetGhostPos(),IMAGES[RANGE_INDICATOR]->Decal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},{col.r,col.g,col.b,transparency});
	}
}

void Unit::Draw(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor());
	if(IsSelected()){
		game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE);
	}
}

void Unit::DrawHud(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){}

void Unit::_DrawHud(TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	DrawHud(game,IMAGES);
	int initialBarX=ghostPos.x-GetMemorySize()/2*CONSTANT::BAR_SQUARE_SIZE.x-CONSTANT::BAR_SQUARE_SIZE.x/2;
	int initialBarY=ghostPos.y-CONSTANT::BAR_SQUARE_SIZE.y-img.Sprite()->height/2-2;
	Pixel col=0;


	auto CheckColor=[&](int i,Pixel&col){
		if(health.index==i){
			col=CONSTANT::HEALTH_COLOR;
		}
		if(range.index==i){
			col=CONSTANT::RANGE_COLOR;
		}
		if(atkSpd.index==i){
			col=CONSTANT::ATKSPD_COLOR;
		}
		if(moveSpd.index==i){
			col=CONSTANT::MOVESPD_COLOR;
		}
		if(procedure.index==i){
			col=CONSTANT::PROCEDURE_COLOR;
		}
	};

	if(GetHealth()>0){
		game.FillRectDecal(vf2d{float(initialBarX)+health.index*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*health.size-1,2},CONSTANT::HEALTH_COLOR);
	}
	if(GetAtkSpd()>0){
		game.FillRectDecal(vf2d{float(initialBarX)+atkSpd.index*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*atkSpd.size-1,2},CONSTANT::ATKSPD_COLOR);
	}
	if(GetMoveSpd()>0){
		game.FillRectDecal(vf2d{float(initialBarX)+moveSpd.index*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*moveSpd.size-1,2},CONSTANT::MOVESPD_COLOR);
	}
	if(GetProcedure()>0){
		game.FillRectDecal(vf2d{float(initialBarX)+procedure.index*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*procedure.size-1,2},CONSTANT::PROCEDURE_COLOR);
	}
	if(GetRange()>0){
		game.FillRectDecal(vf2d{float(initialBarX)+range.index*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*range.size-1,2},CONSTANT::RANGE_COLOR);
	}
	
	for(int i=0;i<GetMemorySize();i++){
		CheckColor(i,col);

		game.FillRectDecal({float(initialBarX)+i*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)},CONSTANT::BAR_SQUARE_SIZE,ghostMemory[i]?col:col/4);
		game.DrawRectDecal({float(initialBarX)+i*CONSTANT::BAR_SQUARE_SIZE.x,
			float(initialBarY)},CONSTANT::BAR_SQUARE_SIZE,BLACK);
	} 
}

void Unit::DrawUnitDamageStats(PixelGameEngine*pge,TileTransformedView&game,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(!target.expired()){
		geom2d::line<float>lineToTarget(pos,target.lock()->pos);
		lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2);
		lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4);
		util::ApplyMatrixEffect(game.GetPGE(),targetingLine,*IMAGES[TARGETING_LINE],IMAGES[MATRIX]);
		game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),targetingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*10,0},{lineToTarget.length(),24},{1,1},targetLineCol);
	} else
	if(targetLoc!=CONSTANT::UNSELECTED){
		geom2d::line<float>lineToTarget(pos,targetLoc);
		lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2);
		lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4);
		util::ApplyMatrixEffect(game.GetPGE(),targetingLine,*IMAGES[TARGETING_LINE],IMAGES[MATRIX]);
		game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),targetingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*10,0},{lineToTarget.length(),24},{1,0.6},CONSTANT::MOVE_LINE_COL);
	}
	if(!appliedTarget.expired()){
		geom2d::line<float>lineToTarget(pos,appliedTarget.lock()->pos);
		lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2);
		lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4);
		if(reloadTimer>0){
			util::ApplyMatrixEffect(game.GetPGE(),attackingLine,*IMAGES[ATTACKING_LINE],IMAGES[MATRIX]);
			float reloadSpd=1.f/(GetAtkSpd()/2.f);
			game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),attackingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*30,0},{lineToTarget.length(),24},{1,1+(attackFailed?-0.3f:(reloadTimer/reloadSpd)*0.25f)},attackFailed?Pixel{192,192,192,130}:Pixel{attackingLineCol.r,attackingLineCol.g,attackingLineCol.b,uint8_t(IsFriendly()?200:160)});
		}
	}
	float dist=geom2d::line<float>(game.ScreenToWorld(pge->GetMousePos()),GetGhostPos()).length();
	float range=12*(GetRange()+1);



	auto DrawStatDown=[&](Marker&maxStat,int currentStat,vf2d pos,vf2d downDisplayPos,Image img,uint8_t transparency=255){
		if(maxStat.size>0){
			if(currentStat!=maxStat.size){
				game.DrawDecal(this->pos+pos,IMAGES[img]->Decal(),{1,1},currentStat==0?Pixel{192,64,64,transparency}:Pixel{255,255,255,transparency});
				if(currentStat>0){
					game.DrawDecal(this->pos+downDisplayPos,IMAGES[DOWN_ARROW]->Decal(),{1,1},{255,255,255,transparency});
				} else {
					game.DrawDecal(this->pos+downDisplayPos,IMAGES[RED_X]->Decal(),{1,1},{255,255,255,transparency});
				}
			}
		}
	};

	if(IsSelected()){
		DrawStatDown(atkSpd,GetAtkSpd(),{-48-8,-24},{-48+8,-18},RLD);
		DrawStatDown(moveSpd,GetMoveSpd(),{-48-8,8},{-36+8,2},SPD);
		DrawStatDown(this->range,GetRange(),{8,-24},{20,-18},RNG);
		DrawStatDown(procedure,GetProcedure(),{8,8},{26,2},PRC);
	}else
	if(dist<range*2){
		uint8_t transparency=uint8_t((1.f-(dist/(range*2)))*255);
		DrawStatDown(atkSpd,GetAtkSpd(),{-48-8,-24},{-48+8,-18},RLD,transparency);
		DrawStatDown(moveSpd,GetMoveSpd(),{-48-8,8},{-36+8,2},SPD,transparency);
		DrawStatDown(this->range,GetRange(),{8,-24},{20,-18},RNG,transparency);
		DrawStatDown(procedure,GetProcedure(),{8,8},{26,2},PRC,transparency);
	}
}

int Unit::GetBits(Marker&m){
	int activeBits=0;
	for(int i=0;i<m.size;i++){
		if(memory[i+m.index]){
			activeBits++;
		}
	}
	return activeBits;
}

void Unit::OnDeath(std::map<Sound,std::unique_ptr<Audio>>&SOUNDS){}

int Unit::GetHealth(){
	return GetBits(health);
}

int Unit::GetRange(){
	return GetBits(range);
}

int Unit::GetAtkSpd(){
	return GetBits(atkSpd);
}

int Unit::GetMoveSpd(){
	return GetBits(moveSpd);
}

int Unit::GetProcedure(){
	return GetBits(procedure);
}

int Unit::GetMemorySize(){
	return memory.size();
}

void Unit::RunAI(PixelGameEngine*pge){}

void Unit::_RunAI(PixelGameEngine*pge){
	RunAI(pge);
}

void Unit::_Update(PixelGameEngine*pge,std::map<Sound,std::unique_ptr<Audio>>&SOUNDS,Resources&player_resources,Resources&enemy_resources,std::vector<std::unique_ptr<Unit>>&queuedUnits){
	if(!target.expired()){
		auto ptrTarget=target.lock();
		if(!InRange(ptrTarget)&&CanMove()){
			SetPos(GetPos()+(ptrTarget->GetPos()-pos).norm()*GetMoveSpd()*24*pge->GetElapsedTime());
		}
	} else 
	if(targetLoc!=CONSTANT::UNSELECTED){
		float dist=geom2d::line<float>(pos,targetLoc).length();
		if(dist>24){
			if(CanMove()){
				SetPos(GetPos()+(targetLoc-pos).norm()*GetMoveSpd()*24*pge->GetElapsedTime());
			}
		} else {
			if(willAttachWhenReachingDestination&&!attachTarget.expired()&&attachTarget.lock()->attachedUnit.expired()){
				attachedPoint=attachTarget;
				attachedPoint.lock()->attachedUnit=self_ptr;
			}
			willAttachWhenReachingDestination=false;
			targetLoc=CONSTANT::UNSELECTED;
		}
	}

	if(!attachedPoint.expired()){
		collectionTime-=pge->GetElapsedTime();
		if(collectionTime<=0){
			collectionTime=CONSTANT::COLLECTION_WAIT_TIME;
			Resources&targetResource=IsFriendly()?player_resources:enemy_resources;
			switch(attachedPoint.lock()->type){
				case HEALTH:{
					targetResource.health++;
				}break;
				case RANGE:{
					targetResource.range++;
				}break;
				case ATKSPD:{
					targetResource.atkSpd++;
				}break;
				case MOVESPD:{
					targetResource.moveSpd++;
				}break;
				case PROCEDURE:{
					targetResource.procedure++;
				}break;
			}
		}
	}

	if(!IsFriendly()){
		_RunAI(pge);
	}

	if(!GhostInFogOfWar()&&InFogOfWar()){
		HideGhost();
	}

	reloadTimer=std::max(0.f,reloadTimer-pge->GetElapsedTime());
	lineShift-=pge->GetElapsedTime();
	if(lineShift<-25)lineShift+=25;

	Update(pge,SOUNDS,queuedUnits);
}

std::vector<bool> operator <<(Unit&u,const int n){
	std::vector<bool>tempMem=u.memory;
	for(int i=0;i<u.GetMemorySize()-1;i++){
		tempMem[i]=tempMem[i+1];
	}
	tempMem[u.GetMemorySize()-1]=0;
	return tempMem;
}

std::vector<bool> operator >>(Unit&u,const int n){
	std::vector<bool>tempMem=u.memory;
	for(int i=1;i<u.GetMemorySize();i++){
		tempMem[i]=tempMem[i-1];
	}
	tempMem[0]=0;
	return tempMem;
}

bool Unit::IsFriendly(){
	return friendly;
}


bool Unit::IsSelected(){
	return selected;
}

void Unit::Select(){
	selected=true;
}

void Unit::Deselect(){
	selected=false;
}

vf2d Unit::GetPos(){
	return pos;
}

bool Unit::IsDead(){
	return dead;
}

vf2d Unit::GetUnitSize(){
	return vf2d{radius,radius}*2;
}

void Unit::SetTargetUnit(std::weak_ptr<Unit>target){
	this->target=target;
	this->targetLoc=CONSTANT::UNSELECTED;
	willAttachWhenReachingDestination=false;
}

void Unit::SetTargetLocation(vf2d targetLoc){
	this->target.reset();
	this->targetLoc=targetLoc;
	willAttachWhenReachingDestination=false;
}

bool Unit::InRange(std::shared_ptr<Unit>target){
	return InRange(target.get());
}

bool Unit::InRange(Unit*target){
	float range=12*(GetRange()+1);
	float totalRange=GetUnitSize().x/2+range;
	return geom2d::overlaps(geom2d::circle<float>{GetPos(),totalRange},geom2d::circle<float>{target->GetPos(),target->GetUnitSize().x/2});
}

bool Unit::InRange(vf2d pos){
	float range=12*(GetRange()+1);
	float totalRange=GetUnitSize().x/2+range;
	return geom2d::overlaps(geom2d::circle<float>{GetPos(),totalRange},geom2d::circle<float>{pos,0.1});
}

void Unit::SetPos(vf2d newPos){
	pos=newPos;
	if(!InFogOfWar()){
		ghostPos=pos;
	}
}

void Unit::AttemptAttack(std::weak_ptr<Unit>attacker,std::weak_ptr<Unit>unit,std::vector<std::shared_ptr<Unit>>&otherUnits,std::vector<DebuffIcon>&debuffIcons,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	if(reloadTimer>0)return;
	std::weak_ptr<Unit>finalTarget;
	if(!unit.expired()){
		finalTarget=unit;
		if(!target.expired()){
			auto ptrTarget=target.lock();
			if(InRange(ptrTarget)){
				finalTarget=ptrTarget;
			}
		}
	}
	if(!finalTarget.expired()){
		if(InRange(finalTarget.lock())){
			appliedTarget=finalTarget;
			bool hadAtkSpd=finalTarget.lock()->GetAtkSpd()>0;
			bool hadMoveSpd=finalTarget.lock()->GetMoveSpd()>0;
			bool hadRange=finalTarget.lock()->GetRange()>0;
			bool hadProcedure=finalTarget.lock()->GetProcedure()>0;
			_Attack(attacker,finalTarget,otherUnits); //Call the parent function first, followed by the child.
			if(hadAtkSpd&&finalTarget.lock()->GetAtkSpd()==0){
				debuffIcons.emplace_back(IMAGES[RLD_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4});
			}
			if(hadMoveSpd&&finalTarget.lock()->GetMoveSpd()==0){
				debuffIcons.emplace_back(IMAGES[SPD_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4});
			}
			if(hadRange&&finalTarget.lock()->GetRange()==0){
				debuffIcons.emplace_back(IMAGES[RNG_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4});
			}
			if(hadProcedure&&finalTarget.lock()->GetProcedure()==0){
				debuffIcons.emplace_back(IMAGES[PRC_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4});
			}
		}
	}
}

void Unit::Update(PixelGameEngine*pge,std::map<Sound,std::unique_ptr<Audio>>&SOUNDS,std::vector<std::unique_ptr<Unit>>&queuedUnits){}

void Unit::Attacked(std::weak_ptr<Unit>attacker){}

void Unit::_Attacked(std::weak_ptr<Unit>attacker){
	Attacked(attacker);
	if(attacker.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){
		SetTargetUnit(attacker);
	}
}

void Unit::_Attack(std::weak_ptr<Unit>attacker,std::weak_ptr<Unit>finalTarget,std::vector<std::shared_ptr<Unit>>&otherUnits){
	if(GetProcedure()>0&&GetAtkSpd()>0){
		attackFailed=false;
		float procChance=float(GetProcedure())/procedure.size;
		if(util::random(1)>=1-procChance){
			Attack(*finalTarget.lock(),otherUnits);
			finalTarget.lock()->_Attacked(attacker);
			reloadTimer=1.f/(GetAtkSpd()/2.f);
			if(GetCurrentTarget().expired()&&!IsFriendly()){
				if(finalTarget.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){
					SetTargetUnit(finalTarget);
				}
			}
		} else {
			reloadTimer=1.f/(GetAtkSpd()/2.f);
			attackFailed=true;
		}
	}
}

bool Unit::InFogOfWar(){
	return TileManager::visibleTiles.count(GetPos()/96)==0;
}

bool Unit::GhostInFogOfWar(){
	return TileManager::visibleTiles.count(ghostPos/96)==0;
}

void Unit::HideGhost(){
	ghostPos={99999,-99999};
}

vf2d Unit::GetGhostPos(){
	return ghostPos;
}

bool Unit::IsMoveable(){
	return moveable&&attachedPoint.expired();
}

bool Unit::CanInteractWithAllies(){
	return friendlyInteractable&&attachedPoint.expired()&&buildTime<=0;
}

bool Unit::CanInteractWithEnemies(){
	return enemyInteractable&&attachedPoint.expired()&&buildTime<=0;
}

Renderable&Unit::GetImage(){
	return img;
}

std::weak_ptr<Unit>Unit::GetCurrentTarget(){
	return target;
}

bool Unit::AutoAcquiresFriendlyTargets(){
	return autoAcquireFriendlyTarget;
}

bool Unit::CanMove(){
	return moveSpd.size>0&&attachedPoint.expired()&&buildTime<=0;
}

void Unit::SetTargetCollectionPoint(std::weak_ptr<CollectionPoint>targetCP,std::weak_ptr<Unit>self_ptr){
	SetTargetLocation(targetCP.lock()->pos);
	attachTarget=targetCP;
	willAttachWhenReachingDestination=true;
	this->self_ptr=self_ptr;
}

Pixel Unit::GetUnitColor(){
	Pixel col;
	if(!attachedPoint.expired()){
		switch(attachedPoint.lock()->type){
			case HEALTH:{
				return CONSTANT::HEALTH_COLOR/2;
			}break;
			case RANGE:{
				return CONSTANT::RANGE_COLOR/2;
			}break;
			case ATKSPD:{
				return CONSTANT::ATKSPD_COLOR/2;
			}break;
			case MOVESPD:{
				return CONSTANT::MOVESPD_COLOR/2;
			}break;
			case PROCEDURE:{
				return CONSTANT::PROCEDURE_COLOR/2;
			}break;
		}
	} else {
		return friendly?Pixel{192,192,255}:Pixel{255,192,192};
	}
}

bool Unit::ClickHandled(TileTransformedView&game,Resources&player_resources,std::vector<std::shared_ptr<Unit>>&units,std::map<Image,std::unique_ptr<Renderable>>&IMAGES){
	return false;
}; 

void Unit::UpdateGUIState(TileTransformedView&game,Resources&player_resources){};

bool Unit::IsAllocator(){
	return isAllocator&&attachedPoint.expired()&&buildTime<=0;
}

void Unit::SetBuildUnit(float buildTime,std::unique_ptr<Unit>finalUnit){
	this->buildTime=buildTime;
	this->buildTransformUnit=std::move(finalUnit);
}

bool Unit::IsBuilding(){
	return buildTime>0;
}