local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local monitors = require("level.MonitorLibrary")
local thisObj, player
local enabled = false
local state, startFrame
local startClosed = false
local instaKillFrame, closeFrame
local killCloseFrameOffset = 5
local closeSpeed
local openFrame = 0
local openSpeed
local animSpeed = 1
local animMonitor, delayTimer
local timeRemaining = -1
local closeDelay, openDelay, combatCollisionObj
local instaKillCombatCol = "Traps_CrusherBlock_InstantKill"
local orientation, crusherObj
local instaKill_Immediate = false
local instaKillOn = true
local playerInCrushArea = false
local crusherString
local log = ""
function OnScriptLoaded(level, obj)
  thisObj = obj
  player = game.Player.FindPlayer()
  enabled = thisObj:GetLuaTableAttribute("StartEnabled")
  combatCollisionObj = thisObj:FindSingleGOByName("CombatCollision")
  crusherString = thisObj:GetLuaTableAttribute("CrusherDistance")
  closeFrame = ConvertMetersToFrames(crusherString)
  instaKillOn = thisObj:GetLuaTableAttribute("IsInstantKill_On")
  orientation = thisObj:GetLuaTableAttribute("Orientation")
  if instaKillOn == true then
    instaKillFrame = ConvertInstaKillToFrames(crusherString)
  end
  startFrame = thisObj:GetLuaTableAttribute("StartDistance")
  startFrame = ConvertMetersToFrames(startFrame)
  openDelay = thisObj:GetLuaTableAttribute("StayOpen_Delay")
  closeDelay = thisObj:GetLuaTableAttribute("StayClose_Delay")
  instaKillCombatCol = instaKillCombatCol .. "_" .. orientation
  game.SubObject.SetEntityZoneHandler(thisObj, thisObj)
  SoundInit()
  game.SubObject.Sleep(thisObj)
  game.SubObject.SetForgetOnCheckpoint(obj)
  obj:SetInhibitSave()
end
function OnStart(level, obj)
  ResetTrap()
  SetAnimationSpeed(crusherString)
  ResetAnimMonitor()
  if enabled == true then
    Enable()
  else
    Disable()
  end
end
function ResetTrap()
  if 0 <= startFrame then
    if startFrame > closeFrame then
      startFrame = closeFrame
    end
    thisObj:JumpAnimToFrame(startFrame)
    thisObj:PauseAnim()
  end
  if startFrame < closeFrame then
    state = "Opening"
  else
    state = "Closing"
  end
  delayTimer = nil
end
function Enable()
  enabled = true
  if animMonitor ~= nil then
    animMonitor:Start()
    animMonitor:SetPrintsEnabled(false)
  end
  if instaKill_Immediate == true then
    SetToInstantKill()
  end
  RestartAnimation()
  StartAnimSounds()
end
function Disable()
  enabled = false
  thisObj:ClearAllAnimCallbacks()
  thisObj:PauseAnim()
  if animMonitor ~= nil then
    animMonitor:Stop()
  end
  if delayTimer ~= nil and delayTimer.running == true and delayTimer:GetRemainingTime() > 0 then
    delayTimer:Pause()
  end
  StopAnimSounds()
end
function OnMarkerEnterZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if markerGameObject == player and (game.LargeInteger.HashString(3) == markerId or game.LargeInteger.HashString(3) == game.LargeInteger.HashString(tostring(markerId))) then
    playerInCrushArea = true
  end
end
function OnMarkerExitZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if markerGameObject == player and (game.LargeInteger.HashString(3) == markerId or game.LargeInteger.HashString(3) == game.LargeInteger.HashString(tostring(markerId))) then
    playerInCrushArea = false
  end
end
function OpenCrusherWithDelay()
  DisableCombatCollision()
  state = "Closed"
  delayTimer = timers.StartLevelTimer(closeDelay, OpenCrusher)
end
function OpenCrusher()
  state = "Opening"
  DisableCombatCollision()
  thisObj:PlayAnimToFrame(openFrame, -1 * animSpeed)
  thisObj:ClearAllAnimCallbacks()
  thisObj:OnAnimDone(thisObj, "CloseCrusherWithDelay")
end
function CloseCrusherWithDelay()
  state = "Opened"
  delayTimer = timers.StartLevelTimer(openDelay, CloseCrusher)
end
function CloseCrusher()
  state = "Closing"
  SetToInstantKill()
  local closeFrameCheck = closeFrame - killCloseFrameOffset - 1
  if closeFrameCheck <= thisObj.AnimFrame then
    CheckToSeeIfCrusherWillKill()
  else
    thisObj:PlayAnimationToFrame(closeFrameCheck, {Rate = animSpeed})
    thisObj:OnAnimationDone(thisObj, "CheckToSeeIfCrusherWillKill", {Force = true})
  end
end
function CheckToSeeIfCrusherWillKill()
  if IsPlayingCrusherDeathMove() or playerInCrushArea then
    thisObj:PlayAnimationToFrame(closeFrame - killCloseFrameOffset, {Rate = animSpeed})
  else
    thisObj:PlayAnimationToFrame(closeFrame, {Rate = animSpeed})
    thisObj:OnAnimationDone(thisObj, "OpenCrusherWithDelay", {Force = true})
  end
end
local deathMovs = {
  "MOV_DeathFlattenHorizontal",
  "MOV_DeathFlattenFront"
}
function IsPlayingCrusherDeathMove()
  for _, v in ipairs(deathMovs) do
    if player:IsPlayingMove(v) then
      return true
    end
  end
  return false
end
function ChangeCrusherDistance(crusherDistance)
  closeFrame = ConvertMetersToFrames(crusherDistance)
  instaKillFrame = ConvertInstaKillToFrames(crusherDistance)
  RestartAnimation()
  ResetAnimMonitor()
end
function ConvertMetersToFrames(distance)
  local frame = -1
  return 10 * tonumber(string.sub(distance, 1, 1))
end
function ConvertInstaKillToFrames(distance)
  local frame = -1
  if distance == "1m" or distance == "2m" then
    frame = 0
    instaKill_Immediate = true
  elseif orientation == "Vertical" then
    if distance == "3m" then
      instaKill_Immediate = true
      frame = ConvertMetersToFrames("1m")
    elseif distance == "4m" then
      frame = ConvertMetersToFrames("2m")
    elseif distance == "5m" then
      frame = ConvertMetersToFrames("3m")
    elseif distance == "6m" then
      frame = ConvertMetersToFrames("4m")
    elseif distance == "7m" then
      frame = ConvertMetersToFrames("5m")
    elseif distance == "8m" then
      frame = ConvertMetersToFrames("6m")
    end
  elseif orientation == "Horizontal" then
    if distance == "3m" then
      instaKill_Immediate = true
      frame = ConvertMetersToFrames("2m")
    elseif distance == "4m" then
      frame = ConvertMetersToFrames("3m")
    elseif distance == "5m" then
      frame = ConvertMetersToFrames("4m")
    elseif distance == "6m" then
      frame = ConvertMetersToFrames("5m")
    elseif distance == "7m" then
      frame = ConvertMetersToFrames("6m")
    elseif distance == "8m" then
      frame = ConvertMetersToFrames("7m")
    end
  end
  return frame
end
function SetToInstantKill()
  log = "Instant Kill"
  combatCollisionObj.LuaObjectScript.ChangeCombatCollisionType(instaKillCombatCol)
  if not combatCollisionObj.LuaObjectScript.IsEnabled() then
    combatCollisionObj.LuaObjectScript.Enable()
  end
end
function DisableCombatCollision()
  log = "None"
  combatCollisionObj.LuaObjectScript.Disable()
end
function SetAnimationSpeed(distance)
  local baseSpeed = 4
  animSpeed = baseSpeed * (ConvertMetersToFrames(distance) / 40)
end
function RestartAnimation()
  thisObj:ClearAllAnimCallbacks()
  thisObj:PauseAnim()
  if delayTimer ~= nil and delayTimer.running == false and delayTimer:GetRemainingTime() > 0 then
    delayTimer:Unpause()
  elseif state == "Closing" or state == nil then
    CloseCrusher()
  elseif state == "Closed" then
    if 0 < timeRemaining then
      timeRemaining = -1
      delayTimer = timers.StartLevelTimer(timeRemaining, OpenCrusher)
    else
      CloseCrusherWithDelay()
    end
  elseif state == "Opening" then
    OpenCrusher()
  elseif state == "Opened" then
    if 0 < timeRemaining then
      timeRemaining = -1
      delayTimer = timers.StartLevelTimer(timeRemaining, CloseCrusher)
    else
      OpenCrusherWithDelay()
    end
  end
end
function ResetAnimMonitor()
  if animMonitor ~= nil then
    animMonitor:Stop()
    animMonitor:Terminate()
    animMonitor = nil
  end
  animMonitor = monitors.CreateAnimFrameMonitor(thisObj)
  animMonitor:SetPrintsEnabled(false)
  if instaKillOn == true then
    if instaKill_Immediate == false then
      animMonitor:OnFrameForward(instaKillFrame, SetToInstantKill)
    elseif instaKill_Immediate == true then
      SetToInstantKill()
    end
  end
end
function ForceOpenCrusher()
  state = "Opening"
  Disable()
  thisObj:JumpAnimToFrame(openFrame)
  thisObj:PauseAnim()
end
function ForceCloseCrusher()
  state = "Closing"
  Disable()
  thisObj:JumpAnimToFrame(closeFrame)
  thisObj:PauseAnim()
end
function SetToSingleSidedKill()
  SetKillCloseFrame(5)
end
function SetToDoubleSidedKill()
  SetKillCloseFrame(3)
end
function SetKillCloseFrame(frame)
  if frame then
    killCloseFrameOffset = frame
  end
end
function ShowDebugTable(x, y)
  local debugTable = {}
  debugTable.Title = "Crusher Block Info"
  debugTable.X = x or 120
  debugTable.Y = y or 10
  debugTable.TitleColor = engine.Vector.New(255, 0, 128)
  debugTable[#debugTable + 1] = {
    " ",
    thisObj.Parent:GetName()
  }
  debugTable[#debugTable + 1] = {
    "AnimFrame: ",
    thisObj.AnimFrame
  }
  debugTable[#debugTable + 1] = {
    "CrusherDistance: ",
    crusherString
  }
  debugTable[#debugTable + 1] = {"Log  ", log}
  debugTable[#debugTable + 1] = {
    "IsLoopingSound  ",
    GetSoundEvents().isLooping
  }
  if delayTimer ~= nil then
    debugTable[#debugTable + 1] = {
      "RemainingTime ",
      delayTimer:GetRemainingTime()
    }
  end
  debugTable[#debugTable + 1] = {
    "CheckPointed Time ",
    timeRemaining
  }
  engine.DrawDebugTable(debugTable)
end
local soundEmitter, soundOnOpen, soundOnClose, CrusherSoundActionMonitor
local soundEvents = {
  isLooping = true,
  OnRetract = "SND_TRAP_Block_Crusher_Retract_LP",
  OnRetractHit = "SND_TRAP_Block_Crusher_Retract_Hit_Top",
  OnProtract = "SND_TRAP_Block_Crusher_Protract_LP",
  OnProtractHit = "SND_TRAP_Block_Crusher_Protract_Hit_Bottom"
}
local soundAnimActions = {}
function SoundInit()
  soundEmitter = thisObj.SoundEmitters[1]
  SoundSetup(soundEvents)
end
function SoundSetup(sounds)
  if sounds ~= nil then
    if sounds.SoundEmitter ~= nil then
      soundEmitter = thisObj:FindSingleSoundEmitterByName(sounds.SoundEmitter)
    end
    for k, v in pairs(soundEvents) do
      for i, j in pairs(sounds) do
        if i == k and j ~= nil and j ~= "" then
          soundEvents[k] = j
        end
      end
      LD.SoundDebug(tostring(k) .. ": " .. tostring(v))
    end
  end
  if soundEvents.isLooping then
    soundAnimActions = {
      {
        action = "Play",
        frame = openFrame + 3,
        emitter = soundEmitter,
        direction = "forward",
        soundEvent = soundEvents.OnProtract
      },
      {
        action = "Stop",
        frame = closeFrame - 4,
        emitter = soundEmitter,
        direction = "forward",
        soundEvent = soundEvents.OnProtract
      },
      {
        action = "Play",
        frame = closeFrame - 3,
        emitter = soundEmitter,
        direction = "forward",
        soundEvent = soundEvents.OnProtractHit
      },
      {
        action = "Play",
        frame = closeFrame - 3,
        emitter = soundEmitter,
        direction = "backward",
        soundEvent = soundEvents.OnRetract
      },
      {
        action = "Stop",
        frame = openFrame + 4,
        emitter = soundEmitter,
        direction = "backward",
        soundEvent = soundEvents.OnRetract
      },
      {
        action = "Play",
        frame = openFrame + 3,
        emitter = soundEmitter,
        direction = "backward",
        soundEvent = soundEvents.OnRetractHit
      }
    }
  else
    soundAnimActions = {
      {
        action = "Play",
        frame = openFrame + 3,
        emitter = soundEmitter,
        direction = "forward",
        soundEvent = soundEvents.OnProtract
      },
      {
        action = "Play",
        frame = closeFrame - 3,
        emitter = soundEmitter,
        direction = "forward",
        soundEvent = soundEvents.OnProtractHit
      },
      {
        action = "Play",
        frame = closeFrame - 3,
        emitter = soundEmitter,
        direction = "backward",
        soundEvent = soundEvents.OnRetract
      },
      {
        action = "Play",
        frame = openFrame + 3,
        emitter = soundEmitter,
        direction = "backward",
        soundEvent = soundEvents.OnRetractHit
      }
    }
  end
  CrusherSoundActionMonitor = monitors.CreateAudioAnimFrameMonitor(thisObj)
  CrusherSoundActionMonitor:AddSoundActionList(soundAnimActions)
end
function StopAnimSounds()
  if CrusherSoundActionMonitor ~= nil then
    CrusherSoundActionMonitor:Stop()
  end
end
function StartAnimSounds()
  if CrusherSoundActionMonitor ~= nil then
    CrusherSoundActionMonitor:Start()
  end
end
function GetSoundEvents()
  return soundEvents
end
