local LD = require("design.LevelDesignLibrary")
local class = require("core.class")
local timers = require("level.timer")
local EntityZoneEvents = class.Class("EntityZoneEvents")
function EntityZoneEvents:init()
  self.player = nil
  self.son = nil
  self.thisObj = nil
  self.thisLevel = nil
  self.zoneObject = nil
  self.markers = nil
  self.entryCallbacks = nil
  self.exitCallbacks = nil
  self.luaEntryCallbacks = nil
  self.luaExitCallbacks = nil
  self.usePlayer = nil
  self.useSon = nil
  self.entryRefractoryPeriod = nil
  self.exitRefractoryPeriod = nil
  self.onEnterCooldown = false
  self.onExitCooldown = false
  self.callbackDelay = nil
  self.timeRemaining_Enter = nil
  self.callbackDelayTimer_Enter = nil
  self.timeRemaining_Exit = nil
  self.callbackDelayTimer_Exit = nil
  self.disableAfterUse = nil
  self.triggerLimit = 0
  self.triggered = 0
  self.exitTriggerLimit = nil
  self.entryTriggerLimit = nil
  self.entriesTriggered = 0
  self.exitsTriggered = 0
  self.exitTriggersEnabled = true
  self.entryTriggersEnabled = true
  self.requirementsTable = nil
  self.requirementString = nil
end
local attributeList = {
  "disableAfterUse",
  "usePlayer",
  "useSon",
  "callbackDelay",
  "entryRefractoryPeriod",
  "exitRefractoryPeriod",
  "entryTriggerLimit",
  "exitTriggerLimit",
  "Requirements",
  "zoneEntryCallbacks",
  "zoneExitCallbacks",
  markers = "list"
}
function EntityZoneEvents:OnScriptLoaded(level, obj)
  self.thisObj = obj
  self.thisLevel = level
  local attributes = obj:GetLuaTableAttributes(attributeList)
  self.disableAfterUse = attributes.disableAfterUse
  self.usePlayer = attributes.usePlayer
  self.useSon = attributes.useSon
  self.callbackDelay = attributes.callbackDelay
  self.entryRefractoryPeriod = attributes.entryRefractoryPeriod
  self.exitRefractoryPeriod = attributes.exitRefractoryPeriod
  self.entryTriggerLimit = attributes.entryTriggerLimit
  self.exitTriggerLimit = attributes.exitTriggerLimit
  self.requirementString = attributes.Requirements
  self.markers = attributes.markers
  if self.requirementString ~= "" and self.requirementString ~= nil then
    if self.requirementsTable == nil then
      self.requirementsTable = {}
    end
    local delimIndex = string.find(self.requirementString, "-")
    if delimIndex ~= nil then
      self.requirementsTable.min = tonumber(string.sub(self.requirementString, 1, delimIndex - 1))
      self.requirementsTable.max = tonumber(string.sub(self.requirementString, delimIndex + 1))
    elseif tonumber(self.requirementString) ~= nil then
      self.requirementsTable.min = tonumber(self.requirementString)
    else
      self.requirementsTable.func = self.requirementString
    end
  end
  if self.entryTriggerLimit == 0 and self.exitTriggerLimit == 0 then
    engine.Warning("Entry Trigger Limit & Exit Trigger Limit can't be equal to 0 in toolbox for zone callback object: ", obj)
  end
  local zoneEntryCallbacks = attributes.zoneEntryCallbacks
  if zoneEntryCallbacks ~= nil then
    self.entryCallbacks = LD.ExtractCallbacksForEvent(level, obj, zoneEntryCallbacks)
  else
    self.entryCallbacks = nil
  end
  local zoneExitCallbacks = attributes.zoneExitCallbacks
  if zoneExitCallbacks ~= nil then
    self.exitCallbacks = LD.ExtractCallbacksForEvent(level, obj, zoneExitCallbacks)
  else
    self.exitCallbacks = nil
  end
  if self.usePlayer then
    if self.markers == nil then
      self.markers = {3}
    else
      self.markers[#self.markers + 1] = 3
    end
  end
  if self.useSon then
    if self.markers == nil then
      self.markers = {5}
    else
      self.markers[#self.markers + 1] = 5
    end
  end
  self.zoneObject = obj.Parent.Parent
  game.SubObject.SetEntityZoneHandler(obj, self.zoneObject)
  game.SubObject.Sleep(obj)
end
function EntityZoneEvents:OnStart(level, obj)
  if self.timeRemaining_Enter ~= nil then
    self.callbackDelayTimer_Enter = timers.StartLevelTimer(self.timeRemaining_Enter, function()
      self:FireEnterCallbacks()
    end, self)
  end
  if self.timeRemaining_Exit ~= nil then
    self.callbackDelayTimer_Exit = timers.StartLevelTimer(self.timeRemaining_Exit, function()
      self:FireExitCallbacks()
    end, self)
  end
  if self.usePlayer then
    local player = game.Player.FindPlayer()
    if player:InsideZone(self.zoneObject) then
      local playerMarker = 3
      self:OnMarkerEnterZone(level, self.thisObj, self.zoneObject, player, playerMarker)
    end
  end
  if self.useSon then
    local son = game.AI.FindSon()
    if son:InsideZone(self.zoneObject) then
      local sonMarker = 5
      self:OnMarkerEnterZone(level, self.thisObj, self.zoneObject, son, sonMarker)
    end
  end
end
function EntityZoneEvents:Enable()
  self.entryTriggersEnabled = true
  self.exitTriggersEnabled = true
end
function EntityZoneEvents:Disable()
  self.entryTriggersEnabled = false
  self.exitTriggersEnabled = false
end
function EntityZoneEvents:EnableEntryTriggers()
  self.entryTriggersEnabled = true
end
function EntityZoneEvents:EnableExitTriggers()
  self.exitTriggersEnabled = true
end
function EntityZoneEvents:DisableEntryTriggers()
  self.entryTriggersEnabled = false
end
function EntityZoneEvents:DisableExitTriggers()
  self.exitTriggersEnabled = false
end
function EntityZoneEvents:RegisterEntryCallback(fn)
  if self.luaEntryCallbacks == nil then
    self.luaEntryCallbacks = {}
  end
  self.luaEntryCallbacks[#self.luaEntryCallbacks + 1] = fn
end
function EntityZoneEvents:FireLuaEntryCallbacks(markerGameObject, markerId)
  if self.luaEntryCallbacks ~= nil then
    for _, fn in pairs(self.luaEntryCallbacks) do
      fn(markerGameObject, markerId)
    end
  end
end
function EntityZoneEvents:RegisterExitCallback(fn)
  if self.luaExitCallbacks == nil then
    self.luaExitCallbacks = {}
  end
  self.luaExitCallbacks[#self.luaExitCallbacks + 1] = fn
end
function EntityZoneEvents:FireLuaExitCallbacks(markerGameObject, markerId)
  if self.luaExitCallbacks ~= nil then
    for _, fn in pairs(self.luaExitCallbacks) do
      fn(markerGameObject, markerId)
    end
  end
end
function EntityZoneEvents:CheckCineRequirement()
  if self.requirementsTable == nil then
    return true
  end
  local completedCineNum = game.Level.GetVariable("CompletedCineNumber")
  if self.requirementsTable.func ~= nil and self.requirementsTable.func ~= false then
    return LD.ExecuteSingleCallbackAndReturn(self.thisLevel, self.thisObj, self.requirementsTable.func)
  elseif self.requirementsTable.max ~= nil and self.requirementsTable.min ~= nil then
    return completedCineNum >= self.requirementsTable.min and completedCineNum <= self.requirementsTable.max
  elseif self.requirementsTable.min ~= nil then
    return completedCineNum >= self.requirementsTable.min
  end
end
function EntityZoneEvents:CheckEntryRequirements()
  if not self.entryTriggersEnabled then
    return false
  end
  if self.onEnterCooldown then
    return false
  end
  if self.entryCallbacks == nil and self.luaEntryCallbacks == nil then
    return false
  end
  return true
end
function EntityZoneEvents:OnMarkerEnterZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if self:CheckEntryRequirements() and self:CheckCineRequirement() and self.markers then
    for _, marker in ipairs(self.markers) do
      if game.LargeInteger.HashString(marker) == markerId or game.LargeInteger.HashString(marker) == game.LargeInteger.HashString(tostring(markerId)) then
        if self.callbackDelay > 0 then
          print(" - - Marker " .. tostring(markerId) .. " entered zone " .. LD.GetParentTrace(self.thisObj.Parent.Parent) .. " with delay: " .. tostring(self.callbackDelay))
          self.callbackDelayTimer_Enter = timers.StartLevelTimer(self.callbackDelay, function()
            self:FireEnterCallbacks(markerGameObject, markerId)
          end)
        else
          self:FireEnterCallbacks(markerGameObject, markerId)
        end
        if self.disableAfterUse then
          if self.zoneObject then
            self.zoneObject:HideEntityVolume()
          end
          self:Disable()
        else
          if self.entryTriggerLimit ~= -1 then
            self.entriesTriggered = self.entriesTriggered + 1
            if self.entriesTriggered == self.entryTriggerLimit then
              self:DisableEntryTriggers()
            end
          end
          if 0 < self.entryRefractoryPeriod then
            self.onEnterCooldown = true
            LD.CallFunctionAfterDelay(function()
              self.onEnterCooldown = false
            end, self.entryRefractoryPeriod)
          end
        end
        return
      end
    end
  end
end
function EntityZoneEvents:FireEnterCallbacks(markerGameObject, markerId)
  if self.callbackDelayTimer_Enter ~= nil then
    self.callbackDelayTimer_Enter:Stop()
    self.callbackDelayTimer_Enter = nil
  end
  print(LD.GetParentTrace(self.thisObj.Parent.Parent) .. ": Entry Triggered")
  if self.entryCallbacks ~= nil then
    LD.ExecuteCallbacksForEvent(self.thisLevel, self.thisObj, self.entryCallbacks, "Zone Entry Callback for object '" .. LD.GetParentTrace(self.thisObj) .. "'", markerGameObject, markerId)
  end
  self:FireLuaEntryCallbacks(markerGameObject, markerId)
end
function EntityZoneEvents:CheckExitRequirements()
  if not self.exitTriggersEnabled then
    return false
  end
  if self.onExitCooldown then
    return false
  end
  if self.exitCallbacks == nil and self.luaExitCallbacks == nil then
    return false
  end
  return true
end
function EntityZoneEvents:OnMarkerExitZone(level, scriptObj, volumeGameObject, markerGameObject, markerId)
  if self:CheckExitRequirements() and self:CheckCineRequirement() and self.markers then
    for _, marker in ipairs(self.markers) do
      if game.LargeInteger.HashString(marker) == markerId or game.LargeInteger.HashString(marker) == game.LargeInteger.HashString(tostring(markerId)) then
        if self.callbackDelay > 0 then
          print(" - - Marker " .. tostring(markerId) .. " exited zone " .. LD.GetParentTrace(self.thisObj.Parent.Parent) .. " with delay: " .. tostring(self.callbackDelay))
          self.callbackDelayTimer_Exit = timers.StartLevelTimer(self.callbackDelay, function()
            self:FireExitCallbacks(markerGameObject, markerId)
          end)
        else
          self:FireExitCallbacks(markerGameObject, markerId)
        end
        if self.disableAfterUse then
          if self.zoneObject then
            self.zoneObject:HideEntityVolume()
          end
          self:Disable()
        else
          if self.exitTriggerLimit ~= -1 then
            self.exitsTriggered = self.exitsTriggered + 1
            if self.exitsTriggered == self.exitTriggerLimit then
              self:DisableExitTriggers()
            end
          end
          if 0 < self.exitRefractoryPeriod then
            self.onExitCooldown = true
            LD.CallFunctionAfterDelay(function()
              self.onExitCooldown = false
            end, self.exitRefractoryPeriod)
          end
        end
        return
      end
    end
  end
end
function EntityZoneEvents:FireExitCallbacks(markerGameObject, markerId)
  if self.callbackDelayTimer_Exit ~= nil then
    self.callbackDelayTimer_Exit:Stop()
    self.callbackDelayTimer_Exit = nil
  end
  if self.exitCallbacks ~= nil then
    print(LD.GetParentTrace(self.thisObj.Parent.Parent) .. ": Exit Triggered")
    LD.ExecuteCallbacksForEvent(self.thisLevel, self.thisObj, self.exitCallbacks, "Zone Exit Callback for object '" .. LD.GetParentTrace(self.thisObj) .. "'", markerGameObject, markerId)
  end
  self:FireLuaExitCallbacks(markerGameObject, markerId)
end
function EntityZoneEvents:SetEntryTriggerLimit(num)
  self.entryTriggerLimit = num
end
function EntityZoneEvents:SetDisableAfterUse(bool)
  self.disableAfterUse = bool
end
function EntityZoneEvents:SetZoneVisibility(bool)
  if bool == true then
    self.zoneObject:ShowEntityVolume()
  else
    self.zoneObject:HideEntityVolume()
  end
end
function EntityZoneEvents:OnSaveCheckpoint(level, obj)
  self.timeRemaining_Enter = nil
  self.timeRemaining_Exit = nil
  if self.callbackDelayTimer_Enter ~= nil and self.callbackDelayTimer_Enter.running then
    self.timeRemaining_Enter = self.callbackDelayTimer_Enter:GetRemainingTime()
  end
  if self.callbackDelayTimer_Exit ~= nil and self.callbackDelayTimer_Exit.running then
    self.timeRemaining_Exit = self.callbackDelayTimer_Exit:GetRemainingTime()
  end
  return {
    entryTriggersEnabled = self.entryTriggersEnabled,
    exitTriggersEnabled = self.exitTriggersEnabled,
    timeRemaining_Enter = self.timeRemaining_Enter,
    timeRemaining_Exit = self.timeRemaining_Exit
  }
end
function EntityZoneEvents:OnRestoreCheckpoint(level, obj, savedInfo)
  self.entryTriggersEnabled = savedInfo.entryTriggersEnabled
  self.exitTriggersEnabled = savedInfo.exitTriggersEnabled
  self.timeRemaining_Enter = savedInfo.timeRemaining_Enter
  self.timeRemaining_Exit = savedInfo.timeRemaining_Exit
end
local New = function(_ENV)
  local self = EntityZoneEvents.New()
  for functionName in _ENV.pairs(EntityZoneEvents) do
    _ENV[functionName] = function(...)
      return self[functionName](self, ...)
    end
  end
  return self
end
return {New = New}
