--[[
	This script handles the serialization of data using the marshal library and the saving of this data to disk depending
	on save game name. What this means is that you can save entire tables to disk instead of saving to object packet.
	This script was created to alleviate issues with using packets to save dynamic data (pstor) which lead to save corruption.
	Also it removes some restrictions on what you can save.
	
	*only store valid lua types such as numbers, strings, boolean, functions or tables that contain these valid types. Userdata needs to have a special
	__persist function defined in it's metatable. See how it is done for CTime in _G.script
	
	*Supposedly you can save userdata if you write a proper __persist method for the metatable but I have failed to achieve proper results with serializing CTime.
	
	*You must register for 'save_state' and 'load_state' and add your own table to m_data for it to be encoded then stored in *.scoc
	*Although marshal is pretty fast, keep in mind that encoding/decoding a ton of data, saves will start to noticeablely take longer to save/load.
	*For testing/debugging you can uncomment the print_table calls in save_state and load_state. It will save the before and after tables to print_table.txt in your main directory.
		
	
	by: Alundaio
--]]
local m_data = {}

-- store stuff that you want to persist even offline
m_data.se_object = {}

-- store stuff only for online objects. When object goes offline this table is purged.
m_data.game_object = {}

-- PDA known contacts
-- m_data.actor_contacts = {}

saved_game_extension_ex	= save_extension() .. "_data"

local function on_pstor_load_all(obj,packet)
	local id = obj:id()
	local state = get_game_object_state(obj,false)
	if (state and db.storage[id]) then
		if (state.pstor_all) then
			db.storage[id].pstor = state.pstor_all
			state.pstor_all = nil
		end 

		if (state.pstor_ctime) then
			db.storage[id].pstor_ctime = state.pstor_ctime
			state.pstor_ctime = nil
		end
	end
end

function on_game_start()
	if not isMarshal then 
		return 
	end
	--RegisterScriptCallback("on_pstor_load_all",on_pstor_load_all)
end

-- called from engine!
function CALifeStorageManager_before_save(fname)
	if not isMarshal then 
		return 
	end

	--printf("CALifeStorageManager_before_save BEFORE callback")
	
	m_data.GAME_VERSION = GAME_VERSION
	
	SendScriptCallback("save_state",m_data)
	
	--printf("CALifeStorageManager_before_save AFTER callback")
	
	-- save pstor
	for id,t in pairs(db.storage) do
		if (m_data.game_object[id]) then
			if (t.pstor and not is_empty(t.pstor)) then
				m_data.game_object[id].pstor_all = t.pstor
			end
			
			-- serialization with game.CTime.__persist
			if (t.pstor_ctime and not is_empty(t.pstor_ctime)) then 
				m_data.game_object[id].pstor_ctime = t.pstor_ctime
			end
		end
	end
	
	--ProcessEventQueueState(m_data,true)
	
	-- clean out game_object table of empty sub tables
	for id,tbl in pairs(m_data.game_object) do 
		for k,v in pairs(tbl) do
			if (type(v) == "table" and is_empty(v)) then 
				m_data.game_object[id][k] = nil
			end
		end
	end
	
	local data = marshal.encode(m_data)
	if not (data) then 
		return 
	end
		
	local path = getFS():update_path('$game_saves$', '')

	lfs.mkdir(path) -- incase savegame folder doesn't exist yet
	path = path .. fname:sub(0,-6):lower() .. saved_game_extension_ex

	local savegame = io.open(path,"wb")
	if not (io.type(savegame) == "file") then
		printf("Error: Unable to write to %s",path)
		return 
	end
	
	--printf("axr_main: saving custom data %s",path)
	savegame:write(data)
	savegame:close()
	
	--printf("CALifeStorageManager_before_save FINISHED")
end 

-- called from engine!
function CALifeStorageManager_after_save(fname)
	if ffx_path_utils then
		fname = ffx_path_utils.get_file_name(fname, false)
	end
	
	local _path = getFS():update_path('$game_saves$', '')
	
	SendScriptCallback("save_file_created", _path, fname)
end

-- called from engine!
function CALifeStorageManager_new_game(fname)
	if ffx_path_utils then
		fname = ffx_path_utils.get_file_name(fname, false)
	end
	
	local _path = getFS():update_path('$game_saves$', '')
	
	SendScriptCallback("new_game_created", _path, fname)
end

-- called from engine!
function CALifeStorageManager_save(fname)
	--printf("CALifeStorageManager_save START FINISHED")
end 

-- called from engine
function CALifeStorageManager_load(fname)
	if not isMarshal then 
		return 
	end 

	local path = fname:sub(0,-6) .. saved_game_extension_ex

	--alun_utils.debug_write(strformat("CALifeStorageManager_load %s",path))
	
	local savegame = io.open(path,"rb")
	if not (io.type(savegame) == "file") then
		return
	end

	local data = savegame:read("*all")
	savegame:close()

	if not (data and data ~= "") then
		printf("Error: Failed to read %s",path)
		return
	end

	m_data = marshal.decode(data)
	
	--ProcessEventQueueState(m_data,false)
	
	-- For debugging save state
	--alun_utils.print_table(m_data,"m_data_on_load ("..path..")")

	SendScriptCallback("load_state",m_data)
	
	if ffx_path_utils then
		fname = ffx_path_utils.get_file_name(fname, false)
	end
	local _path = getFS():update_path('$game_saves$', '')
	SendScriptCallback("save_file_loaded", _path, fname)
	
	--alun_utils.debug_write(strformat("CALifeStorageManager_load END"))
end

function get_state()
	return m_data
end

function decode(t)
	return marshal.decode(t)
end

-- storage based on ID but verified by object name
function get_game_object_state(obj,create_if_dont_exist)
	local id = obj:id()
	local name = obj:name()
	if not (m_data.game_object[id]) then
		if not (create_if_dont_exist) then
			return 
		end
		m_data.game_object[id] = {}
		m_data.game_object[id].name = name
	end
	return m_data.game_object[id]
end	

function get_se_obj_state(se_obj,create_if_dont_exist)
	local id = se_obj.id
	local name = se_obj:name()
	if not (m_data.se_object[id]) then
		if not (create_if_dont_exist) then
			return 
		end
		m_data.se_object[id] = {}
		m_data.se_object[id].name = name
	end
	return m_data.se_object[id]
end
