local PATH_FIND_ALGORITHM
PATH_FIND_ALGORITHM = "Dijkstra"
-- PATH_FIND_ALGORITHM = "A*_noprefetch"
-- PATH_FIND_ALGORITHM = "A*"

PyuTest.ENTITY_BLOOD_AMOUNT = 6
PyuTest.HUMAN_LIKE_CBOX = { -0.25, -1, -0.25, 0.25, 1, 0.25 }

PyuTest.get_nearest_entity = function(entity, pos, range, only_player, dont_ignore_allies)
  local closet_distance = math.huge
  local nearest

  local function set_nearest_and_distance(n, d)
    nearest = n
    closet_distance = d
  end

  for obj in core.objects_inside_radius(pos, range) do
    local dist = vector.distance(pos, obj:get_pos())

    if dist < closet_distance and obj ~= entity then
      if only_player then
        if obj:is_player() then
          set_nearest_and_distance(obj, dist)
        end
      else
        local e = obj:get_luaentity()
        -- Ignore items
        if e then
          if e.name ~= "__builtin:item" then
            if not dont_ignore_allies then
              local self = entity:get_luaentity()

              if self then
                if self.name ~= e.name then
                  set_nearest_and_distance(obj, dist)
                end
              end
            end
          end
        else
          set_nearest_and_distance(obj, dist)
        end
      end
    end
  end

  return nearest
end

PyuTest.register_entity_spawn = function(name, entity, def)
  if def == nil then
    error("Table expected for options!")
  end

  core.register_node(name, {
    description = "Entity Spawner",
    groups = {
      not_in_creative_inventory = 1,
    },
    drawtype = "airlike",
    walkable = false,
    pointable = false
  })

  core.register_decoration({
    sidelen = 80,
    decoration = name,
    deco_type = "simple",
    place_on = def.place_on,
    spawn_by = def.spawn_by,
    num_spawn_by = def.num_spawn_by,
    fill_ratio = def.fill_ratio or 0.0008,
    y_max = def.y_max or 31000,
    y_min = def.y_min or -31000,
    biomes = {}
  })

  core.register_lbm({
    name = name .. "_spawn",
    run_at_every_load = true,
    nodenames = { name },
    action = function(pos)
      core.remove_node(pos)

      local min = def.min or 1
      local max = def.max or 1

      if max == 1 then
        core.add_entity(pos, entity)
      else
        for _ = min, math.random(min, max) do
          core.add_entity(pos, entity)
        end
      end
    end
  })
end

local class = {}

function class:do_physics()
  local obj = self.object
  local state = self.state
  local cfg = self.options
  local moveresult = self.moveresult
  local pos = obj:get_pos()


  if cfg.gravity then
    if not moveresult.touching_ground then

    end
  end
end

function class:path_find_nearest_entity(follow_only_player)
  local obj = self.object
  local state = self.state
  local cfg = self.options
  local pos = obj:get_pos()

  if state.target.path == nil then
    state.target.object = PyuTest.get_nearest_entity(obj, pos, cfg.sight_range, follow_only_player)

    if state.target.object == nil then
      return
    end

    state.target.position = state.target.object:get_pos()
    state.target.path = core.find_path(pos, state.target.position, cfg.view_range, cfg.max_jump, cfg.max_drop,
      PATH_FIND_ALGORITHM)
    state.target.pathindex = 1
  else
    if state.target.pathindex == #state.target.path then
      state.target.path = nil
      state.target.object = nil
      state.target.position = nil
      state.target.pathindex = nil
    else
      local p = state.target.path[state.target.pathindex]
      -- obj:set_velocity(p * dtime)
      obj:move_to(p, true)
      state.target.pathindex = state.target.pathindex + 1
    end
  end
end

PyuTest.make_mob = function(name, properties, options)
  local default_options = {
    ai = "dummy",
    max_jump = 1,
    max_drop = 8,
    speed = 3,
    view_range = 3,
    sight_range = 10,
    gravity = true,
    gravity_multiplier = 1,
    health_regen = true,

    sounds = {
      hurt = "pyutest-entity-hurt"
    },
    drops = {}
  }
  local cfg = {}

  for k, v in pairs(options) do
    cfg[k] = v
  end

  for k, v in pairs(default_options) do
    if cfg[k] == nil then
      cfg[k] = v
    end
  end

  local collisionbox = properties.collisionbox or PyuTest.HUMAN_LIKE_CBOX

  core.register_entity(name, setmetatable({
    initial_properties = PyuTest.util.tableconcat(properties, {
      hp_max = properties.hp_max or 20,
      physical = true,
      collide_with_objects = true,
      stepheight = properties.stepheight or 1.1,
      collisionbox = collisionbox,
      selectionbox = properties.selectionbox or collisionbox,
      show_on_minimap = properties.show_on_minimap or true,
      infotext = "",
    }),
    options = cfg,
    state = {
      target = {
        object = nil,
        position = nil,
        path = nil,
        pathindex = nil
      }
    },

    on_step = function(self, dtime, moveresult)
      self.dtime = dtime
      self.moveresult = moveresult

      local ai = self.options.ai
      local obj = self.object
      local p = obj:get_properties()


      local hp = obj:get_hp()
      if self.options.health_regen and not (hp >= p.hp_max) then
        obj:set_hp(hp + 0.5)
      end

      if obj:get_hp() > p.hp_max then
        obj:set_hp(p.hp_max)
      end

      p.infotext = string.format("Mob Health: %d/%d", obj:get_hp(), p.hp_max)
      obj:set_properties(p)

      if ai == nil or ai == "dummy" then
        return
      end

      self:do_physics()

      if ai == "follownearest" then
        self:path_find_nearest_entity(false)
      end
    end,

    on_punch = function(self)
      local pos = self.object:get_pos()
      core.sound_play(self.options.sounds.hurt, { pos = pos })

      core.add_particlespawner({
        amount = 8,
        time = 0.4,
        minexptime = 0.4,
        maxexptime = 0.8,
        minsize = 1.5,
        maxsize = 1.62,
        vertical = false,
        glow = core.LIGHT_MAX,

        collisiondetection = false,
        texture = "pyutest-blood.png",

        minpos = pos,
        maxpos = pos,
        minvel = vector.new(-1, -1, 1),
        maxvel = vector.new(1, 1, 1),
      })
    end,

    on_death = function(self)
      local pos = self.object:get_pos()

      for _, v in pairs(self.options.drops) do
        PyuTest.drop_item(pos, v)
      end
    end
  }, { __index = class }))
end
