local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local monitors = require("level.MonitorLibrary")
local thisObj, thisLevel, cooldown
local ringing = false
local dontAnimate
local startRingingCallback = {}
local stopRingingCallback = {}
local hitFrontCallbacks, hitBackCallbacks, bellGeo, myRune, myHammerAnim
local challengeComplete = false
local cooldownTimer, targetFront, targetBack
local targetingEnabled = true
local animTimeSteps = {
  3,
  9,
  15
}
local frontAnims = {
  "envGlobBellRingF_03Sec",
  "envGlobBellRingF_09Sec",
  "envGlobBellRingF_15Secs"
}
local backAnims = {
  "envGlobBellRingB_03Sec",
  "envGlobBellRingB_09Sec",
  "envGlobBellRingB_15Sec"
}
local animIdle = "envGlobBellIdle"
local frontAnim, backAnim, animSpeed, animMonitor
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  bellGeo = thisObj:FindSingleGOByName("bellGeo")
  cooldown = thisObj:GetLuaTableAttribute("Cooldown")
  dontAnimate = thisObj:GetLuaTableAttribute("DontAnimate")
  targetingEnabled = thisObj:GetLuaTableAttribute("startTargetingEnabled")
  local callbacks
  callbacks = thisObj:GetLuaTableAttribute("ringingStartCallbacks")
  if callbacks ~= nil and callbacks ~= "" then
    startRingingCallback = LD.ExtractCallbacksForEvent(level, obj, callbacks)
  else
    startRingingCallback = {}
  end
  callbacks = thisObj:GetLuaTableAttribute("ringingStopCallbacks")
  if callbacks ~= nil and callbacks ~= "" then
    stopRingingCallback = LD.ExtractCallbacksForEvent(level, obj, callbacks)
  else
    stopRingingCallback = {}
  end
  callbacks = thisObj:GetLuaTableAttribute("hitFrontCallbacks")
  if callbacks ~= nil and callbacks ~= "" then
    hitFrontCallbacks = LD.ExtractCallbacksForEvent(level, obj, callbacks)
  end
  callbacks = thisObj:GetLuaTableAttribute("hitBackCallbacks")
  if callbacks ~= nil and callbacks ~= "" then
    hitBackCallbacks = LD.ExtractCallbacksForEvent(level, obj, callbacks)
  end
  local runeType = thisObj:GetLuaTableAttribute("RuneType")
  if runeType and runeType ~= "" then
    myRune = thisObj:FindSingleGOByName(runeType)
    if myRune then
      myRune:Show()
    end
  end
  local hideStand = thisObj:GetLuaTableAttribute("HideStand")
  local hideClapper = thisObj:GetLuaTableAttribute("HideClapper")
  local standCollision = thisObj:FindSingleGOByName("StandCollision")
  if hideStand and hideClapper then
    bellGeo:SetCharacterConfig("bothHide")
    standCollision:Hide()
  elseif hideStand then
    bellGeo:SetCharacterConfig("standHide")
    standCollision:Hide()
  elseif hideClapper then
    bellGeo:SetCharacterConfig("clapperHide")
  end
  targetFront = thisObj:FindSingleGOByName("BellTargetFront")
  targetBack = thisObj:FindSingleGOByName("BellTargetBack")
  SoundInit()
  thisObj:AddMarker("BellTarget")
  local avg
  animSpeed = nil
  for i = 1, #animTimeSteps do
    avg = (animTimeSteps[i] + (animTimeSteps[i + 1] or cooldown)) / 2
    if avg >= cooldown then
      frontAnim = frontAnims[i]
      backAnim = backAnims[i]
      animSpeed = animTimeSteps[i] / cooldown
      break
    end
  end
  game.SubObject.Sleep(thisObj)
end
function OnPreStart()
  targetFront.LuaObjectScript.RegisterHitCallback(HitFront)
  targetBack.LuaObjectScript.RegisterHitCallback(HitBack)
  if thisObj:FindSingleGOByName("myHammerAnim") ~= nil then
    myHammerAnim = thisObj:FindSingleGOByName("myHammerAnim")
    myHammerAnim:PauseAnim()
  end
end
function OnFirstStart(level, obj)
  if targetingEnabled == true then
    EnableTargeting()
  else
    DisableTargeting()
  end
end
function OnStart()
  BellIdle()
  if challengeComplete and myRune then
    myRune:JumpAnimToPercent(1)
  end
end
function EnableTargeting()
  targetingEnabled = true
  targetFront.LuaObjectScript.Enable()
  targetBack.LuaObjectScript.Enable()
end
function DisableTargeting()
  targetingEnabled = false
  targetFront.LuaObjectScript.Disable()
  targetBack.LuaObjectScript.Disable()
end
function HitFront()
  if HitBell(frontAnim) then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, hitFrontCallbacks)
  end
end
function HitBack()
  if HitBell(backAnim) then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, hitBackCallbacks)
  end
end
function HitBell(bellAnim)
  if targetingEnabled == false then
    return false
  end
  if bellAnim and dontAnimate == false then
    bellGeo:ClearAllAnimCallbacks()
    bellGeo:StartAnim(bellAnim)
    bellGeo:JumpAnimToFrame(0)
    bellGeo:PlayAnimToEnd(animSpeed)
    if myHammerAnim ~= nil then
      myHammerAnim:JumpAnimToFrame(0)
      myHammerAnim:PlayAnimToEnd()
    end
  end
  if ringing == false then
    ringing = true
    if challengeComplete == false then
      FireStartRingingCallback(thisObj)
      if myRune then
        myRune:JumpAnimToPercent(1)
      end
    end
  end
  if cooldownTimer then
    cooldownTimer:Restart()
  else
    cooldownTimer = timers.StartLevelTimer(cooldown, function()
      CooldownElapsed()
    end)
  end
  BellHitSound()
  return true
end
function CooldownElapsed()
  if challengeComplete == false then
    if myRune then
      myRune:PlayAnimToEnd(-1)
    end
    FireStopRingingCallback(thisObj)
  end
  ringing = false
  BellIdle()
end
function BellIdle()
  StopBellSwingSound()
  if animMonitor ~= nil then
    animMonitor:Stop()
    animMonitor:Terminate()
    animMonitor = nil
  end
  if dontAnimate then
    return
  end
  bellGeo:ClearAllAnimCallbacks()
  bellGeo:StartAnim(animIdle)
  bellGeo:PlayAnimCycle(1)
  if not challengeComplete then
    BellIdleSound()
  end
end
function RegisterStartRingingCallback(fn)
  table.insert(startRingingCallback, fn)
end
function FireStartRingingCallback(thisBell)
  if startRingingCallback ~= nil then
    for _, functionToTrigger in ipairs(startRingingCallback) do
      functionToTrigger(thisObj)
    end
  end
end
function RegisterStopRingingCallback(fn)
  table.insert(stopRingingCallback, fn)
end
function FireStopRingingCallback(thisBell)
  if stopRingingCallback ~= nil then
    for _, functionToTrigger in ipairs(stopRingingCallback) do
      functionToTrigger(thisObj)
    end
  end
end
function ChallengeComplete()
  challengeComplete = true
end
function SoftSaveCleanup()
  ChallengeComplete()
  DisableTargeting()
  if myRune then
    myRune:JumpAnimToPercent(1)
  end
end
local soundEmitter
local soundEvents = {
  OnHit = "SND_MECH_Bell_Puzzle_Hit_Oneshot",
  OnSwingLoop = "SND_MECH_Bell_Puzzle_Swing_LP",
  OnIdleLoop = "SND_MECH_Bell_Puzzle_Light_LP",
  OnSqueak = "SND_MECH_Bell_Puzzle_Hit_Oneshot_Squeak",
  VolumeFadeThreeSeconds = "SND_MECH_Bell_Puzzle_Hit_Squeak_Fade_03Sec",
  VolumeFadeNineSeconds = "SND_MECH_Bell_Puzzle_Hit_Squeak_Fade_09Sec",
  VolumeFadeFifteenSeconds = "SND_MECH_Bell_Puzzle_Hit_Squeak_Fade_15Sec",
  VolumeReset = "SND_MECH_Bell_Puzzle_Hit_Squeak_Reset_Volume"
}
function SoundInit()
  soundEmitter = thisObj:FindSingleSoundEmitterByName("SNDBell")
  if soundEmitter == nil then
    soundEmitter = bellGeo.Child:FindSingleSoundEmitterByName("SNDBell")
  end
  BellIdleSound()
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 BellHitSound()
  LD.StopSound(soundEmitter, soundEvents.OnIdleLoop)
  LD.PlaySound(soundEmitter, soundEvents.OnHit)
  LD.PlaySound(soundEmitter, soundEvents.OnSwingLoop)
  if ringing then
    if animMonitor ~= nil then
      animMonitor:Stop()
      animMonitor:Terminate()
      animMonitor = nil
    end
    animMonitor = monitors.CreateAnimFrameMonitor(bellGeo)
    local currentTimeStep = animSpeed * cooldown
    BellVolumeReset()
    if currentTimeStep == 3 then
      BellVolumeFadeThreeSeconds()
      animMonitor:OnFrameForward(20, BellSqueakSound)
      animMonitor:OnFrameForward(49, BellSqueakSound)
    end
    if currentTimeStep == 9 then
      BellVolumeFadeNineSeconds()
      animMonitor:OnFrameForward(16, BellSqueakSound)
      animMonitor:OnFrameForward(35, BellSqueakSound)
      animMonitor:OnFrameForward(57, BellSqueakSound)
      animMonitor:OnFrameForward(80, BellSqueakSound)
      animMonitor:OnFrameForward(101, BellSqueakSound)
      animMonitor:OnFrameForward(125, BellSqueakSound)
      animMonitor:OnFrameForward(144, BellSqueakSound)
      animMonitor:OnFrameForward(170, BellSqueakSound)
      animMonitor:OnFrameForward(193, BellSqueakSound)
      animMonitor:OnFrameForward(217, BellSqueakSound)
      animMonitor:OnFrameForward(236, BellSqueakSound)
    end
    if currentTimeStep == 15 then
      BellVolumeFadeFifteenSeconds()
      animMonitor:OnFrameForward(14, BellSqueakSound)
      animMonitor:OnFrameForward(34, BellSqueakSound)
      animMonitor:OnFrameForward(56, BellSqueakSound)
      animMonitor:OnFrameForward(78, BellSqueakSound)
      animMonitor:OnFrameForward(100, BellSqueakSound)
      animMonitor:OnFrameForward(122, BellSqueakSound)
      animMonitor:OnFrameForward(144, BellSqueakSound)
      animMonitor:OnFrameForward(165, BellSqueakSound)
      animMonitor:OnFrameForward(187, BellSqueakSound)
      animMonitor:OnFrameForward(210, BellSqueakSound)
      animMonitor:OnFrameForward(231, BellSqueakSound)
      animMonitor:OnFrameForward(253, BellSqueakSound)
      animMonitor:OnFrameForward(276, BellSqueakSound)
      animMonitor:OnFrameForward(299, BellSqueakSound)
      animMonitor:OnFrameForward(323, BellSqueakSound)
      animMonitor:OnFrameForward(346, BellSqueakSound)
      animMonitor:OnFrameForward(369, BellSqueakSound)
      animMonitor:OnFrameForward(394, BellSqueakSound)
      animMonitor:OnFrameForward(423, BellSqueakSound)
    end
  end
end
function StopBellSwingSound()
  LD.StopSound(soundEmitter, soundEvents.OnSwingLoop)
end
function BellIdleSound()
  LD.PlaySound(soundEmitter, soundEvents.OnIdleLoop)
end
function BellSqueakSound()
  LD.PlaySound(soundEmitter, soundEvents.OnSqueak)
end
function BellVolumeFadeThreeSeconds()
  LD.PlaySound(soundEmitter, soundEvents.VolumeFadeThreeSeconds)
end
function BellVolumeFadeNineSeconds()
  LD.PlaySound(soundEmitter, soundEvents.VolumeFadeNineSeconds)
end
function BellVolumeFadeFifteenSeconds()
  LD.PlaySound(soundEmitter, soundEvents.VolumeFadeFifteenSeconds)
end
function BellVolumeReset()
  LD.PlaySound(soundEmitter, soundEvents.VolumeReset)
end
function OnSaveCheckpoint(level, obj)
  return {challengeComplete = challengeComplete, targetingEnabled = targetingEnabled}
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  challengeComplete = savedInfo.challengeComplete
  targetingEnabled = savedInfo.targetingEnabled
end
function ShowBellTarget()
  bellGeo:Show()
  for i = 1, 3 do
    thisObj:FindSingleGOByName("RuneSymbol" .. tostring(i)):Hide()
  end
  if myRune then
    myRune:Show()
  end
  targetFront:Show()
  targetBack:Show()
  local hideStand = thisObj:GetLuaTableAttribute("HideStand")
  if not hideStand then
    thisObj:FindSingleGOByName("StandCollision"):Show()
  end
end
function HideBellTarget()
  bellGeo:Hide()
  for i = 1, 3 do
    thisObj:FindSingleGOByName("RuneSymbol" .. tostring(i)):Hide()
  end
  targetFront:Hide()
  targetBack:Hide()
  local hideStand = thisObj:GetLuaTableAttribute("HideStand")
  if not hideStand then
    thisObj:FindSingleGOByName("StandCollision"):Hide()
  end
end
function ResetBellTarget()
  challengeComplete = false
  EnableTargeting()
  if myRune then
    myRune:PlayAnimToEnd(-1)
  end
end
