--[[
   Copyright (c) The OpenRA Developers and Contributors
   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.
]]
FrenchSquad = { "2tnk", "2tnk", "mcv" }

TimerTicks = DateTime.Minutes(10)
AttackTicks = DateTime.Seconds(52)
AttackAtFrame = DateTime.Seconds(18)
AttackAtFrameIncrement = DateTime.Seconds(18)
Producing = true
SpawningInfantry = true
ProduceAtFrame = DateTime.Seconds(12)
ProduceAtFrameIncrement = DateTime.Seconds(12)
SovietGroupSize = 4
SovietAttackGroupSize = 7

InfantryGuards = { }
HarvGuards = { HarvGuard1, HarvGuard2, HarvGuard3 }
SovietPlatoonUnits = { "e1", "e1", "e2", "e4", "e4", "e1", "e1", "e2", "e4", "e4" }
SovietTanks = { "3tnk", "3tnk", "3tnk" }
SovietVehicles = { "3tnk", "3tnk", "v2rl" }
SovietInfantry = { "e1", "e4", "e2" }
SovietEntryPoints = { SovietEntry1, SovietEntry2, SovietEntry3 }
SovietRallyPoints = { SovietRally2, SovietRally4, SovietRally5, SovietRally6 }
NewSovietEntryPoints = { SovietParaDropEntry, SovietEntry3 }
NewSovietRallyPoints = { SovietRally3, SovietRally4, SovietRally8 }

ParaWaves =
{
	{ delay = AttackTicks, type = "SovietSquad", target = SovietRally5  },
	{ delay = 0, type = "SovietSquad", target = SovietRally6 },
	{ delay = AttackTicks * 2, type = "SovietSquad", target = SovietParaDrop3 },
	{ delay = 0, type = "SovietPlatoonUnits", target = SovietRally5 },
	{ delay = 0, type = "SovietPlatoonUnits", target = SovietRally6 },
	{ delay = 0, type = "SovietSquad", target = SovietRally2 },
	{ delay = AttackTicks * 2, type = "SovietSquad", target = SovietParaDrop2 },
	{ delay = AttackTicks * 2, type = "SovietSquad", target = SovietParaDrop1 },
	{ delay = AttackTicks * 3, type = "SovietSquad", target = SovietParaDrop1 }
}

GuardHarvester = function(unit, harvester)
	if not unit.IsDead then
		unit.Stop()

		local start = unit.Location
		if not harvester.IsDead then
			unit.AttackMove(harvester.Location)
		else
			unit.Hunt()
		end

		Trigger.OnIdle(unit, function()
			if unit.Location == start then
				Trigger.ClearAll(unit)
			else
				unit.AttackMove(start)
			end
		end)

		Trigger.OnCapture(unit, function()
			Trigger.ClearAll(unit)
		end)
	end
end

Ticked = TimerTicks
Tick = function()
	if Soviets.HasNoRequiredUnits() then
		if DestroyObj then
			Allies.MarkCompletedObjective(DestroyObj)
		else
			DestroyObj = AddPrimaryObjective(Allies, "destroy-all-soviet-forces")
			Allies.MarkCompletedObjective(DestroyObj)
		end
	end

	if Allies.HasNoRequiredUnits() then
		Soviets.MarkCompletedObjective(SovietObj)
	end

	if Soviets.Resources > Soviets.ResourceCapacity / 2 then
		Soviets.Resources = Soviets.ResourceCapacity / 2
	end

	if DateTime.GameTime == ProduceAtFrame then
		if SpawningInfantry then
			ProduceAtFrame = ProduceAtFrame + ProduceAtFrameIncrement
			ProduceAtFrameIncrement = ProduceAtFrameIncrement * 2 - 5
			SpawnSovietInfantry()
		end
	end

	if DateTime.GameTime == AttackAtFrame then
		AttackAtFrame = AttackAtFrame + AttackAtFrameIncrement
		AttackAtFrameIncrement = AttackAtFrameIncrement * 2 - 5
		if Producing then
			SpawnSovietVehicle(SovietEntryPoints, SovietRallyPoints)
		else
			SpawnSovietVehicle(NewSovietEntryPoints, NewSovietRallyPoints)
		end
	end

	if DateTime.Minutes(5) == Ticked then
		Media.PlaySpeechNotification(Allies, "WarningFiveMinutesRemaining")
		InitCountDown()
	end

	if Ticked > 0 then
		if (Ticked % DateTime.Seconds(1)) == 0 then
			Timer = UserInterface.GetFluentMessage("soviet-reinforcements-arrive-in", { ["time"] = Utils.FormatTime(Ticked) })
			UserInterface.SetMissionText(Timer, TimerColor)
		end
		Ticked = Ticked - 1
	elseif Ticked == 0 then
		FinishTimer()
		Ticked = Ticked - 1
	end
end

SendSovietParadrops = function(table)
	local paraproxy = Actor.Create(table.type, false, { Owner = Soviets })
	local aircraft = paraproxy.TargetParatroopers(table.target.CenterPosition)
	Utils.Do(aircraft, function(a)
		Trigger.OnPassengerExited(a, function(t, p)
			IdleHunt(p)
		end)
	end)
	paraproxy.Destroy()
end

SpawnSovietInfantry = function()
	Soviets.Build({ Utils.Random(SovietInfantry) }, function(units)
		IdleHunt(units[1])
	end)
end

SpawnSovietVehicle = function(spawnpoints, rallypoints)
	local route = Utils.RandomInteger(1, #spawnpoints + 1)
	local rally = Utils.RandomInteger(1, #rallypoints + 1)
	local unit = Reinforcements.Reinforce(Soviets, { Utils.Random(SovietVehicles) }, { spawnpoints[route].Location })[1]
	unit.AttackMove(rallypoints[rally].Location)
	IdleHunt(unit)

	Trigger.OnCapture(unit, function()
		Trigger.ClearAll(unit)
	end)
end

SpawnAndAttack = function(types, entry)
	local units = Reinforcements.Reinforce(Soviets, types, { entry })
	Utils.Do(units, function(unit)
		IdleHunt(unit)

		Trigger.OnCapture(unit, function()
			Trigger.ClearAll(unit)
		end)
	end)
	return units
end

SendFrenchReinforcements = function()
	local camera = Actor.Create("camera", true, { Owner = Allies, Location = SovietRally1.Location })
	Beacon.New(Allies, FranceEntry.CenterPosition - WVec.New(0, 3 * 1024, 0))
	Media.PlaySpeechNotification(Allies, "AlliedReinforcementsArrived")
	Reinforcements.Reinforce(Allies, FrenchSquad, { FranceEntry.Location, FranceRally.Location })
	Trigger.AfterDelay(DateTime.Seconds(3), function() camera.Destroy() end)
end

FrenchReinforcements = function()
	Camera.Position = SovietRally1.CenterPosition

	if drum1.IsDead or drum2.IsDead or drum3.IsDead then
		SendFrenchReinforcements()
		return
	end

	local powerproxy = Actor.Create("powerproxy.parabombs", false, { Owner = Allies })
	powerproxy.TargetAirstrike(drum1.CenterPosition, Angle.NorthEast + Angle.New(16))
	powerproxy.TargetAirstrike(drum2.CenterPosition, Angle.NorthEast)
	powerproxy.TargetAirstrike(drum3.CenterPosition, Angle.NorthEast - Angle.New(16))
	powerproxy.Destroy()

	Trigger.AfterDelay(DateTime.Seconds(3), function()
		SendFrenchReinforcements()
	end)
end

FinalAttack = function()
	local units1 = SpawnAndAttack(SovietTanks, SovietEntry1.Location)
	local units2 = SpawnAndAttack(SovietTanks, SovietEntry1.Location)
	local units3 = SpawnAndAttack(SovietTanks, SovietEntry2.Location)
	local units4 = SpawnAndAttack(SovietPlatoonUnits, SovietEntry1.Location)
	local units5 = SpawnAndAttack(SovietPlatoonUnits, SovietEntry2.Location)

	local units = { }
	local insert = function(table)
		local count = #units
		Utils.Do(table, function(unit)
			units[count] = unit
			count = count + 1
		end)
	end

	insert(units1)
	insert(units2)
	insert(units3)
	insert(units4)
	insert(units5)

	Trigger.OnAllKilledOrCaptured(units, function()
		if not DestroyObj then
			Media.DisplayMessage(UserInterface.GetFluentMessage("reinforced-position-initiate-counter-attack"), UserInterface.GetFluentMessage("incoming-report"))
			DestroyObj = AddPrimaryObjective(Allies, "destroy-remaining-soviet-forces-area")
		end
		Allies.MarkCompletedObjective(SurviveObj)
	end)
end

SovietReinforcementsArrived = UserInterface.GetFluentMessage("soviet-reinforcements-arrived")
FinishTimer = function()
	for i = 0, 9, 1 do
		local c = TimerColor
		if i % 2 == 0 then
			c = HSLColor.White
		end

		Trigger.AfterDelay(DateTime.Seconds(i), function() UserInterface.SetMissionText(SovietReinforcementsArrived, c) end)
	end
	Trigger.AfterDelay(DateTime.Seconds(10), function() UserInterface.SetMissionText("") end)
end

Wave = 1
SendParadrops = function()
	SendSovietParadrops(ParaWaves[Wave])

	Wave = Wave + 1
	if Wave > #ParaWaves then
		Trigger.AfterDelay(AttackTicks, FrenchReinforcements)
	else
		Trigger.AfterDelay(ParaWaves[Wave].delay, SendParadrops)
	end
end

SetupBridges = function()
	local count = 0
	local counter = function()
		count = count + 1
		if count == 2 then
			Allies.MarkCompletedObjective(RepairBridges)
		end
	end

	Media.DisplayMessage(UserInterface.GetFluentMessage("repair-bridges-for-reinforcement"), UserInterface.GetFluentMessage("incoming-report"))
	RepairBridges = AddSecondaryObjective(Allies, "repair-two-southern-bridges")

	local bridgeA = Map.ActorsInCircle(BrokenBridge1.CenterPosition, WDist.FromCells(1), function(self) return self.Type == "bridge1" end)
	local bridgeB = Map.ActorsInCircle(BrokenBridge2.CenterPosition, WDist.FromCells(1), function(self) return self.Type == "bridge1" end)

	Utils.Do(bridgeA, function(bridge)
		Trigger.OnDamaged(bridge, function()
			Utils.Do(bridgeA, function(self) Trigger.ClearAll(self) end)
			Media.PlaySpeechNotification(Allies, "AlliedReinforcementsArrived")
			Reinforcements.Reinforce(Allies, { "1tnk", "2tnk", "2tnk" }, { ReinforcementsEntry1.Location, ReinforcementsRally1.Location })
			counter()
		end)
	end)
	Utils.Do(bridgeB, function(bridge)
		Trigger.OnDamaged(bridge, function()
			Utils.Do(bridgeB, function(self) Trigger.ClearAll(self) end)
			Media.PlaySpeechNotification(Allies, "AlliedReinforcementsArrived")
			Reinforcements.Reinforce(Allies, { "jeep", "1tnk", "1tnk" }, { ReinforcementsEntry2.Location, ReinforcementsRally2.Location })
			counter()
		end)
	end)
end

InitCountDown = function()
	Trigger.AfterDelay(DateTime.Minutes(1), function() Media.PlaySpeechNotification(Allies, "WarningFourMinutesRemaining") end)
	Trigger.AfterDelay(DateTime.Minutes(2), function() Media.PlaySpeechNotification(Allies, "WarningThreeMinutesRemaining") end)
	Trigger.AfterDelay(DateTime.Minutes(3), function() Media.PlaySpeechNotification(Allies, "WarningTwoMinutesRemaining") end)
	Trigger.AfterDelay(DateTime.Minutes(4), function() Media.PlaySpeechNotification(Allies, "WarningOneMinuteRemaining") end)
end

AddObjectives = function()
	InitObjectives(Allies)

	SurviveObj = AddPrimaryObjective(Allies, "enforce-position-hold-out-onslaught")
	SovietObj = AddPrimaryObjective(Soviets, "")

	Trigger.AfterDelay(DateTime.Seconds(15), function()
		SetupBridges()
	end)

	Trigger.OnPlayerWon(Allies, function()
		Media.DisplayMessage(UserInterface.GetFluentMessage("remaining-soviet-presence-destroyed"), UserInterface.GetFluentMessage("incoming-report"))
	end)
end

InitMission = function()
	Camera.Position = AlliesBase.CenterPosition
	TimerColor = HSLColor.Red

	Trigger.AfterDelay(DateTime.Seconds(1), function() Media.PlaySpeechNotification(Allies, "MissionTimerInitialised") end)

	Trigger.AfterDelay(TimerTicks, function()
		Media.DisplayMessage(UserInterface.GetFluentMessage("soviet-reinforcements-approaching"), UserInterface.GetFluentMessage("incoming-report"))
		Media.PlaySpeechNotification(Allies, "SovietReinforcementsArrived")
		SpawnSovietVehicle(NewSovietEntryPoints, NewSovietRallyPoints)
		FinalAttack()
		Producing = false
	end)

	Trigger.AfterDelay(AttackTicks, SendParadrops)

	Trigger.OnKilled(drum1, function() --Kill the remaining stuff from FrenchReinforcements
		if not boom2.IsDead then boom2.Kill() end
		if not boom4.IsDead then boom4.Kill() end
		if not drum2.IsDead then drum2.Kill() end
		if not drum3.IsDead then drum3.Kill() end
	end)
	Trigger.OnKilled(drum2, function()
		if not boom1.IsDead then boom1.Kill() end
		if not boom5.IsDead then boom5.Kill() end
		Trigger.AfterDelay(DateTime.Seconds(1), function() if not drum1.IsDead then drum1.Kill() end end)
	end)
	Trigger.OnKilled(drum3, function()
		if not boom1.IsDead then boom1.Kill() end
		if not boom3.IsDead then boom3.Kill() end
		Trigger.AfterDelay(DateTime.Seconds(1), function() if not drum1.IsDead then drum1.Kill() end end)
	end)
end

SetupSoviets = function()
	Barrack1.IsPrimaryBuilding = true
	Barrack1.RallyPoint = SovietRally.Location
	Trigger.OnKilledOrCaptured(Barrack1, function()
		SpawningInfantry = false
	end)

	Harvester1.FindResources()
	Trigger.OnDamaged(Harvester1, function()
		Utils.Do(HarvGuards, function(unit)
			GuardHarvester(unit, Harvester1)
		end)
	end)
	Trigger.OnCapture(Harvester1, function()
		Trigger.ClearAll(Harvester1)
	end)

	Harvester2.FindResources()
	Trigger.OnDamaged(Harvester2, function()
		Utils.Do(InfantryGuards, function(unit) GuardHarvester(unit, Harvester2) end)

		local toBuild = { }
		for i = 1, 6, 1 do
			toBuild[i] = Utils.Random(SovietInfantry)
		end

		Soviets.Build(toBuild, function(units)
			Utils.Do(units, function(unit)
				InfantryGuards[#InfantryGuards + 1] = unit
				GuardHarvester(unit, Harvester2)
			end)
		end)
	end)
	Trigger.OnCapture(Harvester2, function()
		Trigger.ClearAll(Harvester2)
	end)

	Trigger.AfterDelay(0, function()
		local buildings = Utils.Where(Map.ActorsInWorld, function(self) return self.Owner == Soviets and self.HasProperty("StartBuildingRepairs") end)
		Utils.Do(buildings, function(actor)
			Trigger.OnDamaged(actor, function(building)
				if building.Owner == Soviets and building.Health < building.MaxHealth * 3/4 then
					building.StartBuildingRepairs()
				end
			end)
		end)
	end)
end

WorldLoaded = function()
	Allies = Player.GetPlayer("Allies")
	Soviets = Player.GetPlayer("Soviets")

	AddObjectives()
	InitMission()
	SetupSoviets()
end
