local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local thisLevel, thisObj
local bEnabled = true
local bPlaying = false
local playRate = 0
local impulseAmount = 0
local currentTargetFrameIndex = 1
local lastThrowType = "small"
local impulseFrame = 0
local GroundShakeTimer, bDragObjectEnabled
local maxVelocityObtained = 0
local timeTilDrag = 0
local largeImpulse = 0
local mediumImpulse = 0
local smallImpulse = 0
local targetFrames = {}
local minRate = 0.1
local maxRate = 16
local minRateBottom = 0.005
local OnAddImpulseCallbacks, OnReachedTargetFrameCallbacks, OnReachedLastTargetFrameCallbacks, DoorObject
local GetTargetFrame = function()
  return tonumber(targetFrames[currentTargetFrameIndex])
end
local ValueChecks = function()
  if smallImpulse >= mediumImpulse then
    engine.Warning(thisObj.Parent.Parent.Parent, "has an invalid value!\n", "Small Impulse: ", smallImpulse, " must be a lower value than Medium Impulse: ", mediumImpulse)
  end
  if mediumImpulse >= largeImpulse then
    engine.Warning(thisObj.Parent.Parent.Parent, "has an invalid value!\n", "Medium Impulse: ", mediumImpulse, " must be a lower value than Large Impulse ", largeImpulse)
  end
  if minRate < minRateBottom then
    engine.Warning(thisObj.Parent.Parent.Parent, "has an invalid value!\n", "minRate: ", minRate, " can not be less than: ", minRateBottom)
    minRate = minRateBottom
  end
  if minRate > smallImpulse then
    engine.Warning(thisObj.Parent.Parent.Parent, "has an invalid value!\n", "minRate: ", minRate, " can not be greater than smallImpulse: ", smallImpulse)
    minRate = smallImpulse
  end
  if maxRate < largeImpulse then
    engine.Warning(thisObj.Parent.Parent.Parent, "has an invalid value!\n", "maxRate: ", maxRate, " can not be less than largeImpulse: ", largeImpulse)
    maxRate = largeImpulse
  end
end
local GetImpulseAmount = function()
  if lastThrowType == "large" then
    return largeImpulse
  elseif lastThrowType == "medium" then
    return mediumImpulse
  else
    return smallImpulse
  end
end
local GetPlayRate = function()
  local frameLength = GetTargetFrame() - impulseFrame
  local remainingLength = GetTargetFrame() - DoorObject.AnimFrame
  local rate = remainingLength / frameLength * impulseAmount
  if currentTargetFrameIndex >= #targetFrames and impulseFrame > tonumber(targetFrames[#targetFrames - 1]) and rate < rate + GetImpulseAmount() / 1.5 then
    rate = rate + GetImpulseAmount() / 1.5
  end
  if rate < minRate then
    return minRate
  else
    return rate
  end
end
local UpdateThrowType = function()
  if game.Player.FindPlayer():IsPlayingMove("MOV_AxeTossHorizontalEnterToss") then
    lastThrowType = "small"
  elseif game.Player.FindPlayer():IsPlayingMove("MOV_AxeTossHorizontalEnterChargeHoldLoop") then
    lastThrowType = "medium"
  elseif game.Player.FindPlayer():IsPlayingMove("MOV_AxeTossVerticalEnterToss") then
    lastThrowType = "medium"
  elseif game.Player.FindPlayer():IsPlayingMove("MOV_AxeTossVerticalEnterTossCook") then
    lastThrowType = "large"
  end
end
local convertDistanceToFXOnAnimDone = function()
  if playRate > mediumImpulse / 2 then
    return {
      EffectName = "FSE_shake_temp_Generic_Medium",
      Duration = 0.4
    }, {EffectName = "FFB_MEDIUM", Duration = 0.4}
  elseif playRate > smallImpulse / 2 then
    return {
      EffectName = "FSE_shake_Temp_Generic_Small",
      Duration = 0.4
    }, {EffectName = "FFB_SMALL", Duration = 0.4}
  else
    return nil, {
      EffectName = "FFB_SMALLER",
      Duration = 0.4
    }
  end
end
local convertDistanceToFXOnHit = function(dist)
  if dist < 10 then
    return nil, {EffectName = "FFB_MEDIUM", Duration = 0.2}
  elseif dist < 20 then
    return nil, {EffectName = "FFB_SMALL", Duration = 0.2}
  else
    return nil, {
      EffectName = "FFB_SMALLER",
      Duration = 0.2
    }
  end
end
local SubmitForceFeedback = function(cam, con)
  if not cam and not con then
    return
  end
  local camShake = cam
  local conRumble = con
  if camShake then
    game.FX.SubmitEffect(camShake)
  end
  if conRumble then
    game.FX.SubmitEffect(conRumble)
  end
end
function OnScriptLoaded(level, obj)
  thisLevel = level
  thisObj = obj
  largeImpulse = obj:GetLuaTableAttribute("largeImpulse")
  mediumImpulse = obj:GetLuaTableAttribute("mediumImpulse")
  smallImpulse = obj:GetLuaTableAttribute("smallImpulse")
  targetFrames = LD.ConvertStringListToTable(obj:GetLuaTableAttribute("targetFrames"))
  minRate = obj:GetLuaTableAttribute("minRate")
  maxRate = obj:GetLuaTableAttribute("maxRate")
  OnAddImpulseCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnAddImpulseCallbacks"))
  OnReachedTargetFrameCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnReachedTargetFrameCallbacks"))
  OnReachedLastTargetFrameCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnReachedLastTargetFrameCallbacks"))
  table.insert(targetFrames, 1, 0)
  ValueChecks()
end
function OnFirstPreStart(level, obj)
  bEnabled = obj:GetLuaTableAttribute("startEnabled")
  bDragObjectEnabled = obj:GetLuaTableAttribute("bDragObjectEnabled")
  if not bDragObjectEnabled then
    obj.Parent.Parent:FindSingleGOByName(obj:GetLuaTableAttribute("DragObject")):Hide()
  else
    bDragObjectEnabled = true
  end
end
function OnPreStart(level, obj)
  DoorObject = obj.Parent.Parent:FindSingleGOByName(obj:GetLuaTableAttribute("AxeDoorObject"))
  DoorObject:OnAnimationDone(obj, "DoorObject_OnAnimDone", {Force = true})
  obj.Parent.Parent:FindSingleGOByName(obj:GetLuaTableAttribute("EmbedObject")).LuaObjectScript.RegisterOnWeaponEmbed(TriggerImpulse)
  local targetFrame = GetTargetFrame()
  impulseFrame = DoorObject.AnimFrame
  if DoorObject.AnimFrame ~= targetFrame then
    DoorObject:JumpAnimToFrame(targetFrame)
    DoorObject:PauseAnim()
  end
  SoundInit()
end
function OnSaveCheckpoint(level, obj)
  return {
    bEnabled = bEnabled,
    bDragObjectEnabled = bDragObjectEnabled,
    currentTargetFrameIndex = currentTargetFrameIndex
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  bEnabled = savedInfo.bEnabled
  bDragObjectEnabled = savedInfo.bDragObjectEnabled
  currentTargetFrameIndex = savedInfo.currentTargetFrameIndex
end
function OnUpdate(level, obj)
  UpdateThrowType()
  if DoorObject.AnimFrame < GetTargetFrame() then
    playRate = GetPlayRate()
    DoorObject:PlayAnimToFrame(GetTargetFrame(), playRate * level:GetUnitTime() * 30)
  end
  DebugUpdate(level, obj)
end
function IsEnabled()
  return bEnabled
end
function Enable()
  if bEnabled then
    return
  end
  bEnabled = true
end
function Disable()
  if not bEnabled then
    return
  end
  bEnabled = false
end
function TriggerImpulse()
  if not bEnabled then
    return
  end
  impulseFrame = DoorObject.AnimFrame
  if currentTargetFrameIndex < #targetFrames then
    currentTargetFrameIndex = currentTargetFrameIndex + 1
  end
  StopGroundShakeTimer()
  impulseAmount = math.min(playRate + GetImpulseAmount(), maxRate)
  SubmitForceFeedback(convertDistanceToFXOnHit(LD.GetDistanceBetweenTwoObjects(game.Player.FindPlayer(), thisObj)))
  timers.StartLevelTimer(0.05, SubmitGroundShake_Recursive)
  if DoorObject.AnimFrame < tonumber(targetFrames[#targetFrames]) then
    PlaySoundOnHit()
  else
    PlaySoundOnHit_Fail()
  end
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnAddImpulseCallbacks, "OnAddImpulseCallbacks for Axe Door", GetTargetFrame())
end
function DoorObject_OnAnimDone()
  DoorObject:OnAnimationDone(thisObj, "DoorObject_OnAnimDone", {Force = true})
  SubmitForceFeedback(convertDistanceToFXOnAnimDone())
  playRate = 0
  impulseAmount = 0
  maxVelocityObtained = 0
  DoorObject:PauseAnim()
  StopGroundShakeTimer()
  if currentTargetFrameIndex >= #targetFrames then
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnReachedLastTargetFrameCallbacks, "OnReachedLastTargetFrameCallbacks for Axe Door")
    game.SubObject.Sleep(thisObj)
    PlaySoundOnStopAtEnd()
  else
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, OnReachedTargetFrameCallbacks, "OnReachedTargetFrameCallbacks for Axe Door", GetTargetFrame())
    PlaySoundOnStop()
  end
end
function SubmitGroundShake_Recursive()
  if bDragObjectEnabled then
    if 0 < playRate then
      timeTilDrag = 1 - playRate / GetMaxVelocity()
      GroundShakeTimer = timers.StartLevelTimer(timeTilDrag, SubmitGroundShake_Recursive)
      SubmitForceFeedback(nil, {EffectName = "FFB_TINY", Duration = 0.15})
    else
      StopGroundShakeTimer()
    end
  end
end
function StopGroundShakeTimer()
  if GroundShakeTimer then
    GroundShakeTimer:Stop()
  end
end
function GetMaxVelocity()
  if playRate > maxVelocityObtained then
    maxVelocityObtained = playRate
  end
  return maxVelocityObtained
end
local bDebugEnabled = true
function DebugEnable()
  bDebugEnabled = true
end
function DebugDisable()
  bDebugEnabled = false
end
function DebugUpdate(level, obj)
  if engine.IsDebug() and bDebugEnabled and LD.IsPositionInFrontOfObject(thisObj:GetWorldPosition(), game.Player.FindPlayer()) and LD.GetDistanceBetweenTwoObjects(game.Player.FindPlayer(), thisObj) < 5 then
    local dt = {}
    table.insert(dt, {
      "DoorObject",
      DoorObject:GetName()
    })
    table.insert(dt, {"bEnabled", bEnabled})
    table.insert(dt, {"bPlaying", bPlaying})
    table.insert(dt, {"playRate", playRate})
    table.insert(dt, {
      "impulseAmount",
      impulseAmount
    })
    table.insert(dt, {
      "currentTargetFrameIndex",
      currentTargetFrameIndex
    })
    table.insert(dt, {
      "AnimFrame",
      DoorObject.AnimFrame
    })
    table.insert(dt, {
      "TargetFrame",
      GetTargetFrame()
    })
    table.insert(dt, {
      "ImpulseFrame",
      impulseFrame
    })
    table.insert(dt, {
      "lastThrowType",
      lastThrowType
    })
    table.insert(dt, {
      "largeImpulse",
      largeImpulse
    })
    table.insert(dt, {
      "mediumImpulse",
      mediumImpulse
    })
    table.insert(dt, {
      "smallImpulse",
      smallImpulse
    })
    table.insert(dt, {
      "timeTilDrag",
      timeTilDrag
    })
    table.insert(dt, {
      "maxVelocityObtained",
      maxVelocityObtained
    })
    table.insert(dt, {
      "targetFrames",
      LD.ConvertTableToStringList(targetFrames)
    })
    table.insert(dt, {"minRate", minRate})
    dt.TitleColor = engine.Vector.New(33, 33, 128)
    dt.TitleAlpha = 0
    dt.Title = obj:GetName() .. " Axe Door Debug"
    dt.X, dt.Y = -5, -10
    engine.DrawDebugTable(dt)
  end
end
local soundEmitter
local soundEvents = {
  OnHit = "SND_WPN_Axe_Door_Spikes_Puzzle_Hit_Big",
  OnHitFail = "SND_DOOR_Metal_Spike_Door_Open_Fail",
  OnLoop = "SND_DOOR_Spikes_Puzzle_Open_Start_LP",
  OnStop = "SND_DOOR_Spikes_Puzzle_Open_Interrupt",
  OnStopAtEnd = "SND_DOOR_Spikes_Puzzle_Open_End"
}
function SoundInit()
  soundEmitter = DoorObject:FindSingleSoundEmitterByName("SNDPanelLeft")
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
    end
  end
end
function PlaySoundOnHit()
  LD.PlaySound(soundEmitter, soundEvents.OnHit)
  LD.PlaySound(soundEmitter, soundEvents.OnLoop)
end
function PlaySoundOnHit_Fail()
  LD.PlaySound(soundEmitter, soundEvents.OnHitFail)
end
function PlaySoundOnStop()
  LD.PlaySound(soundEmitter, soundEvents.OnStop)
end
function PlaySoundOnStopAtEnd()
  LD.StopSound(soundEmitter, soundEvents.OnLoop)
  LD.PlaySound(soundEmitter, soundEvents.OnStopAtEnd)
end
