local LD = require("design.LevelDesignLibrary")
local timers = require("level.timer")
local color = require("core.color")
local thisObj, thisLevel, player, banterName, requirementsTable, delays, banterIndex, completeTable, lastBanterIndex, repeatFinalBanter, isPaused
local enableBtrError = {}
local delayTimer
local leftOverTime = 0
local hideZoneAfterFirstTrigger, hideZoneInitially, triggerOnZones, zoneObj, pauseIfNotInZone
local isInZone = false
local logPrefix = "[Banter Timed Sequence]- '"
local banterStringList, delaysString, parentTrace, onFirstBanterFire, onBanterFire, onBanterFinished, onFinalBanterFinished, requirementString
local resetOnCombatFinish = false
local waitingForCombatToEnd = false
function OnScriptLoaded(level, obj)
  local hitError = false
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  zoneObj = obj.Parent.Parent
  banterStringList = thisObj:GetLuaTableAttribute("BanterName")
  requirementString = thisObj:GetLuaTableAttribute("Requirements")
  delaysString = thisObj:GetLuaTableAttribute("Delays")
  triggerOnZones = thisObj:GetLuaTableAttribute("TriggerOnZoneEntry")
  hideZoneAfterFirstTrigger = thisObj:GetLuaTableAttribute("HideZoneAfterTriggered")
  hideZoneInitially = thisObj:GetLuaTableAttribute("HideZoneInitially")
  onFirstBanterFire = thisObj:GetLuaTableAttribute("OnFirstBanterFire")
  onBanterFire = thisObj:GetLuaTableAttribute("OnBanterFire")
  onBanterFinished = thisObj:GetLuaTableAttribute("OnBanterFinished")
  onFinalBanterFinished = thisObj:GetLuaTableAttribute("onFinalBanterFinished")
  pauseIfNotInZone = thisObj:GetLuaTableAttribute("PauseIfNotInZone")
  repeatFinalBanter = thisObj:GetLuaTableAttribute("RepeatFinalBanter")
  resetOnCombatFinish = thisObj:GetLuaTableAttribute("AutoEnableZone_IfDisabledFromCombat") and triggerOnZones
  onFirstBanterFire = LD.ExtractCallbacksForEvent(level, obj, onFirstBanterFire)
  onBanterFire = LD.ExtractCallbacksForEvent(level, obj, onBanterFire)
  onBanterFinished = LD.ExtractCallbacksForEvent(level, obj, onBanterFinished)
  onFinalBanterFinished = LD.ExtractCallbacksForEvent(level, obj, onFinalBanterFinished)
  parentTrace = "(" .. LD.GetParentTrace(thisObj) .. ")"
  banterName = LD.ConvertStringListToTable(banterStringList)
  delays = ConvertStringListToNumericTable(delaysString)
  local stringTable = LD.ConvertStringListToTable(requirementString)
  if stringTable[1] ~= nil then
    local tableEntry = {
      min = nil,
      max = nil,
      func = nil
    }
    local delimIndex = string.find(stringTable[1], "-")
    if delimIndex ~= nil then
      tableEntry.min = tonumber(string.sub(stringTable[1], 1, delimIndex - 1))
      tableEntry.max = tonumber(string.sub(stringTable[1], delimIndex + 1))
      if tableEntry.min == nil or tableEntry.max == nil then
        engine.Error("Banter Zone '" .. zoneObj:GetName() .. "' in level '" .. level.Name .. "' got cine number requirement that was not a number value [ " .. stringTable[1] .. " ]  (" .. LD.GetParentTrace(zoneObj) .. ")")
      end
    elseif tonumber(stringTable[1]) ~= nil then
      tableEntry.min = tonumber(stringTable[1])
    else
      tableEntry.func = stringTable[1]
    end
    requirementsTable = tableEntry
  end
  if requirementsTable == nil then
    requirementsTable = {min = 0}
  end
  if #banterName == 0 then
    engine.Error(logPrefix, "'", zoneObj:GetName(), "' in level '", level.Name, "' does not have any banter to play.  Add the appropriate banter to the lua toolbox or delete this refnode. ", parentTrace)
  end
  if #banterName ~= #delays then
    engine.Error(logPrefix, "'", zoneObj:GetName(), "' in level '", level.Name, "' number of time delays (", tostring(#delays), ") doesn't match number of banter names (", tostring(#banterName), ").  Add the appropriate amount of Banter lines or time delays. ", parentTrace)
  end
  if 1 < #stringTable then
    engine.Error(logPrefix, "'", zoneObj:GetName(), "' in level '", level.Name, "' has too many requirements, Requirements: [ ", requirementString, " ] . This module only takes 1 cine number requirement ", parentTrace)
  end
  if hideZoneInitially == true and triggerOnZones == false then
    engine.Warning(logPrefix, "'", zoneObj:GetName(), "' in level '", level.Name, "' HideZoneInitially is set to true, but TriggerOnZoneEntry is false. ", parentTrace)
  end
  banterIndex = 1
  isPaused = false
  if triggerOnZones == true and hitError == false then
    game.SubObject.SetEntityZoneHandler(thisObj, zoneObj)
  end
  if hitError == true then
    banterIndex = -1
  end
  for i = 1, #banterName do
    enableBtrError[banterName[i]] = true
  end
  game.SubObject.Sleep(thisObj)
end
function OnSaveCheckpoint(level, obj)
  return {
    banterIndex = banterIndex,
    isPaused = isPaused,
    waitingForCombatToEnd = waitingForCombatToEnd
  }
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  banterIndex = savedInfo.banterIndex
  isPaused = savedInfo.isPaused
  waitingForCombatToEnd = savedInfo.waitingForCombatToEnd
  if isPaused then
    Cancel()
    banterIndex = 1
  elseif 1 < banterIndex and banterIndex <= #banterName then
    Start()
  end
end
function OnFirstStart(level, obj)
  if triggerOnZones and hideZoneInitially then
    zoneObj:HideEntityVolume()
    engine.Print(logPrefix, "Zone hidden initially: '", zoneObj:GetName(), "' in level '", level.Name, "' TriggerOnZoneEntry ", tostring(triggerOnZones), parentTrace)
  end
end
function OnStart(level, obj)
  if waitingForCombatToEnd then
    waitingForCombatToEnd = true
    ShowEntityZones()
  end
end
function OnUpdate(level, obj)
  if game.Combat.GetCombatStatus() == false then
    waitingForCombatToEnd = false
    ShowEntityZones()
    game.SubObject.Sleep(thisObj)
  end
end
function OnMarkerEnterZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if markerGameObject == player and 0 < banterIndex and not isInZone and triggerOnZones then
    isInZone = true
    if isPaused and pauseIfNotInZone then
      Resume(true)
    elseif banterIndex <= #banterName then
      Start(true)
    end
  end
end
function OnMarkerExitZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if markerGameObject == player and triggerOnZones then
    isInZone = false
    if pauseIfNotInZone then
      Pause()
    end
  end
end
function CheckCanPlayDuringCombat()
  if game.Combat.GetCombatStatus() then
    if resetOnCombatFinish then
      waitingForCombatToEnd = true
      game.SubObject.Wake(thisObj)
    end
    zoneObj:HideEntityVolume()
    return false
  else
    return true
  end
end
function CheckRequirement()
  local completedCineNum = game.Level.GetVariable("CompletedCineNumber")
  if requirementsTable.func ~= nil and requirementsTable.func ~= false then
    return LD.ExecuteSingleCallbackAndReturn(thisLevel, thisObj, requirementsTable.func)
  elseif requirementsTable.max ~= nil and requirementsTable.min ~= nil then
    return completedCineNum >= requirementsTable.min and completedCineNum <= requirementsTable.max
  elseif requirementsTable.min ~= nil then
    return completedCineNum >= requirementsTable.min
  end
end
function GetLastBanterName()
  if lastBanterIndex ~= nil then
    return banterName[lastBanterIndex]
  end
  return nil
end
function ShowEntityZones()
  if zoneObj then
    zoneObj:ShowEntityVolume()
  else
    engine.Print(logPrefix, "There is no ZoneObject in level '", thisLevel.Name, "' for this BanterTimedSequence module ", parentTrace)
  end
end
function Start(startFromZone)
  if banterIndex == -1 then
    return
  end
  if isPaused then
    Resume(startFromZone)
    return
  end
  if CheckCanPlayDuringCombat() and CheckRequirement() and delayTimer == nil then
    banterIndex = 1
    isPaused = false
    if triggerOnZones and hideZoneAfterFirstTrigger and not pauseIfNotInZone then
      engine.Print(logPrefix, " Hiding entity volume(s).")
      zoneObj:HideEntityVolume()
    end
    delayTimer = timers.StartLevelTimer(delays[banterIndex], PlayNextBanter)
    if startFromZone then
      engine.Print(logPrefix, "STARTING from ZONE '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has been triggered.", parentTrace)
    else
      engine.Print(logPrefix, "STARTING '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has been triggered. Banter Names include: ", banterStringList, parentTrace)
    end
  elseif 1 < banterIndex and banterIndex <= #banterName and delayTimer ~= nil then
    engine.Print(logPrefix, " Banter is already in progress. To start the Banter Sequence from the beginning call Cancel() then Start() or StartFromBeginning()")
  else
    engine.Print(logPrefix, " FAILED '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has not met Requirements: [ ", requirementString, " ] ", parentTrace)
  end
end
function Cancel()
  if delayTimer ~= nil then
    delayTimer:Stop()
    delayTimer = nil
  end
  isPaused = false
  banterIndex = #banterName + 1
  engine.Print(logPrefix, "CANCELED'", zoneObj:GetName(), "' in level '", thisLevel.Name, "' ", parentTrace)
end
function Pause()
  if banterIndex == -1 or isPaused then
    return
  end
  if banterIndex <= #banterName or repeatFinalBanter then
    if delayTimer ~= nil then
      leftOverTime = delayTimer:GetRemainingTime()
      delayTimer:Stop()
      delayTimer = nil
    end
    isPaused = true
    engine.Print(logPrefix, "PAUSED'", zoneObj:GetName(), "' in level '", thisLevel.Name, "' ", parentTrace)
  end
end
function Resume(startFromZone)
  if banterIndex == -1 or not isPaused then
    return
  elseif banterIndex > #banterName then
    engine.Print(logPrefix, " FAILED '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' attempting to Resume Banter Time Sequence after the final banterName has played. If you want to restart the final banter name, check RepeatFinalBanter in LuaToolBox ", parentTrace)
    return
  end
  if CheckCanPlayDuringCombat() and CheckRequirement() then
    if banterIndex <= #banterName then
      engine.Print(logPrefix, " Banter in progress, Resuming sequence")
    end
    if isPaused then
      delayTimer = timers.StartLevelTimer(leftOverTime, PlayNextBanter)
    else
      delayTimer = timers.StartLevelTimer(delays[banterIndex], PlayNextBanter)
    end
    isPaused = false
    leftOverTime = 0
    if startFromZone then
      engine.Print(logPrefix, "RESUMING from ZONE '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has been triggered.", parentTrace)
    else
      engine.Print(logPrefix, "RESUMING '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has been triggered. Banter Names include: ", banterStringList, parentTrace)
    end
  else
    engine.Print(logPrefix, " FAILED '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has not met Requirements: [ ", requirementString, " ] ", parentTrace)
  end
end
function StartFromBeginning()
  if banterIndex == -1 then
    return
  end
  Cancel()
  Start()
end
function PlayNextBanter()
  if CheckCanPlayDuringCombat() and banterIndex <= #banterName then
    print("Banter ", banterIndex, " of ", #banterName)
    if banterIndex < #banterName then
      game.Audio.PlayBanter(banterName[banterIndex], function()
        LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onBanterFinished, "Banter Finished Event ")
      end, nil, enableBtrError[banterName[banterIndex]])
    else
      game.Audio.PlayBanter(banterName[banterIndex], function()
        LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onFinalBanterFinished, "Final Banter Finished Event ")
      end, nil, enableBtrError[banterName[banterIndex]])
    end
    lastBanterIndex = banterIndex
    engine.Print(logPrefix, "'", zoneObj:GetName(), "' in level '", thisLevel.Name, "' triggered.  Playing banter '", banterName[banterIndex], "'", parentTrace)
    if banterIndex == 1 then
      LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onFirstBanterFire, "First Banter Event Fired")
    end
    LD.ExecuteCallbacksForEvent(thisObj.Level, thisObj, onBanterFire, "Banter Event Fired")
    banterIndex = banterIndex + 1
    if banterIndex > #banterName then
      if repeatFinalBanter then
        banterIndex = #banterName
        delayTimer = timers.StartLevelTimer(delays[banterIndex], PlayNextBanter)
      else
        if triggerOnZones and hideZoneAfterFirstTrigger and pauseIfNotInZone then
          engine.Print(logPrefix, " Hiding entity volume(s).")
          zoneObj:HideEntityVolume()
        end
        isPaused = false
        delayTimer = nil
        engine.Print(logPrefix, " '", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has been triggered. Last banter line in sequence fired > '", banterName[banterIndex - 1], parentTrace)
      end
    else
      delayTimer = timers.StartLevelTimer(delays[banterIndex], PlayNextBanter)
    end
  end
end
function DisableConsoleError(banterKeyName)
  if enableBtrError[banterKeyName] then
    enableBtrError[banterKeyName] = false
  end
end
function ConvertStringListToNumericTable(str)
  local returnTable = {}
  local strTable = LD.ConvertStringListToTable(str)
  for _, req in ipairs(strTable) do
    local num = tonumber(req)
    if num ~= nil then
      table.insert(returnTable, tonumber(req))
    else
      engine.Error(logPrefix, "'", zoneObj:GetName(), "' in level '", thisLevel.Name, "' has Non Numerical value for delay or cine number requirement. Check Delays and Cine Number requirements in LuaToolBox . String List [ ", str, " ]", parentTrace)
      break
    end
  end
  return returnTable
end
function ShowDebugText(text)
  local debugText = text or ""
  if isPaused then
    debugText = "[ Paused ]"
  elseif 1 < banterIndex then
    debugText = "Current Banter Playing: " .. banterName[banterIndex - 1]
  end
  debugText = debugText .. [[

 InCombat: ]] .. tostring(game.Combat.GetCombatStatus()) .. "\n"
  debugText = debugText .. [[

------------LuaToolBox------------]]
  debugText = debugText .. [[

 Trigger OnZoneEntry		= ]] .. tostring(triggerOnZones)
  debugText = debugText .. [[

 Hide Zone After Triggered	= ]] .. tostring(hideZoneAfterFirstTrigger)
  debugText = debugText .. [[

 Hide Zone Initially		= ]] .. tostring(hideZoneInitially)
  debugText = debugText .. [[

 Pause If not in Zone		= ]] .. tostring(pauseIfNotInZone)
  debugText = debugText .. [[

 Repeat Final Banter		= ]] .. tostring(repeatFinalBanter)
  debugText = debugText .. [[

---------------------------------
]]
  if 0 < banterIndex and banterIndex <= #banterName then
    debugText = debugText .. [[

 ->Next Banter: ]] .. banterName[banterIndex]
    if delayTimer ~= nil then
      debugText = debugText .. [[

 ->Time till next Banter: ]] .. tostring(delayTimer:GetRemainingTime())
    elseif isPaused then
      debugText = debugText .. [[

 ->Next DelayTime: ]] .. leftOverTime
    else
      debugText = debugText .. [[

 ->Next DelayTime: ]] .. delays[banterIndex]
    end
    debugText = debugText .. [[

 ->BanterNames Left: ]] .. tostring(#banterName - banterIndex)
  elseif banterIndex > #banterName then
    debugText = " Banter Finished "
    debugText = debugText .. [[

 InCombat: ]] .. tostring(game.Combat.GetCombatStatus()) .. "\n"
  elseif banterIndex == -1 then
    debugText = "!! Hit an Error !!"
    engine.DrawTextInWorld(thisObj:GetWorldPosition(), debugText, color.white)
    return
  end
  debugText = debugText .. " index [ " .. tostring(banterIndex) .. "]"
  if requirementsTable ~= nil then
    debugText = debugText .. [[

-----------------------------
Requirements: ]]
    if requirementsTable.func ~= nil then
      debugText = debugText .. " func: " .. requirementsTable.func .. "() "
    elseif requirementsTable.max ~= nil and requirementsTable.min ~= nil then
      debugText = debugText .. " min: " .. tostring(requirementsTable.min) .. " max: " .. tostring(requirementsTable.max)
    elseif requirementsTable.min ~= nil then
      debugText = debugText .. " min: " .. tostring(requirementsTable.min)
    else
      debugText = debugText .. (requirementsTable or "nil")
    end
  end
  engine.DrawTextInWorld(thisObj:GetWorldPosition(), debugText, color.white)
end
