--[[----------------------------------------------------------------------------

KSTableArchiving.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2003 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

--------------------------------------------------------------------------------

NOTE: Branched from Lightroom's //shadowland/main/frameworks/substrate/source/lua_utils/AgTableArchiving.lua.
Please use the wichita-integration-lightroom branch in Perforce to pick up
changes from the Lightroom team.

------------------------------------------------------------------------------]]


--============================================================================--
--[[
	
== Description ==

This API provides functions to can archive a table to a string and recreate
the table from the archived string. Both simple and some complex tables are
supported.

Simple tables are ones that do not contain multiple references to the same
table from multiple places in the table's closure. In this case, the table
will be written out in a format that maps as well as possible to something
a Lua programmer might put into source code when declaring such a table.

A complex table is one that has multiple references to the same table or
archivable userdata object.

Types that can be archived are boolean, number, string, and table. Some
userdata classes, such as AgPoint, AgRect, and the like are also supported.
(WICHITA NOTE: These are disabled.)

This code supports two forms of userdata archiving. Simple userdatas are
ones like AgPoint that can be written out using constructor syntax, such as
"AgPoint( 1, 0 )". Other userdatas for which an archiving and unarchiving
function have been provided via {|userdataArchivers|} can also be written
by being converted by these functions into tables.

AgClasses may be archived if they provide a toStorage and fromStorage
method.  Plain tables may be archived even if they contain non-archivable
members by also providing specially named fields AgTableArchiving_unarchiveMethod
and AgTableArchiving_archiveMethod.

== Limitations ==

Currently, tables containing keys that are themselves tables or archivable
userdata are not supported. Simple userdata can be used as table keys (as
can boolean, number, and string, of course).

== Functions ==

	{|*archiveNameAndTableToString*( _name_, _table_ )|}
		Generates and returns a string that contains an archived version of _table_ and is built in such a way that it will assign it to a variable named _name_.

	{|*unarchiveNameAndStringToTable*( _name_, _archivedString_, _useSimpleEnvironment_, _optAllowZSTR_ )|}
		Unarchives _archivedString_ by executing it in a temporary environment, returning the variable defined in _archivedString_ with the identifier _name_.  _name_ should be the same thing that was passed to {|archiveNameAndTableToString|} when the string was originally archived.  If
_optAllowZSTR_ is supplied and is true, then the ZSTR function will be made available.

	{|*simpleTableToChunkString*( _aTable_ )|}
		Subset of archive which supports only "simple" tables and produces only a chunk that defines the table (no assignment statement).  This routine does not take kindly to disobedience; it asserts if you break its rules about simplicity or pass it something other than a table and by using this routine you take on the responsibility for how the chunk is used to reconstitute the table later.
		
	{|*isStringValidIdentifier*( _aString_ )|}
		Returns true if the string is a valid Lua identifier. If so, it will be cached for future reference.

==Values==

	{|*simpleUserdataWriters*|}
		This is a table that maps classes to functions that can be used to write the class into a string which, when executed by the Lua interpreter, will regenerate an object with the original's value.  Note that this should be used only for simple objects that don't have links to other objects and which

	{|*userdataArchivers*|}
		This is a table that maps classes to tables (or objects) which contain information about how to write and read userdata objects.  The table/object must contain a "className" field which contains the class name written into the format (and used to find the reader when reading it back in), a {|toStorage( _uesrdata_ )|} function that writes the object to a table, and a {|fromStorage( _table_ )|} function which creates an object based on the table.

]]--

--============================================================================--

local string_find = string.find
local string_len = string.len
local string_format = string.format
local string_rep = string.rep

local table_concat = table.concat
local table_insert = table.insert
local table_sort = table.sort

local type = type
local pairs = pairs
local tostring = tostring

--[[ WICHITA DISABLED [kcarlson 2013-05-31]
local import = rawget( _G, "import" )  -- not defined in Wichita

--============================================================================--

local simpleUserdataWriters = {

--	AgPoint = tostring,
	AgMutablePoint = tostring,
	
	AgRange = tostring,
	AgMutableRange = tostring,
	
	AgRect = tostring,
	AgMutableRect = tostring,
	
	[ { namespace = 'AgUI', field = 'color', className = 'AgColor' } ] = tostring,

}
WICHITA DISABLED ]]--


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------

--[[ WICHITA [scouten 2010-11-05]: Disabled all of these.
local function AgPointToStorage( p ) return { x = p.x, y = p.y } end
local function AgPointFromStorage( t ) return AgPoint( t.x, t.y ) end

local function AgMutablePointToStorage( p ) return { x = p.x, y = p.y } end
local function AgMutablePointFromStorage( t ) return AgMutablePoint( t.x, t.y ) end

--------------------------------------------------------------------------------

local function AgRectToStorage( r ) return { xmin = r.x, ymin = r.ymin, xmax = r.xmax, ymax = r.ymax } end
local function AgRectFromStorage( t ) return AgRect( t.xmin, t.ymin, t.xmax, t.ymax ) end

local function AgMutableRectToStorage( r ) return { xmin = r.x, ymin = r.ymin, xmax = r.xmax, ymax = r.ymax } end
local function AgMutableRectFromStorage( t ) return AgMutableRect( t.xmin, t.ymin, t.xmax, t.ymax ) end

--------------------------------------------------------------------------------
-- SPAM [tgaul 2007-07-13]: based on the fact that AgRangeFromStorage was
-- wrong until now, it doesn't look like we're depending on being able to
-- archive AgRange objects anywhere.

local function AgRangeToStorage( r ) return { min = r.min, max = r.max } end
local function AgRangeFromStorage( t ) return AgRange( t.min, t.max ) end

local function AgMutableRangeToStorage( r ) return { min = r.min, max = r.max } end
local function AgMutableRangeFromStorage( t ) return AgMutableRange( t.min, t.max ) end
]]--

--------------------------------------------------------------------------------

--[[ WICHITA [scouten 2010-11-05]: Disabled all of these.
local userdataArchivers = {

	-- SPAM [tgaul]: The writers here aren't actually needed anymore since the
	-- table above handles the geometry types as simple userdatas. The read
	-- code is only needed if there are any existing files with the old version
	-- of these classes. (I don't know of any offhand.)

	AgPoint = { toStorage = AgPointToStorage, fromStorage = AgPointFromStorage },
	AgMutablePoint = { toStorage = AgMutablePointToStorage, fromStorage = AgMutablePointFromStorage },

	AgRect = { toStorage = AgRectToStorage, fromStorage = AgRectFromStorage },
	AgMutableRect = { toStorage = AgMutableRectToStorage, fromStorage = AgMutableRectFromStorage },

	AgRange = { toStorage = AgRangeToStorage, fromStorage = AgRangeFromStorage },
	AgMutableRange = { toStorage = AgMutableRangeToStorage, fromStorage = AgMutableRangeFromStorage },
	
	-- These can also be keys that are class objects, but in that case the value
	-- must be a table with a className field.

}
]]--


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------
--[[ WICHITA DISABLED [kcarlson 2013-05-31]

local function classFromString( className )

	if type( className ) == 'string' then
		return _env[ className ]
	elseif type( className ) == 'table' then
		return import( className.namespace )[ className.field ]
	end

	return nil

end

---------------------------------------------------------------------------------

local function getSimpleUserdataWriter( value )

	for className, writer in pairs( simpleUserdataWriters ) do
		if classFromString( className ):testObject( value ) then return writer end
	end

	return nil

end

---------------------------------------------------------------------------------

local function canWriteUserdataSimply( value )

	return getSimpleUserdataWriter( value ) ~= nil

end

---------------------------------------------------------------------------------

local function userdataAsStringSimply( value )

	local writer = getSimpleUserdataWriter( value )
	if writer then
		return writer( value )
	end

	error( "Can't simply-write userdata value: " .. tostring( value ) )

end


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------

local function archiverForValue( val )

	for class, archiver in pairs( userdataArchivers ) do
		if type( class ) == 'string' then
			if classFromString( class ):testObject( val ) then
				return archiver, class
			end
		elseif class:testObject( val ) then
			return archiver, archiver.className
		end
	end
	
	return nil

end

---------------------------------------------------------------------------------

local function archiverForClass( inClassName )

	for class, archiver in pairs( userdataArchivers ) do
		if type( class ) == "string" then
			if class == inClassName then
				return archiver
			end
		elseif archiver.className == inClassName then
			return archiver
		end
	end
	
	return nil

end

---------------------------------------------------------------------------------

local function getClassOfRootInStorage( v )

	if v then
		return v._ag_className
	end

end

---------------------------------------------------------------------------------

local function callArchiverMethod( archiver, methodName, val )

	if archiver == nil then
		return nil
	end
	
	local f = archiver[ methodName ]
	
	if f then
		return f( val )
	end
	
end

---------------------------------------------------------------------------------

local function objectToStorage( v )

	local archiver, className = archiverForValue( v )
	local t = callArchiverMethod( archiver, 'toStorage', v )

	if t == nil then
		error( "A userdata value returned nil when attempting to archive itself; it should have returned a table." )
	end

	t._ag_className = className
	return t

end

---------------------------------------------------------------------------------

local function objectFromStorage( v )

	local archiver = archiverForClass( getClassOfRootInStorage( v ) )
	return callArchiverMethod( archiver, 'fromStorage', v )

end

WICHITA DISABLED ]]

--============================================================================--

--@mark	-
--@mark	TextStream

---------------------------------------------------------------------------------

local function TextStream_appendText( textStream, text )

	local nextIndex = textStream.n + 1
	textStream.n = nextIndex
	
	textStream[ nextIndex ] = text

end

---------------------------------------------------------------------------------

local function TextStream_text( textStream )

	local str = table_concat( textStream )
	textStream[ 1 ] = str

	-- Manually nil out the remaining elements.
	-- Note that AgTableArchiving is a special, early loaded file that can't
	-- use the AgTableUtils namespace (tgogolin)

    for i = 2, textStream.n do
		textStream[ i ] = nil
	end
	
	return str

end

---------------------------------------------------------------------------------

local function TextStream_new()

	return { n = 0 }

end


--============================================================================--

--@mark	-
--@mark	Utilities

---------------------------------------------------------------------------------
--[[
	Generic compare of any two values. If the two types are not the same type,
	then the types are compared (thus ensuring that all values of each type
	will sort together).
]]--

local function compareValues( a, b )

	local typeA = type( a )
	local typeB = type( b )

	if typeA ~= typeB then
		return typeA < typeB
	end
	
	if typeA == 'boolean' then
		return ( not a ) and b
	else
		return a < b
	end

end

---------------------------------------------------------------------------------

local function indentString( indent )

	return string_rep( '\t', indent )

end

---------------------------------------------------------------------------------
--[[
	Return an array of all the keys in a table.
]]--

local function allKeys( t )

	local result = {}

	for k, _ in pairs( t ) do
		table_insert( result, k )
	end

	return result

end

---------------------------------------------------------------------------------

local function sortedKeys( t )

	local result = allKeys( t )
	table_sort( result, compareValues )
	return result

end

---------------------------------------------------------------------------------

local kLuaKeywords = {
	[ "and" ] = true,
	[ "break" ] = true,
	[ "do" ] = true,
	[ "else" ] = true,
	[ "elseif" ] = true,
	[ "end" ] = true,
	[ "false" ] = true,
	[ "for" ] = true,
	[ "function" ] = true,
	[ "if" ] = true,
	[ "in" ] = true,
	[ "local" ] = true,
	[ "nil" ] = true,
	[ "not" ] = true,
	[ "or" ] = true,
	[ "repeat" ] = true,
	[ "return" ] = true,
	[ "then" ] = true,
	[ "true" ] = true,
	[ "until" ] = true,
	[ "while" ] = true,

		-- [tgaul 2008-01-31]: technically, these aren't keywords,
		-- but it seems prudent to protect them as well.
	[ "arg" ] = true,
	[ "self" ] = true,
	
	-- [tgaul 2008-01-31]: Note that if a future version of Lua adds keywords,
	-- this code won't be preventing those from being written as an identifier.
	--
	-- We could quote all key string using the clumsy ["foo"] syntax in our
	-- serialized tables, but that would make them appear much less pretty, so
	-- I'm inclined to just live with this exclusion list and that possibility.
	--
	-- What we probably really need is a serialized table format that doesn't
	-- require a Lua interpreter to expand...
}

local gValidIdentifiers = {}

local gInvalidIdentifiers = setmetatable( {}, { __mode = 'kv' } )
	-- A map from invalid strings to tables. Cleared at GC time.

---------------------------------------------------------------------------------

local function isStringValidIdentifier( v )

	if gValidIdentifiers[ v ] then
		return true
	end
	
	if kLuaKeywords[ v ] then
		return false
	end
	
	if gInvalidIdentifiers[ v ] then
		return false
	end
	
	if #v == 0 then
		return false
	end
	
	local first, last = string_find( v, '[%w_]*' )
	
	if first ~= 1 or last ~= string_len( v ) or string_find( v, '%d' ) == 1 then
		gInvalidIdentifiers[ v ] = { }
		return false
	end
	
	gValidIdentifiers[ v ] = true
	
	return true

end

---------------------------------------------------------------------------------

local function isValueValidIdentifier( v )
	return type( v ) == 'string' and isStringValidIdentifier( v )
end

---------------------------------------------------------------------------------

local function isScalarType( theType )
	return theType == 'number' or theType == 'string' or theType == 'boolean'
end

---------------------------------------------------------------------------------

local function isScalarValue( val )
	return isScalarType( type( val ) )
end

---------------------------------------------------------------------------------

local function isSimpleObject( val )
--[[ WICHITA DISABLED [kcarlson 2013-05-31]
	return type( val ) == 'userdata' and canWriteUserdataSimply( val )
]]
	return false
end

---------------------------------------------------------------------------------

local function isSimpleValue( val )
	return isScalarValue( val ) or isSimpleObject( val )
end

---------------------------------------------------------------------------------
-- See chapter 12 in "Programming in Lua" for some of these ideas

local function serializeSimpleValue( v )
	
	local theType = type( v )
	
	if theType == 'string' then

		return string_format( '%q', v )

	elseif theType == 'number' or theType == 'boolean' then
		if v == 1 / 0 then -- inf (aka math.huge) the same regardless of numerator
			return "1 / 0"
		elseif v == -1 / 0 then -- -inf (aka -math.huge)
			return "-1 / 0"
		elseif v ~= v then -- NaN is the only value not equal to itself
			return "0 / 0"
		else
			return tostring( v )
		end
--[[ WICHITA DISABLED [kcarlson 2013-05-31]
	elseif theType == 'userdata' then
		return userdataAsStringSimply( v )
]]
	end
	
	return nil
		
end

---------------------------------------------------------------------------------

local function countAppearances( val, appearanceCounts )

	local theType = type( val )
	if theType == 'table' or theType == 'userdata' then

		appearanceCounts[ val ] = 1 + ( appearanceCounts[ val ] or 0 )
		
		-- Only walk into the table and account for its children the first time
		-- we see the table. (Prevents infinite recursion.)

		if theType == 'table' and appearanceCounts[ val ] == 1 then
			for k, v in pairs( val ) do
				countAppearances( v, appearanceCounts )
			end
		end

	end

end

--------------------------------------------------------------------------------

local function isScalarOrSimpleObjectOrSingleton( val, appearanceCounts )
	
	return isSimpleValue( val ) or appearanceCounts[ val ] == 1
	
end

--------------------------------------------------------------------------------
--[[
	A table is "simple" if all of its subtables (and their subtables) and
	userdata are referenced only ONCE. Any multiple references kills the "simple"
	property, and prohibits the use of "constructor syntax" for writing tables
	out to strings. [tgogolin]

	Returns a bool if the passed in table is simple.
]]--

local function allChildrenAreSingleton( val, appearanceCounts, singletonTables )

	if isSimpleValue( val ) then
		return true
	end

	-- It's a table or userdata; have we already cached the answer to this question?

	local precomputed = singletonTables[ val ]
	if type( precomputed ) == 'boolean' then
		return precomputed
	end
	
	-- Not already computed before, so do the work now.
	
	if type( val ) == 'table' then

		local result = true

		for k, v in pairs( val ) do

			local ImOK = isScalarOrSimpleObjectOrSingleton( v, appearanceCounts )
			result = ImOK and allChildrenAreSingleton( v, appearanceCounts, singletonTables )

			if result == false then
				singletonTables[ val ] = false
				return false
			end

		end
		
		singletonTables[ val ] = true
		return true

	end
	
	return false
	
end


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------

local saveSimple

--[[ WICHITA DISABLED [kcarlson 2013-05-31]
local doSave

---------------------------------------------------------------------------------

local function topLevelSave( stream, name, val )

	local appearanceCounts = {}
	local singletonTables = {}
	local saved = {}
	countAppearances( val, appearanceCounts )
	doSave( stream, name, val, saved, appearanceCounts, singletonTables )
	
end

---------------------------------------------------------------------------------

function doSave( stream, name, value, saved, appearanceCounts, singletonTables )

	if name then
		TextStream_appendText( stream, name )
		TextStream_appendText( stream, ' = ' )
	end
	
	if isSimpleValue( value ) then

		TextStream_appendText( stream, serializeSimpleValue( value ) )
		TextStream_appendText( stream, '\n' )

	elseif type( value ) == 'table' then

		if saved[ value ] then
			TextStream_appendText( stream, saved[ value ] ) -- use its previous name
			TextStream_appendText( stream, '\n' )
		else

			saved[ value ] = name   		-- save name for next time
	
			if allChildrenAreSingleton( value, appearanceCounts, singletonTables ) then

				-- All of this table's children are singletons, but this table itself may be
				-- referred to again; remember it in the "saved" table so the next usage will
				-- be output as an assignment.
				
				saveSimple( stream, value )
				TextStream_appendText( stream, '\n' )

			else

				-- First time we've seen this table, so create a new one.
				
				TextStream_appendText( stream, '{}\n' )

				for k, v in pairs( value ) do	-- save its fields

					local fieldname
					if isValueValidIdentifier( k ) then
						fieldname = string.format( '%s.%s', name, k )
					else
						fieldname = string.format( '%s[ %s ]', name, serializeSimpleValue( k ) )
					end

					doSave( stream, fieldname, v, saved, appearanceCounts, singletonTables )

				end

			end

		end

	else
		error( "Cannot save a " .. type( value ) )
	end
	
end


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------

local replaceObjectsWithTableStorage

---------------------------------------------------------------------------------

local function archiveNameAndTableToString( inName, inTable )
	--( string table -- string )

	-- Write it out to a stream.

	os.setlocale( 'C', 'all' )
		-- [tgaul 2007-09-10]: Seems some code is changing the C locale out from
		-- under us.  This is a stopgap to make sure at least our table archiving
		-- code is immune to that nastiness.  We don't set it back to what it
		-- used to be, since our app assumes its C anyway.
	
	local stream = TextStream_new()
	
	-- Tablify all the objects.

	local storage = replaceObjectsWithTableStorage( inTable )
	
	-- Stream the pure table out to a text stream.
	
	topLevelSave( stream, inName, storage )
	
	-- Get the stream results.
	
	return TextStream_text( stream )

end

---------------------------------------------------------------------------------

local replaceTableStorageWithObjects

---------------------------------------------------------------------------------

local function makeUnarchivingEnvironment()

	local env = {}
	
	-- We need to provide the protected environment access to the
	-- classes that use simpleUserdataWriters.

	for className, _ in pairs( simpleUserdataWriters ) do
		local envName = className
		if type( envName ) == 'table' then
			envName = envName.className
		end
		env[ envName ] = classFromString( className )
	end
	
	return env

end

---------------------------------------------------------------------------------

local function unarchiveNameAndStringToTable( inName, inString, useSimpleEnvironment, optAllowZSTR, optAdditionalEnvironment )

	--( string string -- table )
	
	local t = nil
	local result = nil
	
	local f, _ = loadstring( inString, "unarchived table " .. tostring( inName ) )
	if f ~= nil then

		-- Don't let the function mess with _G if it can be avoided.

		local tempEnv
		if useSimpleEnvironment then
			tempEnv = {}
		else
			tempEnv = makeUnarchivingEnvironment()
		end
		
		if optAllowZSTR then
			tempEnv.ZSTR = rawget( _G, "ZSTR" )
		end
		
		if optAdditionalEnvironment then
			for k,v in pairs( optAdditionalEnvironment ) do
				tempEnv[k] = v
			end
		end

		setfenv( f, tempEnv )
		f()

		t = tempEnv[ inName ]
		if t then
			result = replaceTableStorageWithObjects( t )
		end

	end

	return result

end

WICHITA DISABLED ]]

---------------------------------------------------------------------------------

local function writeKeyWithBrackets( stream, k )

	TextStream_appendText( stream, '[' )
	saveSimple( stream, k, 0 )
	TextStream_appendText( stream, ']' )

end

---------------------------------------------------------------------------------

local function writeTableKey( stream, k )

	local kType = type( k )

	if isValueValidIdentifier( k ) then

		-- If the key is an identifier, use the simple "k = val" table syntax.

		TextStream_appendText( stream, k )

	elseif isSimpleValue( k ) then

		-- Key is not an identifier string, so use more cumbersome "[k] = val" syntax.

		writeKeyWithBrackets( stream, k )

	else

		error( "can't write key with type ".. kType ..": "..tostring( k ) )

	end

end


--============================================================================--

--@mark	-

---------------------------------------------------------------------------------
--[[
	This function is only good for serializing simple tables. It can handle
	nested tables. It can NOT handle tables that shares subtable references.
	It can NOT handle circular references. For those "simple" tables that don't
	have a complex topology though, the output of this function is very readable.
	It is also reproducible and reliably diffable, since the keys to all tables
	are sorted before outputting them. [tgogolin]
	
	All UserData should be removed from this hierarchy before we get here.
]]--

local function saveSimpleTable( stream, value, indent )

	assert( type( value ) == 'table' )

	indent = indent or 0

	TextStream_appendText( stream, '{\n' )
	indent = indent + 1
	
	-- Sort the table keys, then output the entries in the sorted order.
	
	local theSortedKeys = sortedKeys( value )
	
	-- First write out all of the array values that can be written without
	-- a key (i.e. 1..n consecutive).
	
	local nextIntKey = 1
	
	-- Now write the other keys in sorted order (skipping the ones
	-- we've already written).

	for _, k in ipairs( theSortedKeys ) do
		
		TextStream_appendText( stream, indentString( indent ) )

		if k ~= nextIntKey then
			writeTableKey( stream, k )
			TextStream_appendText( stream, ' = ' )
		else
			nextIntKey = nextIntKey + 1
		end
		
		saveSimple( stream, value[ k ], indent )
		TextStream_appendText( stream, ',\n' )
		
	end
			
	indent = indent - 1
	TextStream_appendText( stream, indentString( indent ) )
	TextStream_appendText( stream, '}' )

end

---------------------------------------------------------------------------------

saveSimple = function( stream, value, indent )

	if isSimpleValue( value ) then
		TextStream_appendText( stream, serializeSimpleValue( value ) )
	elseif type( value ) == 'table' then
		saveSimpleTable( stream, value, indent )
	else
		error( "Cannot save a " .. type( value ) )
	end

end

---------------------------------------------------------------------------------

-- use the simple test at the end of this file to verify changes

local function simpleTableToChunkString( aTable )

	if type( aTable ) ~= 'table' then
		error( "not a table: " .. type( aTable ), 2 )
	end
	
	local counts = {}
	countAppearances( aTable, counts )
	if not allChildrenAreSingleton( aTable, counts, {} ) then
		debug.print( "table not simple", debug.traceback() )
		error "table not simple"
	end
	
	os.setlocale( 'C', 'all' )
		-- [tgaul 2007-09-10]: Seems some code is changing the C locale out from
		-- under us.  This is a stopgap to make sure at least our table archiving
		-- code is immune to that nastiness.  We don't set it back to what it
		-- used to be, since our app assumes its C anyway.
	
	local stream = TextStream_new()
	saveSimpleTable( stream, aTable )
	return TextStream_text( stream )

end

---------------------------------------------------------------------------------
--[[ WICHITA DISABLED [kcarlson 2013-05-31]

local tableIsSimple, AgObservations

local function valueIsSimple( aValue, seenThisTableBefore )

	local vType = type( aValue )
	
	if vType == 'boolean' or vType == 'number' or vType == 'string' then
		return true
	end
	
	if vType == 'table' then
		return tableIsSimple( aValue, seenThisTableBefore )
	end
	
	return false

end

---------------------------------------------------------------------------------

tableIsSimple = function( aTable, seenThisTableBefore )

	-- Class values are not allowed.

	local mt = getmetatable( aTable )
	if mt and mt.__classPtr then return false end

	-- Make sure we haven't seen this table before.
	
	if seenThisTableBefore[ aTable ] then
		return false
	end
	seenThisTableBefore[ aTable ] = true

	-- Scan each value.
	
	AgObservations = AgObservations or import 'AgObservations'

	for k, v in AgObservations.pairs( aTable ) do
		if not valueIsSimple( k, seenThisTableBefore ) then return false end
		if not valueIsSimple( v, seenThisTableBefore ) then return false end
	end
	
	return true

end

---------------------------------------------------------------------------------

local function isValidSimpleTable( aTable )

	if type( aTable ) ~= 'table' then
		return false
	end

	local seenThisTableBefore = {}
	return tableIsSimple( aTable, seenThisTableBefore )

end

---------------------------------------------------------------------------------

local function isObjectStorage( t )
	return t._ag_className ~= nil
end

---------------------------------------------------------------------------------

function replaceTableStorageWithObjects( storage, seenValues )

	seenValues = seenValues or {}

	local rememberedValue = seenValues[ storage ]
	if rememberedValue ~= nil then
		return rememberedValue
	end
	
	local theType = type( storage )

	if isSimpleValue( storage ) or theType == 'function' then

		return storage

	elseif theType == 'table' then

		if isObjectStorage( storage ) then
			local object = objectFromStorage( storage )
			seenValues[ storage ] = object	-- remember it
			return object
		end
		
		local unarchive = storage.AgTableArchiving_unarchiveMethod

		if type( unarchive ) == 'table' and
			type( unarchive.namespace ) == 'string' and
			type( unarchive.func ) == 'string'
		then

			return import( unarchive.namespace )[ unarchive.func ]( storage )

		else

			local aTable = {}
			seenValues[ storage ] = aTable	-- remember it
			for k, v in pairs( storage ) do
				aTable[ k ] = replaceTableStorageWithObjects( v, seenValues )
			end
			return aTable

		end

	end

end

WICHITA DISABLED ]]

---------------------------------------------------------------------------------
--[[
	Given a tree root, walk all children and return a tree that is identical
	except that all AgObjects are replaced by the results of their toTableStorage
	methods.
]]--

--[[ WICHITA DISABLED [kcarlson 2013-05-31]
function replaceObjectsWithTableStorage( val, savedObjects )

	savedObjects = savedObjects or {}

	local rememberedTable = savedObjects[ val ]
	if rememberedTable ~= nil then
		return rememberedTable
	end

	local theType = type( val )
	if theType == 'userdata' then

		if canWriteUserdataSimply( val ) then
			return val
		end

		-- Never seen this object before, convert it to a table.

		rememberedTable = objectToStorage( val )
		savedObjects[ val ] = rememberedTable		-- remember it here

		return rememberedTable

	end
	
	-- If val is a table, recurse on its entries.
	
	if theType == 'table' then

		if hasmethod( val, 'AgTableArchiving_archiveMethod' ) then
			-- this table wants to take care of archiving itself
			return val:AgTableArchiving_archiveMethod()
		end
			
		rememberedTable = {}
		savedObjects[ val ] = rememberedTable		-- remember it here

		for k, v in pairs( val ) do
			rememberedTable[k] = replaceObjectsWithTableStorage( v, savedObjects )
		end

		return rememberedTable

	end
	
	-- It must be a scalar; simply return it.

	return val

end
WICHITA DISABLED ]]

--------------------------------------------------------------------------------

return {
-- WICHITA [scouten 2010-11-05]: Export doesn't exist in Wichita.
-- export 'AgTableArchiving' {
--[[ WICHITA DISABLED [kcarlson 2013-05-31]

	simpleUserdataWriters = simpleUserdataWriters,
	userdataArchivers = userdataArchivers,
	
	archiveNameAndTableToString = archiveNameAndTableToString, -- ( name table -- tableAsString )
	unarchiveNameAndStringToTable = unarchiveNameAndStringToTable, -- ( name archiveString -- table )
]]
	simpleTableToChunkString_deprecated = simpleTableToChunkString,
--[[ WICHITA DISABLED [kcarlson 2013-05-31]
	isValidSimpleTable = isValidSimpleTable,
]]
	
	isStringValidIdentifier = isStringValidIdentifier,
}


--============================================================================--

--@mark	-
--@mark Test code

--------------------------------------------------------------------------------
--[[
function p( t ) table.foreach( t, print ) end

-- test walk thru:
-- create test data structure:
test = { a = AgMutablePoint( 1, 2 ) }
test.b = test.a

-- write it out to a stream
stream = TextStream_new()

-- tablify all the objects
storage = replaceObjectsWithTableStorage( test )

-- stream the pure table out to a text stream
save( stream, "foo", storage )

-- print the stream results
TextStream_text( stream)

-- restore original data structure
t = replaceTableStorageWithObjects( foo )

p(t)

t.b.y = 30

]]--
