local profile = require("core.profile")
local monitors = require("level.MonitorLibrary")
local timer = require("level.timer")
local cinesequencelib = require("narrative.cinesequence")
local FindFreya = function()
  local objArray = game.World.FindGameObjectsByMarker("freya00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindBaldur = function()
  local objArray = game.World.FindGameObjectsByMarker("baldur00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindSindri = function()
  local objArray = game.World.FindGameObjectsByMarker("sindri00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindBrok = function()
  local objArray = game.World.FindGameObjectsByMarker("brok00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindMagni = function()
  local objArray = game.World.FindGameObjectsByMarker("magni00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindModi = function()
  local objArray = game.World.FindGameObjectsByMarker("modi00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local FindStoneMason = function()
  local objArray = game.World.FindGameObjectsByMarker("stonemason00")
  for _, obj in ipairs(objArray) do
    if obj:GetCreature() ~= nil and obj:GetCreature():GetAI() ~= nil then
      return obj:GetCreature():GetAI()
    end
  end
  return nil
end
local CatchFreyaReference = function(freyaAiObject)
  if freyaAiObject == nil then
    local freyaVariable = FindFreya()
    if freyaVariable ~= nil then
      return freyaVariable
    else
      return nil
    end
  end
  return freyaAiObject
end
local SpawnFreyaAtVector = function(spawnPosVector, spawnDirVector)
  game.FindLevel("FreyaLvl100_Global"):CallScript("SpawnFreyaWithPosAndDir", spawnPosVector, spawnDirVector)
end
local MasterToLocationAndGroupSync = function(syncObj, slave_MoveTable, animNameObject, useGOAsReference, syncObjJointName, startLocationJointName, runSpeed)
  local debugStringMaster = "Sync-ing " .. tostring(slave_MoveTable[1][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
  local masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringMaster, slave_MoveTable[1][1])
  local slavePuppeteers = {}
  for i = 2, #slave_MoveTable do
    local debugStringFull = "Sync'ing " .. tostring(slave_MoveTable[i][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
    local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
    newSlavePup:AcceptSync()
    table.insert(slavePuppeteers, {
      Slave = newSlavePup,
      Branch = slave_MoveTable[i][2]
    })
  end
  if syncObjJointName ~= "" and syncObjJointName ~= nil then
    if startLocationJointName ~= "" and startLocationJointName ~= nil then
      masterPuppeteer:Approach({
        pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(startLocationJointName)),
        speed = runSpeed or 1,
        stop = true
      })
      masterPuppeteer:OnArrival(function()
        masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, syncObjJointName)
        masterPuppeteer:OnComplete(function()
          masterPuppeteer:Clear()
          masterPuppeteer:DetachPuppet()
          masterPuppeteer = nil
        end)
        if animNameObject ~= "" and animNameObject ~= nil then
          syncObj:StartAnim(animNameObject)
        else
          syncObj:JumpAnimToFrame(0)
          syncObj:PlayAnimToEnd()
        end
        for i = 1, #slavePuppeteers do
          slavePuppeteers[i].Slave:OnComplete(function()
            slavePuppeteers[i].Slave:Clear()
            slavePuppeteers[i].Slave:DetachPuppet()
            slavePuppeteers[i].Slave = nil
          end)
        end
      end, {
        pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(startLocationJointName)),
        radius = 1
      })
    else
      error("startLocationJointName is empty or null, please check MasterToLocationAndGroupSync function call.")
    end
  else
    error("syncObjJointName is empty or null, please check MasterToLocationAndGroupSync function call.")
  end
end
local GroupToLocationsAndSyncBranch = function(syncObj, slave_MoveTable, animNameObject, useGOAsReference, syncObjJointName)
  local debugStringMaster = "Sync-ing " .. tostring(slave_MoveTable[1][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
  local masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringMaster, slave_MoveTable[1][1])
  local slavePuppeteers = {}
  for i = 2, #slave_MoveTable do
    local debugStringFull = "Sync'ing " .. tostring(slave_MoveTable[i][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
    local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
    table.insert(slavePuppeteers, {
      Slave = newSlavePup,
      Branch = slave_MoveTable[i][2]
    })
  end
  if syncObjJointName ~= "" and syncObjJointName ~= nil then
    if slave_MoveTable[1][3] ~= "" and slave_MoveTable[1][3] ~= nil then
      masterPuppeteer:Approach({
        pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[1][3])),
        speed = slave_MoveTable[1][4] or 1,
        stop = true
      })
      masterPuppeteer:SetStageName("In Approach State")
    else
      error("master puppeteer's start location joint is empty or null, please check GroupToLocationsAndSyncBranch function call.")
    end
    for i = 2, #slave_MoveTable do
      if slave_MoveTable[i][3] ~= "" and slave_MoveTable[i][3] ~= nil then
        local j = i - 1
        slavePuppeteers[j].Slave:Approach({
          pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
          speed = slave_MoveTable[i][4] or 1,
          stop = true
        })
        slavePuppeteers[j].Slave:SetStageName("In Approach State")
      else
        error("slave puppeteer number " .. tostring(i) .. " start location joint is empty or null, please check GroupToLocationsAndSyncBranch function call.")
      end
    end
    masterPuppeteer:OnArrival(function()
      game.UI.Idle(true)
      masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, syncObjJointName, function()
        if animNameObject ~= "" and animNameObject ~= nil then
          syncObj:StartAnim(animNameObject)
        else
          syncObj:JumpAnimToFrame(0)
          syncObj:PlayAnimToEnd()
        end
      end)
      masterPuppeteer:SetStageName("In Sync State")
      masterPuppeteer:OnComplete(function()
        print("sync master oncomplete")
        game.UI.Idle(false)
        masterPuppeteer:Clear()
        masterPuppeteer:DetachPuppet()
        masterPuppeteer = nil
      end)
    end, {
      pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[1][3])),
      radius = 1
    })
    for i = 2, #slave_MoveTable do
      do
        local j = i - 1
        slavePuppeteers[j].Slave:OnArrival(function()
          slavePuppeteers[j].Slave:AcceptSync()
          slavePuppeteers[j].Slave:SetStageName("In AcceptSync")
          slavePuppeteers[j].Slave:OnComplete(function()
            print("sync slave oncomplete")
            slavePuppeteers[j].Slave:Clear()
            slavePuppeteers[j].Slave:DetachPuppet()
            slavePuppeteers[j].Slave = nil
          end)
        end, {
          pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
          radius = 1
        })
      end
    end
  else
    error("syncObjJointName is empty or null, please check GroupToLocationsAndSyncBranch function call.")
  end
end
local MultiZoneGroupToLocationAndSync = function(syncObj, slave_MoveTable, animNameObject, useGOAsReference, syncObjJointName)
  local masterPuppeteer
  local slavePuppeteers = {}
  slave_MoveTable.ZoneMonitors = {}
  for i = 1, #slave_MoveTable do
    slave_MoveTable.ZoneMonitors[i] = monitors.CreateEntityZoneMonitor(slave_MoveTable[i][1], slave_MoveTable[i][5])
    slave_MoveTable.ZoneMonitors[i]:OnEnter(function()
      slave_MoveTable.ZoneMonitors[i]:Stop()
      if syncObjJointName ~= "" and syncObjJointName ~= nil then
        if i == 1 then
          if slave_MoveTable[1][3] ~= "" and slave_MoveTable[1][3] ~= nil then
            local debugStringMaster = "Sync-ing " .. tostring(slave_MoveTable[1][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
            masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringMaster, slave_MoveTable[1][1])
            masterPuppeteer:Approach({
              pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[1][3])),
              speed = slave_MoveTable[1][4] or 1,
              stop = true
            })
            masterPuppeteer:SetStageName("In Approach State")
            masterPuppeteer:OnArrival(function()
              masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, syncObjJointName, function()
                if animNameObject ~= "" and animNameObject ~= nil then
                  syncObj:StartAnim(animNameObject)
                else
                  syncObj:JumpAnimToFrame(0)
                  syncObj:PlayAnimToEnd()
                end
              end)
              masterPuppeteer:SetStageName("In Sync State")
              masterPuppeteer:OnComplete(function()
                print("sync master oncomplete")
                masterPuppeteer:Clear()
                masterPuppeteer:DetachPuppet()
                masterPuppeteer = nil
              end)
            end, {
              pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[1][3])),
              radius = 1
            })
          else
            error("master puppeteer's start location joint is empty or null, please check MultiZoneGroupToLocationAndSync function call.")
          end
        elseif slave_MoveTable[i][3] ~= "" and slave_MoveTable[i][3] ~= nil then
          local debugStringFull = "Sync'ing " .. tostring(slave_MoveTable[i][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
          local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
          table.insert(slavePuppeteers, {
            Slave = newSlavePup,
            Branch = slave_MoveTable[i][2]
          })
          print("------------ Debug ------------  Speed: " .. tostring(slave_MoveTable[i][4]))
          print("------------ Debug ------------  start joint: " .. tostring(slave_MoveTable[i][3]))
          print("------------ Debug ------------  pos of start joint: " .. tostring(syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3]))))
          newSlavePup:Approach({
            pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
            speed = slave_MoveTable[i][4] or 1,
            stop = true
          })
          newSlavePup:SetStageName("In Approach State")
          newSlavePup:OnArrival(function()
            newSlavePup:AcceptSync()
            newSlavePup:SetStageName("In AcceptSync")
            newSlavePup:OnComplete(function()
              print("sync slave oncomplete")
              newSlavePup:Clear()
              newSlavePup:DetachPuppet()
              newSlavePup = nil
            end)
          end, {
            pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
            radius = 1
          })
        else
          error("slave puppeteer number " .. tostring(i) .. " start location joint is empty or null, please check MultiZoneGroupToLocationAndSync function call.")
        end
      else
        error("syncObjJointName is empty or null, please check MultiZoneGroupToLocationAndSync function call.")
      end
    end)
  end
end
local GroupToLocationSyncAndIdle = function(syncObj, slave_MoveTable, animNameObject, useGOAsReference, syncObjJointName)
  local debugStringMaster = "Sync-ing " .. tostring(slave_MoveTable[1][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
  local masterPuppeteer = game.Puppeteer.NewForce(syncObj, debugStringMaster, slave_MoveTable[1][1])
  slave_MoveTable[1].puppet = masterPuppeteer
  local slavePuppeteers = {}
  for i = 2, #slave_MoveTable do
    local debugStringFull = "Sync'ing " .. tostring(slave_MoveTable[i][1]:GetName()) .. " With Object: " .. tostring(syncObj:GetName())
    local newSlavePup = game.Puppeteer.NewForce(syncObj, debugStringFull, slave_MoveTable[i][1])
    table.insert(slavePuppeteers, {
      Slave = newSlavePup,
      Branch = slave_MoveTable[i][2]
    })
    slave_MoveTable[i].puppet = newSlavePup
  end
  if syncObjJointName ~= "" and syncObjJointName ~= nil then
    if slave_MoveTable[1][3] ~= "" and slave_MoveTable[1][3] ~= nil then
      masterPuppeteer:Approach({
        pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[1][3])),
        speed = slave_MoveTable[1][4] or 1,
        stop = true
      })
      masterPuppeteer:SetStageName("In Approach State")
    else
      error("master puppeteer's start location joint is empty or null, please check GroupToLocationsAndSyncBranch function call.")
    end
    for i = 2, #slave_MoveTable do
      if slave_MoveTable[i][3] ~= "" and slave_MoveTable[i][3] ~= nil then
        local j = i - 1
        slavePuppeteers[j].Slave:Approach({
          pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
          speed = slave_MoveTable[i][4] or 1,
          stop = true
        })
        slavePuppeteers[j].Slave:SetStageName("In Approach State")
      else
        error("slave puppeteer number " .. tostring(i) .. " start location joint is empty or null, please check GroupToLocationsAndSyncBranch function call.")
      end
    end
    slave_MoveTable.arrivalCounter = 0
    for i = 1, #slave_MoveTable do
      slave_MoveTable[i].puppet:OnArrival(function()
        slave_MoveTable.arrivalCounter = slave_MoveTable.arrivalCounter + 1
        if 1 < i then
          slave_MoveTable[i].puppet:AcceptSync()
          slave_MoveTable[i].puppet:SetStageName("In AcceptSync State")
        end
        if slave_MoveTable.arrivalCounter >= #slave_MoveTable then
          masterPuppeteer:Sync(slave_MoveTable[1][2], useGOAsReference, slavePuppeteers, syncObjJointName, function()
            if animNameObject ~= "" and animNameObject ~= nil then
              syncObj:StartAnim(animNameObject)
            else
              syncObj:JumpAnimToFrame(0)
              syncObj:PlayAnimToEnd()
            end
          end)
          masterPuppeteer:SetStageName("In Sync/Idle State")
          slave_MoveTable.timer = timer.StartLevelTimer(0.1, function()
            for j = 1, #slave_MoveTable do
              slave_MoveTable[j].puppet:Clear()
              slave_MoveTable[j].puppet:DetachPuppet()
              slave_MoveTable[j].puppet = nil
            end
          end)
        end
      end, {
        pos = syncObj:GetWorldJointPosition(syncObj:GetJointIndex(slave_MoveTable[i][3])),
        radius = 1
      })
    end
  else
    error("syncObjJointName is empty or null, please check GroupToLocationsAndSyncBranch function call.")
  end
end
local GetJointPosAndDirection = function(objectName, jointName)
  local go = GameObjects[objectName]
  local joint = GameObjectJoints[objectName][jointName]
  return go:GetWorldJointPosition(joint), go:GetWorldJointForward(joint)
end
local SetMarkerOnSpawnedAI = function(aiObject, markerId)
  if markerId ~= nil and aiObject ~= nil then
    aiObject:AddMarker(markerId)
  end
end
local SpawnAIAtJoint = function(level, objectName, jointName, npcName, animBranchName, markerId, startConfig)
  local aiObject
  local pos, direction = GetJointPosAndDirection(objectName, jointName)
  if startConfig ~= nil then
    aiObject = game.AI.Spawn(level, pos, direction, npcName, animBranchName, startConfig)
  else
    aiObject = game.AI.Spawn(level, pos, direction, npcName, animBranchName)
  end
  SetMarkerOnSpawnedAI(aiObject, markerId)
  return aiObject
end
local SpawnAIAtJoint_GetAsyncSpawnParams = function(level, objectName, jointName, npcName, animBranchName, markerId, startConfig)
  if startConfig == nil then
    startConfig = {}
  end
  startConfig.branchName = animBranchName
  local pos, direction = GetJointPosAndDirection(objectName, jointName)
  return level, pos, direction, npcName, startConfig, function(creature)
    SetMarkerOnSpawnedAI(creature, markerId)
  end
end
local CreateCineSequence = function(level, level_obj, name)
  return cinesequencelib.CineSequence.New(level, level_obj, name)
end
local SafePickupSetStage = function(ai, pickupName, desiredStage)
  if ai:PickupIsAcquired(pickupName) then
    if ai:PickupIsActive(pickupName) then
      local numStages = ai:PickupCountStages(pickupName)
      print("Number of stages: " .. tostring(numStages))
      if desiredStage <= numStages then
        local curStage = ai:PickupGetStage(pickupName)
        if curStage ~= desiredStage then
          ai:PickupSetStage(pickupName, desiredStage)
        else
          print("pickup '" .. pickupName .. "' is already at stage " .. tostring(desiredStage))
        end
      else
        engine.Warning("pickup stage '" .. tostring(desiredStage) .. "' is not a valid stage for pickup '" .. pickupName .. "' (it has " .. tostring(numStages) .. " stages, indexed from 0)")
      end
    else
      engine.Warning("pickup '" .. pickupName .. "' is not active")
    end
  else
    engine.Warning("pickup '" .. pickupName .. "' is not acquired")
  end
end
local SonWalkSpeed = 1.3
local SonJogSpeed = 2.5
local SonRunSpeed = 4
local KraWalkSpeed = 1.5
local KraJogSpeed = 3
local KraRunSpeed = 4
local FreyaWalkSpeed = 1.779
local FreyaJogSpeed = 3.5
local FreyaRunSpeed = 4.423
local ArrivalRadius = 0.9
local RunArrivalRadius = 1.5
local StopTurnRadius = 0.15
local InteractStopDistance = 0.15
local SonRunArrivalRadius = 1.44
local SonJogArrivalRadius = 0.95
local SonWalkArrivalRadius = 0.69
return profile.WrapLibrary({
  FindFreya = FindFreya,
  FindBaldur = FindBaldur,
  FindBrok = FindBrok,
  FindSindri = FindSindri,
  FindMagni = FindMagni,
  FindModi = FindModi,
  FindStoneMason = FindStoneMason,
  CatchFreyaReference = CatchFreyaReference,
  SpawnFreyaAtVector = SpawnFreyaAtVector,
  MasterToLocationAndGroupSync = MasterToLocationAndGroupSync,
  GroupToLocationsAndSyncBranch = GroupToLocationsAndSyncBranch,
  MultiZoneGroupToLocationAndSync = MultiZoneGroupToLocationAndSync,
  GroupToLocationSyncAndIdle = GroupToLocationSyncAndIdle,
  SpawnAIAtJoint = SpawnAIAtJoint,
  SpawnAIAtJoint_GetAsyncSpawnParams = SpawnAIAtJoint_GetAsyncSpawnParams,
  CreateCineSequence = CreateCineSequence,
  SafePickupSetStage = SafePickupSetStage,
  SonJogSpeed = SonJogSpeed,
  SonRunSpeed = SonRunSpeed,
  SonWalkSpeed = SonWalkSpeed,
  KraJogSpeed = KraJogSpeed,
  KraWalkSpeed = KraWalkSpeed,
  KraRunSpeed = KraRunSpeed,
  FreyaWalkSpeed = FreyaWalkSpeed,
  FreyaJogSpeed = FreyaJogSpeed,
  FreyaRunSpeed = FreyaRunSpeed,
  ArrivalRadius = ArrivalRadius,
  RunArrivalRadius = RunArrivalRadius,
  SonRunArrivalRadius = SonRunArrivalRadius,
  SonJogArrivalRadius = SonJogArrivalRadius,
  SonWalkArrivalRadius = SonWalkArrivalRadius,
  StopTurnRadius = StopTurnRadius,
  InteractStopDistance = InteractStopDistance
})
