--[[
	Save Table to File/Stringtable
	Load Table from File/Stringtable
	v 0.94
	
	Lua 5.1 compatible
	
	Userdata and indices of these are not saved
	Functions are saved via string.dump, so make sure it has no upvalues
	References are saved
	----------------------------------------------------
	table.save( table [, filename] )
	
	Saves a table so it can be called via the table.load function again
	table must a object of type 'table'
	filename is optional, and may be a string representing a filename or true/1
	
	table.save( table )
		on success: returns a string representing the table (stringtable)
		(uses a string as buffer, ideal for smaller tables)
	table.save( table, true or 1 )
		on success: returns a string representing the table (stringtable)
		(uses io.tmpfile() as buffer, ideal for bigger tables)
	table.save( table, "filename" )
		on success: returns 1
		(saves the table to file "filename")
	on failure: returns as second argument an error msg
	----------------------------------------------------
	table.load( filename or stringtable )
	
	Loads a table that has been saved via the table.save function
	
	on success: returns a previously saved table
	on failure: returns as second argument an error msg
	----------------------------------------------------
	
	chillcode, http://lua-users.org/wiki/SaveTableToFile
	Licensed under the same terms as Lua itself.
]]--
do
	-- declare local variables
	--// exportstring( string )
	--// returns a "Lua" portable version of the string
	local function exportstring( s )
		s = string.format( "%q",s )
		-- to replace
		s = string.gsub( s,"\\\n","\\n" )
		s = string.gsub( s,"\r","\\r" )
		s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" )
		return s
	end
--// The Save Function
function table.save(  tbl,filename )
	local charS,charE = "   ","\n"
	local file,err
	-- create a pseudo file that writes to a string and return the string
	if not filename then
		file =  { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
		charS,charE = "",""
	-- write table to tmpfile
	elseif filename == true or filename == 1 then
		charS,charE,file = "","",io.tmpfile()
	-- write table to file
	-- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
	else
		file,err = io.open( filename, "w" )
		if err then return _,err end
	end
	-- initiate variables for save procedure
	local tables,lookup = { tbl },{ [tbl] = 1 }
	file:write( "return {"..charE )
	for idx,t in ipairs( tables ) do
		if filename and filename ~= true and filename ~= 1 then
			file:write( "-- Table: {"..idx.."}"..charE )
		end
		file:write( "{"..charE )
		local thandled = {}
		for i,v in ipairs( t ) do
			thandled[i] = true
			-- escape functions and userdata
			if type( v ) ~= "userdata" then
				-- only handle value
				if type( v ) == "table" then
					if not lookup[v] then
						table.insert( tables, v )
						lookup[v] = #tables
					end
					file:write( charS.."{"..lookup[v].."},"..charE )
				elseif type( v ) == "function" then
					file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE )
				else
					local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
					file:write(  charS..value..","..charE )
				end
			end
		end
		for i,v in pairs( t ) do
			-- escape functions and userdata
			if (not thandled[i]) and type( v ) ~= "userdata" then
				-- handle index
				if type( i ) == "table" then
					if not lookup[i] then
						table.insert( tables,i )
						lookup[i] = #tables
					end
					file:write( charS.."[{"..lookup[i].."}]=" )
				else
					local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i )
					file:write( charS..index.."=" )
				end
				-- handle value
				if type( v ) == "table" then
					if not lookup[v] then
						table.insert( tables,v )
						lookup[v] = #tables
					end
					file:write( "{"..lookup[v].."},"..charE )
				elseif type( v ) == "function" then
					file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE )
				else
					local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
					file:write( value..","..charE )
				end
			end
		end
		file:write( "},"..charE )
	end
	file:write( "}" )
	-- Return Values
	-- return stringtable from string
	if not filename then
		-- set marker for stringtable
		return file.str.."--|"
	-- return stringttable from file
	elseif filename == true or filename == 1 then
		file:seek ( "set" )
		-- no need to close file, it gets closed and removed automatically
		-- set marker for stringtable
		return file:read( "*a" ).."--|"
	-- close file and return 1
	else
		file:close()
		return 1
	end
end

--// The Load Function
function table.load( sfile )
	-- catch marker for stringtable
	local tables,err
	if string.sub( sfile,-3,-1 ) == "--|" then
		tables,err = loadstring( sfile )
	else
		tables,err = loadfile( sfile )
	end
	if err then return nil,err
	end
	tables = tables()
	for idx = 1,#tables do
		local tolinkv,tolinki = {},{}
		for i,v in pairs( tables[idx] ) do
			if type( v ) == "table" and tables[v[1]] then
				table.insert( tolinkv,{ i,tables[v[1]] } )
			end
			if type( i ) == "table" and tables[i[1]] then
				table.insert( tolinki,{ i,tables[i[1]] } )
			end
		end
		-- link values, first due to possible changes of indices
		for _,v in ipairs( tolinkv ) do
			tables[idx][v[1]] = v[2]
		end
		-- link indices
		for _,v in ipairs( tolinki ) do
			tables[idx][v[2]],tables[idx][v[1]] =  tables[idx][v[1]],nil
		end
	end
	return tables[1]
end
-- close do
end


function table.simpleRecursiveSave(tbl, file, charE , depth)
	local sorted = {}
	for i,v in pairs( tbl ) do
		table.insert(sorted, i)
	end
	shellsort(sorted, "<")
	file:write( "{" )
	for sortIndex,index in ipairs( sorted ) do
		local v = tbl[index]
		local i = index

		if sortIndex > 1 then
			file:write(",")
		end

		if string.len(charE) > 0 then
			file:write(charE)
			for i=1,depth do
				file:write("	")
			end
		end
		if type( v ) ~= "userdata" and type( v ) ~= "function" then
			-- handle index

			if type( i ) == "string" then
				file:write( i.." = " )
			elseif type ( i ) == "number" then
				file:write( )
			end
			-- handle value
			if type( v ) == "table" then
				if depth < 2 then
					table.simpleRecursiveSave(v, file, "\n", depth +1)
				else
					table.simpleRecursiveSave(v, file, "", depth +1)
				end
			elseif type ( v ) == "string" then
				file:write( "\""..v.."\"" )
			elseif type ( v ) == "number" then
				file:write( tostring(v).."" )
			elseif type ( v ) == "boolean" then
				file:write( tostring(v).."" )
			end
		end
	end
	if string.len(charE) > 0 then
		file:write(charE)
		for i=1,depth-1 do
			file:write("	")
		end
	end
	file:write( "}" )
end

function table.simpleSave(  tbl,filename )
	local charS,charE = "","\n"
	local file,err
	-- create a pseudo file that writes to a string and return the string
	if not filename then
		file =  { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
		charS,charE = "",""
	-- write table to tmpfile
	elseif filename == true or filename == 1 then
		charS,charE,file = "","",io.tmpfile()
	-- write table to file
	-- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
	else
		file,err = io.open( filename, "w" )
		if err then return nil,err end
	end
	-- initiate variables for save procedure
	local tables,lookup = { tbl },{ [tbl] = 1 }
	table.simpleRecursiveSave(tbl, file, charE, 1)
	-- Return Values
	-- return stringtable from string
	if not filename then
		-- set marker for stringtable
		return file.str.."--|"
	-- return stringttable from file
	elseif filename == true or filename == 1 then
		file:seek ( "set" )
		-- no need to close file, it gets closed and removed automatically
		-- set marker for stringtable
		return file:read( "*a" ).."--|"
	-- close file and return 1
	else
		file:close()
		return 1
	end
end

function table.simpleLoad(filename)
	local file,err = io.open( filename, "r" )
	if file then
		local cfg = file:read("*a")
		local presetLoad = loadstring("return "..cfg)
		local preset = presetLoad() or nil
		file:close()
		return preset
	else
		return false, err--D.p(1, "table", tostring(filename).. " "..tostring(err))
	end
end
