// Experimental opt-in for an existing island scenario. Native multiplayer
// replication must be qualified before recommending this route on live servers.
enum ERR_TerrainTakeoverStage
{
 INACTIVE,
 SETTLING,
 DRAINING,
 RUNNING,
 FAILED
}

class ERR_TerrainTakeoverRules
{
 static bool TimedOut(float elapsed) { return elapsed >= 10; }
 static bool MayDrain(float elapsed, bool sameMode) { return elapsed >= 0.5 && sameMode; }
 static bool MayCreate(float elapsed, bool empty) { return elapsed >= 0.5 && empty && !TimedOut(elapsed); }
 static bool SameAdmission(bool connected, bool sameController, bool identityWasPresent, bool sameIdentity)
 {
  return connected && sameController && (!identityWasPresent || sameIdentity);
 }
}

class ERR_TerrainTakeoverAdmission
{
 int Connection;
 SCR_PlayerController Controller;
 UUID AccountId;
}

modded class SCR_EntityCatalogManagerComponent
{
 // Vanilla's singleton is not explicitly reset when its owner is removed.
 static void ERR_ReleaseTerrainCatalog(IEntity owner)
 {
  if (s_Instance && s_Instance.GetOwner() == owner) s_Instance = null;
 }
}

modded class SCR_BaseGameMode
{
 override void OnPlayerAuditSuccess(int iPlayerID)
 {
  if (GetGame().ERR_QueueTerrainAdmission(this, iPlayerID)) return;
  super.OnPlayerAuditSuccess(iPlayerID);
 }
 protected override void OnPlayerDisconnected(int playerId, KickCauseCode cause, int timeout)
 {
  GetGame().ERR_ForgetTerrainAdmission(playerId);
  super.OnPlayerDisconnected(playerId, cause, timeout);
 }
}

modded class ERR_GameMode
{
 [RplProp()] protected bool m_ERRTerrainTakenOver;
 bool ERR_TerrainTakenOver() { return m_ERRTerrainTakenOver; }
 void ERR_StartTerrainTakeover(World world)
 {
  m_ERRTerrainTakenOver = true;
  Replication.BumpMe();
  OnWorldPostProcess(world);
  OnGameStart();
 }
}

modded class ArmaReforgerScripted
{
 protected ERR_TerrainTakeoverStage m_ERRTerrainStage;
 protected float m_ERRTerrainElapsed;
 protected RadioManagerEntity m_ERRTerrainRadio;
 protected BaseWorld m_ERRTerrainWorld;
 protected SCR_BaseGameMode m_ERRTerrainOriginal;
 protected ref array<ref ERR_TerrainTakeoverAdmission> m_ERRTerrainAdmissions = {};
 protected ref array<vector> m_ERRTerrainAirfields = {};
 protected ref array<int> m_ERRTerrainMarkerSlots = {};

 void ERR_ForgetTerrainAdmission(int connection)
 {
  for (int i = m_ERRTerrainAdmissions.Count() - 1; i >= 0; i--)
   if (m_ERRTerrainAdmissions[i].Connection == connection) m_ERRTerrainAdmissions.Remove(i);
 }
 bool ERR_QueueTerrainAdmission(SCR_BaseGameMode mode, int connection)
 {
  if (m_ERRTerrainStage != ERR_TerrainTakeoverStage.SETTLING && m_ERRTerrainStage != ERR_TerrainTakeoverStage.DRAINING) return false;
  if (mode != m_ERRTerrainOriginal || ERR_GameMode.Cast(mode) || !Replication.IsServer()) return false;
  ERR_ForgetTerrainAdmission(connection);
  SCR_PlayerController controller = SCR_PlayerController.Cast(GetPlayerManager().GetPlayerController(connection));
  if (!controller || !GetPlayerManager().IsPlayerConnected(connection)) return true;
  ERR_TerrainTakeoverAdmission admission = new ERR_TerrainTakeoverAdmission();
  admission.Connection = connection;
  admission.Controller = controller;
  admission.AccountId = SCR_PlayerIdentityUtils.GetPlayerIdentityId(connection);
  m_ERRTerrainAdmissions.Insert(admission);
  PrintFormat("Reintroduced [terrain takeover]: held native admission connection=%1", connection);
  return true;
 }
 protected void ERR_ResetTerrainTakeover()
 {
  m_ERRTerrainStage = ERR_TerrainTakeoverStage.INACTIVE;
  m_ERRTerrainElapsed = 0;
  m_ERRTerrainRadio = null;
  m_ERRTerrainWorld = null;
  m_ERRTerrainOriginal = null;
  m_ERRTerrainAdmissions.Clear();
  m_ERRTerrainAirfields.Clear();
  m_ERRTerrainMarkerSlots.Clear();
 }
 protected void ERR_FailTerrainTakeover(string reason)
 {
  m_ERRTerrainStage = ERR_TerrainTakeoverStage.FAILED;
  m_ERRTerrainAdmissions.Clear();
  Print("Reintroduced [terrain takeover failed]: " + reason, LogLevel.ERROR);
 }
 protected bool ERR_PreflightTerrainTakeover()
 {
  if (!m_ERRTerrainOriginal || !GetWorldEntity() || !GetAIWorld() || !GetAIWorld().GetNavmeshWorldComponent("Soldiers"))
  {
   ERR_FailTerrainTakeover("Select a plain scenario with SCR_BaseGameMode and a Soldiers navmesh; original scenario retained.");
   return false;
  }
  Resource prefab = Resource.Load("{9D182739ABEF405C}Prefabs/ERR/TerrainTakeover.et");
  if (!prefab || !prefab.IsValid())
  {
   ERR_FailTerrainTakeover("Escape bootstrap prefab is missing; original scenario retained.");
   return false;
  }
  vector low, high;
  GetWorldEntity().GetWorldBounds(low, high);
  if (!ERR_TerrainSites.Bounds(low, high) || !ERR_TerrainTakeoverSettings.ParseAirfields(ERR_TerrainTakeoverSettings.Airfields(), m_ERRTerrainAirfields))
  {
   ERR_FailTerrainTakeover("Invalid terrain size or m_sERR_Airfields; use up to 16 X Z pairs separated by semicolons. Original scenario retained.");
   return false;
  }
  foreach (vector centre : m_ERRTerrainAirfields)
  {
   if (centre[0] < low[0] || centre[0] > high[0] || centre[2] < low[2] || centre[2] > high[2] || ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetWorld(), centre))
   {
    ERR_FailTerrainTakeover("An airport centre is outside this terrain or over water; original scenario retained.");
    return false;
   }
  }
  for (int slot = 0; slot < 16; slot++)
   if (!GetWorld().FindEntityByName("ERR_Airfield_" + slot.ToString())) m_ERRTerrainMarkerSlots.Insert(slot);
  if (m_ERRTerrainMarkerSlots.Count() < m_ERRTerrainAirfields.Count())
  {
   ERR_FailTerrainTakeover("Not enough unused ERR_Airfield marker names for configured centres; original scenario retained.");
   return false;
  }
  return true;
 }
 protected override void OnWorldPostProcess(World world)
 {
  // Do not opt an existing authored Escape mission into replacement.
  if (ERR_TerrainTakeoverSettings.Enabled() && !ERR_GameMode.Get() && Replication.IsServer())
  {
   ERR_ResetTerrainTakeover();
   m_ERRTerrainWorld = world;
   m_ERRTerrainOriginal = SCR_BaseGameMode.Cast(GetGameMode());
   if (ERR_PreflightTerrainTakeover())
   {
    m_ERRTerrainStage = ERR_TerrainTakeoverStage.SETTLING;
    PrintFormat("Reintroduced [terrain takeover]: enabled; configured airports=%1", m_ERRTerrainAirfields.Count());
   }
  }
  super.OnWorldPostProcess(world);
 }
 override void OnUpdate(BaseWorld world, float timeslice)
 {
  super.OnUpdate(world, timeslice);
  if (m_ERRTerrainStage != ERR_TerrainTakeoverStage.SETTLING && m_ERRTerrainStage != ERR_TerrainTakeoverStage.DRAINING) return;
  if (world != m_ERRTerrainWorld || GetWorld() != m_ERRTerrainWorld || !Replication.IsServer()) { ERR_ResetTerrainTakeover(); return; }
  m_ERRTerrainElapsed += timeslice;
  if (ERR_TerrainTakeoverRules.TimedOut(m_ERRTerrainElapsed)) { ERR_FailTerrainTakeover("Native manager registrations did not settle within 10 seconds; restart the scenario."); return; }
  if (m_ERRTerrainStage == ERR_TerrainTakeoverStage.SETTLING)
  {
   if (!m_ERRTerrainOriginal || GetGameMode() != m_ERRTerrainOriginal) { ERR_FailTerrainTakeover("Original mode changed during startup; takeover cancelled."); return; }
   if (ERR_TerrainTakeoverRules.MayDrain(m_ERRTerrainElapsed, true)) ERR_DrainTerrainMode();
  }
  else if (ERR_TerrainTakeoverRules.MayCreate(m_ERRTerrainElapsed, ERR_TerrainRegistrationsEmpty())) ERR_CreateTerrainMode();
 }
 protected void ERR_DrainTerrainMode()
 {
  array<IEntity> managers = {m_ERRTerrainOriginal, GetLoadoutManager(), GetFactionManager(), GetPerceptionManager(), GetChat()};
  ChimeraWorld chimera = ChimeraWorld.CastFrom(GetWorld());
  if (chimera) m_ERRTerrainRadio = chimera.GetRadioManager();
  if (m_ERRTerrainRadio)
  {
   IEntity ancestor = m_ERRTerrainRadio.GetParent();
   while (ancestor)
   {
    if (managers.Contains(ancestor))
    {
     m_ERRTerrainRadio.GetParent().RemoveChild(m_ERRTerrainRadio, true);
     if (m_ERRTerrainRadio.GetParent()) { ERR_FailTerrainTakeover("Could not retain the radio manager; original mode retained."); return; }
     break;
    }
    ancestor = ancestor.GetParent();
   }
  }
  Print("Reintroduced [terrain takeover]: stopping original scenario; preserving radio manager");
  m_ERRTerrainOriginal.OnGameEnd();
  if (m_DataCollectorComponent && m_DataCollectorComponent.GetOwner() == m_ERRTerrainOriginal) m_DataCollectorComponent = null;
  SCR_EntityCatalogManagerComponent.ERR_ReleaseTerrainCatalog(m_ERRTerrainOriginal);
  foreach (IEntity manager : managers) if (manager) SCR_EntityHelper.DeleteEntityAndChildren(manager);
  m_ERRTerrainStage = ERR_TerrainTakeoverStage.DRAINING;
  m_ERRTerrainElapsed = 0;
 }
 protected bool ERR_TerrainRegistrationsEmpty()
 {
  ChimeraWorld chimera = ChimeraWorld.CastFrom(GetWorld());
  return !GetGameMode() && !GetLoadoutManager() && !GetFactionManager() && !GetPerceptionManager() && !GetChat() && (!chimera || chimera.GetRadioManager() == m_ERRTerrainRadio) && !SCR_EntityCatalogManagerComponent.GetInstance() && !m_DataCollectorComponent;
 }
 protected void ERR_CreateTerrainMode()
 {
  Print("Reintroduced [terrain takeover]: native registrations cleared");
  if (!m_ERRTerrainRadio)
  {
   m_ERRTerrainRadio = RadioManagerEntity.Cast(SpawnEntityPrefab(Resource.Load("{B8E09FAB91C4ECCD}Prefabs/Systems/Radio/RadioManager.et"), GetWorld()));
   if (!m_ERRTerrainRadio) { ERR_FailTerrainTakeover("Could not create missing radio manager; restart the scenario."); return; }
  }
  for (int i = 0; i < m_ERRTerrainAirfields.Count(); i++)
  {
   EntitySpawnParams parameters = new EntitySpawnParams();
   parameters.TransformMode = ETransformMode.WORLD;
   parameters.Transform[3] = m_ERRTerrainAirfields[i];
   IEntity marker = SpawnEntity(GenericEntity, GetWorld(), parameters);
   if (!marker) { ERR_FailTerrainTakeover("Could not create configured airport marker; restart the scenario."); return; }
   marker.SetName("ERR_Airfield_" + m_ERRTerrainMarkerSlots[i].ToString());
  }
  ERR_GameMode mode = ERR_GameMode.Cast(SpawnEntityPrefab(Resource.Load("{9D182739ABEF405C}Prefabs/ERR/TerrainTakeover.et"), GetWorld()));
  if (!mode || GetGameMode() != mode) { ERR_FailTerrainTakeover("Escape game mode could not register; restart the scenario."); return; }
  m_ERRTerrainStage = ERR_TerrainTakeoverStage.RUNNING;
  mode.ERR_StartTerrainTakeover(GetWorld());
  foreach (ERR_TerrainTakeoverAdmission admission : m_ERRTerrainAdmissions)
  {
   int connection = admission.Connection;
   bool connected = GetPlayerManager().IsPlayerConnected(connection);
   bool sameController = admission.Controller && GetPlayerManager().GetPlayerController(connection) == admission.Controller;
   UUID identity = SCR_PlayerIdentityUtils.GetPlayerIdentityId(connection);
   if (ERR_TerrainTakeoverRules.SameAdmission(connected, sameController, !admission.AccountId.IsNull(), identity == admission.AccountId)) mode.OnPlayerAuditSuccess(connection);
   else PrintFormat("Reintroduced [terrain takeover]: discarded stale admission connection=%1", connection);
  }
  m_ERRTerrainAdmissions.Clear();
  m_ERRTerrainOriginal = null;
  Print("Reintroduced [terrain takeover]: Escape started on selected scenario");
 }
 override protected void OnBeforeWorldCleanup()
 {
  ERR_ResetTerrainTakeover();
  super.OnBeforeWorldCleanup();
 }
}

modded class SCR_SpawnRequestComponent
{
 override SCR_SpawnHandlerComponent GetHandlerComponent()
 {
  ERR_GameMode mode = ERR_GameMode.Get();
  if (mode && mode.ERR_TerrainTakenOver())
  {
   // Controllers that arrived before handover retain the removed handler.
   // Resolve directly from the current owner, avoiding any old singleton.
   SCR_RespawnSystemComponent system = SCR_RespawnSystemComponent.Cast(mode.FindComponent(SCR_RespawnSystemComponent));
   if (system) return SCR_SpawnHandlerComponent.Cast(system.FindComponent(GetHandlerType()));
   return null;
  }
  return super.GetHandlerComponent();
 }
}
