local status_effect_count = 0
local timer_reducers = {}

local reg_err = "Error registering status effect: "

local do_nothing = function()end


local storage = minetest.get_mod_storage()



--constructor for status effect
function status_effects.StatusEffect(def)
	local this = {}
	
	--storing name and callbacks from definition
	assert(type(def.name) == "string", reg_err .. "name must be a string")
	this.name = def.name
	
	
	--callback registering
	--------------------------------------------------------------------
	--on_elapsed
	--run function when timer has elapsed with player name as argument
	local do_on_elapsed = {}
	function this.register_on_elapsed(fn)
		table.insert(do_on_elapsed, fn)
	end
	
	local function on_elapsed(playername)
		for i, f in ipairs(do_on_elapsed)
		do
			f(playername)
		end
	end
	
	--on_given
	--run function when a player is given the effect with playername and
	--time as arguments, IF the player doesn't already have the effect
	local do_on_given = {}
	function this.register_on_given(fn)
		table.insert(do_on_given, fn)
	end
	
	local function on_given(playername, time)
		for i, f in ipairs(do_on_given)
		do
			f(playername, time)
		end
	end
	
	--on_fresh
	--run function EVERY TIME a player is given the effect with
	--playername, added time and total time as arguments
	local do_on_fresh = {}
	function this.register_on_fresh(fn)
		table.insert(do_on_fresh, fn)
	end
	local function on_fresh(playername, time)
		local total_time = this.get_remaining_time(playername) + time
		
		for i, f in ipairs(do_on_fresh)
		do
			f(playername, time, total_time)
		end
	end
	--on_save
	--runs when a player's timer is saved with playername as argument
	local do_on_save = {}
	function this.register_on_save(fn)
		table.insert(do_on_save, fn)
	end
	local function on_save(playername)
		for i, f in ipairs(do_on_save)
		do
			f(playername)
		end
	end
	--on_load
	--runs when a player's timer is loaded with playername as argument
	local do_on_load = {}
	function this.register_on_load(fn)
		table.insert(do_on_load, fn)
	end
	local function on_load(playername)
		for i, f in ipairs(do_on_load)
		do
			f(playername)
		end
	end
	
	--functions for giving or otherwise interacting with status effects
	--------------------------------------------------------------------
	local timer_count = 0
	
	local ids = {} --stores ids, indexed by player name
	local players = {} --stores player names, indexed by id
	local effect_timers = {} --stores remaining times, indexed by id
	
	
	--returns remaining time duh
	this.get_remaining_time = function(playername)
		local id = ids[playername]
		if id
		then
			return effect_timers[id]
		else
			return 0
		end
	end
	
	--increases timer for player by time
	this.give_to = function(playername, time)
		on_fresh(playername, time)
		local id = ids[playername]
		if id
		then
			effect_timers[id] = effect_timers[id] + time
		else
			on_given(playername, time)
			timer_count = timer_count + 1
			effect_timers[timer_count] = time
			ids[playername] = timer_count
			players[timer_count] = playername
		end
	end
	
	--returns true if player has effect, false otherwise
	this.player_has = function(name)
		return ids[name] ~= nil
	end
	
	this.revoke = function(playername)
		local time = this.get_remaining_time(playername)
		if time ~= 0
		then
			this.give_to(playername, -time)
		end
	end
	
	
	--decreasing the timers
	--------------------------------------------------------------------
	local function decrease_effect_time(index, dtime)
		local t = effect_timers[index] - dtime
		if t <= 0 --timer elapsed
		then
			if timer_count ~= index
			then
				effect_timers[index] = effect_timers[timer_count]
				local name = players[timer_count]
				
				players[index] = name
				ids[name] = index
				players[timer_count] = nil
				decrease_effect_time(index, dtime)				
			else
				effect_timers[index] = nil
				local name = players[index]
				ids[name] = nil
				players[name] = nil
			end
			
			on_elapsed(players[index])
			timer_count = timer_count - 1
		else
			effect_timers[index] = t
		end
	end

	this.decrease_effect_times = function(dtime)
		for i = 1, timer_count
		do
			decrease_effect_time(i, dtime)
		end
	end
	
	
	status_effect_count = status_effect_count + 1
	timer_reducers[status_effect_count] = this.decrease_effect_times
	
	
	--storage stuff
	--------------------------------------------------------------------
	local storage_table = minetest.deserialize(storage:get_string(this.name)) or {}
	local function save_timer(playername, bulk)
		--store thing
		local id = ids[playername]
		if not id
		then
			return
		end
		ids[playername] = nil
		
		on_save(playername)
		
		do--store timer in mod storage
			storage_table[playername] = (id and effect_timers[id] or 0)
			if not bulk
			then
				storage:set_string(this.name, minetest.serialize(storage_table))
			end
		end
		
		--close gap in timer list
		effect_timers[id] = effect_timers[timer_count]
		local name = players[timer_count]
		players[id] = name
		ids[name] = id
		players[timer_count] = nil
		timer_count = timer_count - 1
	end
	
	local function load_timer(playername)
		local num = storage_table[playername] or 0
		if num ~= 0
		then
			on_load(playername)
			storage_table[playername] = nil
			this.give_to(playername, num)
		end
	end
	
	
	--actually storing
	minetest.register_on_joinplayer(function(player)
			local name = player:get_player_name()
			load_timer(name)
		end)
	minetest.register_on_leaveplayer(function(player)
			local name = player:get_player_name()
			save_timer(name, false)
		end)
	minetest.register_on_leaveplayer(function(player)
			local name = player:get_player_name()
			save_timer(name, false)
		end)
	minetest.register_on_shutdown(function()
			local players = minetest.get_connected_players()
			for _, p in pairs(players)
			do
				local name = p:get_player_name()
				save_timer(name, true)
			end
			storage:set_string(this.name, minetest.serialize(storage_table))
		end)
	
	return this
end


minetest.register_globalstep(
	function(dtime)
		for i = 1, status_effect_count
		do
			timer_reducers[i](dtime)
		end
	end)










