BotPlayers = {}
IsAnyBotsHere = false
CurrentConyards = {}
CheckPlayerTechtree = false
ActivePlayers = {}
PlayersThatNotchoosedyet = 0 --Numbver of players that didnt choose any subfaction yet
FactionsMode = 0 -- default Faction mode: vannila
-- must be local fuction. otherwise it crash when multiple AirStrikes are called at ones.
--Airstrike variables

SpamWposOffsets = {-1256, 0, 1256} -- offset for each aircraft

ReinforcementSquads = {
	{"light_inf","light_inf","light_inf.vet3","trooper","trooper"},
	{ "light_inf.vet3", "trooper", "trooper", "trooper"},
	{"mercenary","mercenary","light_inf"},
	{"mercenary","mercenary","mercenary","mercenary","light_inf","trooper","light_inf"}
}
--different Squads for different Factons 1.Atreides, 2. Ordos 3. Harkonnen
AirSquads = {
	{"ornithopter_a","ornithopter_a", "ornithopter_a"},
	{"ornithopter_a","ornithopter_a"},
	{"ornithopter_o", "ornithopter_o", "ornithopter_o"}
}
DropActor = {"carryall.reinforce_with_sound", "carryall.reinforce_with_sound", "frigate.harkonnen_cargo"}


-- Reinforcements via Starport

function CHOAMDeliverCustomUnits(player, squad)
	local starport = SelectStarport(player)
	--Media.Debug("Starting custom units delivery "..player.InternalName)
	if starport == nil then
		--Media.Debug("Error, cant find any starport. Starport reset"..player.Name)
		ResetStartport(player)
		return
	end
	local rallypoint = starport.RallyPoint
	local path = {Map.ClosestEdgeCell(starport.Location),starport.Location + CVec.New(1,1)}
	local airUnits = FilterAirUnits(player.InternalName)
	--wait for Choam to come
	local units = Reinforcements.ReinforceWithTransport(player,"frigate_choam",squad, path,{path[2], path[1]})
	local frigate = units[1]
	Reinforcements.Reinforce(player, airUnits,path)
	-- when killed or deliver finish remove Frigade and reset Starport settings
	Trigger.OnRemovedFromWorld(starport, function()
		--Media.Debug("Starport removed redirecting order "..frigate.Type)
		local starport_new = SelectStarport(player)
		if starport_new == nil then
			if not frigate.IsDead then
				--Media.Debug("no more starports canceling order"..player.Name)
				frigate.Stop()
				frigate.Move(path[1])
			end
		else
		--	Media.Debug("Starport removed redirecting order "..frigate.Type)
			if not frigate.IsDead then
				frigate.Stop()
				frigate.Move(starport_new.Location + CVec.New(1,1))
				frigate.UnloadPassengers()
			end
		end
	end)
		--in case frigate stuck
	Trigger.OnIdle(frigate, function()
		frigate.UnloadPassengers()
	end)
	Trigger.OnPassengerExited(frigate,
		function(carry, pass)
				if pass ~= nil then
					Trigger.AfterDelay(10, function()
						pass.Move(rallypoint)
					end)
				end
			end)
end
-------------------------------
-- Multi pass Airstrikes  - 
--warring use only as local function (global function can crash)
--------------------------------
CallAirstrike = function(dummy,squadType, player, useOffset, squadSpamDelay)
	if dummy.IsDead then
		return
	end
	squadType = squadType or 1
	player = player or dummy.Owner
	useOffset = useOffset or false
	squadSpamDelay = squadSpamDelay or 6
	local dummyPosition = dummy.Location
	local spawnEdgeCell = Map.ClosestEdgeCell(dummyPosition)
	local angle = CalculateBestAngle(spawnEdgeCell)
	local wposDummyLocation = Map.CenterOfCell(dummyPosition)
	local wposAirCraftSpawnLocation = Map.CenterOfCell(spawnEdgeCell)
	local airUnits = AirSquads[squadType]
	local squadRefference = {}
	local dummyRefference = {}
	local pon = 1
	local arrayLenght = #SpamWposOffsets + 1
	-- calculate WPos offset for every aircraft
	while pon <= #airUnits do
		local newLocation
		if useOffset == true then
			if angle == Angle.West or angle == Angle.East then
				newLocation =WPos.New( wposDummyLocation.X + SpamWposOffsets[arrayLenght - pon], wposDummyLocation.Y+SpamWposOffsets[pon], 0)
			else
				newLocation =WPos.New( wposDummyLocation.X + SpamWposOffsets[pon], wposDummyLocation.Y+SpamWposOffsets[pon], 0)
			end
		else
			newLocation = wposDummyLocation
		end
		dummyRefference[pon] = Actor.Create("dummy.generic",true, {Owner = player, CenterPosition = newLocation })
		pon = pon + 1
	end
	--Media.Debug("calling Airstrike"..tostring(spawnPosition.X).."  ,   "..tostring(spawnPosition.Y))
	-- calculate squadron
	for i, actorName in pairs(airUnits) do
		local spamDelay = i * squadSpamDelay
		Trigger.AfterDelay(spamDelay, function()
			local airUnit
			local finalWposPosition =  wposAirCraftSpawnLocation + WVec.New(0,0,Actor.CruiseAltitude(actorName))
			if angle == Angle.West or angle == Angle.East then
				finalWposPosition = wposAirCraftSpawnLocation + WVec.New(SpamWposOffsets[arrayLenght - i], SpamWposOffsets[i], Actor.CruiseAltitude(actorName))
			else
				finalWposPosition = wposAirCraftSpawnLocation + WVec.New(SpamWposOffsets[i], SpamWposOffsets[i], Actor.CruiseAltitude(actorName))
			end
			if useOffset == true or dummy.IsCloaked then
				airUnit = Actor.Create(actorName ,true, {Owner = player ,Facing = angle,  CenterPosition = finalWposPosition })
				airUnit.Attack(dummyRefference[i], true, true)
			else
				airUnit = Actor.Create(actorName ,true, {Owner = player, CenterPosition = finalWposPosition, Facing = angle})
				if not dummy.IsDead then
					airUnit.Attack(dummy, true, false)
					airUnit.Attack(dummyRefference[i], true, true)
				else
					airUnit.Attack(dummyRefference[i], true, true)
				end
			end
			table.insert(squadRefference, airUnit)
			LeaveOnEmpty(airUnit)
		end)
	end
	Trigger.AfterDelay(squadSpamDelay + 1 * 5, function()
		Trigger.OnAllRemovedFromWorld(squadRefference, function ()
			-- edge case check. if player surrender while airstrike is in progress -> crash
			if not dummy.IsDead then
				if dummy.Type == "waypoint.airtstrike_a" or dummy.Type == "waypoint.airtstrike_o" then
					dummy.Destroy()
				end
				--Trigger.ClearAll(dummy)
				for i, dummy_generic in pairs(dummyRefference) do
					dummy_generic.Kill()
				end
			end
		end)
		for _, airUnit in pairs(squadRefference) do
			Trigger.OnIdle(airUnit, function()
				DeArmSquad(squadRefference)
			end)
		end
	end)
	Trigger.OnKilled(dummy, function()
		DeArmSquad(squadRefference)
	end)
end

DeArmSquad = function(airSquad)
	for _, airunit in pairs(airSquad) do
		if not airunit.IsDead then
			airunit.Reload("primary", -15)
		end
	end
end

function LeaveOnEmpty(unit)
	if not unit.IsDead then
		if unit.AmmoCount("primary") < 1 then
			unit.Stop()
			local position = Map.ClosestEdgeCell(unit.Location)
			unit.Move(position)
			unit.Destroy()
		else
			Trigger.AfterDelay(30, function()
				LeaveOnEmpty(unit)
			end)
		end
	end
end

function CalculateBestAngle(position)
	if position.X <= 2 then
		return Angle.East
	elseif position.Y <= 2 then
		return Angle.South
	elseif position.X < position.Y then
		return Angle.North
	else
		return Angle.West
	end
end


function GiveAirSquad(faction)
	if faction == "waypoint.airtstrike_a" then
		--Media.DisplayMessage("atreides squad")
		return AirSquads[1]
	elseif faction == "waypoint.airtstrike_h"then
		return AirSquads[2]
	elseif faction == "waypoint.airtstrike_o"then
		return AirSquads[3]
	end
end
----------------------
---Air Reinforcements-
----------------------
function DropAirReinforcements(dummy)
	local squad
	if dummy.Type=="waypoint.reinforce_a" then squad=1
	elseif dummy.Type=="waypoint.reinforce_o" then squad=2
	elseif dummy.Type=="waypoint.reinforce_h" then squad=3 
	end
	--Carryall reinforcements
	local spawnCell = GetSpawnPoint(dummy)
	local path = {spawnCell,dummy.Location}
	local exitPath = {dummy.Location, Map.ClosestEdgeCell(dummy.Location) }
	--Media.Debug("reinforcements on the way"..dummy.Owner.InternalName)
	Beacon.New(dummy.Owner,Map.CenterOfCell(dummy.Location))
	local units = Reinforcements.ReinforceWithTransport(dummy.Owner,DropActor[squad] , ReinforcementSquads[squad], path, exitPath, nil, nil, 5)
	local carryall = units[1]
	Trigger.OnIdle(carryall, function()
		carryall.UnloadPassengers()

	end)
	Trigger.OnPassengerExited(carryall,
		function(carry, pass) 
			if not carry.HasPassengers 
			then 
				carry.Stop()
				carry.Move(path[1]) 
				carry.Destroy()
			end
		end
	)
end

function GetSpawnPoint(dummy)
	local buildings = dummy.Owner.GetActorsByType("high_tech_factory")
	for i, building in pairs(buildings) do
		if building.IsPrimaryBuilding then
			return Map.ClosestEdgeCell(building.Location)
		end
	end
	if (buildings[1].Location ~= nil) then
		return Map.ClosestEdgeCell(buildings[1].Location)
	else
		return dummy.Owner.HomeLocation
	end
end

--wormrider logic
FindCloseTarget = function (saboteur, distance)
	local closeActors = Map.ActorsInCircle(saboteur.CenterPosition, WDist.FromCells(distance))
	return Utils.Where(closeActors, function(actor)
		return
			actor.HasProperty("Attack") and
			actor.Type ~= "medium_gun_turret" and
			actor.Owner ~= Creeps and
			actor.Type ~= "large_gun_turret" and not
			saboteur.Owner.IsAlliedWith(actor.Owner)
	end)
end

FindAnyTarget = function (saboteur)
	local filteredPlayers = FilterPlayers(saboteur.Owner)
	if filteredPlayers[1] == nil then
		-- no suitable players for targeting
		return nil
	end
	local randomplayer = Utils.Random(filteredPlayers)
	if randomplayer ~= nil then
		local suitableActors = Utils.Where(randomplayer.GetActors(), function(actor)
			return
				actor.HasProperty("Attack") and
				actor.Type ~= "medium_gun_turret" and
				actor.Type ~= "large_gun_turret"
		end)
		if (next(suitableActors) ~= nil) then
			return suitableActors
		else
			-- case when random player is dead or have no buildings
			return nil
		end
	else
		-- case when no suitable players found
		return nil
	end
end

FindSaboteur = function(bot)
	local saboteurs = bot.GetActorsByType("mp_sandworm")
	for i, saboteur in pairs(saboteurs) do
		Media.Debug("found worm")
		if  saboteur.HasTag("close_target") == false and saboteur.HasTag("random_target") == false then
			local targets = FindCloseTarget(saboteur, 15)
			if (next(targets) ~= nil) then
				saboteur.AddTag("close_target")
				SendSaboteur(saboteur, targets)
			else
				targets = FindAnyTarget(saboteur)
				if targets ~= nil then
					saboteur.AddTag("random_target")
					SendSaboteur(saboteur, targets)
				end
			end
		elseif not saboteur.HasTag("close_target") and saboteur.HasTag("random_target") then
			local targets = FindCloseTarget(saboteur, 15)
			if (next(targets) ~= nil) then
				saboteur.AddTag("close_target")
				SendSaboteur(saboteur, targets)
			end
		end
	end
	Trigger.AfterDelay(200, function ()
		FindSaboteur(bot) end)
end

SendSaboteur = function(saboteur, targets)
	local token = saboteur.GrantCondition('stop_wander')
	if targets == nil then
		--Media.Debug("no targets for", saboteur.Owner.Name)
		saboteur.RemoveTag("close_target")
		saboteur.RemoveTag("random_target")
		return
	end
	saboteur.Stop()
	local sortedTargets = Utils.Where(targets, function(actor)
		return Map.TerrainType(actor.Location) == "Sand" or 
		Map.TerrainType(actor.Location) == "Dune" or
		Map.TerrainType(actor.Location) == "Spice" or 
		Map.TerrainType(actor.Location) == "SpiceSand" 
	end)
	if sortedTargets[1] == nil then
		return
	end
	local target = Utils.Random(sortedTargets)
	Media.Debug("worm got target"..tostring(target))
	saboteur.Demolish(target)
	target = Utils.Random(sortedTargets)
	saboteur.Demolish(target)
	target = Utils.Random(sortedTargets)
	saboteur.Demolish(target)
-- 'target' was removed from the world in the meantime
	saboteur.CallFunc(function()
		saboteur.RemoveTag("close_target")
		saboteur.RemoveTag("random_target")
		saboteur.RevokeCondition(token)
	end)
end

function FilterPlayers(forPlayer)
	local filteredPlayers = ActivePlayers
	for i, player in pairs(filteredPlayers) do
		if forPlayer.IsAlliedWith(player) or forPlayer.InternalName == player.InternalName then
			table.remove(filteredPlayers, i)
		end
	end
	return filteredPlayers
end
