Difficulty = Map.LobbyOption("difficulty")

UnitsEvacuatedThreshold =
{
	hard = 200,
	normal = 100,
	easy = 50
}

AttackAtFrame =
{
	hard = 500,
	normal = 500,
	easy = 600
}

MinAttackAtFrame =
{
	hard = 100,
	normal = 100,
	easy = 150
}

MaxSovietYaks =
{
	hard = 4,
	normal = 2,
	easy = 0
}

SovietParadrops =
{
	hard = 40,
	normal = 20,
	easy = 0
}

SovietParadropTicks =
{
	hard = DateTime.Minutes(17),
	normal = DateTime.Minutes(20),
	easy = DateTime.Minutes(20)
}

SovietUnits2Ticks =
{
	hard = DateTime.Minutes(12),
	normal = DateTime.Minutes(15),
	easy = DateTime.Minutes(15)
}

SovietEntryPoints =
{
	SovietEntryPoint1, SovietEntryPoint2, SovietEntryPoint3, SovietEntryPoint4, SovietEntryPoint5, SovietEntryPoint6
}

SovietRallyPoints =
{
	SovietRallyPoint1, SovietRallyPoint2, SovietRallyPoint3, SovietRallyPoint4, SovietRallyPoint5, SovietRallyPoint6
}

SovietAirfields =
{
	SovietAirfield1, SovietAirfield2, SovietAirfield3, SovietAirfield4,
	SovietAirfield5, SovietAirfield6, SovietAirfield7, SovietAirfield8
}

MountainEntry =
{
	MountainEntry1.Location, MountainEntry2.Location, MountainEntry3.Location, MountainEntry4.Location,
	MountainEntry5.Location, MountainEntry6.Location, MountainEntry7.Location, MountainEntry8.Location
}

BridgeEntry =
{
	BridgeEntry1.Location, BridgeEntry2.Location, BridgeEntry3.Location, BridgeEntry4.Location
}

MobileConstructionVehicle = { "mcv" }
Yak = { "yak" }

ReinforcementsTicks1 = DateTime.Minutes(5)
Reinforcements1 =
{
	"mgg", "2tnk", "2tnk", "2tnk", "2tnk", "1tnk", "1tnk",
	"jeep", "jeep", "e1", "e1", "e1", "e1", "e3", "e3"
}

ReinforcementsTicks2 = DateTime.Minutes(10)
Reinforcements2 =
{
	"mgg", "2tnk", "2tnk", "2tnk", "2tnk", "truk", "truk", "truk",
	"truk",	"truk", "truk", "1tnk", "1tnk", "jeep", "jeep"
}

SovietUnits1 =
{
	"3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "v2rl", "v2rl", "ftrk",
	"apc", "e1", "e1", "e2", "e3", "e3", "e4"
}

SovietUnits2 =
{
	"4tnk", "4tnk", "4tnk", "4tnk", "3tnk", "3tnk", "3tnk", "3tnk", "v2rl",
	"v2rl", "ftrk", "apc", "e1", "e1", "e2", "e3", "e3", "e4"
}

CurrentReinforcement1 = 0
CurrentReinforcement2 = 0
SpawnAlliedUnit = function(units)
	Reinforcements.Reinforce(allies1, units, { Allies1EntryPoint.Location, Allies1MovePoint.Location })

	if allies2 then
		Reinforcements.Reinforce(allies2, units, { Allies2EntryPoint.Location, Allies2MovePoint.Location })
	end

	Utils.Do(humans, function(player)
		Trigger.AfterDelay(DateTime.Seconds(2), function()
			Media.PlaySpeechNotification(player, "AlliedReinforcementsNorth")
		end)
	end)

	if CurrentReinforcement1 < #Reinforcements1 then
		CurrentReinforcement1 = CurrentReinforcement1 + 1
		Trigger.AfterDelay(ReinforcementsTicks1, function()
			reinforcements1 = { Reinforcements1[CurrentReinforcement1] }
			SpawnAlliedUnit(reinforcements1)
		end)
	end

	if CurrentReinforcement2 < #Reinforcements2 then
		CurrentReinforcement2 = CurrentReinforcement2 + 1
		Trigger.AfterDelay(ReinforcementsTicks2, function()
			reinforcements2 = { Reinforcements2[CurrentReinforcement2] }
			SpawnAlliedUnit(reinforcements2)
		end)
	end
end

SovietGroupSize = 5
SpawnSovietUnits = function()

	local units = SovietUnits1
	if DateTime.GameTime >= SovietUnits2Ticks[Difficulty] then
		units = SovietUnits2
	end

	local unitType = Utils.Random(units)
	local spawnPoint = Utils.Random(SovietEntryPoints)
	local rallyPoint = Utils.Random(SovietRallyPoints)
	local actor = Actor.Create(unitType, true, { Owner = soviets, Location = spawnPoint.Location })
	actor.AttackMove(rallyPoint.Location)
	IdleHunt(actor)

	local delay = math.max(attackAtFrame - 5, minAttackAtFrame)
	Trigger.AfterDelay(delay, SpawnSovietUnits)
end

SovietParadrop = 0
SendSovietParadrop = function()
	local sovietParadrops = SovietParadrops[Difficulty]

	if (SovietParadrop > sovietParadrops) then
		return
	end

	SovietParadrop = SovietParadrop + 1

	Utils.Do(humans, function(player)
		Media.PlaySpeechNotification(player, "SovietForcesApproaching")
	end)

	local x = Utils.RandomInteger(ParadropBoxTopLeft.Location.X, ParadropBoxBottomRight.Location.X)
	local y = Utils.RandomInteger(ParadropBoxBottomRight.Location.Y, ParadropBoxTopLeft.Location.Y)

	local randomParadropCell = CPos.New(x, y)
	local lz = Map.CenterOfCell(randomParadropCell)

	local powerproxy = Actor.Create("powerproxy.paratroopers", false, { Owner = soviets })
	powerproxy.SendParatroopers(lz)
	powerproxy.Destroy()

	Trigger.AfterDelay(sovietParadropTicks, SendSovietParadrop)
end

IdleHunt = function(unit)
	Trigger.OnIdle(unit, unit.Hunt)
	Trigger.OnCapture(unit, function()
		Trigger.ClearAll(unit)
	end)
end

AircraftTargets = function(yak)
	local targets = Utils.Where(Map.ActorsInWorld, function(a)
		return (a.Owner == allies1 or a.Owner == allies2) and a.HasProperty("Health") and yak.CanTarget(a)
	end)

	-- Prefer mobile units
	table.sort(targets, function(a, b) return a.HasProperty("Move") and not b.HasProperty("Move") end)

	return targets
end

YakAttack = function(yak, target)
	if not target or target.IsDead or (not target.IsInWorld) or (not yak.CanTarget(target)) then
		local targets = AircraftTargets(yak)
		if #targets > 0 then
			target = Utils.Random(targets)
		end
	end

	if target and yak.AmmoCount() > 0 and yak.CanTarget(target) then
		yak.Attack(target)
	else
		-- Includes yak.Resupply()
		yak.ReturnToBase()
	end

	yak.CallFunc(function()
		YakAttack(yak, target)
	end)
end

ManageSovietAircraft = function()
	if allies1.IsObjectiveCompleted(destroyAirbases) then
		return
	end

	local maxSovietYaks = MaxSovietYaks[Difficulty]
	local sovietYaks = soviets.GetActorsByType('yak')
	if #sovietYaks < maxSovietYaks then
		soviets.Build(Yak, function(units)
			local yak = units[1]
			YakAttack(yak)
		end)
	end
end

UnitsEvacuated = 0
EvacuateAlliedUnit = function(unit)
	if (unit.Owner == allies1 or unit.Owner == allies2) and unit.HasProperty("Move") then
		unit.Stop()
		unit.Owner = allies

		if unit.Type == 'mgg' then
			Utils.Do(humans, function(player)
				if player then
					player.MarkCompletedObjective(evacuateMgg)
				end
			end)
		end

		UnitsEvacuated = UnitsEvacuated + 1
		if unit.HasProperty("HasPassengers") then
			UnitsEvacuated = UnitsEvacuated + unit.PassengerCount
		end

		local exitCell = Map.ClosestEdgeCell(unit.Location)
		Trigger.OnIdle(unit, function()
			unit.ScriptedMove(exitCell)
		end)

		local exit = Map.CenterOfCell(exitCell)
		Trigger.OnEnteredProximityTrigger(exit, WDist.FromCells(1), function(a)
			a.Destroy()
		end)

		UserInterface.SetMissionText(UnitsEvacuated .. "/" .. unitsEvacuatedThreshold .. " units evacuated.", TextColor)

		if UnitsEvacuated >= unitsEvacuatedThreshold then
			Utils.Do(humans, function(player)
				if player then
					player.MarkCompletedObjective(evacuateUnits)
				end
			end)
		end
	end
end

Tick = function()
	if DateTime.GameTime % 100 == 0 then
		ManageSovietAircraft()

		Utils.Do(humans, function(player)
			if player and player.HasNoRequiredUnits() then
				soviets.MarkCompletedObjective(sovietObjective)
			end
		end)
	end
end

WorldLoaded = function()
	-- NPC
	neutral = Player.GetPlayer("Neutral")
	allies = Player.GetPlayer("Allies")
	soviets = Player.GetPlayer("Soviets")

	-- Player controlled
	allies1 = Player.GetPlayer("Allies1")
	allies2 = Player.GetPlayer("Allies2")

	humans = { allies1, allies2 }
	Utils.Do(humans, function(player)
		if player and player.IsLocalPlayer then
			Trigger.OnObjectiveAdded(player, function(p, id)
				Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. string.lower(p.GetObjectiveType(id)) .. " objective")
			end)

			Trigger.OnObjectiveCompleted(player, function(p, id)
				Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective completed")
			end)

			Trigger.OnObjectiveFailed(player, function(p, id)
				Media.DisplayMessage(p.GetObjectiveDescription(id), "Objective failed")
			end)

			Trigger.OnPlayerWon(player, function()
				Media.PlaySpeechNotification(player, "MissionAccomplished")
			end)

			Trigger.OnPlayerLost(player, function()
				Media.PlaySpeechNotification(player, "MissionFailed")
			end)

			TextColor = player.Color
		end
	end)

	unitsEvacuatedThreshold = UnitsEvacuatedThreshold[Difficulty]
	UserInterface.SetMissionText(UnitsEvacuated .. "/" .. unitsEvacuatedThreshold .. " units evacuated.", TextColor)
	Utils.Do(humans, function(player)
		if player then
			evacuateUnits = player.AddPrimaryObjective("Evacuate " .. unitsEvacuatedThreshold .. " units.")
			destroyAirbases = player.AddSecondaryObjective("Destroy the nearby Soviet airbases.")
			evacuateMgg = player.AddSecondaryObjective("Evacuate at least one mobile gap generator.")
		end
	end)

	Trigger.OnAllKilledOrCaptured(SovietAirfields, function()
		Utils.Do(humans, function(player)
			if player then
				player.MarkCompletedObjective(destroyAirbases)
			end
		end)
	end)

	sovietObjective = soviets.AddPrimaryObjective("Eradicate all allied troops.")

	if not allies2 or allies1.IsLocalPlayer then
		Camera.Position = Allies1EntryPoint.CenterPosition
	else
		Camera.Position = Allies2EntryPoint.CenterPosition
	end

	if not allies2 then
		allies1.Cash = 10000
		Media.DisplayMessage("Transferring funds.", "Co-Commander is missing")
	end

	SpawnAlliedUnit(MobileConstructionVehicle)

	minAttackAtFrame = MinAttackAtFrame[Difficulty]
	attackAtFrame = AttackAtFrame[Difficulty]
	Trigger.AfterDelay(attackAtFrame, SpawnSovietUnits)

	sovietParadropTicks = SovietParadropTicks[Difficulty]
	Trigger.AfterDelay(sovietParadropTicks, SendSovietParadrop)

	Trigger.OnEnteredFootprint(MountainEntry, EvacuateAlliedUnit)
	Trigger.OnEnteredFootprint(BridgeEntry, EvacuateAlliedUnit)
end
