local class = require("core.class")
local tablex = require("core.tablex")
local Actor = class.Class("Actor")
function Actor:init(actor_name, actor_func)
  if type(actor_name) ~= "string" then
    return error("Expected name of Actor as first argument")
  end
  if actor_func ~= nil and type(actor_func) ~= "function" then
    return error("Expected nil or function to access creature as second argument")
  end
  self.name = actor_name
  self.actor_func = actor_func
end
function Actor:SetSpawnFunction(spawn_func)
  if self.actor_func then
    return error("Calling SetSpawnFunction on an Actor that has already been assigned")
  end
  if type(spawn_func) ~= "function" then
    return error("Expected function as argument to SetSpawnFunction")
  end
  function self.actor_func()
    return self.spawned_object
  end
  self.spawn_func = spawn_func
end
function Actor:SetAsyncSpawn(level, position, direction, tweak_name, optionalArgs, callback)
  if self.actor_func then
    return error("Calling SetAsyncSpawn on an Actor that has already been assigned")
  end
  if optionalArgs == nil then
    optionalArgs = {}
  else
    optionalArgs.callbackTarget = nil
  end
  function self.actor_func()
    return self.spawned_object
  end
  self.asyncSpawnArgs = {
    level,
    "ActorAsyncSpawnHandler",
    position,
    direction,
    tweak_name,
    optionalArgs
  }
  self.asyncSpawnCallback = callback
end
local AsyncSpawnStates = {}
local NextAsyncSpawnId = 0
local StoreAsyncSpawnState = function(state)
  local id = NextAsyncSpawnId
  NextAsyncSpawnId = NextAsyncSpawnId + 1
  AsyncSpawnStates[id] = state
  return id
end
local RetrieveAsyncSpawnState = function(id)
  local state = AsyncSpawnStates[id]
  AsyncSpawnStates[id] = nil
  if tablex.IsEmpty(AsyncSpawnStates) then
    NextAsyncSpawnId = 0
  end
  return state
end
local ActorAsyncSpawnHandler = function(level, creature, id)
  local state = RetrieveAsyncSpawnState(id)
  local actor = state
  actor.spawned_object = creature
  if actor.asyncSpawnCallback then
    actor.asyncSpawnCallback(creature)
  end
end
_G.ActorAsyncSpawnHandler = ActorAsyncSpawnHandler
function Actor:Spawn()
  if self.actor_func and self.actor_func() ~= nil then
    return
  end
  if self.asyncSpawnArgs then
    local state = self
    local id = StoreAsyncSpawnState(state)
    local optionalArgs = self.asyncSpawnArgs[6]
    optionalArgs.id = id
    game.AI.AsyncSpawn(unpack(self.asyncSpawnArgs))
    return
  end
  if self.spawn_func then
    self.spawned_object = self.spawn_func()
    if self.spawned_object == nil then
      return error("Spawn function on Actor did not return an object")
    end
    return
  end
  return error("Actor '", self.name, "' was unable to be spawned. Creature reference function (if given) returned nil, and spawn function did not exist.")
end
function Actor:GetCreature()
  if self.actor_func then
    local creature = self.actor_func()
    if creature then
      if creature == nil then
        engine.Warning("GetCreature was called on Actor " .. self.name .. " but the actor_func returned userdata containing a nil. They have been spawned and then somehow despawned.")
      end
      return creature
    else
      engine.Warning("GetCreature was called on Actor " .. self.name .. " but the actor_func returned nil. They have not been spawned or were somehow deleted entirely, including leftover userdata.")
    end
  else
    error("Calling Actor:GetCreature on Actor " .. self.name .. " but the actor has no actor_func")
  end
end
function Actor:CatchCreature()
  if self.actor_func then
    local creature = self.actor_func()
    if creature then
      if creature == nil then
        engine.Warning("GetCreature was called on Actor " .. self.name .. " but the actor_func returned userdata containing a nil. They have been spawned and then somehow despawned.")
      end
      return creature
    end
  else
    error("Calling Actor:CatchCreature on Actor " .. self.name .. " but the actor has no actor_func")
  end
end
function Actor:StartPuppeting(owner_object)
  if owner_object == nil then
    return error("No owning GameObject passed to StartPuppeting")
  end
  if self.puppeteer ~= nil then
    self.puppeteer:Clear()
  else
    local creature = self:GetCreature()
    if not creature then
      return error("Can't puppeteer Actor '", self.name, "', isn't spawned")
    end
    self.puppeteer = game.Puppeteer.New(owner_object, self.name, creature)
  end
  return self.puppeteer
end
function Actor:StartPuppetingForce(owner_object)
  if owner_object == nil then
    return error("No owning GameObject passed to StartPuppeting")
  end
  if self.puppeteer ~= nil then
    self.puppeteer:Clear()
  else
    local creature = self:GetCreature()
    if not creature then
      return error("Can't puppeteer Actor '", self.name, "', wasn't spawned")
    elseif creature == nil then
      print("creature: " .. tostring(creature))
      print("creature's type: " .. tostring(type(creature)))
      return error("Can't puppeteer Actor '", self.name, "', was spawned but deleted or despawned somehow.")
    end
    self.puppeteer = game.Puppeteer.NewForce(owner_object, self.name, creature)
  end
  return self.puppeteer
end
function Actor:GetActivePuppeteer()
  return self.puppeteer
end
function Actor:StopPuppeting()
  if self.puppeteer ~= nil then
    self.puppeteer:Clear()
    self.puppeteer:DetachPuppet()
    self.puppeteer = nil
  end
end
function Actor:Despawn()
  self:StopPuppeting()
  local creature = self:GetCreature()
  if creature then
    creature:Destroy()
  end
end
return {Actor = Actor}
