local LD = require("design.LevelDesignLibrary")
local mpicon = require("ui.mpicon")
local DL = require("design.DesignerLibrary")
local tempDisable = false
local thisObj, thisLevel, player, son, sonPuppeteer, sonInteractZone
local startDisableInteract = false
local Event_OnCompleted, Event_OnInterrupted, Event_OnEngaged
local DispellTime = 10
local sonBranch
local StartEnabled = false
local CollisionSize
local MaxEnemyAggro = 5
local KratosRange = 20
local hintRange = 20
local noSonInteract = false
local DragonDispel = false
local DragonDispelScale = 0.215
local DragonDispelWaitingForIdleFX = true
local DragonDispelRemoveIdleWhenSpawned = false
local fpActivateCameraOnArrival, fpActivateCameraOnComplete, fpActivateCameraOnButtonPressed
local STATE_IDLE, STATE_INIT, STATE_WAIT_FOR_SON, STATE_FAILED_APPROACH, STATE_APPROACH, STATE_INTERACTING, STATE_INTERRUPTED, STATE_COMPLETED, STATE_DISABLED = 0, 1, 2, 3, 4, 5, 6, 7, 8
local currentState = STATE_IDLE
local poiTimer = 0
local aggroTime = 0
local HINT_STATE_ON, HINT_STATE_OFF = 0, 1
local currentHintState = HINT_STATE_OFF
local line_effect_table = {}
local startPos, endPos, Mask, FX_Idle, FX_Agitated, FX_Death, FX_Mesh, FX_MeshInner
local MAX_AGGRO_NUMBER = 10
local minDistance = 5
local maxDistance = 40
local minAggroDelay = 0.75
local maxAggroDelay = 3
local Lerp = function(pValue1, pValue2, pLerp)
  local result = (pValue2 - pValue1) * pLerp + pValue1
  return result
end
local GetAggroTime = function()
  local approachPos = thisObj:GetWorldJointPosition(thisObj:GetJointIndex("synchJoint"))
  local distance = game.AIUtil.Distance(son, approachPos)
  local range = maxDistance - minDistance
  local percent = (distance - minDistance) / range
  return Lerp(minAggroDelay, maxAggroDelay, percent)
end
local CreateSonInteractZone = function(obj)
  sonInteractZone = LD.CreateSonInteractZone_Dispell_180(obj, "promptJoint_Son")
  sonInteractZone:SetEnableLineOfSightTest(false)
  sonInteractZone:Disable()
end
function GetSonInteractZone()
  return sonInteractZone
end
local RemoveEffect = function()
  FX_Mesh:JumpAnimToFrame(650)
  FX_Mesh:PlayAnimToFrame(700)
  FX_MeshInner:JumpAnimToFrame(650)
  FX_MeshInner:PlayAnimToFrame(700)
  if FX_Agitated ~= nil then
    LD.HideFX(FX_Agitated)
  end
end
local UpdateFailApproach = function()
end
local AGGRO_PULSE_TIME = 1.5
local UpdateAggro = function()
  aggroTime = aggroTime + thisLevel:GetUnitTime()
  if aggroTime > AGGRO_PULSE_TIME then
    aggroTime = 0
    son:CallScript("LuaHook_ForceAggro", MAX_AGGRO_NUMBER)
  end
end
local ResetPOI = function()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:ReturnToNav()
    sonPuppeteer:DetachPuppet()
    sonPuppeteer = nil
  end
  son:AbortInteract()
  son:RemoveAvailabilityRequest("SonInteraction")
end
local SonHitReaction = function()
  print("son react")
  if FX_Agitated ~= nil then
    LD.HideFX(FX_Agitated)
    if FX_Idle ~= nil then
      FX_Idle:SetWorldPosition(thisObj:GetWorldPosition() + thisObj:GetWorldUp() * 1)
      LD.ShowFX(FX_Idle)
    end
  end
  ResetPOI()
  poiTimer = 0
  currentState = STATE_INTERRUPTED
  son:CallScript("LuaHook_DispelNav", false)
  if Event_OnInterrupted ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, Event_OnInterrupted, "Son Aborted")
  end
end
local SonApproachFailed = function()
  currentState = STATE_FAILED_APPROACH
  mpicon.level.Create(thisObj, "WORLD_INTERACT_SON_UNAVAILABLE")
  poiTimer = 0
  son:TriggerMoveEvent("kLE_StartBackPedal")
  ResetPOI()
end
local SonDispellStarted = function()
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:OnEvent(SonHitReaction, "kEHitReaction")
  end
  son:CallScript("LuaHook_ForceAggro", MAX_AGGRO_NUMBER)
  aggroTime = 0
  if FX_Idle ~= nil then
    LD.HideFX(FX_Idle)
  end
  if FX_Agitated ~= nil then
    FX_Agitated:SetWorldPosition(thisObj:GetWorldPosition() + thisObj:GetWorldUp() * 1)
    LD.ShowFX(FX_Agitated)
  end
end
function LuaHook_SonDispellEnterComplete()
  SonDispellStarted()
end
function SetOnArrivalCameraCallback(pFunction)
  fpActivateCameraOnArrival = pFunction
end
function SetOnCompleteCameraCallback(pFunction)
  fpActivateCameraOnComplete = pFunction
end
function SetOnButtonPressedCameraCallback(pFunction)
  fpActivateCameraOnButtonPressed = pFunction
end
local SonArrived = function()
  if fpActivateCameraOnArrival ~= nil then
    fpActivateCameraOnArrival()
  end
  son:ClearFocus()
  currentState = STATE_INTERACTING
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:OnEvent(SonHitReaction, "kEHitReaction")
    sonPuppeteer:Sync("BRA_DispellMagicDoorStart", true, {
      {
        Slave = thisObj,
        Anim = "envMaskDispelEnter"
      }
    }, "synchJoint")
  end
end
local SonOnEngaged = function()
  local testAngle = 15
  local testRange = 2.25
  local livingenemies = DL.FindLivingEnemies(son, testRange)
  local sonPos = son:GetWorldPosition()
  local dirToPOI = thisObj:GetWorldPosition() - sonPos
  for _, enemy in pairs(livingenemies) do
    if not enemy:HasMarker("React") and game.AIUtil.IntersectPointCone(enemy:GetWorldPosition(), sonPos, dirToPOI, testAngle, testRange) then
      SonApproachFailed()
      return
    end
  end
  son:CallScript("LuaHook_DispelNav", true)
  if sonPuppeteer ~= nil then
    sonPuppeteer:Clear()
    sonPuppeteer:OnEvent(SonHitReaction, "kEHitReaction")
    sonPuppeteer:OnEvent(SonApproachFailed, "BreakOut")
  end
  son:ClearFocus()
  son:RequestInteract(thisObj)
  if thisObj:FindJointIndex("synchJoint") ~= nil then
    local approachPos = thisObj:GetWorldJointPosition(thisObj:GetJointIndex("synchJoint"))
    engine.DrawLine(approachPos, approachPos + thisObj:GetWorldJointForward(thisObj:GetJointIndex("synchJoint")) * 5, 16776960, 22)
    local approachParam = {
      branch_name = "BRA_DispellMagicDoorStart",
      joint_name = "synchJoint",
      focus_end_direction = true,
      speed = 4,
      complete_radius = 1
    }
    if sonPuppeteer ~= nil then
      if sonPuppeteer.SetUserFlag then
        sonPuppeteer:SetUserFlag("ForcePushAway")
      end
      sonPuppeteer:SetType("DispellDoor")
      sonPuppeteer:Approach(approachParam)
      sonPuppeteer:OnComplete(SonArrived)
    end
  end
  if Event_OnEngaged ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, Event_OnEngaged, "Son Engaged")
  end
  game.Audio.PlayBanterNonCritical("ca_generic_affirmation")
end
local SonDispellCompleted = function()
  son:CallScript("LuaHook_DispelNav", false)
  son:CallScript("SetRelaxedBehavior", 3, 20)
  son:TriggerMoveEvent("kLE_StopDispell")
  if Event_OnCompleted ~= nil then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, Event_OnCompleted, "Son Completed")
  end
  son:RemoveAvailabilityRequest("SonInteraction")
  if fpActivateCameraOnComplete ~= nil then
    fpActivateCameraOnComplete()
  end
end
function LuaHook_SonDispellHitMask()
  DestroyMask()
end
function OnInteractFinish(level, obj, creature)
  ResetPOI()
  currentState = STATE_COMPLETED
end
local dispelProgressState = 0
local UpdateBanter = function(poiTimer)
  local triggerTime = 9000
  if dispelProgressState == 0 then
    triggerTime = DispellTime / 4
  elseif dispelProgressState == 1 then
    triggerTime = DispellTime / 2
  elseif dispelProgressState == 2 then
    triggerTime = DispellTime / 4 * 3
  end
  if poiTimer > triggerTime then
    dispelProgressState = dispelProgressState + 1
  end
end
local ShowSonInteractionIcon = function(obj)
  local son_bb = son:GetBlackboard()
  if son_bb:IsObject("InteractObject") and son_bb:GetObject("InteractObject") == obj then
    return true
  end
  return false
end
local UpdateState = function(level, obj)
  if currentState == STATE_INIT then
    if noSonInteract then
      return
    end
    sonPuppeteer = game.Puppeteer.New(obj, "SonDispelling" .. obj.Parent:GetName(), son)
    sonPuppeteer:SetUserFlag("ForcePushAway")
    sonPuppeteer:SetType("DispellDoor")
    sonPuppeteer:OnEvent(SonHitReaction, "kEHitReaction")
    sonPuppeteer:Advertise("INTERACT_SON")
    sonPuppeteer:OnEngaged(SonOnEngaged)
    if not startDisableInteract then
      sonInteractZone:Enable()
    end
    currentState = STATE_WAIT_FOR_SON
  elseif currentState == STATE_FAILED_APPROACH then
    poiTimer = poiTimer + level:GetUnitTime()
    if 1.5 < poiTimer then
      currentState = STATE_INIT
    end
    UpdateFailApproach()
  elseif currentState == STATE_APPROACH then
    if 0 < aggroTime then
      aggroTime = aggroTime - level:GetUnitTime()
      if aggroTime <= 0 then
        son:CallScript("LuaHook_ForceAggro", 2)
      end
    end
  elseif currentState == STATE_INTERACTING then
    poiTimer = poiTimer + level:GetUnitTime()
    UpdateAggro()
    UpdateBanter(poiTimer)
    if poiTimer > DispellTime then
      SonDispellCompleted()
      currentState = STATE_COMPLETED
      poiTimer = 0
    end
  elseif currentState == STATE_INTERRUPTED then
    poiTimer = poiTimer + level:GetUnitTime()
    if 1.05 < poiTimer then
      son:CallScript("LuaHook_DispelNav", false)
      if FX_Idle ~= nil then
        FX_Idle:SetWorldPosition(thisObj:GetWorldPosition() + thisObj:GetWorldUp() * 1)
        LD.ShowFX(FX_Idle)
      end
      currentState = STATE_INIT
      sonInteractZone:Enable()
    end
  end
end
local UpdateIdleFX = function(level, obj)
  if DragonDispelWaitingForIdleFX and DragonDispel and FX_Idle then
    thisObj:AddChild(FX_Idle)
    DragonDispelWaitingForIdleFX = false
  end
  if DragonDispel and DragonDispelRemoveIdleWhenSpawned then
    LD.HideFX(FX_Idle)
    DragonDispelRemoveIdleWhenSpawned = false
  end
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  son = game.AI.FindSon()
  obj:SetBlackboardSize(4)
  obj:StartAnim("maskInactiveIdle")
  obj:PlayAnimCycle()
  obj:HideNavObstacle()
  FX_Mesh = obj:FindSingleGOByName("fx_mesh")
  FX_MeshInner = obj:FindSingleGOByName("fx_meshInner")
  FX_Mesh:Hide()
  FX_MeshInner:Hide()
  player = player
  Event_OnCompleted = Event_OnCompleted
  Event_OnInterrupted = Event_OnInterrupted
  Event_OnEngaged = Event_OnEngaged
  DispellTime = DispellTime
  sonBranch = sonBranch
  StartEnabled = StartEnabled
  if obj:FindLuaTableAttribute("Event_OnCompleted") ~= "" then
    Event_OnCompleted = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnCompleted"))
  end
  if obj:FindLuaTableAttribute("Event_OnInterrupted") ~= "" then
    Event_OnInterrupted = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnInterrupted"))
  end
  if obj:FindLuaTableAttribute("Event_OnEngaged") ~= "" then
    Event_OnEngaged = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("Event_OnEngaged"))
  end
  if obj:FindLuaTableAttribute("DispellTime") ~= nil then
    DispellTime = obj:FindLuaTableAttribute("DispellTime")
  end
  if obj:FindLuaTableAttribute("sonBranch") ~= nil then
    sonBranch = obj:FindLuaTableAttribute("sonBranch")
  end
  if obj:FindLuaTableAttribute("StartEnabled") ~= nil then
    StartEnabled = obj:FindLuaTableAttribute("StartEnabled")
  end
  if obj:FindLuaTableAttribute("CollisionSize") ~= nil then
    CollisionSize = obj:FindLuaTableAttribute("CollisionSize")
  end
  if obj:FindLuaTableAttribute("NoSonInteract") ~= nil then
    noSonInteract = obj:FindLuaTableAttribute("NoSonInteract")
  end
  DragonDispel = obj:FindLuaTableAttribute("DragonDispel")
  if DragonDispel then
    FX_Mesh:SetScale(DragonDispelScale)
    FX_MeshInner:SetScale(DragonDispelScale)
  end
  for _, child in pairs(obj.Children) do
    child:HideCollision()
  end
  CreateSonInteractZone(obj)
  SoundInit()
end
function OnFirstStart(level, obj)
  if FX_Idle == nil then
    FX_Idle = obj:FindSingleGOByName("dispelbarrier_idle")
  end
  if FX_Agitated == nil then
    FX_Agitated = obj:FindSingleGOByName("dispelBarrierAgitated")
  end
  if FX_Death == nil then
    FX_Death = obj:FindSingleGOByName("dispelBarrierDeath")
  end
  LD.HideFX(FX_Idle)
  LD.HideFX(FX_Agitated)
  LD.HideFX(FX_Death)
end
function OnStart(level, obj)
  if FX_Idle == nil then
    FX_Idle = obj:FindSingleGOByName("dispelbarrier_idle")
  end
  if FX_Agitated == nil then
    FX_Agitated = obj:FindSingleGOByName("dispelBarrierAgitated")
  end
  if FX_Death == nil then
    FX_Death = obj:FindSingleGOByName("dispelBarrierDeath")
  end
  if StartEnabled then
    Enable()
  end
  SoundOnStart()
end
function OnUpdate(level, obj)
  UpdateState(level, obj)
  UpdateIdleFX(level, obj)
end
function OnSonInteractButtonPressed(level, obj)
  sonInteractZone:Disable()
  son:SetNewAvailabilityRequest("SonInteraction", {
    AvailableForSync = false,
    AvailableForCombat = false,
    Unoccupied = false
  })
  if fpActivateCameraOnButtonPressed ~= nil then
    fpActivateCameraOnButtonPressed()
  end
end
function OnSonInteractButtonPressedWhileOccupied(level, obj)
  game.Audio.PlayBanterNonCritical("ca_generic_not_ready", nil, nil, false)
end
function ReEnable()
  thisObj:Show()
  thisObj:StartAnim("maskInactiveIdle")
  thisObj:PlayAnimCycle()
  Enable()
end
function Enable()
  if tempDisable and not string.find(thisLevel.Name, "WAD_TB") then
    return false
  end
  if currentState ~= STATE_INIT and currentState ~= STATE_IDLE and currentState ~= STATE_COMPLETED and currentState ~= STATE_INTERRUPTED then
    return
  end
  thisObj:ShowNavObstacle()
  if not DragonDispel then
    local collsion = thisObj:FindSingleGOByName(CollisionSize)
    collsion:ShowCollision()
  end
  thisObj:StartAnim("maskRiseUp")
  thisObj:OnAnimationDone(thisObj, "OnMaskRiseDone")
  FX_Mesh:Show()
  FX_Mesh:JumpAnimToFrame(0)
  FX_Mesh:PlayAnimToFrame(650)
  FX_Mesh:OnAnimationDone(thisObj, "OnFXMeshDone", {Force = true})
  FX_MeshInner:Show()
  FX_MeshInner:JumpAnimToFrame(0)
  FX_MeshInner:PlayAnimToFrame(650)
  FX_MeshInner:OnAnimationDone(thisObj, "OnFXMeshInnerDone", {Force = true})
  FX_Idle:SetWorldPosition(thisObj:GetWorldPosition() + thisObj:GetWorldUp() * 1)
  LD.ShowFX(FX_Idle)
  currentState = STATE_INIT
end
function Disable()
  ResetPOI()
  local collsion = thisObj:FindSingleGOByName(CollisionSize)
  collsion:HideCollision()
  currentState = STATE_DISABLED
  if noSonInteract then
    DestroyMask()
  end
  if DragonDispel then
    if FX_Idle then
      LD.HideFX(FX_Idle)
      DragonDispelRemoveIdleWhenSpawned = true
    end
    thisObj:Hide()
  end
  sonInteractZone:Disable()
end
function DestroyMask()
  if FX_Death ~= nil then
    FX_Death:SetWorldPosition(thisObj:GetWorldPosition() + thisObj:GetWorldUp() * 1)
    LD.ShowFX(FX_Death)
  end
  thisObj:Hide()
  RemoveEffect()
  if FX_Idle ~= nil then
    LD.HideFX(FX_Idle)
  end
  for _, child in pairs(thisObj.Children) do
    child:HideCollision()
  end
end
function SetDispelled()
  DestroyMask()
  ResetPOI()
  StopSoundDispelBarrier()
  currentState = STATE_COMPLETED
end
function Set_DispellTime(newTime)
  DispellTime = newTime
end
function OnMaskRiseDone(level, obj)
  obj:StartAnim("maskLoopFloating")
  obj:PlayAnimCycle()
  obj:OnAnimationDone(obj, "")
end
function OnFXMeshDone(obj, test)
  if currentState == STATE_COMPLETED then
    return
  end
  FX_Mesh:JumpAnimToFrame(50)
  FX_Mesh:PlayAnimToFrame(650)
  FX_Mesh:OnAnimationDone(thisObj, "OnFXMeshDone", {Force = true})
end
function OnFXMeshInnerDone(obj, test)
  if currentState == STATE_COMPLETED then
    return
  end
  FX_MeshInner:JumpAnimToFrame(50)
  FX_MeshInner:PlayAnimToFrame(650)
  FX_MeshInner:OnAnimationDone(thisObj, "OnFXMeshInnerDone", {Force = true})
end
function EnableInteract()
  startDisableInteract = false
  sonInteractZone:Enable()
end
function DisableInteract()
  startDisableInteract = true
  sonInteractZone:Disable()
end
local soundEmitter
local soundEvents = {
  LoopSound = "SND_MAG_Dispel_Barrier_Small_LP"
}
function SoundInit()
  soundEmitter = thisObj:FindSingleSoundEmitterByName("SNDDispelBarrier")
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if sounds.SoundEmitter ~= nil then
      soundEmitter = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter)
    end
    for key in pairs(soundEvents) do
      for newKey, newValue in pairs(sounds) do
        if newKey == key and newValue ~= nil and newValue ~= "" then
          soundEvents[key] = newValue
        end
      end
      LD.SoundDebug(tostring(key) .. ": " .. tostring(soundEvents[key]))
    end
  end
end
function SoundOnStart()
  LD.PlayRestartableSoundLoop(soundEmitter, soundEvents.LoopSound)
end
function StopSoundDispelBarrier()
  LD.StopRestartableSoundLoop(soundEmitter, soundEvents.LoopSound)
end
