local LD = require("design.LevelDesignLibrary")
local collisionCheck, thisObj, son, player
local yOffset = engine.Vector.New(0, 1, 0)
local useLineOfSight = false
local warpReference, warpDistanceFromReference
local warpPending = false
local customLocators, cancelDistance
function OnScriptLoaded(level, obj)
  thisObj = obj
  son = game.AI.FindSon()
  player = game.Player.FindPlayer()
  local customLocatorGroup = obj:GetLuaTableAttribute("customLocatorGroup")
  warpReference = obj:GetLuaTableAttribute("warpReference")
  warpDistanceFromReference = obj:GetLuaTableAttribute("warpDistanceFromReference")
  useLineOfSight = obj:GetLuaTableAttribute("useLineOfSight")
  cancelDistance = obj:GetLuaTableAttribute("cancelDistance")
  if warpReference == "CustomLocators" then
    customLocators = level:FindSingleGameObject(customLocatorGroup).Children
  end
  if useLineOfSight then
    collisionCheck = require("camera.collisioncheck")
  end
  game.SubObject.Sleep(obj)
  game.SubObject.SetForgetOnCheckpoint(obj)
end
local SonWithinInteractRange = function()
  return game.AIUtil.Distance(son, thisObj) <= cancelDistance
end
local SuggestedLocationIsCloserThanCurrent = function(loc)
  return game.AIUtil.Distance(loc, thisObj) < game.AIUtil.Distance(son, thisObj)
end
local LocationIsOnScreen = function(loc)
  if useLineOfSight then
    local check = collisionCheck.isTargetInFrustumUnobstructed(loc + yOffset, 1, 1, 0, false)
    if check then
      return true
    else
      return false
    end
  end
  return game.World.IsSphereOnScreen(loc + yOffset, 0.25)
end
local SonIsCloserThanPlayer = function()
  return game.AIUtil.Distance(son, thisObj) < game.AIUtil.Distance(game.Player.FindPlayer(), thisObj)
end
local FindValidLocationAndWarp = function(location)
  warpPending = false
  son:Warp(location, son:GetWorldForward(), true)
  print("|||| Son warped by SonWarpSafe.lua to location: ", location)
end
function CancelPendingWarp()
  if warpPending then
    warpPending = false
    game.SubObject.Sleep(thisObj)
  end
end
function WarpSon()
  if son then
    if SonWithinInteractRange() or SonIsCloserThanPlayer() then
      warpPending = false
      return
    end
    if LocationIsOnScreen(son:GetWorldPosition()) then
      warpPending = true
      game.SubObject.Wake(thisObj)
      return
    end
    local location
    if warpReference == "CustomLocators" then
      location = SuggestLocationUsingLocators()
    elseif warpReference == "Player" then
      location = SuggestLocationUsingProximityToObject(player)
    elseif warpReference == "ThisObject" then
      location = SuggestLocationUsingProximityToObject(thisObj)
    end
    if not location then
      warpPending = true
      game.SubObject.Wake(thisObj)
      return
    end
    FindValidLocationAndWarp(location)
  end
end
function OnUpdate(level, obj)
  if warpPending and SonWithinInteractRange() == false then
    WarpSon()
  else
    game.SubObject.Sleep(obj)
  end
end
local CompareDistanceToSon = function(loc1, loc2)
  local dist1 = game.AIUtil.Distance(son, loc1)
  local dist2 = game.AIUtil.Distance(son, loc2)
  return dist1 < dist2
end
function SuggestLocationUsingLocators()
  table.sort(customLocators, CompareDistanceToSon)
  if engine.IsDebug() then
    for _, loc in pairs(customLocators) do
      engine.DrawFillSphere(loc:GetWorldPosition(), 0.1, 255, 5)
    end
  end
  for _, locator in ipairs(customLocators) do
    local loc = game.NavMesh.ClosestPoint(locator:GetWorldPosition()) or locator:GetWorldPosition()
    if not LocationIsOnScreen(loc) and SuggestedLocationIsCloserThanCurrent(loc) then
      if engine.IsDebug() then
        engine.DrawFillSphere(thisObj.WorldPosition, 0.05, 255, 5)
        engine.DrawLine(thisObj.WorldPosition, loc, 16711680, 5)
        engine.DrawFillSphere(loc, 0.05, 16711680, 5)
      end
      return loc
    end
  end
  return nil
end
function SuggestLocationUsingProximityToObject(obj)
  if player == nil then
    player = game.Player.FindPlayer()
  end
  local referenceLoc = obj:GetWorldPosition()
  local playerFwd = player:GetWorldForward()
  local playerLeft = player:GetWorldLeft()
  local warpLocs = {}
  warpLocs[#warpLocs + 1] = referenceLoc - playerFwd * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + playerLeft * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc - playerLeft * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + (playerLeft - playerFwd):Normalized() * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + (-playerLeft - playerFwd):Normalized() * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + playerFwd * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + (playerLeft + playerFwd):Normalized() * warpDistanceFromReference
  warpLocs[#warpLocs + 1] = referenceLoc + (-playerLeft + playerFwd):Normalized() * warpDistanceFromReference
  if engine.IsDebug() then
    for _, loc in pairs(warpLocs) do
      engine.DrawFillSphere(loc, 0.1, 255, 5)
    end
  end
  table.sort(warpLocs, CompareDistanceToSon)
  for _, loc in ipairs(warpLocs) do
    loc = game.NavMesh.ClosestPoint(loc) or loc
    if not LocationIsOnScreen(loc) and SuggestedLocationIsCloserThanCurrent(loc) then
      if engine.IsDebug() then
        engine.DrawLine(son.WorldPosition, loc, 255, 5)
        engine.DrawCircle(loc, 0.2, engine.Vector.New(0, 0.2, 0), 16711680, 5)
      end
      return loc
    end
  end
  return nil
end
