-- Original references:
-- how it works https://github.com/minetest/minetest/blob/stable-0.3/src/content_sao.cpp
-- how it looks https://github.com/minetest/minetest/blob/stable-0.3/src/content_cao.cpp

--
-- helpers
--

assert(minetest.has_feature("object_step_has_moveresult"))

local gravity = tonumber(minetest.settings:get("movement_gravity")) or 9.81

local function limit_interval(self, prop, dtime, wanted_interval)
	self[prop] = self[prop] + dtime
	if self[prop] < wanted_interval then
		return false
	end
	self[prop] = self[prop] - wanted_interval
	return true
end

--
-- ItemSAO
--

if not default.modernize.new_item_entity then
	minetest.log("warning", "old ItemSAO unimplemented")
end

--
-- BaseSao
--
local BaseSao = {
	initial_properties = {
		physical = true,
		collide_with_objects = false,
		collisionbox = {},
		selectionbox = {},
		visual_size = {},
		visual = "mesh",
		mesh = "",
		textures = { "" },
		backface_culling = false,
	},

	sound_timer = 0,
	sound = "",
	speed = 0,
	drop_item = "",
}

function BaseSao:initialize(mesh, texture, collisionbox, selectionbox, speed, sound, drop_item, size)
	self.initial_properties.mesh = mesh
	self.initial_properties.textures = { texture }
	self.initial_properties.collisionbox = collisionbox
	self.initial_properties.selectionbox = selectionbox
	self.speed = speed
	self.sound = sound
	self.drop_item = drop_item
	self.initial_properties.visual_size = { x = size, y = size }
end

function BaseSao:on_activate(staticdata, dtime_s)
	self.object:set_yaw(math.random(0, 6))
	self.object:set_acceleration(vector.new(0, -gravity, 0))
	self.object:set_armor_groups({ punch_operable = 1 })
end

function BaseSao:walk(dtime, moveresult)
	local pos = self.object:get_pos()
	local vel = self.object:get_velocity()
	local yaw = self.object:get_yaw()
	yaw = yaw + math.pi / 2
	self.object:set_yaw(yaw)
	local dir = vector.new(math.cos(yaw), 0, math.sin(yaw))
	local speed = self.speed
	vel.x = speed * dir.x
	vel.z = speed * dir.z

	if moveresult.touching_ground and math.random(0, 20) == 0 then
		vel.y = 5
	end

	self.sound_timer = self.sound_timer + 1
	if self.sound_timer > math.random(1000, 4000) then
		minetest.sound_play(self.sound, {
			pos = pos,
		}, true)
	end

	if math.random(0, 1) == 0 then
		self.object:set_yaw(yaw + math.random(-1, 1) / 20 * math.pi)
	end

	self.object:set_velocity(vel)
	yaw = self.object:get_yaw()
	yaw = yaw - math.pi / 2
	self.object:set_yaw(yaw)
end

function BaseSao:on_punch(hitter)
	if hitter and hitter:is_player() then
		hitter:get_inventory():add_item("main", self.drop_item)
	end
	self.object:remove()
end

--
-- CowSao
--
local CowSAO = table.copy(BaseSao)
CowSAO.initialize(
	CowSAO,
	"cow.b3d",
	"cow.png",
	{ -1, 0, -1, 1, 1, 1 },
	{ -1, 0, -1, 1, 1, 1 },
	3,
	"",
	"default:raw_beef",
	10
)

function CowSAO:on_step(dtime, moveresult)
	self:walk(dtime, moveresult)
end

function CowSAO:on_punch(hitter)
	if hitter and hitter:is_player() then
		local item = hitter:get_wielded_item()
		if item and item:get_name() == "bucket:bucket_empty" then
			hitter:get_inventory():remove_item("main", "bucket:bucket_empty")
			hitter:get_inventory():add_item("main", "bucket:bucket_milk")
			return
		end
		hitter:get_inventory():add_item("main", self.drop_item)
	end
	self.object:remove()
end

minetest.register_entity(":default:cow", CowSAO)

-- ChickenSao
local ChickenSAO = table.copy(BaseSao)
ChickenSAO.initialize(
	ChickenSAO,
	"chicken.b3d",
	"chicken.png",
	{ -1, 0, -1, 1, 1, 1 },
	{ -1, 0, -1, 1, 1, 1 },
	3,
	"",
	"default:raw_chicken",
	10
)

function ChickenSAO:on_step(dtime, moveresult)
	self:walk(dtime, moveresult)
	if math.random(0, 500) == 5 then
		local pos = self.object:get_pos()
		minetest.add_item(pos, "default:chicken")
	end
end

minetest.register_entity(":default:chicken", ChickenSAO)

--
-- RatSAO
--

local RatSAO = {
	initial_properties = {
		physical = true,
		collide_with_objects = false,
		collisionbox = { -1 / 3, 0, -1 / 3, 1 / 3, 2 / 3, 1 / 3 },
		selectionbox = { -1 / 3, 0, -1 / 3, 1 / 3, 1 / 2, 1 / 3 },
		visual = "mesh",
		mesh = "rat.obj",
		textures = { "rat.png" },
		backface_culling = false,
	},

	is_active = false,
	inactive_interval = 0,
	oldpos = vector.zero(),
	counter1 = 0,
	counter2 = 0,
	sound_timer = 0,
}

function RatSAO:on_activate(staticdata, dtime_s)
	self.object:set_yaw(math.random(0, 6))
	self.object:set_acceleration(vector.new(0, -gravity, 0))
	self.object:set_armor_groups({ punch_operable = 1 })
end

function RatSAO:on_step(dtime, moveresult)
	if not self.is_active then
		-- FIXME physics are actually turned off if inactive
		if not limit_interval(self, "inactive_interval", dtime, 0.5) then
			return
		end
	end

	-- Move around if some player is close
	local pos = self.object:get_pos()
	self.is_active = false
	for _, player in ipairs(minetest.get_connected_players()) do
		if vector.distance(player:get_pos(), pos) < 10 then
			self.is_active = true
		end
	end

	local vel = self.object:get_velocity()
	if not self.is_active then
		vel.x = 0
		vel.z = 0
	else
		-- Move around
		local yaw = self.object:get_yaw()
		local dir = vector.new(math.cos(yaw), 0, math.sin(yaw))
		local speed = 2
		vel.x = speed * dir.x
		vel.z = speed * dir.z

		if moveresult.touching_ground and vector.distance(self.oldpos, pos) < dtime * speed / 2 then
			self.counter1 = self.counter1 - dtime
			if self.counter1 < 0 then
				self.counter1 = self.counter1 + 1
				vel.y = 5
			end
		end

		self.counter2 = self.counter2 - dtime
		if self.counter2 < 0 then
			self.counter2 = self.counter2 + math.random(0, 300) / 100
			self.object:set_yaw(yaw + math.random(-100, 100) / 100 * math.pi)
		end

		self.sound_timer = self.sound_timer - dtime
		if self.sound_timer < 0 then
			if moveresult.touching_ground then
				minetest.sound_play("rat", {
					pos = pos,
				}, true)
			end
			self.sound_timer = self.sound_timer + 0.1 * math.random(40, 70)
		end
	end
	self.object:set_velocity(vel)

	self.oldpos = pos
end

function RatSAO:on_punch(hitter)
	if hitter and hitter:is_player() then
		local item = "default:rat"
		minetest.log("action", hitter:get_player_name() .. " picked up " .. item)
		if not minetest.is_creative_enabled(hitter:get_player_name()) then
			hitter:get_inventory():add_item("main", item)
		end
	end
	self.object:remove()
end

minetest.register_entity(":default:rat", RatSAO)

--
-- FireflySAO
--

local FireflySAO = {
	initial_properties = {
		physical = true,
		collide_with_objects = false,
		collisionbox = { -1 / 3, -2 / 3, -1 / 3, 1 / 3, 4 / 3, 1 / 3 },
		selectionbox = { -1 / 3, 0, -1 / 3, 1 / 3, 1 / 2, 1 / 3 },
		visual = "mesh",
		mesh = "firefly.obj",
		textures = { "firefly.png" },
		backface_culling = false,
		glow = minetest.LIGHT_MAX,
	},

	is_active = false,
	inactive_interval = 0,
	oldpos = vector.zero(),
	counter1 = 0,
	counter2 = 0,
}

function FireflySAO:on_activate(staticdata, dtime_s)
	-- Apply (less) gravity
	self.object:set_acceleration(vector.new(0, -3, 0))
	self.object:set_armor_groups({ punch_operable = 1 })
end

function FireflySAO:on_step(dtime, moveresult)
	if not self.is_active then
		-- FIXME physics are actually turned off if inactive
		if not limit_interval(self, "inactive_interval", dtime, 0.5) then
			return
		end
	end

	-- Move around if some player is close
	local pos = self.object:get_pos()
	self.is_active = false
	for _, player in ipairs(minetest.get_connected_players()) do
		if vector.distance(player:get_pos(), pos) < 10 then
			self.is_active = true
		end
	end

	local vel = self.object:get_velocity()
	if not self.is_active then
		vel.x = 0
		vel.z = 0
	else
		-- Move around
		local yaw = self.object:get_yaw()
		local dir = vector.new(math.cos(yaw), 0, math.sin(yaw))
		local speed = 0.5
		vel.x = speed * dir.x
		vel.z = speed * dir.z

		if moveresult.touching_ground and vector.distance(self.oldpos, pos) < dtime * speed / 2 then
			self.counter1 = self.counter1 - dtime
			if self.counter1 < 0 then
				self.counter1 = self.counter1 + 1
				vel.y = 5
			end
		end

		self.counter2 = self.counter2 - dtime
		if self.counter2 < 0 then
			self.counter2 = self.counter2 + math.random(0, 300) / 100
			self.object:set_yaw(yaw + math.random(-100, 100) / 100 * math.pi)
		end
	end
	self.object:set_velocity(vel)

	self.oldpos = pos
end

function FireflySAO:on_punch(hitter)
	if hitter and hitter:is_player() then
		local item = "default:firefly"
		minetest.log("action", hitter:get_player_name() .. " picked up " .. item)
		if not minetest.is_creative_enabled(hitter:get_player_name()) then
			hitter:get_inventory():add_item("main", item)
		end
	end
	self.object:remove()
end

minetest.register_entity(":default:firefly", FireflySAO)
