--[[

	Tube Library 2
	==============

	Copyright (C) 2017-2020 Joachim Stolberg

	LGPLv2.1+
	See LICENSE.txt for more information

	storage.lua

]]--

--
-- Data maintenance
--
local MemStore = {}
local storage = minetest.get_mod_storage()

local function update_mod_storage()
	local gametime = minetest.get_gametime()
	for k,v in pairs(MemStore) do
		if v.used then
			v.used = false
			v.best_before = gametime + (60 * 30)  -- 30 min
			storage:set_string(k, minetest.serialize(v))
		elseif v.best_before < gametime then
			storage:set_string(k, minetest.serialize(v))
			MemStore[k] = nil  -- remove from memory
		end
	end
	-- run every 10 minutes
	minetest.after(600, update_mod_storage)
end

minetest.register_on_shutdown(function()
	for k,v in pairs(MemStore) do
		storage:set_string(k, minetest.serialize(v))
	end
end)

minetest.after(600, update_mod_storage)

local function empty_block(block)
	local empty = true
	local tbl = minetest.deserialize(block)
	for k,v in pairs(tbl) do
		if k ~= "used" and k ~= "best_before" then
			empty = false
		end
	end
	return empty
end

minetest.after(1, function()
	local tbl = storage:to_table()
	for k,v in pairs(tbl.fields) do
		if empty_block(v) then
			storage:set_string(k, "")
		end
	end
end)

--
-- Local helper functions
--
local function new_block(block_key)
	local data = storage:get_string(block_key)
	if data == nil or data == "" then  -- TODO: change for v5
		MemStore[block_key] = {}
	else
		MemStore[block_key] = minetest.deserialize(data)
	end
	return MemStore[block_key]
end

local function new_node(block, node_key)
	block[node_key] = {}
	return block[node_key]
end

local function unlock(pos)
	local block_key = math.floor((pos.z+32768)/16)*4096*4096 +
		math.floor((pos.y+32768)/16)*4096 + math.floor((pos.x+32768)/16)
	local node_key = (pos.z%16)*16*16 + (pos.y%16)*16 + (pos.x%16)
	local block = MemStore[block_key] or new_block(block_key)
	block.used = true
	return block, node_key
end

local function keys_to_pos(block_key, node_key)
	local f = math.floor
	block_key = tonumber(block_key) or 0
	node_key = tonumber(node_key) or 0
	local x = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10)
	block_key, node_key = f(block_key / 0x1000), f(node_key / 0x10)
	local y = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10)
	block_key, node_key = f(block_key / 0x1000), f(node_key / 0x10)
	local z = ((f(block_key % 0x1000) * 0x10) - 32768) + (node_key % 0x10)
	return {x = x, y = y, z = z}
end

-------------------------------------------------------------------------------
-- API functions for a node related and high efficient storage table
-- for all kind of node data.
-------------------------------------------------------------------------------

-- To be called when a node is placed
function tubelib2.init_mem(pos)
	local block, node_key = unlock(pos)
	return new_node(block, node_key)
end

-- To get the node data table
function tubelib2.get_mem(pos)
	local block, node_key = unlock(pos)
	return block[node_key] or new_node(block, node_key)
end

-- To be called when a node is removed
function tubelib2.del_mem(pos)
	local block, node_key = unlock(pos)
	block[node_key] = nil
end

-- Read a value, or return the default value if not available
function tubelib2.get_mem_data(pos, key, default)
	return tubelib2.get_mem(pos)[key] or default
end

function tubelib2.walk_over_all(clbk, key)
	local data = storage:to_table()
	for block_key,sblock in pairs(data.fields) do
		local block = minetest.deserialize(sblock)
		for node_key,mem in pairs(block) do
			if mem and node_key ~= "used" and node_key ~= "best_before" then
				if key == nil or (type(mem) == "table" and mem[key] ~= nil) then
					local pos = keys_to_pos(block_key, node_key)
					local node = tubelib2.get_node_lvm(pos)
					if key ~= nil then
						-- only specified 'key'
						clbk(pos, node, {[key] = mem[key]})
					else
						-- without specified 'key'
						clbk(pos, node, mem)
					end
				end
			end
		end
	end
end
