local PATH_FIND_ALGORITHM
PATH_FIND_ALGORITHM = "Dijkstra"

PyuTest.ENTITY_BLOOD_AMOUNT = 6
PyuTest.HUMAN_LIKE_CBOX = { -0.25, -1, -0.25, 0.25, 1, 0.25 }
PyuTest.SMALL_ANIMAL_CBOX = {-0.25, -0.5, -0.25, 0.25, 0.15, 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

local class = {
	_last_hp = 0,
	_heal_timer = 0,
	_attack_timer = 0,
	_velocity = vector.new(0, 0, 0),
	_state = {
		target = {
			object = nil,
			position = nil,
			path = nil,
			pathindex = nil
		}
	},
}

function class:_do_physics()
	-- TODO
end

function class:_path_find_entity(target)
	-- TODO
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()

	self:_path_find_entity(PyuTest.get_nearest_entity(obj, pos, cfg.view_range, follow_only_player, false))
end

function class:_visualize_damage()
	local pos = self.object:get_pos()
	core.sound_play({
		name = 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

function class:_attack()
	local obj = self.object
	local cfg = self._options

	local function attack(o)
		if cfg.attack_type == "melee" then
			PyuTest.deal_damage(o, cfg.hit_damage, {
				type = "punch",
				object = obj
			})
		elseif cfg.attack_type == "ranged" then
			PyuTest.shoot_projectile_from_object(
			cfg.attack_projectile,
			obj,
			cfg.attack_projectile_velocity
			)
		else
			error(string.format("Expected attack_type to be melee or ranged"))
		end
	end

	for o in core.objects_inside_radius(obj:get_pos(), cfg.hit_range) do
		if o ~= obj then
			for _, v in pairs(cfg.attack_entities) do
				local lua_entity = o:get_luaentity()
				local b =
				(v == "player" and o:is_player()) or (lua_entity and v == lua_entity.name)

				if b then
					attack(o)
				end
			end
		end
	end
end

function class:on_death()
	self:_visualize_damage()

	local pos = self.object:get_pos()

	for _, v in pairs(self._options.drops) do
		PyuTest.drop_item(pos, v)
	end
end

function class:on_step(dtime, moveresult)
	self._dtime = dtime
	self._moveresult = moveresult

	self._attack_timer = self._attack_timer + (2 * dtime)
	self._heal_timer = self._heal_timer + (2 * dtime)

	local obj = self.object
	local cfg = self._options
	local p = obj:get_properties()

	local hp_for_healing = obj:get_hp()
	if self._options.health_regen and hp_for_healing < p.hp_max and self._heal_timer >= 5 then
		self._heal_timer = 0
		obj:set_hp(hp_for_healing + 1)
	end

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

	if obj:get_hp() < self._last_hp then
		self:_visualize_damage()
	end

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

	self:_do_physics()

	-- follow_player
	if self._options.follow_player then
		self:_path_find_nearest_entity(false)
	end

	-- follow_items
	for o in core.objects_inside_radius(obj:get_pos(), cfg.view_range) do
		if o ~= obj then
			for _, v in pairs(self._options.follow_items) do
				local i = o:get_wielded_item()

				if i:get_name() == v then
					self:_path_find_entity(o)
				end
			end
		end
	end

	-- attack_entities
	if self._attack_timer >= 3 then
		self._attack_timer = 0
		self:_attack()
	end

	self.object:set_velocity(self._velocity)

	self._last_hp = self.object:get_hp()
end

PyuTest.make_mob = function(name, properties, options)
	local default_options = {
		max_jump = 1,
		max_drop = 8,
		speed = 3,
		hit_range = 3,
		hit_damage = 3,
		view_range = 10,
		gravity = true,
		gravity_multiplier = 1,
		health_regen = true,
		follow_player = false,
		follow_items = {},

		attack_type = "melee", -- Can be melee or ranged
		attack_projectile = "pyutest_projectiles:arrow",
		attack_projectile_velocity = 20,
		attack_entities = {},

		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,
			infotext = "",
		}),
		_options = cfg,
	}, { __index = class }))
end
