local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local thisObj, thisLevel, rotator, extenders, sawBladeObj, gear_front, gear_back, FrontInteractable, BackInteractable, retractSpeed, rotateSpeed, rotateDirection, bRotateOnStart, bRotatorActive, bWasRotating, rotationStartAngle, bRetractedOnStart, bRetracted, retractionPercent, bRetractionRunning, bRetractingOnStart, RetractedDuration, OnRetractionCompletedCallbacks, OnRetractionStartedCallbacks, RetractionMonitor, bProtractionRunning, ProtractedDuration, OnProtractionCompletedCallbacks, OnProtractionStartedCallbacks, ProtractionTimer
local bActive = false
local bSpinning = false
local bEnabled = true
local GO_CombatCollision
local surfaceIceBack = {embed = nil, unEmbed = nil}
local surfaceIceFront = {embed = nil, unEmbed = nil}
local SetGearCallbacks = function(gear, isFront, embedFxObj, unEmbedFXObj)
  local fnName = "Back"
  if isFront then
    fnName = "Front"
  end
  gear.LuaObjectScript.RegisterUnembedStartCallback(function()
    embedFxObj:Hide()
    LD.ShowFX(unEmbedFXObj)
    unEmbedFXObj:ClearAllAnimationCallbacks()
    unEmbedFXObj:OnAnimDone(thisObj, "Hide" .. fnName .. "_UnEmbedSurfaceIce")
  end)
  gear.LuaObjectScript.RegisterUnembedCallback(Start)
  gear.LuaObjectScript.RegisterEmbedCallback(function()
    Stop(true)
    LD.ShowFX(embedFxObj)
  end)
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  rotator = thisObj
  extenders = thisObj:FindSingleGOByName("extenders")
  sawBladeObj = thisObj:FindSingleGOByName("sawBlade00").Child
  gear_front = thisObj:FindSingleGOByName("gear_front")
  gear_back = thisObj:FindSingleGOByName("gear_back")
  GO_CombatCollision = thisObj:FindSingleGOByName("CombatCollision")
  bActive = thisObj:GetLuaTableAttribute("bStartActive")
  FrontInteractable = thisObj:GetLuaTableAttribute("FrontInteractable")
  BackInteractable = thisObj:GetLuaTableAttribute("BackInteractable")
  bRetractedOnStart = thisObj:GetLuaTableAttribute("bRetractedOnStart")
  rotationStartAngle = thisObj:GetLuaTableAttribute("RotateStartAngle")
  retractSpeed = thisObj:GetLuaTableAttribute("RetractSpeed")
  ProtractedDuration = thisObj:GetLuaTableAttribute("ProtractedDuration")
  RetractedDuration = thisObj:GetLuaTableAttribute("RetractedDuration")
  rotateSpeed = thisObj:GetLuaTableAttribute("RotateSpeed")
  rotateDirection = thisObj:GetLuaTableAttribute("RotateDirection")
  bRotateOnStart = thisObj:GetLuaTableAttribute("bRotateOnStart")
  bRetractingOnStart = thisObj:GetLuaTableAttribute("bRetractingOnStart")
  retractionPercent = thisObj:GetLuaTableAttribute("RetractionPercent")
  OnProtractionCompletedCallbacks = thisObj:GetLuaTableAttribute("OnProtractionCompletedCallbacks")
  OnProtractionStartedCallbacks = thisObj:GetLuaTableAttribute("OnProtractionStartedCallbacks")
  OnRetractionCompletedCallbacks = thisObj:GetLuaTableAttribute("OnRetractionCompletedCallbacks")
  OnRetractionStartedCallbacks = thisObj:GetLuaTableAttribute("OnRetractionStartedCallbacks")
  OnProtractionCompletedCallbacks = LD.ExtractCallbacksForEvent(level, obj, OnProtractionCompletedCallbacks)
  OnProtractionStartedCallbacks = LD.ExtractCallbacksForEvent(level, obj, OnProtractionStartedCallbacks)
  OnRetractionCompletedCallbacks = LD.ExtractCallbacksForEvent(level, obj, OnRetractionCompletedCallbacks)
  OnRetractionStartedCallbacks = LD.ExtractCallbacksForEvent(level, obj, OnRetractionStartedCallbacks)
  if rotateDirection == "CW" then
    rotateSpeed = -math.abs(rotateSpeed)
  elseif rotateDirection == "CCW" then
    rotateSpeed = math.abs(rotateSpeed)
  end
  surfaceIceBack.embed = thisObj:FindSingleGOByName("IceEmbedFX_Surface_Back")
  surfaceIceBack.unEmbed = thisObj:FindSingleGOByName("IceUnembedFX_Surface_Back")
  surfaceIceFront.embed = thisObj:FindSingleGOByName("IceEmbedFX_Surface_Front")
  surfaceIceFront.unEmbed = thisObj:FindSingleGOByName("IceUnembedFX_Surface_Front")
  if FrontInteractable == "Gear" then
    SetGearCallbacks(gear_front, true, surfaceIceFront.embed, surfaceIceFront.unEmbed)
  end
  if BackInteractable == "Gear" then
    SetGearCallbacks(gear_back, false, surfaceIceBack.embed, surfaceIceBack.unEmbed)
  end
  bRetracted = bRetractedOnStart
  bRetractionRunning = false
  bProtractionRunning = false
  SoundInit()
  sawBladeObj:OnAnimationDone(thisObj, "StartSpin_BladeRotateLoop", {
    Animation = "bladeRotate_Start",
    Force = true
  })
  game.SubObject.Sleep(obj)
end
function OnFirstPreStart(level, obj)
  local iceFXNames = {
    "IceEmbedFX_Front",
    "IceEmbedFX_Back",
    "IceUnembedFX_Front",
    "IceUnembedFX_Back"
  }
  for i = 1, #iceFXNames do
    iceFXNames[i] = thisObj:FindSingleGOByName(iceFXNames[i])
    if iceFXNames[i] then
      if iceFXNames[i].IsRefNode then
        iceFXNames[i] = iceFXNames[i].Child
      end
      LD.HideFX(iceFXNames[i])
      iceFXNames[i]:HideModel()
    end
  end
  surfaceIceBack.embed:Hide()
  surfaceIceBack.unEmbed:Hide()
  surfaceIceFront.embed:Hide()
  surfaceIceFront.unEmbed:Hide()
end
function HideBack_UnEmbedSurfaceIce()
  surfaceIceBack.unEmbed:Hide()
end
function HideFront_UnEmbedSurfaceIce()
  surfaceIceFront.unEmbed:Hide()
end
function OnPreStart(level, obj)
  if FrontInteractable == "Gear" then
    gear_front.LuaObjectScript.SetIceEmbedFx(thisObj:FindSingleGOByName("IceEmbedFX_Front"))
    gear_front.LuaObjectScript.SetIceUnembedFx(thisObj:FindSingleGOByName("IceUnembedFX_Front"))
  end
  if BackInteractable == "Gear" then
    gear_back.LuaObjectScript.SetIceEmbedFx(thisObj:FindSingleGOByName("IceEmbedFX_Back"))
    gear_back.LuaObjectScript.SetIceUnembedFx(thisObj:FindSingleGOByName("IceUnembedFX_Back"))
  end
end
function OnFirstStart(level, obj)
  Reset()
end
function OnStart(level, obj)
  local animFrameMonitorLibrary = require("level.animframemonitor")
  RetractionMonitor = animFrameMonitorLibrary.Add(extenders)
  RetractionMonitor:SetPrintsEnabled(false)
  RetractionMonitor:OnFrameForward(extenders.AnimLengthFrames * retractionPercent, OnRetractionComplete)
  RetractionMonitor:OnFrameBackward(0, OnProtractionComplete)
  if bActive then
    if ProtractionTimer and ProtractionTimer.running then
      ProtractionTimer:Pause()
    end
    bActive = false
    Start()
  else
    bActive = true
    Stop()
  end
end
function OnSaveCheckpoint(level, obj)
  return {
    bEnabled = bEnabled,
    bActive = bActive,
    bProtractionRunning = bProtractionRunning,
    bRetractionRunning = bRetractionRunning,
    bRetracted = bRetracted,
    bRotatorActive = bRotatorActive
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  bEnabled = savedInfo.bEnabled
  bActive = savedInfo.bActive
  bRetracted = savedInfo.bRetracted or savedInfo.bRetractionRunning
  bRotatorActive = savedInfo.bRotatorActive
end
function Start()
  if bEnabled then
    if bActive then
      return
    end
    bActive = true
    if bRetractingOnStart or bRetractionRunning or bProtractionRunning then
      bRetractingOnStart = true
      StartRetractor()
    end
    if bRotateOnStart or bWasRotating then
      StartRotator()
    end
    StartSpin()
    PlayBladeSound()
  end
end
function Stop(bAxe)
  if not bActive then
    return
  end
  bWasRotating = bRotatorActive
  bActive = false
  StopSpin(bAxe)
  StopRotator()
  StopRetractor()
  StopBladeSound()
end
function Protract()
  if bRetracted or bRetractionRunning then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnProtractionStartedCallbacks)
    RetractionMonitor:Start()
    bRetractionRunning = false
    bProtractionRunning = true
    extenders:PlayAnimationToEnd({
      Rate = -retractSpeed
    })
    if gear_front then
      gear_front.LuaObjectScript.SetAnimationSpeed("PlayAnimToEnd", -retractSpeed)
    end
    if gear_back then
      gear_back.LuaObjectScript.SetAnimationSpeed("PlayAnimToEnd", -retractSpeed)
    end
    PlayBladeExtendSound()
  end
end
function OnProtractionComplete()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnProtractionCompletedCallbacks)
  RetractionMonitor:Stop()
  bProtractionRunning = false
  bRetracted = false
  ProtractionTimer = timers.StartLevelTimer(ProtractedDuration, Retract)
end
function Retract()
  if not bRetracted or bProtractionRunning then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnRetractionStartedCallbacks)
    RetractionMonitor:Start()
    bProtractionRunning = false
    bRetractionRunning = true
    extenders:PlayAnimationToFrame(extenders.AnimLengthFrames * retractionPercent, {Rate = retractSpeed})
    if gear_front then
      gear_front.LuaObjectScript.SetAnimationSpeed("PlayAnimCycleReverse", retractSpeed)
    end
    if gear_back then
      gear_back.LuaObjectScript.SetAnimationSpeed("PlayAnimCycleReverse", retractSpeed)
    end
    PlayBladeRetractSound()
  end
end
function OnRetractionComplete()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnRetractionCompletedCallbacks)
  RetractionMonitor:Stop()
  bRetractionRunning = false
  bRetracted = true
  ProtractionTimer = timers.StartLevelTimer(RetractedDuration, Protract)
end
function StartSpin()
  if bEnabled then
    bSpinning = true
    sawBladeObj:PlayAnimationToEnd({
      Animation = "bladeRotate_Start"
    })
    if gear_front then
      gear_front.LuaObjectScript.SetAnimationSpeed("PlayAnimCycle", 1)
    end
    if gear_back then
      gear_back.LuaObjectScript.SetAnimationSpeed("PlayAnimCycle", 1)
    end
    GO_CombatCollision.LuaObjectScript.Enable()
  end
end
function StartSpin_BladeRotateLoop()
  if bSpinning then
    sawBladeObj:PlayAnimationCycle({
      Animation = "bladeRotate",
      Rate = 1
    })
  end
  sawBladeObj:OnAnimationDone(thisObj, "StartSpin_BladeRotateLoop", {
    Animation = "bladeRotate_Start"
  })
end
function StopSpin(bAxe)
  bSpinning = false
  if bAxe then
    sawBladeObj:PlayAnimationToEnd("bladeRotate_Stop_Axe")
  else
    sawBladeObj:PlayAnimationToEnd("bladeRotate_Stop")
  end
  if gear_front then
    gear_front.LuaObjectScript.SetAnimationSpeed("PauseAnim")
  end
  if gear_back then
    gear_back.LuaObjectScript.SetAnimationSpeed("PauseAnim")
  end
  GO_CombatCollision.LuaObjectScript.Disable()
end
function StartRetractor()
  if bEnabled then
    StartSpin()
    if ProtractionTimer ~= nil then
      if ProtractionTimer.running == false then
        ProtractionTimer:Unpause()
      end
    elseif bRetracted then
      ProtractionTimer = timers.StartLevelTimer(RetractedDuration, Protract)
    else
      ProtractionTimer = timers.StartLevelTimer(ProtractedDuration, Retract)
    end
  end
end
function StopRetractor()
  if ProtractionTimer and ProtractionTimer.running then
    ProtractionTimer:Pause()
    if RetractionMonitor then
      RetractionMonitor:Stop()
    end
  end
end
function StartRotator()
  if bEnabled then
    StartSpin()
    if not bRotatorActive then
      bRotatorActive = true
      rotator:PauseAnimation()
      rotator:PlayAnimationCycle(rotateSpeed)
    end
  end
end
function StopRotator()
  StopSpin()
  if bRotatorActive then
    bRotatorActive = false
    rotator:PauseAnimation()
  end
end
function GoToSafeState()
  bRotatorActive = false
  thisObj:JumpAnimationToFrame(180)
  thisObj:PauseAnimation()
  extenders:PlayAnimToPercent(1, 3)
end
function Reset()
  rotator:JumpAnimationToFrame(rotationStartAngle)
  rotator:PauseAnimation()
  if bRetractedOnStart then
    extenders:JumpAnimationToPercent(retractionPercent)
    extenders:PauseAnimation()
  end
  if FrontInteractable == "None" then
    gear_front:Hide()
    gear_front = nil
  else
    local cap_front = thisObj:FindSingleGOByName("cap_front")
    cap_front:Hide()
  end
  if BackInteractable == "None" then
    gear_back:Hide()
    gear_back = nil
  else
    local cap_back = thisObj:FindSingleGOByName("cap_back")
    cap_back:Hide()
  end
end
function Enable()
  bEnabled = true
  PlayBladeSound()
end
function Disable()
  bEnabled = false
  StopBladeSound()
  Stop()
end
local emitterDelay = 0.05
local soundEmitter
local soundEvents = {
  SNDBlades = nil,
  SNDOnExtend = "SND_TRAP_Saw_Blades_Shift_Extend",
  SNDOnRetract = "SND_TRAP_Saw_Blades_Shift_Retract"
}
function SoundInit()
  soundEmitter = extenders:FindSingleSoundEmitterByName("SNDSawBlade")
  soundEvents.SNDBlades = "SND_TRAP_Saw_Blades_LP"
end
function PlayBladeSound()
  PlaySoundDelayed(soundEmitter, soundEvents.SNDBlades)
end
function StopBladeSound()
  LD.StopSound(soundEmitter, soundEvents.SNDBlades)
end
function PlayBladeExtendSound()
  LD.PlaySound(soundEmitter, soundEvents.SNDOnExtend)
end
function PlayBladeRetractSound()
  LD.PlaySound(soundEmitter, soundEvents.SNDOnRetract)
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if sounds.SoundEmitter ~= nil then
      soundEmitter = sounds.SoundEmitter
    end
    for key, value 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(value))
    end
  end
end
function PlaySoundDelayed(sndEmitter, sndSource)
  timers.StartLevelTimer(emitterDelay, function()
    LD.PlayRestartableSoundLoop(sndEmitter, sndSource)
  end)
end
