local LD = require("design.LevelDesignLibrary")
local thisObj, thisLevel, player, dragon, struggleTime
local updateDriver = false
local noHorizontal = false
local zoneState
local STATE_ZONE_OUTSIDE, STATE_ZONE_INSIDE, STATE_ZONE_DISABLE = 0, 1, 2
local state
local STATE_WAIT, STATE_IDLE, STATE_ATTACK_START, STATE_ATTACKING, STATE_ATTACK_COOLDOWN, STATE_STRUGGLE_AGGRO, STATE_FREE = 1, 2, 3, 4, 5, 6, 7
local mortarZone, breathZone, projectileZone, currentAttackType
local firstAggro = true
local rainFX, focusAngle, degreeAngle
local value = 0
local isXpl650LevelLoaded = false
local startEnabled, attackZoneMortar, attackZoneBreath, attackZoneProjectile, attackCDTimer, requiredStatues, useAltFlyAway, useAltMortar, dragonSpawner
local vfsEnableDebug = engine.VFSBool.New("Enable Dragon Zone Debug")
local debug_now = false
local DebugGetStateName = function(pState)
  local debugStringTable = {
    "STATE_WAIT",
    "STATE_IDLE",
    "STATE_ATTACK_START",
    "STATE_ATTACKING",
    "STATE_ATTACK_COOLDOWN",
    "STATE_STRUGGLE_AGGRO",
    "STATE_FREE"
  }
  for k, _ in ipairs(debugStringTable) do
    if pState == k then
      return debugStringTable[k]
    end
  end
end
local dragonAimDriver, dragonAimDriverVert
local SetupAnimDriver = function(ai)
  dragonAimDriver = ai:GetAnimDriver("DragonFiring")
  dragonAimDriverVert = ai:GetAnimDriver("DragonFiringVert")
end
function OnScriptLoaded(level, obj)
  thisObj = obj
  thisLevel = level
  player = game.Player.FindPlayer()
  if state == STATE_FREE then
    game.SubObject.Sleep(obj)
  else
    game.SubObject.SetUpdateDisableDistance(obj, 100)
  end
  startEnabled = obj:GetLuaTableAttribute("startEnabled")
  attackZoneMortar = obj:GetLuaTableAttribute("attackZoneMortar")
  attackZoneBreath = obj:GetLuaTableAttribute("attackZoneBreath")
  attackZoneProjectile = obj:GetLuaTableAttribute("attackZoneProjectile")
  attackCDTimer = obj:GetLuaTableAttribute("attackCooldownTime")
  requiredStatues = obj:GetLuaTableAttribute("requiredStatues")
  useAltFlyAway = obj:GetLuaTableAttribute("useAltFlyAway")
  useAltMortar = obj:GetLuaTableAttribute("useAltMortar")
  dragonSpawner = obj:GetLuaTableAttribute("dragonSpawner")
  thisObj = thisObj
  player = player
  zoneState = STATE_ZONE_OUTSIDE
  state = STATE_WAIT
end
function OnStart(level, obj)
  if startEnabled then
    breathZone = level:FindSingleGameObject(attackZoneBreath)
    if breathZone ~= nil then
      game.SubObject.SetEntityZoneHandler(obj, breathZone)
    end
    mortarZone = level:FindSingleGameObject(attackZoneMortar)
    if mortarZone ~= nil then
      game.SubObject.SetEntityZoneHandler(obj, mortarZone)
    end
    projectileZone = level:FindSingleGameObject(attackZoneProjectile)
    if projectileZone ~= nil then
      game.SubObject.SetEntityZoneHandler(obj, projectileZone)
    end
    if state ~= STATE_FREE then
      local perchObj = level:FindSingleGameObject(dragonSpawner)
      local spawner = perchObj:FindSingleGOByName("DragonSpawnPoint")
      local masonCave = game.FindLevel("Xpl650_MasonTrailCave")
      local vf = game.FindLevel("Xpl200_Funeral")
      local httk = game.FindLevel("Xpl100_HTTK")
      local masonDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Fafnir_Objective_01_Return")
      local vfDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Otr_Objective_01_Return")
      local httkDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Reginn_Objective_01_Return")
      if masonCave ~= nil and masonDragonQuestState ~= "Complete" then
        dragon = spawner.LuaObjectScript.SpawnEnemy()
      elseif vf ~= nil and vfDragonQuestState ~= "Complete" then
        dragon = spawner.LuaObjectScript.SpawnEnemy()
      elseif httk ~= nil and httkDragonQuestState ~= "Complete" then
        dragon = spawner.LuaObjectScript.SpawnEnemy()
      end
    end
    if state == STATE_FREE then
      local perchObj = level:FindSingleGameObject(dragonSpawner)
      local spawner = perchObj:FindSingleGOByName("DragonSpawnPoint")
      local masonCave = game.FindLevel("Xpl650_MasonTrailCave")
      local vf = game.FindLevel("Xpl200_Funeral")
      local httk = game.FindLevel("Xpl100_HTTK")
      local masonDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Fafnir_Objective_01_Return")
      local vfDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Otr_Objective_01_Return")
      local httkDragonQuestState = game.QuestManager.GetQuestState("Quest_WorldDragon_Reginn_Objective_01_Return")
      if masonCave ~= nil and masonDragonQuestState == "Active" then
        LD.CompleteQuest("Quest_WorldDragon_Fafnir_Objective_01_Return")
      elseif vf ~= nil and vfDragonQuestState == "Active" then
        LD.CompleteQuest("Quest_WorldDragon_Otr_Objective_01_Return")
      elseif httk ~= nil and httkDragonQuestState == "Active" then
        LD.CompleteQuest("Quest_WorldDragon_Reginn_Objective_01_Return")
      end
    end
    if thisLevel == game.FindLevel("Xpl650_MasonTrailCave") then
      isXpl650LevelLoaded = true
    end
  end
  struggleTime = math.random(8, 15)
end
function OnMarkerEnterZone(level, obj, zoneObject, markerObject, markerId)
  if zoneState == STATE_ZONE_INSIDE or zoneState == STATE_ZONE_DISABLE then
    return
  end
  if markerObject == player then
    OnEnterFunction(zoneObject)
    zoneState = STATE_ZONE_INSIDE
  end
end
function OnMarkerExitZone(level, obj, zoneObject, markerObject, markerId)
  if zoneState == STATE_ZONE_OUTSIDE or zoneState == STATE_ZONE_DISABLE then
    return
  end
  if markerObject == player then
    OnExitFunction(zoneObject)
    zoneState = STATE_ZONE_OUTSIDE
  end
end
function OnUpdate(level, obj)
  if state == STATE_WAIT then
    if dragon ~= nil then
      SetupAnimDriver(dragon)
      state = STATE_IDLE
    end
  elseif state == STATE_IDLE then
    struggleTime = struggleTime - level:GetUnitTime()
    if struggleTime <= 0 then
      if math.random(100) > 50 then
        dragon:TriggerMoveEvent("kLEStruggleA")
      else
        dragon:TriggerMoveEvent("kLEStruggleB")
      end
      state = STATE_STRUGGLE_AGGRO
    elseif zoneState == STATE_ZONE_INSIDE and state ~= STATE_FREE then
      if firstAggro then
        firstAggro = false
        dragon:TriggerMoveEvent("kLEAggroFirstTime")
        state = STATE_STRUGGLE_AGGRO
        return
      end
      state = STATE_ATTACK_START
    end
  elseif state == STATE_ATTACK_START and state ~= STATE_FREE then
    dragon:RequestInteract(thisObj)
    state = STATE_ATTACKING
  elseif state == STATE_ATTACKING then
    if updateDriver then
      UpdateAnimDriver(dragon)
    end
  elseif state == STATE_ATTACK_COOLDOWN and state ~= STATE_FREE then
    attackCDTimer = attackCDTimer - level:GetUnitTime()
    if attackCDTimer <= 0 then
      if zoneState == STATE_ZONE_INSIDE then
        state = STATE_ATTACK_START
      else
        state = STATE_IDLE
      end
    end
  elseif state == STATE_STRUGGLE_AGGRO and state ~= STATE_FREE and dragon:IsInNavigationMove() then
    struggleTime = math.random(8, 15)
    state = STATE_IDLE
  end
  if debug_now and vfsEnableDebug.value then
    local debug_table = {}
    debug_table[#debug_table + 1] = {
      "Player is inside attack zone: ",
      zoneState
    }
    debug_table[#debug_table + 1] = {
      "Dragon State",
      DebugGetStateName(state)
    }
    if state == STATE_ATTACK_COOLDOWN then
      debug_table[#debug_table + 1] = {
        "Attack cooldown time remaining ",
        attackCDTimer
      }
    end
    debug_table[#debug_table + 1] = {
      "Remaining Statues: ",
      requiredStatues
    }
    debug_table[#debug_table + 1] = {
      "Focus Angle: ",
      focusAngle
    }
    debug_table[#debug_table + 1] = {
      "Degree Angle: ",
      degreeAngle
    }
    debug_table[#debug_table + 1] = {
      "Driver Value: ",
      value
    }
    debug_table.Title = "Dragon Info"
    debug_table.Y = 20
    debug_table.X = 22
    engine.DrawDebugTable(debug_table)
    engine.DrawTextInWorld(obj:GetWorldPosition(), "state: " .. state, 15728640)
  end
end
function OnSaveCheckpoint(level, obj)
  return {state = state}
end
function OnRestoreCheckpoint(level, obj, savedInfo)
  local saveState = savedInfo.state
  if saveState == STATE_FREE then
    state = saveState
  else
    state = STATE_WAIT
  end
  print("restored state is ", state)
end
function OnEnterFunction(markerType)
  if markerType == mortarZone then
    currentAttackType = "Mortar"
  elseif markerType == breathZone then
    currentAttackType = "Breathe"
  elseif markerType == projectileZone then
    currentAttackType = "Projectile"
  end
end
function OnExitFunction()
  thisLevel = thisLevel
end
function OnInteractReject(level, obj, creature)
  print("OnInteractReject")
  dragon:AbortInteract()
  engine.Warning("Interaction failed.")
end
function OnInteractStart(obj, ai)
  if currentAttackType == "Mortar" then
    if useAltMortar then
      dragon:TriggerMoveEvent("kLEAttackMortarAlt")
    else
      dragon:TriggerMoveEvent("kLEAttackMortar")
    end
  elseif currentAttackType == "Breathe" then
    if isXpl650LevelLoaded then
      dragon:TriggerMoveEvent("kLEAttackBreathe_XPL650")
    else
      dragon:TriggerMoveEvent("kLEAttackBreathe")
    end
  elseif currentAttackType == "Projectile" then
    if isXpl650LevelLoaded then
      dragon:TriggerMoveEvent("kLEAttackBreathe_XPL650")
    else
      dragon:TriggerMoveEvent("kLEAttackBreathe")
    end
  end
end
function OnInteractFinish(level, obj, creature)
  if state ~= STATE_ATTACK_COOLDOWN then
    state = STATE_ATTACK_COOLDOWN
    attackCDTimer = obj:FindLuaTableAttribute("attackCooldownTime")
  end
  if rainFX ~= nil and rainFX.Spawned then
    rainFX:Remove()
  end
end
local minStrike = 4
local maxStrike = 7
local minAngle = -50
local maxAngle = 50
function LuaHook_FireMortar(level, obj, ai, test)
  print("Fire Mortar")
  local numStrike = math.random(minStrike, maxStrike)
  for i = numStrike, 1, -1 do
    _G.StartLevelTimer(i * 0.85, function()
      SpawnMortar(i)
    end)
  end
end
function SpawnMortar(number)
  if zoneState == STATE_ZONE_INSIDE then
    local mortarWarning = game.FX.Spawn("dragon00_mortar_warning", thisLevel, {EffectCreator = dragon})
    local attackPos = game.NavMesh.ClosestLocation(player:GetWorldPosition(), player):FindConnectedLocationInRadius(number * 1.5)
    mortarWarning:SetWorldPosition(attackPos)
  else
    return
  end
end
local Lerp = function(pValue1, pValue2, pLerp)
  local result = (pValue2 - pValue1) * pLerp + pValue1
  return result
end
local ConvertAngleToPercent = function(ai, target)
  focusAngle = ai:GetLocomotionInfo().FocusAngle
  if focusAngle > maxAngle then
    focusAngle = maxAngle
  elseif focusAngle < minAngle then
    focusAngle = minAngle
  end
  local range = maxAngle - minAngle
  local percent = (focusAngle - minAngle) / range
  value = Lerp(-1, 1, percent)
  degreeAngle = percent
end
local rate = 20
local maxVertAngle = 20
local minVertAngle = -20
local currentVert = maxVertAngle
local UpdateVertical = function()
  local range = maxVertAngle - minVertAngle
  local percent = (currentVert - minVertAngle) / range
  currentVert = currentVert - rate * thisLevel:GetUnitTime()
  local mValue = Lerp(-1, 1, percent)
  dragonAimDriverVert.Value = mValue
end
function UpdateAnimDriver(ai)
  if not noHorizontal then
    ConvertAngleToPercent(ai, player)
    dragonAimDriver.Value = value
  end
  if isXpl650LevelLoaded then
    rate = 16.5
    dragonAimDriver.Value = -0.12961042
  end
  UpdateVertical()
end
function LuaHook_SetDragonAim(level, obj, ai, test)
  UpdateAnimDriver(ai)
end
function LuaHook_UpdateDragonAim(level, obj, ai, test)
  updateDriver = true
  currentVert = maxVertAngle
  noHorizontal = false
end
function LuaHook_StopUpdateDragonAim(level, obj, ai)
  updateDriver = false
end
function LuaHook_UpdateDragonAimStright(level, obj, ai)
  updateDriver = true
  currentVert = maxVertAngle
  noHorizontal = true
end
function LuaHook_UpdateDragonAimStrightEnd(level, obj, ai)
  updateDriver = true
  currentVert = currentVert
  noHorizontal = true
end
function SetDispelInProgress()
  if thisObj == nil then
    return
  end
  game.SubObject.Sleep(thisObj)
end
function SetStatueDestroyed()
  requiredStatues = requiredStatues - 1
  if requiredStatues == 0 then
    print("free dragon")
    SetDragonFree()
  end
end
function SetDragonFree()
  state = STATE_FREE
  if useAltFlyAway then
    dragon:TriggerMoveEvent("kLEFreeAlt")
  else
    dragon:TriggerMoveEvent("kLEFree")
  end
  dragon:HideJoint(dragon:GetJointIndex("JOChainAttachGround1"))
end
