--[[
   Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
   This file is part of OpenRA, which is free software. It is made
   available to you under the terms of the GNU General Public License
   as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version. For more
   information, see COPYING.
]]
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)

	if BridgeTank.IsDead then
		return
	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
