Difficulty = Map.LobbyOption("difficulty")

if Difficulty == "hard" then
	TimerTicks = DateTime.Minutes(25)
elseif Difficulty == "normal" then
	TimerTicks = DateTime.Minutes(28)
else
	TimerTicks = DateTime.Minutes(31)
end

Announcements =
{
	{ speech = "TwentyMinutesRemaining", delay = DateTime.Minutes(20) },
	{ speech = "TenMinutesRemaining", delay = DateTime.Minutes(10) },
	{ speech = "WarningFiveMinutesRemaining", delay = DateTime.Minutes(5) },
	{ speech = "WarningFourMinutesRemaining", delay = DateTime.Minutes(4) },
	{ speech = "WarningThreeMinutesRemaining", delay = DateTime.Minutes(3) },
	{ speech = "WarningTwoMinutesRemaining", delay = DateTime.Minutes(2) },
	{ speech = "WarningOneMinuteRemaining", delay = DateTime.Minutes(1) }
}

TownAttackers = { TownAttacker1, TownAttacker2, TownAttacker3, TownAttacker4, TownAttacker5, TownAttacker6, TownAttacker7 }

PatrolPoints1 = { PatrolPoint11.Location, PatrolPoint12.Location, PatrolPoint13.Location, PatrolPoint14.Location, PatrolPoint15.Location }
PatrolPoints2 = { PatrolPoint21.Location, PatrolPoint22.Location, PatrolPoint23.Location, PatrolPoint24.Location, PatrolPoint25.Location }
PatrolPoints3 = { PatrolPoint31.Location, PatrolPoint32.Location, PatrolPoint33.Location, PatrolPoint34.Location }
PatrolPoints4 = { PatrolPoint41.Location, PatrolPoint42.Location, PatrolPoint43.Location, PatrolPoint44.Location, PatrolPoint45.Location }

Patrol1 = { "e1", "e1", "e1", "e1", "e1" }
Patrol2 = { "e1", "dog.patrol", "dog.patrol" }
Patrol3 = { "e1", "e1", "dog.patrol" }

TransportType = "lst.unselectable.unloadonly"

SecureLabFailed = function()
	Utils.Do(humans, function(player)
		if player then
			player.MarkFailedObjective(secureLab)
		end
	end)
end

timerStarted = false
StartTimer = function()
	Utils.Do(humans, function(player)
		if player.IsLocalPlayer then
			TimerColor = player.Color
		end
	end)
	CountDownTimerAnnouncements()
	ticked = TimerTicks
	timerStarted = true
	Trigger.AfterDelay(DateTime.Seconds(3), function()
		Utils.Do(humans, function(player)
			Media.PlaySpeechNotification(player, "TimerStarted")
		end)
	end)
end

CountDownTimerAnnouncements = function()
	for i = #Announcements, 1, -1 do
		local delay = TimerTicks - Announcements[i].delay
		Trigger.AfterDelay(delay, function()
			if not labSecured then
				Utils.Do(humans, function(player)
					Media.PlaySpeechNotification(player, Announcements[i].speech)
				end)
			end
		end)
	end
end

reinforcementsHaveArrived = false
LabInfiltrated = function()
	Utils.Do(humans, function(player)
		if player then
			secureLab = player.AddPrimaryObjective("Eliminate all units guarding the lab.")
			destroyBase = player.AddPrimaryObjective("Destroy the soviet installation.")
			player.MarkCompletedObjective(infiltrateLab)
			Trigger.ClearAll(Lab)
			Trigger.AfterDelay(0, function()
				Trigger.OnKilled(Lab, SecureLabFailed)
			end)
		end
	end)

	Camera.Position = ReinforcementsUnloadPoint.CenterPosition
	local entryPath = { ReinforcementsEntryPoint.Location, ReinforcementsUnloadPoint.Location }
	local exit = { ReinforcementsEntryPoint.Location }

	mcvActors = { "mcv" }
	if player2 then
		mcvActors = { "mcv", "mcv" }
	end

	local reinforcements = Reinforcements.ReinforceWithTransport(allies, TransportType, mcvActors, entryPath, exit)
	local mcvs = reinforcements[2]

	Trigger.OnAddedToWorld(mcvs[1], function(mcvUnloaded)

		-- Don't call this twice (because of the owner change)
		if mcvUnloaded.Owner == player1 then
			return
		end

		mcvUnloaded.Owner = player1
		if not player2 then
			player1.Cash = 5000
		end
		Media.PlaySpeechNotification(player, "AlliedReinforcementsSouth")
		StartTimer()
		HijackTruck.Destroy()
		reinforcementsHaveArrived = true
	end)

	if player2 then
		Trigger.OnAddedToWorld(mcvs[2], function(mcvUnloaded)

			-- Don't call this twice (because of the owner change)
			if mcvUnloaded.Owner == player2 then
				return
			end

			mcvUnloaded.Owner = player2
			player1.Cash = 2500
			player2.Cash = 2500
		end)
	end

	Utils.Do(humans, function(player)
		for i = 0, 2 do
			Trigger.AfterDelay(DateTime.Seconds(i), function()
				Media.PlaySoundNotification(player, "AlertBuzzer")
			end)
		end
	end)

	local attackPoint = BridgeAttackPoint.CenterPosition
	local radius = WDist.FromCells(5)
	local bridge = Map.ActorsInCircle(attackPoint, radius, function(actor)
		return actor.Type == "br3"
	end)[1]
	BridgeTank.Attack(bridge, true, true)
end

InfiltrateLabFailed = function()
	Utils.Do(humans, function(player)
		if player then
			player.MarkFailedObjective(infiltrateLab)
		end
	end)
end

ChangeOwnerOnAddedToWorld = function(actor, newOwner)
	Trigger.OnAddedToWorld(actor, function(unloadedActor)
		unloadedActor.Owner = newOwner
		Trigger.Clear(unloadedActor, "OnAddedToWorld")
	end)
end

InsertSpies = function()
	Utils.Do(humans, function(player)
		if player then
			infiltrateLab = player.AddPrimaryObjective("Get our spy into the laboratory undetected.")
		end
	end)

	Trigger.OnKilled(Lab, function()
		if not player1.IsObjectiveCompleted(infiltrateLab) then
			InfiltrateLabFailed()
		end
	end)

	-- The delay isn't purely cosmetic, but also prevents a System.InvalidOperationException
	-- "Collection was modified after the enumerator was instantiated." in tick_activities
	local infiltrationCount = 0
	Trigger.OnInfiltrated(Lab, function()
		infiltrationCount = infiltrationCount + 1

		if (player2 and infiltrationCount == 2) or not player2 then
			Trigger.AfterDelay(DateTime.Seconds(3), LabInfiltrated)
		end
	end)

	spyActors = { "spy.strong" }
	if player2 then
		spyActors = { "spy.strong", "spy.strong" }
	end

	local entryPath = { SpyReinforcementsEntryPoint.Location, SpyReinforcementsUnloadPoint.Location }
	local exit = { SpyReinforcementsExitPoint.Location }
	local reinforcements = Reinforcements.ReinforceWithTransport(allies, TransportType, spyActors, entryPath, exit)

	local transport = reinforcements[1]
	Camera.Position = transport.CenterPosition

	spies = reinforcements[2]
	Trigger.OnAnyKilled(spies, InfiltrateLabFailed)

	ChangeOwnerOnAddedToWorld(spies[1], player1)

	if player2 then
		ChangeOwnerOnAddedToWorld(spies[2], player2)
	end
end

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

StopHunt = function(unit)
	if not unit.IsDead then
		Trigger.Clear(unit, "OnIdle")
	end
end

AttackTown = function()
	Utils.Do(TownAttackers, IdleHunt)

	Trigger.OnRemovedFromWorld(Hospital, function()
		Utils.Do(TownAttackers, StopHunt)
	end)
end

CapOre = function(player)
	if player.Resources > player.ResourceCapacity * 0.9 then
		player.Resources = player.ResourceCapacity * 0.8
	end
end

NewPatrol = function(actorType, start, waypoints)
	local guard = Actor.Create(actorType, true, { Owner = soviets, Location = start })
	guard.Patrol(waypoints, true, Utils.RandomInteger(50, 75))
end

SetupPatrols = function()
	Utils.Do(Patrol1, function(patrol1) NewPatrol(patrol1, PatrolPoints1[1], PatrolPoints1) end)
	Utils.Do(Patrol2, function(patrol2) NewPatrol(patrol2, PatrolPoints1[3], PatrolPoints1) end)
	Utils.Do(Patrol2, function(patrol3) NewPatrol(patrol3, PatrolPoints3[1], PatrolPoints3) end)
	Utils.Do(Patrol2, function(patrol4) NewPatrol(patrol4, PatrolPoints4[1], PatrolPoints4) end)

	if Difficulty == "hard" then
		Utils.Do(Patrol3, function(patrol5) NewPatrol(patrol5, PatrolPoints2[1], PatrolPoints2) end)
	end

	local checkpoint = { BaseGuardTruckPos.Location }
	Trigger.OnEnteredFootprint(checkpoint, function(a, id)
		Trigger.RemoveFootprintTrigger(id)
		if not BaseGuard.IsDead then
			BaseGuard.ScriptedMove(BaseGuardMovePos.Location)
		end
	end)
end

ticked = 0
SecureLabTimer = function()
	if not timerStarted or labSecured then
		return
	end

	if ticked > 0 then
		UserInterface.SetMissionText("Secure lab in: " .. Utils.FormatTime(ticked), TimerColor)
		ticked = ticked - 1
	elseif ticked <= 0 then
		TimerColor = soviets.Color
		UserInterface.SetMissionText("The Soviet research laboratory was not secured in time.", TimerColor)
		SecureLabFailed()
	end
end

SovietBaseMaintenanceSetup = function()
	local sovietbuildings = Utils.Where(Map.NamedActors, function(a)
		return a.Owner == soviets
			and a.HasProperty("StartBuildingRepairs") and a.HasProperty("Sell")
	end)

	-- This includes killed, captured (actor is temporarily removed) and sold.
	Trigger.OnAllRemovedFromWorld(sovietbuildings, function()
		Utils.Do(humans, function(player)
			player.MarkCompletedObjective(destroyBase)
		end)
	end)

	Utils.Do(sovietbuildings, function(sovietbuilding)
		Trigger.OnDamaged(sovietbuilding, function(building)
			if building.Owner ~= soviets then
				return
			end
			if building.Health < building.MaxHealth * 3/4 then
				building.StartBuildingRepairs()
			end
			if building.Health < building.MaxHealth * 1/4 then
				building.Sell()
			end
		end)
	end)
end

CheckPlayerDefeat = function()
	if not reinforcementsHaveArrived then
		return
	end

	Utils.Do(humans, function(player)
		if player.HasNoRequiredUnits() then
			player.MarkFailedObjective(destroyBase)
		end
	end)
end

labSecured = false
CheckLabSecured = function()
	if not reinforcementsHaveArrived or labSecured then
		return
	end

	if player1.HasNoRequiredUnits() or (player2 and player2.HasNoRequiredUnits()) then
		Utils.Do(humans, function(player)
			player.MarkFailedObjective(secureLab)
		end)
	end

	local radius = WDist.FromCells(10)
	local labGuards = Utils.Where(Map.ActorsInCircle(LabWaypoint.CenterPosition, radius), function(a)
		return a.Owner == soviets and a.HasProperty("Move")
	end)

	if #labGuards < 1 then
		labSecured = true
		Utils.Do(humans, function(player)
			player.MarkCompletedObjective(secureLab)
		end)
		UserInterface.SetMissionText("")
	end
end

Tick = function()
	CapOre(soviets)
	SecureLabTimer()
	CheckLabSecured()
	CheckPlayerDefeat()
end

WorldLoaded = function()
	allies = Player.GetPlayer("Allies")
	neutral = Player.GetPlayer("Neutral")
	creeps = Player.GetPlayer("Creeps")
	soviets = Player.GetPlayer("Soviets")

	player1 = Player.GetPlayer("Allies1")
	player2 = Player.GetPlayer("Allies2")
	humans = { player1, player2 }

	Utils.Do(humans, function(player)
		if player and player.IsLocalPlayer then
			Trigger.OnObjectiveAdded(player, function(p, id)
				local objectiveType = string.lower(p.GetObjectiveType(id))
				Media.DisplayMessage(p.GetObjectiveDescription(id), "New " .. objectiveType .. " 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)
		end
	end)

	InsertSpies()
	AttackTown()
	SetupPatrols()
	SovietBaseMaintenanceSetup()
end
