local LD = require("design.LevelDesignLibrary")
local timer = require("level.timer")
local thisObj, thisLevel, player, validDropPosition
local combatTagsActive = false
local attachedToPlayer = false
local frontInteractZone, carryActiveIconTable, carryStaticIconTable, pickupInProgress
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisObj:HidePhysics()
  player = game.Player.FindPlayer()
  frontInteractZone = LD.CreateInteractZone_Carryable_360(thisObj, "synchJoint")
  frontInteractZone:SetPromptJoint("promptJointForward")
  carryActiveIconTable = {
    normal = "WORLD_INTERACT_CARRY",
    unavailable = "WORLD_INTERACT_UNAVAILABLE",
    locked = "WORLD_INTERACT_LOCKED",
    hint = "WORLD_INTERACT_HINT"
  }
  carryStaticIconTable = {
    normal = "WORLD_INTERACT",
    unavailable = "WORLD_INTERACT_UNAVAILABLE",
    locked = "WORLD_INTERACT_LOCKED",
    hint = "WORLD_INTERACT_HINT"
  }
  if frontInteractZone.SetRequiresPlayerGrounded then
    frontInteractZone:SetRequiresPlayerGrounded(true)
  end
  if frontInteractZone.SetPlayerMaxYDifference then
    frontInteractZone:SetPlayerMaxYDifference(0.1)
  end
  game.SubObject.SetUpdateDisableDistance(thisObj, 10)
  SoundInit()
end
function OnUseWorld(level, obj)
  if frontInteractZone:PlayerCanInteract() then
    player:RequestInteract(thisObj, frontInteractZone)
  end
end
function OnUpdate(level, obj)
  CheckForHitReact()
  if game.Combat.GetCombatStatus and not attachedToPlayer and game.Combat.GetCombatStatus() and not combatTagsActive then
    SetCombatTags()
  end
end
function DrainGotActivated()
  if frontInteractZone and not player:HasMarker("Carrying") and not pickupInProgress == true then
    frontInteractZone:Lock()
  end
end
function DrainGotDeactivated()
  if frontInteractZone then
    frontInteractZone:Unlock()
  end
end
function SetCombatTags()
  combatTagsActive = true
  frontInteractZone:SetTags("NotInCombat")
end
function ClearCombatTags()
  combatTagsActive = false
  frontInteractZone:ClearTags("NotInCombat")
end
function LuaHook_CarryStarted()
  game.SubObject.Wake(thisObj)
  ClearCombatTags()
  OnPickedUp()
end
function OnInteractStart(level, obj, creature)
  pickupInProgress = true
  if not attachedToPlayer then
    StartPickupCarry()
    HideObjectCollision()
    player:MarkCurrentWeaponMode()
  elseif (not player.IsSafeToDropCarryBlock or player:IsSafeToDropCarryBlock()) and not player:HasMarker("NoDropZone") then
    StartCarryExit()
    attachedToPlayer = false
  else
    player:EndInteract()
  end
end
function OnInteractAbort(level, obj, creature)
  LD.CallFunctionAfterDelay(EnableInteract, 1)
  if player:PickupIsActive("CarryObjectClear") and attachedToPlayer then
    SnapCarryToGround()
  end
end
function OnInteractFinish(level, obj, creature)
end
function OnInteractDone(level, obj, creature)
end
function OnPickedUp()
  game.Interact.DisableTags("CarryActive")
  attachedToPlayer = true
  EnableInteract()
  HideObjectCollision()
  frontInteractZone:SetPromptIconSet(carryActiveIconTable)
  PlaySoundOnCarry()
end
function OnPutDown()
  pickupInProgress = false
  game.Interact.EnableTags("CarryActive")
  attachedToPlayer = false
  if game.Combat.GetCombatStatus() == false then
    timer.StartLevelTimer(0.5, ShowObjectCollision)
  end
  Enable()
  frontInteractZone:SetPromptIconSet(carryStaticIconTable)
  PlaySoundOnDropped()
  SetCombatTags()
end
function DisableInteract()
  if thisObj.PropDisable ~= nil then
    thisObj:PropDisable()
  end
end
function EnableInteract()
  if thisObj.PropDisable ~= nil then
    thisObj:PropEnable()
  end
end
function CheckForHitReact()
  if player:PickupIsActive("CarryObjectClear") and attachedToPlayer then
    CheckHitPosition()
  elseif player:HasMarker("CarryHitReactFront") and attachedToPlayer then
    SnapCarryToGround()
  elseif player:HasMarker("CarryHitReactBack") and attachedToPlayer then
    SnapCarryToGround()
  end
end
function CheckHitPosition()
  local jointIndex = player:GetJointIndex("zeroJoint")
  validDropPosition = player:GetWorldJointPosition(jointIndex)
  player:PickupRelinquish("CarryObjectClear")
end
function SnapCarryToGround()
  game.Interact.EnableTags("CarryActive")
  SetCombatTags()
  thisObj:SetWorldPosition(validDropPosition)
  attachedToPlayer = false
  LD.CallFunctionAfterDelay(OnPutDown, 2)
end
function Enable()
  if frontInteractZone ~= nil then
    frontInteractZone:Enable()
  end
end
function Disable()
  if not player:HasMarker("Carrying") and frontInteractZone ~= nil then
    frontInteractZone:Disable()
  end
end
function HideObjectCollision()
  thisObj:HideCollision()
end
function ShowObjectCollision()
  thisObj:ShowCollision()
end
function StartPickupCarry()
  local linkJointIndex = thisObj:GetJointIndex("linkJoint")
  LD.SetJointFacingTowardObject(thisObj, player, linkJointIndex, "")
  local jointIndex = thisObj:GetJointIndex("synchJoint")
  LD.SetJointFacingTowardObject(thisObj, player, jointIndex, "Sync")
end
function Sync()
  if thisObj.RegisterAsProp ~= nil then
    thisObj:RegisterAsProp("CarryDrain")
  end
  LD.PlaySingleSynchMove_KratosObject(thisObj, "synchJoint", "Carry Place Enter", "BRA_CarryPlaceEnter", "", frontInteractZone, false, "Bare", {completion_percentage = 1})
end
function StartCarryExit()
  OnPutDown()
  local pos, dir = FindValidDropLocation()
  SetMotionWarpDrivers(pos, dir)
  player:RequestInteract(thisObj)
  player:TriggerMoveEvent("DropCarryBlock")
end
function LuaHook_DropOnGround()
  local concussionParams = {
    Tweak = "CNC_VibrateBreakables",
    WorldLocation = player:GetWorldPosition() + player:GetWorldForward(),
    GameObject = thisObj,
    EnemyId = player:GetID()
  }
  game.Combat.PlayConcussion(concussionParams)
end
function FindValidDropLocation()
  local debug_Duration = 10
  local capsuleLength = 1.5
  local capsuleWidth = 0.125
  local yVecOffset_Upper = engine.Vector.New(0, 0.5, 0)
  local hit_F_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_F_endPos = player:GetWorldPosition() + player:GetWorldForward() * capsuleLength + yVecOffset_Upper
  local hit_F = game.World.SpherecastCollision(hit_F_startPos, hit_F_endPos, capsuleWidth, {
    SourceGameObject = player,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local hit_L_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_L_endPos = player:GetWorldPosition() + (player:GetWorldForward() * 2 + player:GetWorldLeft()):Normalized() * capsuleLength + yVecOffset_Upper
  local hit_L = game.World.SpherecastCollision(hit_L_startPos, hit_L_endPos, capsuleWidth, {
    SourceGameObject = player,
    ExcludeGameObject = thisObj,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local hit_R_startPos = player:GetWorldPosition() + yVecOffset_Upper
  local hit_R_endPos = player:GetWorldPosition() + (player:GetWorldForward() * 2 + player:GetWorldLeft() * -1):Normalized() * capsuleLength + yVecOffset_Upper
  local hit_R = game.World.SpherecastCollision(hit_R_startPos, hit_R_endPos, capsuleWidth, {
    SourceGameObject = player,
    ExcludeGameObject = thisObj,
    EntityType = game.CollisionType.New("kEnvironment", "kPlayer", "kRagdoll"),
    CollidesWith = game.CollisionType.New("kEnvironment", "kPlayer", "kThrowable")
  }, true)
  local warpPosition = player:GetWorldPosition()
  local warpRotation = player:GetWorldForward()
  local dropOffset = 1.6
  if not (not hit_F or hit_R or hit_L) or hit_F and hit_R and hit_L then
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif not hit_F and hit_L and hit_R then
    dropOffset = 1.3
    warpPosition = LD.GetPointAlongLine(player:GetWorldPosition() + player:GetWorldForward(), player:GetWorldPosition(), dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif hit_L and not hit_R and not hit_F then
    warpRotation = player:GetWorldForward() - player:GetWorldLeft() / 2
  elseif hit_R and not hit_F and not hit_L then
    warpRotation = player:GetWorldForward() + player:GetWorldLeft() / 2
  elseif hit_F and hit_L and not hit_R then
    dropOffset = 1.3
    warpRotation = player:GetWorldForward() - player:GetWorldLeft() / 2
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  elseif hit_F and hit_R and not hit_L then
    dropOffset = 1.3
    warpRotation = player:GetWorldForward() + player:GetWorldLeft() / 2
    warpPosition = LD.GetPointAlongLine(hit_F.Position, hit_F_startPos, dropOffset)
    warpPosition.y = player:GetWorldPosition().y
  end
  if engine.IsDebug() then
    engine.DrawSphere(warpPosition, 0.2, 255, debug_Duration)
    engine.DrawLine(warpPosition, warpPosition + warpRotation * 1, 255, debug_Duration)
    engine.DrawLine(hit_F_startPos, hit_F_endPos, 16711680, debug_Duration)
    if hit_F then
      engine.DrawSphere(hit_F.Position, 0.05, 65280, debug_Duration)
    end
    engine.DrawLine(hit_L_startPos, hit_L_endPos, 16711680, debug_Duration)
    if hit_L then
      engine.DrawSphere(hit_L.Position, 0.05, 65280, debug_Duration)
    end
    engine.DrawLine(hit_R_startPos, hit_R_endPos, 16711680, debug_Duration)
    if hit_R then
      engine.DrawSphere(hit_R.Position, 0.05, 65280, debug_Duration)
    end
  end
  return warpPosition, warpRotation
end
local translationAnimDriver, rotationAnimDriver
function SetMotionWarpDrivers(pos, dir)
  translationAnimDriver = player:GetAnimDriver("WarpTranslation")
  translationAnimDriver.ValueVec = pos
  rotationAnimDriver = player:GetAnimDriver("WarpRotation")
  rotationAnimDriver.ValueVec = dir
end
function EnableRigidBody()
  thisObj:ShowPhysics()
end
function DisableRigidBody()
  thisObj:HidePhysics()
end
function OnSaveCheckpoint(level, obj)
  return {
    worldPosition = obj:GetWorldPosition(),
    worldForward = obj:GetWorldForward()
  }
end
function OnRestoreCheckpoint(level, obj, tab)
  obj:SetWorldPosition(tab.worldPosition)
  obj:SetWorldFacing(tab.worldForward)
end
local soundEmitter
local soundEvents = {
  OnCarry = "SND_MECH_Hel_Wind_Totem_Pick_Up",
  OnDropped = "SND_MECH_Hel_Wind_Totem_Set_Down",
  OnShoulder = "SND_MECH_Hel_Wind_Totem_Shoulder_Set"
}
function SoundInit()
  local drainPoint = thisObj:FindSingleGOByName("DrainPoint")
  if drainPoint ~= nil then
    soundEmitter = drainPoint:FindSingleSoundEmitterByName("SNDBifrostDrain")
  end
end
function PlaySoundOnCarry()
  LD.PlaySoundAfterDelay(soundEmitter, soundEvents.OnPickedUp, 2.0E-4)
  LD.PlaySoundAfterDelay(soundEmitter, soundEvents.OnShoulder, 0)
end
function PlaySoundOnDropped()
  LD.PlaySoundAfterDelay(soundEmitter, soundEvents.OnDropped, 0.5)
end
