local modpath = minetest.get_modpath("markov_macaws")
local settings = Settings(modpath .. "/markov_macaws.conf")
local MarkovGraph = dofile(modpath .. "/markov_chains.lua")
local parrot_brain = dofile(modpath .. "/parrot_brain.lua")

local function parse_num(str)
	return 0 + str
end

local HEAR_DISTANCE = parse_num(settings:get("hear_distance"))
local MAX_KNOWN_WORDS = parse_num(settings:get("max_known_words"))

--make a list of edible food
local foods = {}
do
	local food = settings:get("foods")
	for name in string.gmatch(food, "[^,%s]+")
	do
		foods[name] = true
	end
end

local parrots = {}
parrots["markov_macaws:macaw"] = true


--put message into markov chain
--if the parrot likes the player this is more likely to happen
local function hear(self, talker_name, message)
	local rel = self.player_relations[talker_name]
	if rel < 0 or math.random() < 0.5
	then
		return
	end
	self.player_relations[talker_name] = rel + math.random() * 0.5
	self.talker:learn_line(message)
	
	if math.random() < 0.1
	then
		self.talker:cull(MAX_KNOWN_WORDS)
	end
end


--Makes parrots in a radius around the player hear messages
local function parrot_chat_listener(name, message)
	local player = minetest.get_player_by_name(name)
	if not player
	then
		return
	end
	if minetest.check_player_privs(player, "shout")
	then
		local pos = player:get_pos()
		local objs = minetest.get_objects_inside_radius(pos, HEAR_DISTANCE)
		--check all entities in a radius around the player if they are
		--parrots. Those that are are called in the hear function
		for i, obj in pairs(objs)
		do
			local luae = obj:get_luaentity()
			if luae
			then
				if parrots[luae.name]
				then
					hear(luae, name, message)
				end
			end
		end
	end
end
minetest.register_on_chat_message(parrot_chat_listener)





--on_activate function. Takes care of mobkit activation and also
--initializes player_relations table and talker (markov chain) object
local function init_parrot(self, staticdata, dtime_s)
	mobkit.actfunc(self, staticdata, dtime_s)
	
	--initialize self.talker
	--self.talker stores a markov chain object
	self.talker = MarkovGraph()
	local words = mobkit.recall(self, "words")
	if not words
	then
		--initialize with new words
		self.talker:learn_from_file(modpath .. "/default_words.txt")
		mobkit.remember(self, "words", self.talker.nodes)
	else
		--initialize with loaded words
		local meta = getmetatable(self.talker.nodes)
		self.talker.nodes = words
		setmetatable(self.talker.nodes, meta)
	end
	
	--initialize self.player_relations
	--self.player_relations maps names of known players to numbers
	--the number indicates how much the parrot likes the player
	self.player_relations = mobkit.recall(self, "player_relations")
	if not self.player_relations
	then
		self.player_relations = {}
		mobkit.remember(self, "player_relations", self.player_relations)
	end
	do
		--accessing relation with unknown player returns 0 instead of nil
		local meta =
		{
			__index = function(self, key)
				return 0
			end,
		}
		setmetatable(self.player_relations, meta)
	end
end

local function lq_animate_for_duration(self, duration, animation)
	local time = 0
	mobkit.animate(self, animation)
	local function func()
		time = time + self.dtime
		if time > duration
		then
			return true
		end
	end
	mobkit.queue_low(self, func)
end


local function hq_nope(self, prty)
	local function func()
		if self.isonground
		then
			mobkit.clear_queue_low(self)
			mobkit.make_sound(self, "squawk")
			lq_animate_for_duration(self, 0.5, "flap")
			return true
		else
			return true
		end
	end
	mobkit.queue_high(self, func, prty)
end

--this doesn't make 100% sense because it makes
--assumptions about how the player model looks
local attach_positions =
player_api and
{
	{"Body", {x = 4, y = 6, z = 0}, {x = 0, y = 180, z = 0}},
	{"Body", {x = 4, y = 6, z = 0}, {x = 0, y = 0, z = 0}},
	{"Body", {x = -4, y = 6, z = 0}, {x = 0, y = 180, z = 0}},
	{"Body", {x = -4, y = 6, z = 0}, {x = 0, y = 0, z = 0}},
} or
{
	--alternative if player_api is not installed
	{"", {x = 4, y = 14, z = 0}, {x = 0, y = 0, z = 0}},
	{"", {x = 4, y = 14, z = 0}, {x = 0, y = 180, z = 0}},
	{"", {x = -4, y = 14, z = 0}, {x = 0, y = 0, z = 0}},
	{"", {x = -4, y = 14, z = 0}, {x = 0, y = 180, z = 0}},
}

--TODO: use low level queue properly
local function hq_perch(self, prty, mount)
	--TODO: make this only work if there is no parrot on the shoulder yet
	local mountpos = attach_positions[math.random(#attach_positions)]
	self.object:set_attach(mount, mountpos[1], mountpos[2], mountpos[3])
	
	
	local trouble = 0
	local troubletimer = 0
	
	local function func()
		--if mount is jumping, increase trouble value
		if mobkit.is_alive(mount)
		then
			trouble = trouble + math.abs(mount:get_player_velocity().y)
		end
		--decay trouble value
		trouble = trouble * 0.9
		if trouble > 3
		then
			--flap if trouble value is high
			troubletimer = troubletimer + self.dtime
			mobkit.clear_queue_low(self)
			lq_animate_for_duration(self, 0.1, "perch_flap")
		else
			if not mobkit.is_queue_empty_low(self)
			then
				return false
			end
			--reset trouble timer
			troubletimer = 0
			
			--dance randomly
			local dance = math.random()
			if dance < 0.3
			then
				lq_animate_for_duration(self, 1, "perch_dance")
				return false
			end
			lq_animate_for_duration(self, 1, "perch")
		end
		--dismount if jumping or falling for long / fast enough
		if troubletimer * trouble > 300 or not self.object:get_attach()
		then
			self.object:set_detach()
			return true
		end
	end
	mobkit.queue_high(self, func, prty)
end

local function eat_food(self, clicker)
	--only eat from non-bad people
	local name = clicker:get_player_name()
	local rel = self.player_relations[name]
	if rel < 0 or
		math.random() > rel
	then
		return false
	end
	
	
	local wielditem = clicker:get_wielded_item()
	if foods[wielditem:get_name()]
	then
		wielditem:take_item()
		clicker:set_wielded_item(wielditem)
		self.player_relations[name] = rel + math.random()
		return true
	end
end

local function on_rightclick(self, clicker)
	if eat_food(self, clicker)
	then
		return
	end
	
	local clickername = clicker:get_player_name()
	local rel = self.player_relations[clickername]
	if rel > 2 and math.random() < rel
	then
		--perch on shoulder if lucky
		hq_perch(self, 20, clicker)
		return
	end
	--play flap animation
	mobkit.clear_queue_high(self)
	hq_nope(self, 10)
end

local parrot =
{
	physical = true,
	stepheight = 0.1,				--EVIL!
	collide_with_objects = true,
	collisionbox = {-0.3, -0.01, -0.3, 0.3, 0.7, 0.3},
	visual = "mesh",
	mesh = "markov_macaws_macaw.b3d",
	textures = {"markov_macaws_macaw.png"},
	static_save = true,
	makes_footstep_sound = true,
		
	timeout = 0,
	buoyancy = -1,
	lung_capacity = 10,
	max_hp = 10,
	jump_height = 1,
	max_speed = 3,
	view_range = 10,
	armor_groups = {fleshy = 3},
	color = "red", --TODO: make more colors
	
	animation =
	{
		walk = {range = {x = 1, y = 29}, speed = 30, loop = true},
		stand = {range = {x = 101, y = 129}, speed = 30, loop = true},
		emote =
		{
			{range = {x = 61, y = 69}, speed = 30, loop = true}, --hop
			{range = {x = 131, y = 139}, speed = 30, loop = true} --dance
		},
		flap = {range = {x = 171, y = 185}, speed = 30, loop = true},
		
		perch = {range = {x = 31, y = 59}, speed = 30, loop = true},
		perch_flap = {range = {x = 155, y = 169}, speed = 30, loop = true},
		perch_dance = {range = {x = 141, y = 153}, speed = 15, loop = true},
		
	},
	sounds =
	{
		squawk =
		{
			name = "markov_macaws_squawk",
			gain = {1, 1.5},
			pitch = {0.7, 1.5},
		},
		talk =
		{
			name = "markov_macaws_talk",
			pitch = {0.7, 1.3},
			gain = {0.5, 1.5},
		},
		playful =
		{
			{
				name = "markov_macaws_whistle",
				pitch = {0.7, 1.5},
				gain = {0.3, 1.5},
			},
			{
				name = "markov_macaws_clack",
				pitch = {0.9, 1.3},
				gain = {0.3, 1.5},
			},
			{
				name = "markov_macaws_squawk",
				pitch = {0.7, 1.3},
				gain = {1, 1.5},
			},
			{
				name = "markov_macaws_talk",
				pitch = {0.7, 1.3},
				gain = {0.5, 1.5},
			},
		}
	},
	
	logic = parrot_brain,
	
	on_step = mobkit.stepfunc,	-- required
	on_activate = init_parrot,
	get_staticdata = mobkit.statfunc,
	on_rightclick = on_rightclick,
	on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
		if mobkit.is_alive(self) then
			local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4)
			self.object:set_velocity({x=hvel.x,y=2,z=hvel.z})
			
			mobkit.hurt(self,tool_capabilities.damage_groups.fleshy or 1)

			if type(puncher)=='userdata' and puncher:is_player() then	-- if hit by a player
				mobkit.clear_queue_high(self)							-- abandon whatever they've been doing
				mobkit.hq_runfrom(self, 50, puncher)					-- flee
			end
		end
	end
}

minetest.register_entity("markov_macaws:macaw", parrot)
