local LD = require("design.LevelDesignLibrary")
local objectlibrary = require("level.environmenteventobjectlibrary")
local lookAtConsts = require("game.lookAtConsts")
local thisObj, thisLevel, Son, player, attributes
local triggered = false
local prevInRange = false
local awarenessTriggered = false
local triggerTimer = 0
local reactionStage = 1
local eventTriggeredOnce = false
local color = 16711680
local thisLookAtEntry
local ignoreBanterErrorConsole = false
local UNIT_Y = engine.Vector.New(0, 1, 0)
local leftAngleRad = 2.443461
local rightAngleRad = 1.2217305
local shouldDisable = false
local sonBB
function OnScriptLoaded(level, obj)
  thisObj, thisLevel = obj, level
  attributes = objectlibrary.CollectLuaTableAttributes(thisLevel, thisObj)
  player = game.Player.FindPlayer()
  Son = game.AI.FindSon()
  if Son then
    sonBB = Son:GetPrivateBlackboard()
  end
  if attributes.EnvEventType == "ReactionStim" or not attributes.EnabledOnStart then
    game.SubObject.Sleep(thisObj)
    game.SubObject.SetForgetOnCheckpoint(thisObj)
  end
  if attributes.EnvEventType == "AwarenessZone" and (attributes.ReactionCalloutStages == 0 or attributes.StageOneMove == nil) and attributes.StageOneBanter == nil and not attributes.InBoatOnly and attributes.OnZoneEnterCallback == nil and attributes.OnZoneExitCallback == nil then
    thisLookAtEntry = game.AddGlobalLookAtTarget(obj, engine.Vector.New(0, 0, 0), lookAtConsts.TargetType.InterestingObject, attributes.MaxRadius, 0.3, 0.3)
    game.SubObject.Sleep(thisObj)
    game.SubObject.SetForgetOnCheckpoint(thisObj)
  end
  game.SubObject.SetUpdateDisableDistance(obj, attributes.MaxRadius + 10)
  ignoreBanterErrorConsole = not obj:FindLuaTableAttribute("IgnoreBanterMessages_In_ErrorConsole")
end
function ClearHeadtracking(obj)
  color = 16711680
  if thisLookAtEntry ~= nil then
    thisLookAtEntry:Remove()
    thisLookAtEntry = nil
  end
end
function UpdateAwarenessRange(level, obj, inRange)
  if inRange == prevInRange then
    if not inRange and triggered or inRange and attributes.IncrementNextStageTimerWhileInRange then
      triggerTimer = triggerTimer + level:GetUnitTime()
    end
    if not inRange and triggered then
      return
    end
  elseif inRange and not prevInRange then
    LD.ExecuteCallbacksForEvent(level, obj, attributes.OnZoneEnterCallback, "OnZoneEnterCallback")
  else
    LD.ExecuteCallbacksForEvent(level, obj, attributes.OnZoneExitCallback, "OnZoneExitCallback")
    ClearHeadtracking(obj)
  end
  if inRange then
    if not triggered and reactionStage ~= -1 then
      Trigger()
      reactionStage = reactionStage + 1
      if reactionStage > attributes.ReactionCalloutStages then
        if attributes.ContinueUsingLastStage then
          reactionStage = attributes.ReactionCalloutStages
        else
          reactionStage = -1
        end
      end
      triggered = true
    end
    if thisLookAtEntry == nil and attributes.UseHeadtrackingToSource then
      thisLookAtEntry = game.AddGlobalLookAtTarget(obj, engine.Vector.New(0, 0, 0), lookAtConsts.TargetType.InterestingObject, 300, 0.3, 0.3)
    end
    if Son ~= nil then
      local fromSonToEvent = obj:GetWorldPosition() - Son:GetWorldPosition()
      local inFrontFacingLeft = Son:IsContextBehaviorNameEqualTo("BOAT_CONTEXT_CONFIG_LEFT")
      local inFrontFacingRight = Son:IsContextBehaviorNameEqualTo("BOAT_CONTEXT_CONFIG_RIGHT")
      if attributes.ChangeContextBehaviorForLeftRight and (inFrontFacingLeft or inFrontFacingRight) then
        local sonLeft = -player:GetWorldLeft()
        local leftToOffsetDot = sonLeft:Dot(fromSonToEvent:Normalized())
        local toLeftSide = 0 < leftToOffsetDot
        local fromLeftSide = not toLeftSide
        if not fromLeftSide and inFrontFacingRight or fromLeftSide and inFrontFacingLeft then
          local angleRad = math.acos(leftToOffsetDot)
          if not fromLeftSide and angleRad < rightAngleRad or fromLeftSide and angleRad > leftAngleRad then
            Son:CallScript("TransitionBehaviorContext", fromLeftSide)
          end
        end
      end
    end
  elseif thisLookAtEntry then
    ClearHeadtracking(obj)
  end
end
function OnUpdate(level, obj)
  if shouldDisable then
    shouldDisable = false
    if thisLookAtEntry then
      ClearHeadtracking(obj)
    end
    game.SubObject.Sleep(thisObj)
    return
  end
  if not (attributes.TriggerByProximity or awarenessTriggered) or attributes.InBoatOnly and game.Boat.GetPlayerBoat() == nil then
    if thisLookAtEntry then
      ClearHeadtracking(obj)
    end
    return
  end
  DisplayDebug(level, obj)
  local inRange
  if awarenessTriggered then
    inRange = true
  else
    local maxLength = attributes.MaxRadius
    local position = thisObj:GetWorldPosition()
    local thisLength = position:Distance(Son:GetWorldPosition())
    inRange = maxLength > thisLength
    if inRange and sonBB ~= nil then
      if sonBB:IsObject("ClosestEnvironmentEvent") then
        local closestEmitter = sonBB:GetObject("ClosestEnvironmentEvent")
        if closestEmitter ~= obj then
          if sonBB:IsNumber("ClosestEnvEventDistance") then
            local otherPosition = sonBB:GetNumber("ClosestEnvEventDistance")
            if thisLength < otherPosition:Distance(Son:GetWorldPosition()) then
              sonBB:Set("ClosestEnvEventDistance", position)
              sonBB:Set("ClosestEnvironmentEvent", obj)
            else
              inRange = false
            end
          else
            sonBB:Set("ClosestEnvEventDistance", position)
            sonBB:Set("ClosestEnvironmentEvent", obj)
          end
        else
          sonBB:Set("ClosestEnvEventDistance", position)
        end
      else
        sonBB:Set("ClosestEnvEventDistance", position)
        sonBB:Set("ClosestEnvironmentEvent", obj)
      end
    end
  end
  if triggerTimer > attributes.ResetReactionTimer then
    triggered = false
    triggerTimer = 0
  end
  if triggerTimer == 0 and not awarenessTriggered and not inRange then
    game.SubObject.SetUpdateDisableDistance(obj, attributes.MaxRadius + 10)
  else
    game.SubObject.SetUpdateDisableDistance(thisObj, 1000000)
  end
  UpdateAwarenessRange(level, obj, inRange)
  prevInRange = inRange
  if game.build.DEBUG and engine.IsDebug() then
    engine.DrawTextInWorld(obj.WorldPosition, "AWARENESS ZONE ACTIVE", 16777215, 1)
  end
end
function DisplayDebug(level, obj)
  if not game.build.DEBUG or engine.IsDebug() == false then
    return
  end
  local debugLoc = thisObj.WorldPosition
  if reactionStage == -1 then
    engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 5, 0), "REACHED END OF STAGES", 16711680, 1)
  end
  engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 4, 0), "ENVIRONMENT EVENT", 16777215, 1)
  engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 3, 0), "RESET TIMER:" .. attributes.ResetReactionTimer - triggerTimer, 16777215, 1)
  engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 2, 0), "STAGE:" .. reactionStage .. "/" .. attributes.ReactionCalloutStages, 16777215, 1)
  if attributes.TriggerByProximity then
    engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 1, 0), "TRIGGERED BY PROXIMITY", 16777215, 1)
  else
    engine.DrawTextInWorld(debugLoc + engine.Vector.New(0, 1, 0), "NOT TRIGGERED BY PROXIMITY", 16777215, 1)
  end
  engine.DrawSphere(debugLoc, attributes.MaxRadius, color)
  engine.DrawCircle(debugLoc, attributes.MaxRadius, UNIT_Y, color)
end
function DisableEventEmitter()
  shouldDisable = true
end
function EnableEventEmitter()
  game.SubObject.Wake(thisObj)
end
function EnableAwarenessRemotely()
  if attributes.EnvEventType ~= "AwarenessZone" then
    return
  end
  game.SubObject.Wake(thisObj)
  if thisObj ~= nil then
    game.SubObject.SetUpdateDisableDistance(thisObj, 1000000)
    awarenessTriggered = true
  end
end
function DisableAwarenessRemotely()
  if attributes.EnvEventType ~= "AwarenessZone" then
    return
  end
  if thisObj ~= nil then
    game.SubObject.SetUpdateDisableDistance(thisObj, attributes.MaxRadius + 10)
    awarenessTriggered = false
    if thisLookAtEntry then
      ClearHeadtracking(thisObj)
    end
  end
end
local ExecuteBanterFinishedCallback = function()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, attributes.OnBanterFinishedCallback, "OnBanterFinishedCallbac")
end
local ExecuteFinalStageBanterFinishedCallback = function()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, attributes.OnFinalStageBanterFinishedCallback, "OnFinalStageBanterFinishedCallback")
end
function Trigger()
  if thisObj ~= nil then
    if attributes.EnvEventType == "ReactionStim" then
      if Son ~= nil then
        local maxLength = attributes.MaxRadius
        local thisObjPos = thisObj:GetWorldPosition()
        local sonPos = Son:GetWorldPosition()
        local diffVec = thisObjPos - sonPos
        local thisLength = diffVec:Length()
        if maxLength > thisLength then
          LD.ExecuteCallbacksForEvent(thisLevel, thisObj, attributes.OnStimTriggerCallback, "OnStimTriggerCallback")
          objectlibrary.TriggerEnvironmentEvent(thisObj, attributes, reactionStage, eventTriggeredOnce)
          Son:CallScript("SetContext_OnExit", attributes.Context_OnExit, attributes.Context_OnExit_Delay)
          eventTriggeredOnce = true
        end
      end
    elseif attributes.EnvEventType == "AwarenessZone" then
      if attributes.ReactionCalloutStages > 0 and attributes.StageOneMove ~= nil then
        objectlibrary.TriggerEnvironmentEvent(thisObj, attributes, reactionStage, eventTriggeredOnce)
        eventTriggeredOnce = true
        Son:CallScript("SetSourcePositionAndTriggerMove", thisObj, reactionStage, attributes, thisLevel)
      end
      local banterName
      if reactionStage == 1 then
        banterName = attributes.StageOneBanter
      elseif reactionStage == 2 then
        banterName = attributes.StageTwoBanter
      elseif reactionStage == 3 then
        banterName = attributes.StageThreeBanter
      end
      if banterName and banterName ~= "" and 0 < #banterName then
        banterName = banterName[math.random(1, #banterName)]
      end
      if banterName and banterName ~= "" then
        if attributes.OnBanterFireCallback then
          LD.ExecuteCallbacksForEvent(thisLevel, thisObj, attributes.OnBanterFireCallback, "OnBanterFireCallback")
        end
        if attributes.OnFinalStageBanterFinishedCallback and reactionStage == attributes.ReactionCalloutStages and not attributes.ContinueUsingLastStage then
          game.Audio.PlayBanter(banterName, ExecuteFinalStageBanterFinishedCallback, nil, ignoreBanterErrorConsole)
        elseif attributes.OnBanterFinishedCallback then
          game.Audio.PlayBanter(banterName, ExecuteBanterFinishedCallback, nil, ignoreBanterErrorConsole)
        else
          game.Audio.PlayBanter(banterName, nil, nil, ignoreBanterErrorConsole)
        end
      end
    end
  end
end
