--[[
   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.
]]

if Difficulty == "easy" then
	AttackAtFrameIncrement = DateTime.Seconds(22)
	AttackAtFrameIncrementInf = DateTime.Seconds(16)
	TimerTicks = DateTime.Minutes(15)
	IncrementTurningPoint = TimerTicks / 2
	DamageModifier = 0.5
	LongBowReinforcements = { "heli", "heli" }
	ParadropArtillery = true
elseif Difficulty == "normal" then
	AttackAtFrameIncrement = DateTime.Seconds(18)
	AttackAtFrameIncrementInf = DateTime.Seconds(12)
	TimerTicks = DateTime.Minutes(20)
	IncrementTurningPoint = TimerTicks / 2
	MoreParas = true
	DamageModifier = 0.75
	LongBowReinforcements = { "heli", "heli" }
else --Difficulty == "hard"
	AttackAtFrameIncrement = DateTime.Seconds(14)
	AttackAtFrameIncrementInf = DateTime.Seconds(8)
	TimerTicks = DateTime.Minutes(25)
	IncrementTurningPoint = DateTime.Minutes(10)
	MoreParas = true
	AttackAtFrameNaval = DateTime.Minutes(3) + DateTime.Seconds(45)
	SpawnNavalUnits = true
	DamageModifier = 1
	LongBowReinforcements = { "heli" }
end

AlliedAirReinforcementsWaypoints =
{
	{ AirReinforcementsEntry1.Location, AirReinforcementsRally1.Location },
	{ AirReinforcementsEntry2.Location, AirReinforcementsRally2.Location }
}
FrenchReinforcements = { "2tnk", "2tnk", "2tnk", "2tnk", "2tnk", "1tnk", "1tnk", "1tnk", "arty", "arty", "arty", "jeep", "jeep" }

SpawningSovietUnits = true
SpawningInfantry = true
AttackAtFrameInf = DateTime.Seconds(12)
AttackAtFrame = DateTime.Seconds(18)
SovietAttackGroupSize = 5
SovietInfantryGroupSize = 7
FactoryClearRange = 10
ParadropTicks = DateTime.Seconds(30)
ParadropWaypoints =
{
	{ Angle.East + Angle.New(16), ParaDrop1},
	{ Angle.East - Angle.New(16), ParaDrop2},
	{ Angle.East + Angle.New(16), Alliesbase2},
	{ Angle.East - Angle.New(16), Alliesbase1}
}
NavalTransportPassengers = { "e1", "e1", "e2", "e4", "e4" }
NavalReinforcementsWaypoints = { NavalWaypoint1, NavalWaypoint2, NavalWaypoint2, NavalWaypoint3 }
Squad1 = { "e1", "e1" }
Squad2 = { "e2", "e2" }
SovietVehicles = { "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "v2rl", "v2rl", "ftrk", "ftrk", "ftrk", "apc", "apc" }
SovietInfantry = { "e1", "e1", "e1", "e1", "e2", "e2", "e2", "e4", "e4", "e3" }
SovietEntryPoints = { SovietEntryPoint1, SovietEntryPoint2, SovietEntryPoint3, SovietEntryPoint4, SovietEntryPoint5 }
SovietRallyPoints = { SovietRallyPoint1, SovietRallyPoint2, SovietRallyPoint3, SovietRallyPoint4, SovietRallyPoint5 }
SovietGateRallyPoints = { AlliesBaseGate2, AlliesBaseGate2, AlliesBaseGate1, AlliesBaseGate1, AlliesBaseGate1 }

Airfields = { SovietAirfield1, SovietAirfield2, SovietAirfield3 }
SovietBuildings = { Barrack1, SubPen, RadarDome, AdvancedPowerPlant1, AdvancedPowerPlant2, AdvancedPowerPlant3, WarFactory, Refinery, Silo1, Silo2, FlameTower1, FlameTower2, FlameTower3, Sam1, Sam2, Sam3, Sam4, SovietAirfield1, SovietAirfield2, SovietAirfield3 }

IdleTrigger = function(units, _)
	Utils.Do(units, function(unit)

		if not unit.IsDead then
			Trigger.OnIdle(unit, function()
				local bool = Utils.All(units, function(unit) return unit.IsIdle end)
				if bool then
					SetupHuntTrigger(units)
				end
			end)

			Trigger.OnDamaged(unit, function()
				SetupHuntTrigger(units)
			end)

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

SetupHuntTrigger = function(units)
	Utils.Do(units, function(unit)
		if not unit.IsDead then
			Trigger.ClearAll(unit)
			Trigger.AfterDelay(0, function()
				if not unit.IsDead then
					Trigger.OnIdle(unit, unit.Hunt)
					Trigger.OnCapture(unit, function()
						Trigger.ClearAll(unit)
					end)
				end
			end)
		end
	end)
end

Ticked = TimerTicks
Tick = function()
	if KillObj and Soviets.HasNoRequiredUnits() then
		Allies.MarkCompletedObjective(KillObj)
	end

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

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

	if Ticked > 0 then
		if DateTime.Minutes(20) == Ticked then
			Media.PlaySpeechNotification(Allies, "TwentyMinutesRemaining")

		elseif DateTime.Minutes(10) == Ticked then
			Media.PlaySpeechNotification(Allies, "TenMinutesRemaining")

		elseif DateTime.Minutes(5) == Ticked then
			Media.PlaySpeechNotification(Allies, "WarningFiveMinutesRemaining")

		elseif DateTime.Minutes(4) == Ticked then
			Media.PlaySpeechNotification(Allies, "WarningFourMinutesRemaining")

			Trigger.AfterDelay(ParadropTicks, function()
				SendSovietParadrops(ParadropWaypoints[3])
				SendSovietParadrops(ParadropWaypoints[2])
			end)
			Trigger.AfterDelay(ParadropTicks * 2, function()
				SendSovietParadrops(ParadropWaypoints[4])
				SendSovietParadrops(ParadropWaypoints[1])
			end)

		elseif DateTime.Minutes(3) == Ticked then
			Media.PlaySpeechNotification(Allies, "WarningThreeMinutesRemaining")

		elseif DateTime.Minutes(2) == Ticked then
			Media.PlaySpeechNotification(Allies, "WarningTwoMinutesRemaining")

			AttackAtFrameIncrement = DateTime.Seconds(4)
			AttackAtFrameIncrementInf = DateTime.Seconds(4)

		elseif DateTime.Minutes(1) == Ticked then
			Media.PlaySpeechNotification(Allies, "WarningOneMinuteRemaining")

		elseif DateTime.Seconds(45) == Ticked then
			Media.PlaySpeechNotification(Allies, "AlliedForcesApproaching")
		end

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

FrenchAlliesArrived = UserInterface.GetFluentMessage("french-allies-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(FrenchAlliesArrived, c) end)
	end
	Trigger.AfterDelay(DateTime.Seconds(10), function() UserInterface.SetMissionText("") end)
end

SendSovietParadrops = function(table)
	local aircraft = ParaTroopersPowerProxy.TargetParatroopers(table[2].CenterPosition, table[1])
	Utils.Do(aircraft, function(a)
		Trigger.OnPassengerExited(a, function(t, p)
			IdleHunt(p)
		end)
	end)
end

SendSovietNavalReinforcements = function()
	if SpawnNavalUnits then
		local entry = NavalEntryPoint.Location
		local units = Reinforcements.ReinforceWithTransport(Soviets, "lst", NavalTransportPassengers, { entry, Utils.Random(NavalReinforcementsWaypoints).Location }, { entry })[2]
		Utils.Do(units, function(unit)
			Trigger.OnIdle(unit, unit.Hunt)
		end)

		local delay = Utils.RandomInteger(AttackAtFrameNaval, AttackAtFrameNaval + DateTime.Minutes(2))

		Trigger.AfterDelay(delay, SendSovietNavalReinforcements)
	end
end

SpawnSovietInfantry = function()
	local units = { }
	for i = 0, SovietInfantryGroupSize - 1, 1 do
		local type = Utils.Random(SovietInfantry)
		units[i] = type
	end

	Soviets.Build(units, function(soldiers)
		Trigger.AfterDelay(25, function() IdleTrigger(soldiers) end)
	end)
end

SpawnSovietUnits = function()
	local units = { }
	for i = 0, SovietAttackGroupSize - 1, 1 do
		local type = Utils.Random(SovietVehicles)
		units[i] = type
	end

	local route = Utils.RandomInteger(1, #SovietEntryPoints + 1)
	local attackers = Reinforcements.Reinforce(Soviets, units, { SovietEntryPoints[route].Location, SovietRallyPoints[route].Location })
	Trigger.AfterDelay(25, function()
		IdleTrigger(attackers, SovietGateRallyPoints[route].Location)
	end)
end

SendInfantryWave = function()
	if SpawningInfantry then
		SpawnSovietInfantry()

		if DateTime.GameTime < IncrementTurningPoint then
			AttackAtFrameIncrementInf = AttackAtFrameIncrementInf + Utils.RandomInteger(DateTime.Seconds(2), DateTime.Seconds(3))
		elseif not (AttackAtFrameIncrementInf <= DateTime.Seconds(4)) then
			AttackAtFrameIncrementInf = AttackAtFrameIncrementInf - Utils.RandomInteger(DateTime.Seconds(2), DateTime.Seconds(3))
		end

		Trigger.AfterDelay(AttackAtFrameInf + AttackAtFrameIncrementInf, SendInfantryWave)
	end
end

SendVehicleWave = function()
	if SpawningSovietUnits then
		SpawnSovietUnits()

		if DateTime.GameTime < IncrementTurningPoint then
			AttackAtFrameIncrement = AttackAtFrameIncrement + Utils.RandomInteger(DateTime.Seconds(4), DateTime.Seconds(6))
		elseif not (AttackAtFrameIncrement <= DateTime.Seconds(4)) then
			AttackAtFrameIncrement = AttackAtFrameIncrement - Utils.RandomInteger(DateTime.Seconds(4), DateTime.Seconds(6))
		end

		Trigger.AfterDelay(AttackAtFrame + AttackAtFrameIncrement, SendVehicleWave)
	end
end

TimerExpired = function()
	SpawningSovietUnits = false
	SpawningInfantry = false
	SpawnNavalUnits = false

	Beacon.New(Allies, SovietEntryPoint7.CenterPosition - WVec.New(3 * 1024, 0, 0))
	Media.PlaySpeechNotification(Allies, "AlliedReinforcementsArrived")
	Reinforcements.Reinforce(Allies, FrenchReinforcements, { SovietEntryPoint7.Location, Alliesbase.Location })

	if DestroyObj then
		KillObj = AddPrimaryObjective(Allies, "takeover-reinforcements-kill-remaining-soviet-forces")
	else
		DestroyObj = AddPrimaryObjective(Allies, "takeover-reinforcements-dismantle-soviet-base")
	end

	Allies.MarkCompletedObjective(SurviveObj)
	if not Allies.IsObjectiveCompleted(KillSams) then
		Allies.MarkFailedObjective(KillSams)
	end
end

DropAlliedArtillery = function(facing, dropzone)
	local proxy = Actor.Create("powerproxy.allied", true, { Owner = Allies })
	proxy.TargetParatroopers(dropzone, facing)
	proxy.Destroy()
end

SendLongBowReinforcements = function()
	Media.PlaySpeechNotification(Allies, "AlliedReinforcementsArrived")
	Reinforcements.Reinforce(Allies, LongBowReinforcements, AlliedAirReinforcementsWaypoints[1])
	Reinforcements.Reinforce(Allies, LongBowReinforcements, AlliedAirReinforcementsWaypoints[2])

	if ParadropArtillery then
		local facing = Angle.New(Utils.RandomInteger(128, 384))
		DropAlliedArtillery(facing, Alliesbase.CenterPosition)
	end
end

AddObjectives = function()
	InitObjectives(Allies)

	SurviveObj = AddPrimaryObjective(Allies, "enforce-position-hold-until-reinforcements")
	KillSams = AddSecondaryObjective(Allies, "destroy-two-sams-before-reinforcements")
	Media.DisplayMessage(UserInterface.GetFluentMessage("soviets-blocking-gps"))
	CaptureAirfields = AddSecondaryObjective(Allies, "capture-hold-soviet-airbase-northeast")
	SovietObj = AddPrimaryObjective(Soviets, "")

	Trigger.OnPlayerWon(Allies, function()
		Media.DisplayMessage(UserInterface.GetFluentMessage("french-survived-dismantled-soviet-presence"))
	end)
end

InitMission = function()
	Camera.Position = Alliesbase.CenterPosition
	local camera1 = Actor.Create("camera.sam", true, { Owner = Allies, Location = Sam1.Location })
	local camera2 = Actor.Create("camera.sam", true, { Owner = Allies, Location = Sam2.Location })
	Trigger.OnKilled(Sam1, function()
		if camera1.IsInWorld then camera1.Destroy() end
	end)
	Trigger.OnKilled(Sam2, function()
		if camera2.IsInWorld then camera2.Destroy() end
	end)
	Trigger.OnAllKilledOrCaptured({ Sam1, Sam2 }, function()
		if not Allies.IsObjectiveFailed(KillSams) then
			Allies.MarkCompletedObjective(KillSams)
			SendLongBowReinforcements()
		end
	end)

	local count = 0
	Utils.Do(Airfields, function(field)
		Trigger.OnCapture(field, function()
			count = count + 1
			if count == #Airfields then
				Allies.MarkCompletedObjective(CaptureAirfields)
				local atek = Actor.Create("atek.mission", true, { Owner = Allies, Location = HiddenATEK.Location })
				Trigger.AfterDelay(DateTime.Seconds(5), atek.Destroy)
			end
		end)
		Trigger.OnKilled(field, function()
			Allies.MarkFailedObjective(CaptureAirfields)
		end)
	end)

	Trigger.OnAllKilledOrCaptured(SovietBuildings, function()
		if DestroyObj then
			if not Soviets.HasNoRequiredUnits() then
				KillObj = AddPrimaryObjective(Allies, "kill-remaining-soviet-forces")
			end
			Allies.MarkCompletedObjective(DestroyObj)
		else
			DestroyObj = AddPrimaryObjective(Allies, "dismantle-nearby-soviet-base")
			Allies.MarkCompletedObjective(DestroyObj)
		end
	end)

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

SetupSoviets = function()
	Barrack1.IsPrimaryBuilding = true
	Barrack1.RallyPoint = SovietInfantryRally1.Location
	Trigger.OnKilledOrCaptured(Barrack1, function()
		SpawningInfantry = false
	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 * DamageModifier then
					building.StartBuildingRepairs()
				end
			end)
		end)
	end)

	Reinforcements.Reinforce(Soviets, Squad1, { AlliesBaseGate1.Location, Alliesbase1.Location })
	Reinforcements.Reinforce(Soviets, Squad2, { AlliesBaseGate2.Location, Alliesbase2.Location })

	ParaTroopersPowerProxy = Actor.Create("powerproxy.paratroopers", false, { Owner = Soviets })
	Trigger.AfterDelay(ParadropTicks, function()
		SendSovietParadrops(ParadropWaypoints[1])
		SendSovietParadrops(ParadropWaypoints[2])
	end)
	Trigger.AfterDelay(ParadropTicks * 2, function()
		SendSovietParadrops(ParadropWaypoints[3])
		SendSovietParadrops(ParadropWaypoints[4])
	end)

	Trigger.AfterDelay(AttackAtFrame, SendVehicleWave)
	Trigger.AfterDelay(AttackAtFrameInf, SendInfantryWave)

	if MoreParas then
		local delay = Utils.RandomInteger(TimerTicks/3, TimerTicks*2/3)
		Trigger.AfterDelay(delay, function()
			SendSovietParadrops(ParadropWaypoints[Utils.RandomInteger(1,3)])
			SendSovietParadrops(ParadropWaypoints[Utils.RandomInteger(3,5)])
		end)
	end
	if SpawnNavalUnits then
		Trigger.AfterDelay(AttackAtFrameNaval, SendSovietNavalReinforcements)
	end
end

WorldLoaded = function()

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

	AddObjectives()
	InitMission()
	SetupSoviets()
end
