local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local thisLevel, thisObj
local enabled = true
local usesMultiCollision, onInitialHitCallbacks, onHitCallbacks, onCooldownCompleteCallbacks, affectRange
local crystalDead = false
local isHazardType
local buildUpTime = 1
local rumbleParams, shakeParams, affectedObjects, cooldown
local crystalCooledDown = true
cooldownTimer = nil
local aimAssist, aimAssistAdded, lights, mainObj, idleFX
function OnScriptLoaded(level, obj)
  obj:AddMarker("VibrateCrystalObj")
  obj:AddMarker("SonTarget")
  thisLevel = level
  thisObj = obj
  cooldown = obj:GetLuaTableAttribute("Cooldown")
  affectRange = obj:GetLuaTableAttribute("BlastRadius")
  aimAssist = obj:GetLuaTableAttribute("AimAssist")
  usesMultiCollision = obj:FindLuaTableAttribute("usesMultiCollision")
  mainObj = obj:FindSingleGOByName("PreMesh")
  if mainObj ~= nil then
    mainObj:JumpAnimToFrame(0)
    mainObj:PauseAnim()
  end
  lights = obj:FindSingleGOByName("lights")
  isHazardType = obj:FindLuaTableAttribute("isHazardType")
  idleFX = obj:FindSingleGOByName("vibratecrystal_idle")
  if obj:FindLuaTableAttribute("AffectedObjects") ~= nil and obj:FindLuaTableAttribute("AffectedObjects") ~= "" then
    affectedObjects = level:FindGameObjects(tostring(obj:FindLuaTableAttribute("AffectedObjects") .. "*"))
  end
  onInitialHitCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnProjectileInitialHit"))
  onHitCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnProjectileHit"))
  onCooldownCompleteCallbacks = LD.ExtractCallbacksForEvent(level, obj, obj:FindLuaTableAttribute("OnCooldownComplete"))
  enabled = obj:GetLuaTableAttribute("StartsEnabled")
  cooldownTimer = timers.StartLevelTimer(cooldown, OnCooldownEnd)
  cooldownTimer:Stop()
  rumbleParams = {
    EffectName = "FFB_GROUNDSHAKE_BUILDUP_START",
    Duration = buildUpTime
  }
  shakeParams = {
    EffectName = "FSE_CRYSTAL_EXPLODE",
    Duration = buildUpTime
  }
  if not crystalDead then
    SoundInit()
  end
  if enabled then
    thisObj:RemoveAimAssist({
      Joint = "aimAssistJ",
      Angle = 12,
      MaxRadius = 1.4
    })
    EnableObject(true)
  end
  game.SubObject.Sleep(thisObj)
end
function OnStart(level, obj)
  if crystalDead then
    Deactivate()
  end
end
function CrystalHitByProjectile()
  if crystalCooledDown then
    local buildUpFx = game.FX.Spawn("vibrate_buildUp", thisLevel, {
      GameObject = thisObj,
      Joint = "aimAssistJ",
      Follow = true,
      AutoDelete = true
    })
    crystalCooledDown = false
    game.FX.SubmitEffect(rumbleParams)
    game.FX.SubmitEffect(shakeParams)
    if mainObj ~= nil then
      mainObj:PlayAnimToFrame(60)
      mainObj:SetAnimRate(1)
    end
    if lights ~= nil then
      lights:JumpAnimToFrame(0)
      lights:PlayAnimToFrame(60)
    end
    timers.StartLevelTimer(buildUpTime, CrystalExplode)
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, onInitialHitCallbacks, "CrystalInitialHitByProjectile")
    EnableObject(false)
    cooldownTimer:Restart()
    PlayVibrateSound()
  end
end
function CrystalExplode()
  LD.ExecuteCallbacksForEvent(thisLevel, thisObj, onHitCallbacks, "CrystalHitByProjectile")
  local intactExplosionFX = game.FX.Spawn("vibrate_explosion_root", thisLevel, {
    GameObject = thisObj,
    Joint = "aimAssistJ",
    Follow = true,
    AutoDelete = true
  })
  TriggerAffectedObjects()
  game.FX.SubmitEffect(rumbleParams)
  game.FX.SubmitEffect(shakeParams)
  if isHazardType then
    local concussionParams = {
      Tweak = "CNC_VibrateExplosion_Large",
      WorldLocation = thisObj:GetWorldPosition(),
      GameObject = thisObj,
      EnemyId = game.Player.FindPlayer():GetID()
    }
    game.Combat.PlayConcussion(concussionParams)
  else
    local concussionParams = {
      Tweak = "CNC_VibrateExplosion",
      WorldLocation = thisObj:GetWorldPosition(),
      GameObject = thisObj,
      EnemyId = game.Player.FindPlayer():GetID()
    }
    game.Combat.PlayConcussion(concussionParams)
  end
  PlaySoundOnBreak()
  ChainEffect()
  ChainEffectBells()
end
function TriggerAffectedObjects()
  if affectedObjects ~= nil then
    for i = 1, #affectedObjects do
      if affectedObjects[i] ~= nil and affectedObjects[i] ~= "" then
        if affectedObjects[i].Child ~= nil then
          if affectedObjects[i].Child:GetBreakable() ~= nil then
            local breakableComponent = affectedObjects[i].Child:GetBreakable()
            if breakableComponent.Broken == false then
              breakableComponent:Break()
            end
          end
        elseif affectedObjects[i]:GetBreakable() ~= nil then
          local breakableComponent = affectedObjects[i]:GetBreakable()
          if breakableComponent.Broken == false then
            breakableComponent:Break()
          end
        end
        affectedObjects[i]:JumpAnimToFrame(0)
        affectedObjects[i]:PlayAnimToEnd()
      end
    end
  end
end
function ChainEffectBells()
  local levelBells = thisLevel:FindGameObjectsByMarker("BellTarget")
  local proximityCount = 0
  for i = 1, #levelBells do
    if levelBells[i] ~= thisObj and game.AIUtil.Distance(thisObj, levelBells[i]) < affectRange then
      proximityCount = proximityCount + 1
      if proximityCount % 2 == 0 then
        levelBells[i].LuaObjectScript.HitFront()
      else
        levelBells[i].LuaObjectScript.HitBack()
      end
    end
  end
end
function ChainEffect()
  local levelVibrateCrystals = thisLevel:FindGameObjectsByMarker("VibrateCrystalObj")
  for i = 1, #levelVibrateCrystals do
    if levelVibrateCrystals[i] ~= thisObj and levelVibrateCrystals[i].LuaObjectScript ~= nil and levelVibrateCrystals[i].LuaObjectScript.GetCrystalAvailable() and (thisObj:GetWorldPosition() - levelVibrateCrystals[i]:GetWorldPosition()):Length() < affectRange and levelVibrateCrystals[i].LuaObjectScript ~= nil then
      levelVibrateCrystals[i].LuaObjectScript.CrystalHitByProjectile(thisLevel, thisObj)
    end
  end
end
function OnCooldownEnd()
  if not crystalDead then
    if mainObj ~= nil then
      mainObj:JumpAnimToFrame(60)
      mainObj:PlayAnimToEnd()
    end
    if lights ~= nil then
      lights:JumpAnimToFrame(60)
      lights:PlayAnimToEnd()
    end
    timers.StartLevelTimer(0.65, function()
      EnableObject(true)
    end)
    LD.ExecuteCallbacksForEvent(thisLevel, thisObj, onCooldownCompleteCallbacks, "CooldownFinished")
    enabled = true
  end
end
function OnHitByArrow(level, go, arrow_owner, arrow_go, arrow_tweak)
  if arrow_go:GetName() == "electricshock_arrow_notrail" and enabled then
    CrystalHitByProjectile(level, go)
  end
end
function OnHitByWeapon(level, obj, weapon, attacker)
  if attacker == game.Player.FindPlayer() then
    game.Audio.IncrementBanterFact("HitBifrost", 1, 10)
    game.Audio.PlayBanterNonCritical("ca_carry_crystal_weapon_hit")
  end
end
function EnableObject(data)
  if not crystalDead then
    if data == false then
      enabled = false
      thisObj:RemoveAimAssist({
        Joint = "aimAssistJ",
        Angle = 12,
        MaxRadius = 1.4
      })
      aimAssistAdded = false
      if idleFX ~= nil then
        idleFX:Hide()
      end
      if usesMultiCollision then
        thisObj:ShowJoint(thisObj:GetJointIndex("disabledCollision"))
        thisObj:HideJoint(thisObj:GetJointIndex("enabledCollision"))
      end
    elseif data == true then
      enabled = true
      if mainObj ~= nil then
        mainObj:JumpAnimToFrame(0)
        mainObj:PauseAnim()
      end
      crystalCooledDown = true
      if usesMultiCollision then
        thisObj:HideJoint(thisObj:GetJointIndex("disabledCollision"))
        thisObj:ShowJoint(thisObj:GetJointIndex("enabledCollision"))
      end
      if lights ~= nil then
        lights:Show()
      end
      if idleFX ~= nil then
        idleFX:Show()
      end
      if aimAssist and not aimAssistAdded then
        thisObj:AddAimAssist({
          Joint = "aimAssistJ",
          Angle = 12,
          MaxRadius = 1.4,
          ZoomSnapScreenAngle = 12,
          ZoomSnapRadius = 1.4,
          MaxDistance = -1
        })
        aimAssistAdded = true
      end
    end
  end
end
function GetCrystalAvailable()
  if crystalCooledDown and enabled then
    return true
  else
    return false
  end
end
function Enable()
  enabled = true
end
function Disable()
  enabled = false
end
function Deactivate()
  cooldownTimer:Stop()
  crystalDead = true
  enabled = false
  if idleFX ~= nil then
    idleFX:Hide()
  end
  if lights ~= nil then
    lights:Hide()
  end
  if usesMultiCollision then
    thisObj:ShowJoint(thisObj:GetJointIndex("disabledCollision"))
    thisObj:HideJoint(thisObj:GetJointIndex("enabledCollision"))
  end
  mainObj = thisObj:FindSingleGOByName("PreMesh")
  if mainObj ~= nil then
    mainObj:JumpAnimToFrame(60)
    mainObj:PauseAnim()
  end
end
function OnSaveCheckpoint(level, obj)
  return {crystalDead = crystalDead}
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  crystalDead = savedInfo.crystalDead
end
local soundEmitter
local soundEvents = {
  isLooping = false,
  IdleLoop = "SND_WPN_Bifrost_Bomb_Idle_LP",
  OnBreak = "SND_WPN_Bifrost_Bomb_Explo",
  OnVibrate = "SND_WPN_Bifrost_Bomb_Explo_Build_Up_LP"
}
function SoundInit()
  soundEmitter = thisObj:FindSingleSoundEmitterByName("SNDBifrostVibrate")
  LD.PlayRestartableSoundLoop(soundEmitter, soundEvents.IdleLoop)
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 PlaySoundOnBreak()
  LD.PlaySound(soundEmitter, soundEvents.OnBreak)
  LD.StopSound(soundEmitter, soundEvents.OnVibrate)
  LD.PlayRestartableSoundLoop(soundEmitter, soundEvents.IdleLoop)
end
function PlayVibrateSound()
  LD.StopRestartableSoundLoop(soundEmitter, soundEvents.IdleLoop)
  LD.PlaySound(soundEmitter, soundEvents.OnVibrate)
end
