require "Debug_1";

local CHUNK_SIZE = 1000;
local cache = {};
local slotNames = {};
local barrier = 1000;

local EEPLoadData = EEPLoadData;
_G.EEPLoadData = nil;
local EEPSaveData = EEPSaveData;
_G.EEPSaveData = nil;
local loadstring = loadstring or load;

local function controlCharSubstituter(ctl, next)
	local len = string.len(ctl);
	local arr = { string.byte(ctl, 1, len) };
	if next ~= "" then
		arr[len] = string.format("%03d", arr[len]);
	end;
	local subst = "\\" .. table.concat(arr, "\\");
	return subst .. next;
end;

local function serialize_recursively(value, tables)
	local s = "";
	local t = type(value);
	if t == "number" then
		s = tostring(value);
	elseif t == "string" then
		s = "'"..string.gsub(value, "([%c\"\']+)([%d]?)", controlCharSubstituter).."'";
	elseif t == "boolean" then
		s = tostring(value);
	elseif t == "table" then
		if tables[value] then
			error("cannot serialize tables with multiple references (at nesting level " .. #tables .. ")");
		else
			tables[value] = true;
			s = "{";
			for n, v in pairs(value) do
				local field = "[" .. serialize_recursively(n, tables) .. "]";
				if string.sub(field, 3, 3) ~= "_" then -- skip _*-fields
					if s ~= "{" then
						s = s .. "; ";
					end;
					s = s .. field .. "=" .. serialize_recursively(v, tables);
				end;
			end;
			s = s .. "}";
		end;
	elseif t == "function" then
		s = serialize_recursively(string.dump(value));
		s = "loadstring(" .. s .. ")";
	else
		error("cannot serialize a " .. t .. " (at nesting level " .. #tables .. ")");
	end;
	return s;
end;

local function serialize(value)
	local tables = {};
	local result = serialize_recursively(value, tables);
	return result;
end;

local function deserialize(text)
	local s = "return " .. text;
	local f, message = loadstring(s);
	if f then
		local value = f();
		return value;
	else
		error(message);
	end;
end;

local function getSlots(name, count)
	local indexes = {};
	for k, v in pairs(slotNames) do
		if v == name then
			table.insert(indexes, k);
		end;
	end;
	if count then
		local n = #indexes;
		local mustUpdate = false;
		if n < count then
			for i = 1, 1000 do
				if slotNames[i] == nil then
					table.insert(indexes, i);
					slotNames[i] = name;
					count = count - 1;
					mustUpdate = true;
					if count <= 0 then
						break;
					end;
				end;
			end;
		elseif n > count then
			for i = count + 1, n do
				local old = table.remove(indexes);
				slotNames[old] = nil;
				if not EEPSaveData(old, nil) then
					error("Persistence_3.getSlots(): EEPSaveData(" .. tostring(i) .. ", nil) failed");
				end;
				mustUpdate = true;
			end;
		end;
		if mustUpdate then
			local s = serialize(slotNames);
			local l = #s;
			if l % CHUNK_SIZE == 0 then
				l = l / CHUNK_SIZE + 1;
				s = s .. " ";
			else
				l = math.ceil(l / CHUNK_SIZE);
			end;
			if #slotNames >= 1001 - l then
				error("!!! Panic: EEP Slots exhausted !!!");
			else
				for i = 1, l do
					local part = string.sub(s, (i - 1) * CHUNK_SIZE + 1, i * CHUNK_SIZE);
					local success = EEPSaveData(1001 - i, part);
					if not success then
						error("Persistence_3.getSlots(): EEPSaveData(" .. tostring(i) .. ", '" .. part .. "') failed");
					end;
				end;
				barrier = 1000 - l;
			end;
		end;
	end;
	table.sort(indexes);
	return indexes;
end;

local function getSortedNames(pattern)
	pattern = pattern or ".*";
	local names = {};
	for i, name in pairs(slotNames) do
		local m, x, y, z = string.match(name, pattern);
		if m then
			local found = false;
			for j, n in pairs(names) do
				if n == name then
					found = true;
					break;
				end;
			end;
			if not found then
				table.insert(names, name);
			end;
		end;
	end;
	table.sort(names);
	Debug.printf("Persistence", "Persistence.getSortedNames('%s') returns %d names", pattern, #names);
	return names;
end;

if Persistence then
	error("Only a single Persistence module must be loaded.");
end;
local s = "";
for i = 1000, 0, -1 do
	local status, content = EEPLoadData(i);
	if status then
		s = s .. content;
		if content:len() < CHUNK_SIZE then
			barrier = i - 2;
			break;
		end;
	else
		if i < 1000 then
			error("Persistence_3: Initialization failed to read slot "..tostring(i));
		end;
		break;
	end;
end;
if s:len() >= 3 then
	slotNames = deserialize(s);
end;
s = nil;

Persistence = {
	_VERSION = "3.1";

	-- Funktion		getSortedNames(pattern)
	-- Bedeutung:	liefert alle Speicherplatznamen, zurück, die zum regulären Ausdruck pattern passen
	-- pettern:		String	(optional) regulärer Ausdruck zur Einschränkung
	-- Rückgabe:	sortierte Tabelle mit den gefundenen Speicherplatznamen, ggf. als leere Tabelle
	getSortedNames = getSortedNames;

	-- Funktion		serialize(value)
	-- Bedeutung:	serialisiert den übergebenen Wert, bricht bei unbekannten Datentypen und bei
	--						Verweis-Schleifen mit einer Fehlermeldung ab.
	-- value:			String	zu serialisierender Wert
	-- Rückgabe:	serialisierter Wert als Zeichenkette
	serialize = serialize;

	-- Funktion 	deserialize(string)
	-- Bedeutung:	wandelt die Zeichenkette in einen Wert um (oder produziert einen Fehler)
	-- string:		String
	-- Rückgabe:	deserialisierter Wert
	deserialize = deserialize;
};

setmetatable(Persistence, {
	__index = function(t, k)
		Debug.printf("Persistence", "\nsearching key %s", tostring(k));
		local v = cache[k];
		if v then
			return v;
		else
			local s = "";
			for i, j in pairs(getSlots(k)) do
				local status, content = EEPLoadData(j);
				if status then
					s = s .. content;
				else
					error("Persistence_3.__index() failed to read slot "..tostring(j));
				end;
			end;
			if #s == 0 then
				return nil;
			else
				return deserialize(s);
			end;
		end;
	end;
	__newindex = function(t, k, v)
		if v == nil then
			Debug.printf("Persistence", "clearing %s", tostring(k));
			getSlots(k, 0);
			cache[k] = nil;
		elseif type(v) ~= "table" and v == cache[k] then
			Debug.printf("Persistence", "keeping %s (unchanged)", tostring(k));
		else
			local s = serialize(v);
			Debug.printf("Persistence", "storing %s = %s", tostring(k), s);
			local count = math.ceil(#s / CHUNK_SIZE);
			local indexes = getSlots(k, count);
			if indexes[#indexes] >= barrier then
				error("!!! Persistence_3.__newindex(): no more slots available !!!");
			else
				for i, j in pairs(indexes) do
					local part = string.sub(s, (i - 1) * CHUNK_SIZE + 1, i * CHUNK_SIZE);
					if not EEPSaveData(j, part) then
						error("Persistence_3.__newindex(): EEPSaveData(" .. tostring(j) .. ", '" .. part .. "') failed");
					end;
				end;
			end;
			cache[k] = v;
		end;
	end;
});

print("Persistence Version " .. Persistence._VERSION);

return Persistence;
