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

BeachheadTrigger =
{
	CPos.New(120, 90), CPos.New(120, 89), CPos.New(120, 88), CPos.New(121, 88), CPos.New(122, 88), CPos.New(123, 88), CPos.New(124, 88),
	CPos.New(125, 88), CPos.New(126, 88), CPos.New(126, 89), CPos.New(127, 89), CPos.New(128, 89), CPos.New(128, 90), CPos.New(129, 90),
	CPos.New(130, 90), CPos.New(130, 91), CPos.New(131, 91), CPos.New(132, 91), CPos.New(133, 91), CPos.New(134, 91), CPos.New(134, 92),
	CPos.New(135, 92), CPos.New(136, 92), CPos.New(137, 92), CPos.New(137, 93), CPos.New(138, 93), CPos.New(139, 93), CPos.New(140, 93),
	CPos.New(140, 94), CPos.New(140, 95), CPos.New(140, 96), CPos.New(140, 97), CPos.New(140, 98), CPos.New(140, 99), CPos.New(140, 100),
	CPos.New(139, 100), CPos.New(139, 101), CPos.New(139, 102), CPos.New(138, 102), CPos.New(138, 103), CPos.New(138, 104),
	CPos.New(137, 104), CPos.New(137, 105), CPos.New(137, 106), CPos.New(136, 106), CPos.New(136, 107)
}

if Difficulty == "easy" then
	BaseRaidInterval = DateTime.Minutes(4)
	BaseFrontAttackInterval = DateTime.Minutes(4) + DateTime.Seconds(30)
	BaseRearAttackInterval = DateTime.Minutes(8)
	UBoatPatrolDelay = DateTime.Minutes(3)
	BaseFrontAttackWpts = { PatrolWpt1.Location, BaseRaidWpt1.Location }
elseif Difficulty == "normal" then
	BaseRaidInterval = DateTime.Minutes(3)
	BaseFrontAttackInterval = DateTime.Minutes(3) + DateTime.Seconds(30)
	BaseRearAttackInterval = DateTime.Minutes(8)
	UBoatPatrolDelay = DateTime.Minutes(2) + DateTime.Seconds(30)
	BaseFrontAttackWpts = { PatrolWpt1.Location, BaseRaidWpt1.Location }
else
	BaseRaidInterval = DateTime.Minutes(2)
	BaseFrontAttackInterval = DateTime.Minutes(2) + DateTime.Seconds(30)
	BaseRearAttackInterval = DateTime.Minutes(5)
	UBoatPatrolDelay = DateTime.Minutes(2)
	BaseFrontAttackWpts = { PatrolWpt1.Location }
end

Village = { FarmHouse1, FarmHouse2, FarmHouse3, FarmHouse4, FarmHouse5, FarmHouse6, FarmHouse7, FarmHouse8, FarmHouse9, Church }
VillageRaidInterval = DateTime.Minutes(3)
VillageRaidAircraft = { "mig.scripted", "mig.scripted" }
VillageRaidWpts = { VillageRaidEntrypoint.Location, VillageRaidWpt1.Location, VillageRaidWpt2.Location }

BaseRaidAircraft = { "mig.scripted", "mig.scripted" }
BaseRaidWpts = { BaseRaidEntrypoint.Location, UboatPatrolWpt1.Location, BaseRaidWpt2.Location }

BaseFrontAttackUnits = { "e3", "e3", "e1", "e1", "e1", "3tnk", "3tnk", "apc" }
BaseRearAttackUnits = { "e3", "e3", "e1", "e1", "3tnk", "3tnk", "v2rl" }
BaseRearAttackWpts = { GroundAttackWpt1.Location, BaseRearAttackWpt1.Location, BaseRearAttackWpt2.Location, BaseRearAttackWpt3.Location }

SovietHarvesters = { Harvester1, Harvester2, Harvester3 }
HarvesterGuard = { HarvGuard1, HarvGuard2, HarvGuard3 }

UboatPatrolWpts1 = { UboatPatrolWpt1.Location, UboatPatrolWpt2.Location, UboatPatrolWpt3.Location, UboatPatrolWpt4.Location }
UboatPatrolWpts2 = { UboatPatrolWpt4.Location, UboatPatrolWpt2.Location, UboatPatrolWpt1.Location }
UBoatPatrolUnits = { "ss" }

HunterSubs = { "ss", "ss" }

GroundPatrolWpts = { PatrolWpt1.Location, PatrolWpt2.Location }
GroundPatrolUnits =
{
	{ "e1", "e1", "e1", "e3", "e3", "dog" },
	{ "apc", "apc", "ftrk" },
	{ "3tnk", "3tnk" }
}

ParadropSovietUnits = function()
	local powerproxy = Actor.Create("powerproxy.paratroopers", false, { Owner = Soviets })
	local aircraft = powerproxy.TargetParatroopers(MCVRally.CenterPosition, Angle.New(812))
	Utils.Do(aircraft, function(a)
		Trigger.OnPassengerExited(a, function(_, p)
			IdleHunt(p)
		end)
	end)

	powerproxy.Destroy()
end

AirRaid = function(planeTypes, ingress, target)
	if target == nil then
		return
	end

	for i = 1, #planeTypes do
		Trigger.AfterDelay((i - 1) * DateTime.Seconds(1), function()
			local start = Map.CenterOfCell(ingress[1]) + WVec.New(0, 0, Actor.CruiseAltitude(planeTypes[i]))
			local plane = Actor.Create(planeTypes[i], true, { CenterPosition = start, Owner = Soviets, Facing = (Map.CenterOfCell(ingress[2]) - start).Facing })

			Utils.Do(ingress, function(wpt) plane.Move(wpt) end)
			plane.Attack(target)
		end)
	end
end

BaseRaid = function()
	local targets = Utils.Where(Allies.GetActors(), function(actor)
		return actor.HasProperty("StartBuildingRepairs") and IsInAlliedBaseArea(actor.CenterPosition.X, actor.CenterPosition.Y)
	end)

	if #targets == 0 then
		return
	end

	local target = Utils.Random(targets)

	AirRaid(BaseRaidAircraft, BaseRaidWpts, target)

	Trigger.AfterDelay(BaseRaidInterval, BaseRaid)
end

VillageRaid = function()
	local target = nil
	Utils.Do(Village, function(tgt)
		if target == nil and not tgt.IsDead then
			target = tgt
			return
		end
	end)

	if target == nil then
		return
	end

	AirRaid(VillageRaidAircraft, VillageRaidWpts, target)

	Trigger.AfterDelay(VillageRaidInterval, VillageRaid)
end

SendUboatPatrol = function(team)
	Trigger.AfterDelay(UBoatPatrolDelay, function()
		Utils.Do(team, function(uboat)
			if not uboat.IsDead then
				uboat.PatrolUntil(UboatPatrolWpts1, function()
					return DateTime.GameTime > DateTime.Minutes(2) + UBoatPatrolDelay
				end)
				uboat.Patrol(UboatPatrolWpts2)
			end
		end)
	end)
end

SendGroundPatrol = function(team)
	Utils.Do(team, function(unit)
		unit.Patrol(GroundPatrolWpts, true, DateTime.Seconds(3))
		Trigger.OnIdle(unit, function(actor) actor.Hunt() end)
	end)
	Trigger.OnAllKilled(team, function()
		Build(Utils.Random(GroundPatrolUnits), SendGroundPatrol)
	end)
end

BaseFrontAttack = function(team)
	Utils.Do(team, function(unit)
		unit.Patrol(BaseFrontAttackWpts, false)
		Trigger.OnIdle(unit, function(actor) actor.Hunt() end)
	end)
	Trigger.AfterDelay(BaseFrontAttackInterval, function() Build(BaseFrontAttackUnits, BaseFrontAttack) end)
end

BaseRearAttack = function(team)
	Utils.Do(team, function(unit)
		unit.Patrol(BaseRearAttackWpts, false)
		Trigger.OnIdle(unit, function(actor) actor.Hunt() end)
	end)
	Trigger.AfterDelay(BaseRearAttackInterval, function() Build(BaseRearAttackUnits, BaseRearAttack) end)
end

Build = function(units, action)
	if not Soviets.Build(units, action) then
		Trigger.AfterDelay(DateTime.Seconds(15), function()
			Build(units, action)
		end)
	end
end

SetupWorld = function()
	Utils.Do(SovietHarvesters, function(harvester)
		harvester.FindResources()
		Trigger.OnDamaged(harvester, function(h)
			Utils.Do(HarvesterGuard, function(g)
				if not g.IsDead then
					g.Stop()
					g.AttackMove(h.Location, 3)
				end
			end)
		end)
	end)

	Utils.Do(Map.NamedActors, function(actor)
		if actor.Owner == Soviets and actor.HasProperty("StartBuildingRepairs") then
			Trigger.OnDamaged(actor, function(building)
				if building.Owner == Soviets then
					building.StartBuildingRepairs()
				end
			end)
		end
	end)

	WarFactory.RallyPoint = Rallypoint.Location
	WarFactory.IsPrimaryBuilding = true
	Barracks.IsPrimaryBuilding = true
	SubPen.IsPrimaryBuilding = true
end

SetupMissionText = function()
	TextColorNormal = HSLColor.White
	TextColorDamaged = HSLColor.Yellow
	TextColorCritical = HSLColor.Red

	CurrentColor = TextColorNormal
	local villageHousesLeft = #Village
	VillagePercentage = 100 - villageHousesLeft * 10

	Utils.Do(Village, function(house)
		Trigger.OnKilled(house, function()
			villageHousesLeft = villageHousesLeft - 1
			VillagePercentage = 100 - villageHousesLeft * 10

			if VillagePercentage > 69 then
				CurrentColor = TextColorCritical
			elseif VillagePercentage > 49 then
				CurrentColor = TextColorDamaged
			else
				CurrentColor = TextColorNormal
			end
		end)
	end)
end

Tick = function()
	if DateTime.GameTime > 2 then
		if Soviets.Resources > Soviets.ResourceCapacity * 0.75 then
			Soviets.Resources = Soviets.Resources - ((Soviets.ResourceCapacity * 0.01) / 25)
		end

		if Allies.HasNoRequiredUnits() then
			Allies.MarkFailedObjective(VillageObjective)
		end

		if CachedVillagePercentage ~= VillagePercentage then
			VillageDestroyed = UserInterface.GetFluentMessage("percentage-village-destroyed", { ["percentage"] = VillagePercentage })
			UserInterface.SetMissionText(VillageDestroyed, CurrentColor)
			CachedVillagePercentage = VillagePercentage
		end
	end
end

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

	InitObjectives(Allies)

	SovietObjective = AddPrimaryObjective(Soviets, "")
	VillageObjective = AddPrimaryObjective(Allies, "save-village")
	BeachheadObjective = AddPrimaryObjective(Allies, "mcv-main-island")

	BeachheadTriggered = false
	Trigger.OnExitedFootprint(BeachheadTrigger, function(a, id)
		if not BeachheadTriggered and a.Owner == Allies and a.Type == "mcv" then
			BeachheadTriggered = true
			Trigger.RemoveFootprintTrigger(id)
			OnBeachheadReached()
		end
	end)

	Trigger.OnAllKilled(Village, function()
		-- There is a small time gap between the HQ capture and victory.
		-- Ensure aircraft can't trigger defeat within that gap.
		if not AirForceHQ.IsDead and AirForceHQ.Owner == Allies then
			return
		end

		Allies.MarkFailedObjective(VillageObjective)
	end)

	SetupWorld()
	SetupMissionText()
	ReinforceBuilder()
	Trigger.AfterDelay(VillageRaidInterval, VillageRaid)
	Trigger.AfterDelay(1, function() Build(UBoatPatrolUnits, SendUboatPatrol) end)
	Trigger.AfterDelay(1, function() Build(Utils.Random(GroundPatrolUnits), SendGroundPatrol) end)

	Camera.Position = CameraSpot.CenterPosition
	Trigger.AfterDelay(DateTime.Seconds(5), function() CameraSpot.Destroy() end)
end

ReinforceBuilder = function()
	Reinforcements.Reinforce(Allies, { "mcv" }, { MCVEntry.Location, MCVRally.Location }, 0, function(mcv)
		mcv.Deploy()
	end)

	Trigger.AfterDelay(1, function()
		CheckBuilder(Allies)

		if Difficulty ~= "easy" then
			return
		end

		CheckNavalYard(Allies)
		local cam = Actor.Create("camera.hq", true, { Owner = Allies, Location = AirForceHQ.Location + CVec.New(1, 1) })
		Trigger.AfterDelay(1, cam.Destroy)
	end)
end

CheckBuilder = function(player)
	if BeachheadTriggered then
		return
	end

	local builders = player.GetActorsByTypes( { "mcv", "fact" } )

	Utils.Do(builders, function(builder)
		Trigger.OnKilled(builder, OnBuilderLost)
		Trigger.OnSold(builder, OnBuilderLost)

		-- The MCV deploy/undeploy creates a new actor each time,
		-- so add these triggers to the newer actor as needed.
		Trigger.OnRemovedFromWorld(builder, function()
			local transported = builder.Type == "mcv" and IsBuilderTransported(builder)
			if transported then
				-- Actors inside transports are temporarily removed from the world.
				-- In that case, more triggers are not necessary.
				return
			end

			CheckBuilder(player)
		end)
	end)
end

OnBuilderLost = function(builder)
	if BeachheadTriggered then
		return
	end

	local speechTime = 36
	-- Let "Unit lost"/"Naval unit lost" play first.
	Trigger.AfterDelay(DateTime.Seconds(2), function()
		Media.PlaySpeechNotification(builder.Owner, "ObjectiveNotReached")
	end)

	Trigger.AfterDelay(DateTime.Seconds(2) + speechTime, function()
		builder.Owner.MarkFailedObjective(BeachheadObjective)
	end)
end

IsBuilderTransported = function(builder)
	local found = false
	local boats = builder.Owner.GetActorsByType("lst")

	Utils.Do(boats, function(boat)
		if found or not boat.HasPassengers then
			return
		end

		Utils.Do(boat.Passengers, function(passenger)
			if passenger == builder then
				found = true
			end
		end)
	end)

	return found
end

CheckNavalYard = function(player)
	if player.HasPrerequisites({ "syrd" }) then
		OnNavalYardBuilt()
		return
	end

	Trigger.AfterDelay(DateTime.Seconds(1), function()
		CheckNavalYard(player)
	end)
end

OnNavalYardBuilt = function()
	local flare = Actor.Create("flare", true, { Owner = Allies, Location = BeachheadFlare.Location })

	Trigger.AfterDelay(DateTime.Seconds(2), function()
		Media.PlaySpeechNotification(Allies, "SignalFlareEast")
	end)

	Trigger.OnObjectiveCompleted(Allies, function(_, objective)
		if objective ~= BeachheadObjective then
			return
		end

		Trigger.AfterDelay(DateTime.Minutes(2), function()
			if flare.IsInWorld then
				flare.Destroy()
			end
		end)
	end)
end

IsInAlliedBaseArea = function(x, y)
	local top, bottom = AlliedAreaTopLeft.CenterPosition, AlliedAreaBottomRight.CenterPosition
	-- Skip bottom.X since it is on the east edge.
	return x > top.X and y > top.Y and y < bottom.Y
end

OnBeachheadReached = function()
	Media.PlaySpeechNotification(Allies, "ObjectiveReached")
	Allies.MarkCompletedObjective(BeachheadObjective)
	CaptureObjective = AddPrimaryObjective(Allies, "capture-air-force-hq")

	Actor.Create("mainland", true, { Owner = Allies })

	if AirForceHQ.IsDead then
		Allies.MarkFailedObjective(CaptureObjective)
		return
	end
	if AirForceHQ.Owner == Allies then
		Allies.MarkCompletedObjective(CaptureObjective)
		Allies.MarkCompletedObjective(VillageObjective)
		return
	end

	Trigger.OnCapture(AirForceHQ, function()
		Trigger.AfterDelay(DateTime.Seconds(3), function()
			Allies.MarkCompletedObjective(CaptureObjective)
			Allies.MarkCompletedObjective(VillageObjective)
		end)
	end)
	Trigger.OnKilled(AirForceHQ, function() Allies.MarkFailedObjective(CaptureObjective) end)

	Trigger.AfterDelay(BaseFrontAttackInterval, function()
		Build(BaseFrontAttackUnits, BaseFrontAttack)
		ParadropSovietUnits()
	end)
	Trigger.AfterDelay(BaseRearAttackInterval, function()
		Build(BaseRearAttackUnits, BaseRearAttack)
	end)
	Trigger.AfterDelay(BaseRaidInterval, BaseRaid)

	Trigger.AfterDelay(UBoatPatrolDelay, function()
		Build(HunterSubs, function(subs)
			Utils.Do(subs, IdleHunt)
		end)
	end)
end
