-- storage getter/setter
local STORAGE_PREFIX = "mail/"

-- create or populate empty fields on an entry
local function populate_entry(e)
	e = e or {}
	e.contacts = e.contacts or {}
	e.inbox = e.inbox or {}
	e.outbox = e.outbox or {}
	e.drafts = e.drafts or {}
	e.lists = e.lists or {}
	return e
end

function mail.get_storage_entry(playername)
	local str = mail.storage:get_string(STORAGE_PREFIX .. playername)
	if str == "" then
		-- new entry
		return populate_entry()
	else
		-- deserialize existing entry
		local e = minetest.parse_json(str)
		return populate_entry(e)
	end
end

function mail.set_storage_entry(playername, entry)
	mail.storage:set_string(STORAGE_PREFIX .. playername, minetest.write_json(entry))
end

-- get a mail by id from the players in- or outbox
function mail.get_message(playername, msg_id)
	local entry = mail.get_storage_entry(playername)
	for _, msg in ipairs(entry.inbox) do
		if msg.id == msg_id then
			return msg
		end
	end
	for _, msg in ipairs(entry.outbox) do
		if msg.id == msg_id then
			return msg
		end
	end
end

-- marks a mail read by its id
function mail.mark_read(playername, msg_id)
	local entry = mail.get_storage_entry(playername)
	for _, msg in ipairs(entry.inbox) do
		if msg.id == msg_id then
			msg.read = true
			mail.set_storage_entry(playername, entry)
			return
		end
	end
end

-- marks a mail unread by its id
function mail.mark_unread(playername, msg_id)
	local entry = mail.get_storage_entry(playername)
	for _, msg in ipairs(entry.inbox) do
		if msg.id == msg_id then
			msg.read = false
			mail.set_storage_entry(playername, entry)
			return
		end
	end
end

-- deletes a mail by its id
function mail.delete_mail(playername, msg_id)
	local entry = mail.get_storage_entry(playername)
	for i, msg in ipairs(entry.inbox) do
		if msg.id == msg_id then
			table.remove(entry.outbox, i)
			mail.set_storage_entry(playername, entry)
			return
		end
	end
	for i, msg in ipairs(entry.outbox) do
		if msg.id == msg_id then
			table.remove(entry.outbox, i)
			mail.set_storage_entry(playername, entry)
			return
		end
	end
	for i, msg in ipairs(entry.drafts) do
		if msg.id == msg_id then
			table.remove(entry.drafts, i)
			mail.set_storage_entry(playername, entry)
			return
		end
	end
end

-- add or update a contact
function mail.update_contact(playername, contact)
	local entry = mail.get_storage_entry(playername)
	local existing_updated = false
	for i, existing_contact in ipairs(entry.contacts) do
		if existing_contact.name == contact.name then
			-- update
			entry.contacts[i] = contact
			existing_updated = true
			break
		end
	end
	if not existing_updated then
		-- insert
		table.insert(entry.contacts, contact)
	end
	mail.set_storage_entry(playername, entry)
end

-- deletes a contact
function mail.delete_contact(playername, contactname)
	local entry = mail.get_storage_entry(playername)
	for i, existing_contact in ipairs(entry.contacts) do
		if existing_contact.name == contactname then
			-- delete
			table.remove(entry.contacts, i)
			mail.set_storage_entry(playername, entry)
			return
		end
	end
end

-- get all contacts
function mail.get_contacts(playername)
	local entry = mail.get_storage_entry(playername)
	return entry.contacts
end

-- returns the maillists of a player
function mail.get_maillists(playername)
	local entry = mail.get_storage_entry(playername)
	return entry.lists
end

-- returns the maillists of a player
function mail.get_maillist_by_name(playername, listname)
	local entry = mail.get_storage_entry(playername)
	for _, list in ipairs(entry.lists) do
		if list.name == listname then
			return list
		end
	end
end

-- updates or creates a maillist
function mail.update_maillist(playername, list)
	local entry = mail.get_storage_entry(playername)
	local existing_updated = false
	for i, existing_list in ipairs(entry.lists) do
		if existing_list.name == list.name then
			-- update
			entry.lists[i] = list
			existing_updated = true
			break
		end
	end
	if not existing_updated then
		-- insert
		table.insert(entry.lists, list)
	end
	mail.set_storage_entry(playername, entry)
end

function mail.delete_maillist(playername, listname)
	local entry = mail.get_storage_entry(playername)
	for i, list in ipairs(entry.lists) do
		if list.name == listname then
			-- delete
			table.remove(entry.lists, i)
			mail.set_storage_entry(playername, entry)
			return
		end
	end
end

function mail.extractMaillists(receivers_string, maillists_owner)
	local globalReceivers = mail.parse_player_list(receivers_string) -- receivers including maillists
	local receivers = {} -- extracted receivers

	-- extract players from mailing lists
	for _, receiver in ipairs(globalReceivers) do
		local receiverInfo = receiver:split("@") -- @maillist
		if receiverInfo[1] and receiver == "@" .. receiverInfo[1] then
			local maillist = mail.get_maillist_by_name(maillists_owner, receiverInfo[1])
			if maillist then
				for _, playername in ipairs(maillist.players) do
					table.insert(receivers, playername)
				end
			end
		else -- in case of player
			table.insert(receivers, receiver)
		end
	end

	return receivers
end

function mail.pairsByKeys(t, f)
	-- http://www.lua.org/pil/19.3.html
	local a = {}
	for n in pairs(t) do table.insert(a, n) end
	table.sort(a, f)
	local i = 0		-- iterator variable
	local iter = function()		-- iterator function
		i = i + 1
		if a[i] == nil then
			return nil
		else
			--return a[i], t[a[i]]
			-- add the current position and the length for convenience
			return a[i], t[a[i]], i, #a
		end
	end
	return iter
end