local class = require("core.class")
local state = require("ai.state")
local DL = require("design.DesignerLibrary")
local lookAtConsts = require("game.lookAtConsts")
local Unaware = class.Class("Unaware_Base", state.State)
_G.lookAtPriorityOverrides = {}
local motion_patrol, motion_wander, motion_wanderStart, motion_idle = 0, 1, 2, 3
local SetAggro = function(ai, global, constants)
  if global.target ~= nil and global.target:OnActiveTraversePath() or game.Player.FindPlayer():HasMarker("QuestGiverInteract") then
    if global.target ~= nil and global.target:OnActiveTraversePath() then
      engine.Warning("Entering aggro state while target is on traverse path.")
    end
    if game.Player.FindPlayer():HasMarker("QuestGiverInteract") then
      engine.Warning("Entering aggro state while QuestGiverInteract is active.")
    end
    return
  end
  DL.BroadcastAggro(ai, global, constants)
  DL.SendAggroEvent(global.target, ai)
  global.aggroState = "INAGGRO"
  if ai.OwnedPOI ~= nil then
    ai.OwnedPOI:SendEvent("BreakOut")
  end
end
local GetSpeed = function(global)
  local speed = StartConfig.SpeedType
  if speed == "Jog" then
    return global.navData.navSpeedJog
  elseif speed == "Run" then
    return 4
  end
  return global.navData.navSpeedWalk
end
local GetPatrolObject = function(ai, global)
  local spawnerLevel = game.FindLevel(StartConfig.thisLevel)
  if spawnerLevel == nil then
    engine.Warning("Unable to find spawner level ", StartConfig.thisLevel)
    return
  end
  local patrolObject = spawnerLevel:FindSingleGameObject(StartConfig.PatrolWanderPath)
  if patrolObject == nil then
    engine.Warning("Unable to find patrol/wander object ", StartConfig.PatrolWanderPath)
    return
  end
  return patrolObject
end
local GetPatrolType = function()
  if StartConfig.NonCombatState == "Patrol_Loop" then
    return 1
  elseif StartConfig.NonCombatState == "Patrol_Loop_Reverse" then
    return 2
  end
  return 0
end
local InitPatrolBehavior = function(ai, global, unawareState)
  local patrolObject = GetPatrolObject(ai, global)
  if unawareState then
    if patrolObject == nil then
      unawareState.failedToInit = true
      engine.Warning("patrolObject is nil! See previous warning for more info. Patrol will not run.")
      return
    else
      unawareState.failedToInit = nil
    end
  end
  local patrol_wps = {}
  for _, v in pairs(patrolObject.Children) do
    local waypoint_name = v:GetName()
    local order = tonumber(waypoint_name:match("0*(%d+)"))
    patrol_wps[order] = v
  end
  local speed = GetSpeed(global)
  local patrolType = GetPatrolType()
  global.forcedPath = game.NavPath.New(patrol_wps)
  ai:SetForcedPath(patrolType, speed, global.forcedPath)
end
local InitWanderBehavior = function(ai, global)
  local patrolObject = GetPatrolObject(ai, global)
  local potentialPoints = patrolObject.Children
  if global.wanderDestination ~= nil then
    for i = 1, #potentialPoints do
      if potentialPoints[i] == global.wanderDestination then
        engine.DrawSphere(global.wanderDestination:GetWorldPosition(), 0.75, 32768, 8)
        table.remove(potentialPoints, i)
        break
      end
    end
  end
  local waypoint = potentialPoints[math.random(#potentialPoints)]
  if waypoint == global.wanderDestination or waypoint == nil then
    engine.Error("Destination is same as previous location. This is bad.")
  end
  global.forcedPath = game.NavPath.New({waypoint})
  local speed = GetSpeed(global)
  engine.DrawSphere(waypoint:GetWorldPosition(), 1.25, 15728640, 8)
  ai:SetForcedPath(0, speed, global.forcedPath)
  global.wanderDestination = waypoint
  global.idleTime = 0
end
local UpdateAggro = function(self, ai, global, constants)
  global.target = ai:FindTarget(self.unawareTargetParams)
  if global.target ~= nil and global.target:OnActiveTraversePath() or game.Player.FindPlayer():HasMarker("QuestGiverInteract") then
    return
  end
  if game.AIUtil.Distance(self.goPlayerCreature, ai) < self.sixthSenseDistance then
    global.target = self.goPlayerCreature
  end
  if global.target ~= nil and not global.target:OnActiveTraversePath() then
    SetAggro(ai, global, constants)
    return
  end
  local axe = self.goPlayerCreature.Axe
  if axe ~= nil then
    local distToAxe
    if axe.ThrowOutStatus == tweaks.tThrowOutStatus.eThrownWeaponStatus.kTOSInFlightOut or axe.ThrowOutStatus == tweaks.tThrowOutStatus.eThrownWeaponStatus.kTOSFalling then
      distToAxe = game.AIUtil.Distance(axe, ai)
    end
    if distToAxe ~= nil and distToAxe < self.axeDetectionRange then
      global.target = self.goPlayerCreature
      if global.target ~= nil and not global.target:OnActiveTraversePath() then
        SetAggro(ai, global, constants)
        return
      end
    end
  end
end
function Unaware:init()
  self.goPlayerCreature = game.Player.FindPlayer()
  self.sixthSenseDistance = self.sixthSenseDistance or 3
  self.unawareTargetParams = self.unawareTargetParams or "FIND_TARGET_PARAMETERS_UNAWARE"
  self.axeDetectionRange = self.axeDetectionRange or 5
end
function Unaware:Enter(ai, global, constants)
  if constants.unawareNavBank ~= nil then
    ai:SetNavBank(constants.unawareNavBank)
  end
  self.awarenessReceiveRange = global.awarenessReceiveRange or 30
  self.awarenessBroadcastRange = global.awarenessBroadcastRange or 100
  if global.forcedPath ~= nil then
    ai:PauseForcedPath(false)
    ai:SetFocus(global.patrolFacing, true)
  elseif StartConfig ~= nil and StartConfig.NonCombatState ~= nil and StartConfig.NonCombatState ~= "Idle" then
    if StartConfig.PatrolWanderPath == "" then
      engine.Warning("No data assign to enemy spawner for patrol or wander.")
      return
    end
    if StartConfig.NonCombatState == "Wander" then
      InitWanderBehavior(ai, global)
      constants.motionType = motion_wanderStart
    elseif string.find(StartConfig.NonCombatState, "Patrol") ~= nil then
      InitPatrolBehavior(ai, global, self)
      constants.motionType = motion_patrol
    end
  end
  _G.lookAtPriorityOverrides.Hero = ai:AddLookAtPriorityOverride(lookAtConsts.TargetType.Hero, lookAtConsts.Priority.Ignore)
  _G.lookAtPriorityOverrides.Kid = ai:AddLookAtPriorityOverride(lookAtConsts.TargetType.Kid, lookAtConsts.Priority.Ignore)
end
function Unaware:Update(ai, global, constants)
  if constants.motionType ~= nil and constants.motionType > motion_patrol then
    local locomotionInfo = ai:GetLocomotionInfo()
    if constants.motionType == motion_wanderStart then
      if locomotionInfo.PathLength > 0 then
        constants.motionType = motion_wander
      end
    elseif constants.motionType == motion_wander then
      if locomotionInfo.TargetSpeed < 0.1 then
        constants.motionType = motion_idle
        constants.wanderIdleTime = math.random(StartConfig.WanderIdleMinTime, math.max(StartConfig.WanderIdleMinTime, StartConfig.WanderIdleMaxTime))
      end
    elseif constants.motionType == motion_idle then
      global.idleTime = global.idleTime + ai:GetFrameTime()
      if global.idleTime > constants.wanderIdleTime then
        InitWanderBehavior(ai, global)
        constants.motionType = motion_wanderStart
      end
    end
  elseif constants.motionType ~= nil and constants.motionType == motion_patrol and self.failedToInit then
    if self.retryPatrolTimer == nil then
      self.retryPatrolTimer = 2
    else
      self.retryPatrolTimer = self.retryPatrolTimer - ai:GetFrameTime()
    end
    if 0 >= self.retryPatrolTimer then
      InitPatrolBehavior(ai, global, self)
    end
  end
end
function Unaware:UpdateData(ai, global, constants)
  if global.aggroState == "UNAWAREIGNORECOMBAT" or global.aggroState == "UNAWAREIGNORETARGET" or constants.isSimple ~= nil and constants.isSimple then
    return
  end
  if self:IsActive() or ai.OwnedPOI ~= nil and global.forcedPath ~= nil then
    UpdateAggro(self, ai, global, constants)
  end
end
function Unaware.Events:OnHitReaction(event, ai, global, constants)
  if global.aggroState == "UNAWAREIGNORECOMBAT" and global.aggroState ~= "UNAWAREIGNORETARGET" or constants.isSimple ~= nil and constants.isSimple then
    return
  end
  if global.aggroState ~= "INCOMBAT" and ai:HasMarker("RestrictLuaReaction") and (ai:HasHitFlag("HIT_SON_MAGIC", event.hitFlags) or event.enemyId == DL.HashCreatureID(ai, "SON00")) then
    return
  end
  if self:IsActive() or global.forcedPath ~= nil and ai.OwnedPOI ~= nil then
    ai:SetNavBank(global.navBank)
    global.target = self.goPlayerCreature
    SetAggro(ai, global, constants)
  end
end
function Unaware.Events:OnBroadcastReaction(event, ai, global, constants)
  if self:IsActive() then
    if constants.isSimple ~= nil and constants.isSimple then
      return true
    end
    if global.target ~= nil and global.target:OnActiveTraversePath() or game.Player.FindPlayer():HasMarker("QuestGiverInteract") then
      return true
    end
    local source = event.source or game.Player.FindPlayer()
    local distanceFromBroadcastingObject = game.AIUtil.Distance(source, ai)
    if DL.CheckCreatureContext(event.broadcastContext, "GRUNT_CRY") and distanceFromBroadcastingObject < self.awarenessReceiveRange then
      DL.BroadcastAggroSecondary(ai, global, constants)
      global.target = self.goPlayerCreature
      SetAggro(ai, global, constants)
    end
    return true
  end
end
function Unaware:Exit(ai, global, constants)
  if ai.OwnedPOI ~= nil then
    ai:PauseForcedPath(true)
  else
    ai:SetNavBank(global.navBank)
    ai:ClearForcedPath()
  end
end
function LuaHook_StartUnaware(ai, branch)
  local locomotionInfo = ai:GetLocomotionInfo()
  local targetSpeed = locomotionInfo.TargetSpeed
  local pathAngle = locomotionInfo.PathAngle
  if pathAngle == nil or targetSpeed <= 0.1 then
    return
  end
  if -60 < pathAngle and pathAngle < 60 then
    return branch:FindOutcomeBranchesEntry("MOV_WalkStart")
  end
  if pathAngle <= -60 then
    return branch:FindOutcomeBranchesEntry("MOV_WalkStartTurn120L")
  end
  if 60 <= pathAngle then
    return branch:FindOutcomeBranchesEntry("MOV_WalkStartTurn120R")
  end
end
function LuaHook_UnawareStop(ai, branch)
  if ai:GetLocomotionInfo().TargetSpeed > 0 then
    return nil
  end
  local angle = ai:GetLocomotionInfo().FocusAngle or ai:GetWorldForward()
  local returnMove
  if angle < -60 then
    returnMove = "MOV_WalkStopTurn120L"
  elseif 60 < angle then
    returnMove = "MOV_WalkStopTurn120R"
  else
    returnMove = "MOV_WalkStop"
  end
  if returnMove ~= nil then
    if ai.OwnedPOI and not ai.OwnedPOI:FindLuaTableAttribute("IgnoreTranslation") then
      returnMove = returnMove .. "Warp"
    end
    return branch:FindOutcomeBranchesEntry(returnMove)
  end
  return returnMove
end
function LuaHook_UnawarePlantTurn(ai, branch)
  local locomotionInfo = ai:GetLocomotionInfo()
  local pathAngle = locomotionInfo.PathAngle
  if pathAngle == nil then
    return
  end
  if pathAngle <= -60 then
    return branch:FindOutcomeBranchesEntry("MOV_WalkPlantTurn120L")
  end
  if 60 <= pathAngle then
    return branch:FindOutcomeBranchesEntry("MOV_WalkPlantTurn120R")
  end
end
local SetDrivers = function(ai, values)
  local rotationAnimDriver = ai:GetAnimDriver("WarpRotation")
  local translationAnimDriver = ai:GetAnimDriver("WarpTranslation")
  if rotationAnimDriver == nil or translationAnimDriver == nil then
    return nil
  end
  if values.Rotation then
    rotationAnimDriver.ValueVec = values.Rotation
  end
  if values.Translation then
    translationAnimDriver.ValueVec = values.Translation
  end
  return values
end
function LuaHook_UnawarePathWarp(ai)
  local info = ai:GetLocomotionInfo()
  local values = {
    Rotation = info.PathDirection,
    Translation = info.Destination
  }
  return SetDrivers(ai, values)
end
function LuaHook_UnawareFocusWarp(ai)
  local info = ai:GetLocomotionInfo()
  local values = {
    Rotation = info.FocusDirection,
    Translation = info.Destination
  }
  if ai.OwnedPOI ~= nil then
    if info.PathDestination ~= nil then
      values.Translation = info.PathDestination
    else
      values.Translation = ai:GetWorldPosition()
    end
  end
  return SetDrivers(ai, values)
end
return {Unaware = Unaware}
