local mod_name = core.get_current_modname()
local mod_path = core.get_modpath(mod_name)
local S = core.get_translator(mod_name)

br_survival.registered_stats = {}
br_survival.registered_stats_array = {}

function br_survival.register_stat(name, def)
	local existing = br_survival.registered_stats[name]
	def.name = name
	br_survival.registered_stats[name] = def
	def.__meta = {
		__index = def,
	}
	def.new = function(self, player)
		return setmetatable({player = player}, self.__meta)
	end
	if existing then return end
	table.insert(br_survival.registered_stats_array, def)
end

function br_survival.get_meta(player, nname)
	local pi = br_survival.pi(player)
	if not pi then return end
	local sdef = br_survival.registered_stats[nname]
	if not sdef then return end
	if not pi.stat_meta then pi.stat_meta = {} end
	local meta = pi.stat_meta[nname]
	if not meta then
		meta = sdef:new(player)
		pi.stat_meta[nname] = meta
	end
	return meta
end

function br_survival.get_stat(player, nname)
	local meta = br_survival.get_meta(player, nname)
	if not meta then return end
	return meta:get_stat()
end

function br_survival.set_stat(player, nname, amount)
	local meta = br_survival.get_meta(player, nname)
	if not meta then return end
	return meta:set_stat(amount)
end

function br_survival.add_stat(player, nname, amount)
	local meta = br_survival.get_meta(player, nname)
	if not meta then return end
	return meta:add_stat(amount)
end

local _t = 0
function br_survival.stats_step(player, dtime, pi)
	local interval = 1
	_t = _t - dtime; if _t <= 0 then _t = _t + interval else return end
	for i, sdef in ipairs(br_survival.registered_stats_array) do
		local meta = br_survival.get_meta(player, sdef.name)
		assert(meta)
		meta:on_tick(interval)
	end
end

br_survival.default_stat = {
	stat_min = 0,
	stat_max = 200,
	on_tick = function(self, dtime)
	end,
	set_stat = function(self, amount)
		local playermeta = self.player:get_meta()
		self.value = math.min(self.stat_max or 100, math.max(self.stat_min or 0, amount))
		playermeta:set_string("br_survival:" .. self.name, self.value)
	end,
	add_stat = function(self, amount)
		local playermeta = self.player:get_meta()
		local v = self:get_stat()
		self.value = math.min(self.stat_max or 100, math.max(self.stat_min or 0, v + amount))
		playermeta:set_string("br_survival:" .. self.name, self.value)
	end,
	get_stat = function(self)
		local playermeta = self.player:get_meta()
		if br_core.dev_mode then return self.value or 0 end
		if self.value == nil then
			self.value = tonumber(playermeta:get_string("br_survival:" .. self.name)) or 0
		end
		return self.value
	end,
}

local function regress_level(player)
    local pos = player:get_pos()
	local cur_level = br_core.level[br_core.get_level_index(pos)]
	local cur_level_index = cur_level and cur_level.index
	if not cur_level_index then return false end
    local level_index = math.max(1, cur_level_index - 1)
    local level = br_core.level_for_index[level_index]
    local y = br_spawn.get_level_y(level)
	local dest = vector.new(pos.x, y, pos.z)
	dest = vector.offset(dest, (math.random() * 2 - 1)*400, 0, (math.random() * 2 - 1)*400)
	core.sound_play(("br_noclip"), {
		gain = (br_sounds.master or 1) * 1,
		pos = player:get_pos(),
		pitch = (math.random()*0.2) + 0.80
	})
	br_survival.set_stat(player, "thirst", 0)
	br_survival.set_stat(player, "hunger", 0)
	player:set_pos(dest)
	core.sound_play(("br_noclip"), {
		gain = (br_sounds.master or 1) * 1,
		pos = player:get_pos(),
		pitch = (math.random()*0.2) + 0.80
	})
end

local function player_level_uses_stats(player)
	local level = br_core.get_level(player:get_pos())
	if not level then return false end
	return level.survival_stats ~= false
end

br_survival.register_stat("thirst", {
	stat_min = 0,
	stat_max = 300,
	formula = function(self, n)
		-- return 1 - (n^6) / ((n^6) + (1-n)^2)
		local k = self.stat_max
		return ((n>=k) and 0) or ((n>k*0.9) and 0.2) or ((n>k*0.8) and 0.6) or 1
	end,
	on_tick = function(self, dtime)
		if br_core.dev_mode then return end
		if not br_core.flags.survival then return end
		local is_active = player_level_uses_stats(self.player)
		local v = self:get_stat()
		local f = self:formula(v)
		local lastf = self.last_updated_value or 0
		if (self.last_updated_value == nil) or (math.abs(f - lastf) > 0.01) then
			self.last_updated_value = f
			self.player:set_lighting({
				saturation = f * 0.7 + 0.3,
			})
		end

		local h = math.floor(math.max(0, math.min(100, self:get_stat() * 100 / self.stat_max)))
		local image = "[fill:2x100:#00000020"
		if h > 0 then
			image = image .. "^[fill:2x" .. h .. ":#5cc3ea" .. (is_active and "ff" or "a0")
		end
		image = image .. "^[transformFY"
		if not self.hud1 then
			self.hud1 = self.player:hud_add({
				type = "image",
				alignment = {x=0, y=-1},
				position = {x=0.0, y=1.0},
				name = "br_survival:" .. self.name .. "_1",
				text = image,
				z_index = 800,
				scale = {x = 2, y = 2},
				offset = {x = 10, y = -10},
			})
		else
			self.player:hud_change(self.hud1, "text", image)
		end

		if not is_active then
			if self:get_stat() > self.stat_max*0.5 then
				self:add_stat(-dtime)
			end
			return
		end
		self:add_stat(dtime * (self.stat_max/1000)) -- takes ~16min (1000 seconds)

		if v >= self.stat_max then
			regress_level(self.player)
		end
	end,
	set_stat = br_survival.default_stat.set_stat,
	add_stat = br_survival.default_stat.add_stat,
	get_stat = br_survival.default_stat.get_stat,
})

br_survival.register_stat("hunger", {
	stat_min = 0,
	stat_max = 300,
	formula = function(self, n)
		local k = self.stat_max
		return ((n>=k) and 0) or ((n>k*0.9) and 0.2) or ((n>k*0.8) and 0.6) or 1
	end,
	on_tick = function(self, dtime)
		if br_core.dev_mode then return end
		if not br_core.flags.survival then return end
		local is_active = player_level_uses_stats(self.player)
		local v = self:get_stat()
		local f = self:formula(v)
		local lastf = self.last_updated_value or 0
		if (self.last_updated_value == nil) or (math.abs(f - lastf) > 0.01) then
			self.last_updated_value = f
			playerphysics.add_physics_factor(self.player, "speed", "br_survival:" .. self.name, f * 0.1 + 0.9)
		end

		local h = math.floor(math.max(0, math.min(100, self:get_stat() * 100 / self.stat_max)))
		local image = "[fill:2x100:#00000020"
		if h > 0 then
			image = image .. "^[fill:2x" .. h .. ":#dfc083" .. (is_active and "ff" or "a0")
		end
		image = image .. "^[transformFY"
		if not self.hud1 then
			self.hud1 = self.player:hud_add({
				type = "image",
				alignment = {x=0, y=-1},
				position = {x=0.0, y=1.0},
				name = "br_survival:" .. self.name .. "_1",
				text = image,
				z_index = 800,
				scale = {x = 2, y = 2},
				offset = {x = 16, y = -10},
			})
		else
			self.player:hud_change(self.hud1, "text", image)
		end

		if not is_active then
			if self:get_stat() > self.stat_max*0.5 then
				self:add_stat(-dtime)
			end
			return
		end
		self:add_stat(dtime * (self.stat_max/2000)) -- takes ~33min (2000 seconds)

		if v >= self.stat_max then
			regress_level(self.player)
		end
	end,
	set_stat = br_survival.default_stat.set_stat,
	add_stat = br_survival.default_stat.add_stat,
	get_stat = br_survival.default_stat.get_stat,
})

core.register_on_joinplayer(function(player, last_login)
	playerphysics.remove_physics_factor(player, "speed", "br_survival:hunger")
end)
