local LD = require("design.LevelDesignLibrary")
local CCOS, thisObj, player
local interactZoneTable = {}
local switchState = 1
local triggerCinematic = false
local interactIndex = 1
local isInteracting = false
local retractingLeverAnims, enabled
local locked = false
local toggleable
local jointsTable = {}
local heroBranch = {}
local heroBranchToggleOff = {}
local objectAnimName, objectAnimNameToggleOff, isSwitchFloor, interactStartTriggers, interactFinishTriggers, cineInteractionTriggers, interactOnSwitchTriggers, switchedOn_OnStart, switchedOff_OnStart, switchOnOff_List, switchComplete_list, onTurnForwardSound, onTurnBackwardSound, preInteract_Callbacks, strCamInteractApproach, camera_InteractApproach, InteractApproachTable, cameraOverrideYaw
local bSkipInteractApproachYaw = false
local cameraWorldSpaceForward
local cameraSubmitDuration = 1.8
local bCancelCameraIfAiming = false
local fCameraApproachPitch
local FORCE_OVERRIDE = {Force = true}
local CCOS_LoadLibrary = function()
  if CCOS == nil then
    CCOS = require("camera.camera_oneshot")
  end
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  cameraWorldSpaceForward = engine.Vector.New(0, 0, 1)
  player = game.Player:FindPlayer()
  local attributes = obj:GetLuaTableAttributes({
    "startsEnabled",
    "toggleable",
    "interactStartTriggers",
    "interactTriggers",
    "switchedOnTriggers",
    "switchedOffTriggers",
    "interactionOnFinish",
    "switchCompleteTriggers",
    "isSwitchFloor",
    "cineInteractionTriggers"
  })
  enabled = attributes.startsEnabled
  toggleable = attributes.toggleable
  interactStartTriggers = attributes.interactStartTriggers
  interactOnSwitchTriggers = attributes.interactTriggers
  switchedOn_OnStart = attributes.switchedOnTriggers
  switchedOff_OnStart = attributes.switchedOffTriggers
  interactFinishTriggers = attributes.interactionOnFinish
  switchComplete_list = attributes.switchCompleteTriggers
  isSwitchFloor = attributes.isSwitchFloor
  cineInteractionTriggers = attributes.cineInteractionTriggers
  local interactAngle, hintAngle
  if isSwitchFloor then
    interactAngle = 120
    hintAngle = 120
    jointsTable = {
      {
        synch1 = "synchJoint1b",
        synch2 = "synchJoint1a",
        prompt = "promptJoint1"
      },
      {
        synch1 = "synchJoint2b",
        synch2 = "synchJoint2a",
        prompt = "promptJoint2"
      },
      {
        synch1 = "synchJoint3b",
        synch2 = "synchJoint3a",
        prompt = "promptJoint3"
      }
    }
    heroBranch = "BRA_SwitchFloor"
    heroBranchToggleOff = "BRA_SwitchFloor_ToggleOff"
    objectAnimName = "envHorLever2HandsOff"
    objectAnimNameToggleOff = "envHorLever2HandsOn"
  else
    interactAngle = 180
    hintAngle = 180
    jointsTable[1] = {
      synch1 = "synchJoint",
      synch2 = "synchJoint",
      prompt = "promptJoint"
    }
    heroBranch = "BRA_SwitchWall"
    heroBranchToggleOff = "BRA_SwitchWall_ToggleOff"
    objectAnimName = "envVertLever2HandsOff"
    objectAnimNameToggleOff = "envVertLever2HandsOn"
  end
  if cineInteractionTriggers then
    cineInteractionTriggers = LD.ExtractCallbacksForEvent(level, obj, cineInteractionTriggers)
  end
  interactOnSwitchTriggers = LD.ExtractCallbacksForEvent(level, obj, interactOnSwitchTriggers)
  interactStartTriggers = LD.ExtractCallbacksForEvent(level, obj, interactStartTriggers)
  interactFinishTriggers = LD.ExtractCallbacksForEvent(level, obj, interactFinishTriggers)
  switchedOn_OnStart = LD.ExtractCallbacksForEvent(level, obj, switchedOn_OnStart)
  switchedOff_OnStart = LD.ExtractCallbacksForEvent(level, obj, switchedOff_OnStart)
  switchComplete_list = LD.ExtractCallbacksForEvent(level, obj, switchComplete_list)
  local objWithPromptJoint = thisObj
  retractingLeverAnims = thisObj:FindGOsByName("LeverAnimGroup")
  if retractingLeverAnims and 0 < #retractingLeverAnims and not isSwitchFloor then
    for i = 1, #retractingLeverAnims do
      if retractingLeverAnims[i]:FindJointIndex(jointsTable[1].prompt) ~= nil then
        objWithPromptJoint = retractingLeverAnims[i]
        break
      end
    end
  else
    retractingLeverAnims = nil
  end
  for i = 1, #jointsTable do
    local interactZone = LD.CreateInteractZone_Standard_180(objWithPromptJoint, jointsTable[i].prompt)
    interactZone:SetRequiresSonUnoccupied(true)
    interactZone:SetAngle(interactAngle)
    interactZone:SetHintAngle(hintAngle)
    LD.EnableInteractZoneGlint(interactZone)
    interactZone:SetDebugOffset(0, 0.5 * i, 0)
    table.insert(interactZoneTable, interactZone)
  end
  interactZoneTable.hasMultiple = true
  game.SubObject.Sleep(obj)
  SoundInit()
end
function OnStart(level, obj)
  if not enabled then
    Disable()
  else
    Enable()
  end
  if not locked then
    Unlock()
  else
    Lock()
  end
end
function OnUpdate(level, obj)
  if camera_InteractApproach ~= nil then
    if bCancelCameraIfAiming == true and player ~= nil and player:HasMarker("Aiming") then
      DestroyInteractCamera()
    else
      camera_InteractApproach:Update()
    end
  end
end
function OnUseWorld(level, obj)
  for i = 1, #interactZoneTable do
    if interactZoneTable[i]:PlayerCanInteract() then
      if cineInteractionTriggers then
        LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, cineInteractionTriggers, "Cine Interaction Event")
      end
      ExecutePreInteractCallbacks(i)
      if triggerCinematic == false then
        interactIndex = i
        player:RequestInteract(obj, interactZoneTable[i])
      end
      triggerCinematic = false
      return
    end
  end
end
function ForcePerformInteraction()
  player:RequestInteract(thisObj, interactZoneTable[interactIndex])
  triggerCinematic = false
end
function OnInteractStart(level, obj)
  LD.ExecuteCallbacksForEvent(thisObj, thisObj, interactStartTriggers, "interact start")
  PerformInteraction(interactIndex)
  CreateInteractApproachCamera()
  if camera_InteractApproach ~= nil then
    game.SubObject.Wake(thisObj)
    camera_InteractApproach:SetCallback(DestroyInteractCamera)
    camera_InteractApproach:Start()
  end
  print("Interact start")
  isInteracting = true
end
function CreateInteractApproachCamera()
  if isSwitchFloor == nil then
    if strCamInteractApproach == nil then
      strCamInteractApproach = "ENV_Interact_WallSwitch_LS"
    end
    if fCameraApproachPitch ~= nil then
      SetInteractApproachTableValue("Pitch", fCameraApproachPitch)
    elseif bSkipInteractApproachYaw == false then
      local kCHOSENPITCHVALUE = -12
      SetInteractApproachTableValue("Pitch", kCHOSENPITCHVALUE)
    end
    if cameraOverrideYaw ~= nil then
      SetInteractApproachTableValue("Yaw", cameraOverrideYaw)
    elseif bSkipInteractApproachYaw == false then
      local playerPosition = player:GetWorldPosition()
      local interactPosition = thisObj:GetWorldPosition()
      local toInteract = interactPosition - playerPosition
      local interactFwd = thisObj:GetWorldForward()
      local toInteractAngle
      if toInteract:Dot(interactFwd) > 0 then
        print("Approaching from front")
        toInteractAngle = LD.GetAngleBetweenVector(-interactFwd, cameraWorldSpaceForward)
      else
        print("Approaching from behind")
        toInteractAngle = LD.GetAngleBetweenVector(interactFwd, cameraWorldSpaceForward)
      end
      local yawOffset = 0
      if strCamInteractApproach == "ENV_Interact_WallSwitch_LS" or strCamInteractApproach == "ENV_Interact_WallSwitch_LS_Wide" then
        yawOffset = -25
      else
        yawOffset = 25
      end
      SetInteractApproachTableValue("Yaw", toInteractAngle + yawOffset)
      print("InteractApproachTable: ", toInteractAngle)
    end
    game.Camera.Recenter({
      TimeStart = 0,
      TimeDuration = 2,
      LockRecenter = 1,
      YawRange = -1,
      TriggerLeft = 0,
      TriggerRight = 0,
      ReturnLeft = 180,
      ReturnRight = -180,
      PitchRange = -1,
      ReturnUp = 20,
      ReturnDown = -20
    })
    CCOS_LoadLibrary()
    camera_InteractApproach = CCOS.OneShotCamera.New(strCamInteractApproach, cameraSubmitDuration, InteractApproachTable)
  else
    if cameraOverrideYaw ~= nil then
      SetInteractApproachTableValue("Yaw", cameraOverrideYaw)
    end
    if strCamInteractApproach ~= nil then
      CCOS_LoadLibrary()
      camera_InteractApproach = CCOS.OneShotCamera.New(strCamInteractApproach, cameraSubmitDuration, InteractApproachTable)
    end
  end
end
function OnInteractAbort()
end
function OnInteractFinish()
  if interactFinishTriggers ~= nil then
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactFinishTriggers, "Interaction Event Finish")
  end
end
function OnInteractDone(level, obj)
  print("Interact complete")
  isInteracting = false
end
function PerformInteraction(index)
  if switchState == 1 then
    switchState = 2
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactOnSwitchTriggers, "Interaction Event ")
    LD.PlaySingleSynchMove_KratosObject(thisObj, jointsTable[index].synch1, "basicSwitch" .. thisObj:GetName(), heroBranch, objectAnimName, interactZoneTable, not toggleable, nil, {completion_percentage = 50})
  elseif switchState == 2 then
    switchState = 1
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, interactOnSwitchTriggers, "Interaction Event ")
    LD.PlaySingleSynchMove_KratosObject(thisObj, jointsTable[index].synch2, "basicSwitch" .. thisObj:GetName(), heroBranchToggleOff, objectAnimNameToggleOff, interactZoneTable, not toggleable, nil, {completion_percentage = 50})
  end
end
function IsSwitchEnabled()
  return enabled
end
function ToggleState()
  if switchState == 1 then
    thisObj:StartAnimation({Animation = objectAnimName})
    switchState = 2
    if not isInteracting then
      PlaySwitchForwardSoundOnFrame()
    end
  elseif switchState == 2 then
    thisObj:StartAnimation({Animation = objectAnimNameToggleOff})
    switchState = 1
    if not isInteracting then
      PlaySwitchBackwardSoundOnFrame()
    end
  end
end
function SetIsInteracting(boolinteract)
  isInteracting = boolinteract
end
function Enable()
  enabled = true
  if interactZoneTable ~= {} then
    if not retractingLeverAnims then
      for i = 1, #interactZoneTable do
        interactZoneTable[i]:Enable()
      end
    else
      Extend_Switch()
    end
  end
end
function Disable(forceRetract)
  enabled = false
  if interactZoneTable ~= {} then
    for i = 1, #interactZoneTable do
      interactZoneTable[i]:Disable()
    end
    if forceRetract then
      Retract_Switch()
    end
  end
end
function Lock()
  locked = true
  for i = 1, #interactZoneTable do
    interactZoneTable[i]:Lock()
  end
end
function Unlock()
  locked = false
  for i = 1, #interactZoneTable do
    interactZoneTable[i]:Unlock()
  end
end
function RemoveInteract(index)
  if 0 <= index and index <= #interactZoneTable then
    local interactZone = table.remove(interactZoneTable, index)
    table.remove(jointsTable, index)
    interactZone:Disable()
  end
end
function GetInteractZoneTable()
  return interactZoneTable
end
function Retract_Switch()
  if retractingLeverAnims and enabled == false then
    retractingLeverAnims[1]:ClearAllAnimCallbacks()
    for i = 1, #retractingLeverAnims do
      if retractingLeverAnims[i].AnimFrame <= retractingLeverAnims[i].AnimLengthFrames then
        retractingLeverAnims[i]:PlayAnimToEnd()
      end
    end
  end
end
function Extend_Switch()
  if retractingLeverAnims then
    for i = 1, #retractingLeverAnims do
      if retractingLeverAnims[i].AnimFrame > 0 then
        retractingLeverAnims[i]:PlayAnimToFrame(0, -1)
      end
    end
    retractingLeverAnims[1]:OnAnimationDone(thisObj, "EnableInteract", FORCE_OVERRIDE)
  end
end
function EnableInteract()
  retractingLeverAnims[1]:ClearAllAnimCallbacks()
  for i = 1, #interactZoneTable do
    interactZoneTable[i]:Enable()
  end
end
function SwitchIsBeingTurnedOn()
  return switchState == 2
end
function SwitchIsBeingTurnedOff()
  return switchState == 1
end
function EnableCinematicTrigger()
  triggerCinematic = true
end
function DisableCinematicTrigger()
  triggerCinematic = false
end
function OnSaveCheckpoint(level, obj)
  return {
    switchState = switchState,
    toggleable = toggleable,
    enabled = enabled,
    locked = locked
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  switchState = savedInfo.switchState
  toggleable = savedInfo.toggleable
  enabled = savedInfo.enabled
  locked = savedInfo.locked
end
function LuaHook_RunCallbacks_SwitchedOn()
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, switchedOn_OnStart, "Interaction Event - SwitchedOn OnStart")
  ExecuteSwitchOnOffCallbacks()
  PlaySwitchForwardSound()
end
function LuaHook_RunCallbacks_SwitchedOff()
  LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, switchedOff_OnStart, "Interaction Event - SwitchedOff OnStart")
  ExecuteSwitchOnOffCallbacks()
  PlaySwitchBackwardSound()
end
function LuaHook_RunCallbacks_SwitchedComplete()
  if not toggleable then
    Disable(true)
  end
  ExecuteSwitchedCompleteCallbacks()
end
function AddPreInteractCallback(fn)
  if preInteract_Callbacks == nil then
    preInteract_Callbacks = {}
  end
  table.insert(preInteract_Callbacks, fn)
end
function ExecutePreInteractCallbacks(_interactIndex)
  interactIndex = _interactIndex
  if preInteract_Callbacks ~= nil then
    for _, callback in ipairs(preInteract_Callbacks) do
      callback()
    end
  end
end
function AddSwitchOnOffCallback(fn)
  if switchOnOff_List == nil then
    switchOnOff_List = {}
  end
  table.insert(switchOnOff_List, fn)
end
function ExecuteSwitchOnOffCallbacks()
  if switchOnOff_List ~= nil then
    for _, callback in ipairs(switchOnOff_List) do
      callback()
    end
  end
end
function AddSwitchedCompleteCallback(fn)
  if switchComplete_list == nil then
    switchComplete_list = {}
  end
  table.insert(switchComplete_list, fn)
end
function ExecuteSwitchedCompleteCallbacks()
  if switchComplete_list ~= nil then
    for _, callback in ipairs(switchComplete_list) do
      callback()
    end
  end
end
local soundEvents = {
  SoundEmitter = nil,
  OnForward = "SND_MECH_Lever_Dial_Turn",
  OnBackward = "SND_MECH_Lever_Dial_Turn",
  OnForwardAnimFrame = 0,
  OnBackwardAnimFrame = 28
}
local soundOverrideEvents = {SoundEmitter = nil}
function SoundInit()
  soundEvents.SoundEmitter = thisObj:FindSingleSoundEmitterByName("SNDSwitch")
  soundOverrideEvents.SoundEmitter = thisObj:FindSingleSoundEmitterByName("SNDSwitch")
  onTurnForwardSound = thisObj:FindLuaTableAttribute("soundSwitchForward")
  onTurnBackwardSound = thisObj:FindLuaTableAttribute("soundSwitchBackward")
end
function PlaySwitchForwardSound()
  if onTurnForwardSound ~= nil and onTurnForwardSound ~= "" then
    LD.PlaySound(soundOverrideEvents.SoundEmitter, onTurnForwardSound)
  else
    LD.PlaySound(soundEvents.SoundEmitter, soundEvents.OnForward)
  end
end
function PlaySwitchBackwardSound()
  if onTurnBackwardSound ~= nil and onTurnBackwardSound ~= "" then
    LD.PlaySound(soundOverrideEvents.SoundEmitter, onTurnBackwardSound)
  else
    LD.PlaySound(soundEvents.SoundEmitter, soundEvents.OnBackward)
  end
end
function PlaySwitchForwardSoundOnFrame()
  LD.PlaySoundOnFrame(soundEvents.SoundEmitter, thisObj, soundEvents.OnForward, soundEvents.OnForwardAnimFrame)
end
function PlaySwitchBackwardSoundOnFrame()
  LD.PlaySoundOnFrame(soundEvents.SoundEmitter, thisObj, soundEvents.OnBackward, soundEvents.OnBackwardAnimFrame)
end
function OverideInteractApproachYaw(val)
  bSkipInteractApproachYaw = val
end
function OverrideDefaultCameraYaw(bVal, overrideYaw)
  bSkipInteractApproachYaw = bVal
  if bVal == true and overrideYaw ~= nil then
    cameraOverrideYaw = overrideYaw
  end
end
function SetCameraApproachPitch(fVal)
  fCameraApproachPitch = fVal
end
function OverrideCameraInteractApproach(newCamera)
  strCamInteractApproach = newCamera
end
function OverrideCameraSubmissionTime(newTime)
  cameraSubmitDuration = newTime
end
function OverrideCancelCameraIfAiming(bValue)
  bCancelCameraIfAiming = bValue
end
function DestroyInteractCamera()
  camera_InteractApproach = nil
  game.SubObject.Sleep(thisObj)
end
function SetInteractApproachTableValue(property, value)
  if InteractApproachTable == nil then
    InteractApproachTable = {}
  end
  InteractApproachTable[property] = value
end
function ShowDebugTable(X, Y, Title)
  local debugTable = {}
  debugTable.Title = Title or "Basic Switch Info"
  debugTable.X = 10
  debugTable.Y = 10
  debugTable.TitleColor = engine.Vector.New(20, 0, 128)
  debugTable[1] = {
    "AnimFrame: ",
    thisObj.AnimFrame
  }
  engine.DrawDebugTable(debugTable)
end
