
local Msg			= Msg
local tostring 		= tostring
local type			= type
local pairs			= pairs
local string		= string
local gmod			= gmod
local PrintTable	= PrintTable
local CLIENT		= CLIENT
local table			= table
local debug			= debug


/*

  This module is to help bind the engine's saverestore stuff to Lua.
  
  This is so we can save Lua stuff in the save game file. The entities
  should automatically save most of their table contents to the save file.
  
  !!Warning!!: Editing this file may render old saves useless.
  
  You can hook into this system like so
  
	local function MySaveFunction( save )
		saverestore.WriteTable( my_table, save )
	end

	local function MyRestoreFunction( restore )
		my_table = saverestore.ReadTable( restore )
	end
  
	saverestore.AddSaveHook( "HookNameHere", MySaveFunction )
	saverestore.AddRestoreHook( "HookNameHere", MyRestoreFunction )

*/

module("saverestore")

local TYPE_NONE 	= 0
local TYPE_FLOAT	= 1 // Treat all numbers as floats (they're all the same to Lua)
local TYPE_STRING 	= 2
local TYPE_ENTITY 	= 3
local TYPE_VECTOR	= 4
local TYPE_TABLE	= 5
local TYPE_BOOL		= 6
local TYPE_ANGLE	= 7

local SaveHooks = {}
local RestoreHooks = {}
local TableRefs = {}
local TableRef = 1

function PreSave( )

	TableRefs = {}
	TableRef = 1

end

function PreRestore( )

	TableRefs = {}
	TableRef = 1

end

/*---------------------------------------------------------
   Name: GetTypeStr
   Desc: Given a string returns a TYPE_ 
---------------------------------------------------------*/
local function GetTypeStr( name )

	if (name == "function") then return TYPE_NONE end

	if (name == "number") then return TYPE_FLOAT end
	if (name == "string") then return TYPE_STRING end
	if (name == "Entity") then return TYPE_ENTITY end
	if (name == "Vehicle") then return TYPE_ENTITY end
	if (name == "NPC") then return TYPE_ENTITY end
	if (name == "Player") then return TYPE_ENTITY end
	if (name == "Weapon") then return TYPE_ENTITY end
	if (name == "Vector") then return TYPE_VECTOR end
	if (name == "Angle") then return TYPE_ANGLE end
	if (name == "table") then return TYPE_TABLE end
	if (name == "boolean") then return TYPE_BOOL end
	
	// These aren't saved
	if (name == "ConVar") then return TYPE_NONE end
	if (name == "PhysObj") then return TYPE_NONE end
	
	// Bitch about it incase I've forgot to hook a savable type up
	Msg("Can't save unknown type "..name.."\n")
	return TYPE_NONE

end

/*---------------------------------------------------------
   Name: GetType
   Desc: Given a variable returns a TYPE_ 
---------------------------------------------------------*/
local function GetType( var )

	return GetTypeStr( type(var) )

end


/*---------------------------------------------------------
   Name: IsWritable
   Desc: Returns true if we can save the K/V pair
---------------------------------------------------------*/
local function IsWritable( k, v )

	local itype = GetType( k )
	if ( itype == TYPE_NONE ) then return false end
	if ( itype == TYPE_STRING && k == "SR_Recursion" ) then return false end

	local itype = GetType( v )
	if ( itype == TYPE_NONE ) then return false end
		
	return true

end


/*---------------------------------------------------------
   Name: WriteVar
   Desc: Writes a single variable to the save
---------------------------------------------------------*/
function WritableKeysInTable ( t )
  
	local i = 0
  
	for k,v in pairs(t) do 
		if ( IsWritable( k, v ) ) then
			i = i + 1 
		end
	end
  
	return i

end


/*---------------------------------------------------------
   Name: WriteVar
   Desc: Writes a single variable to the save
---------------------------------------------------------*/
function WriteVar( var, save )

	local itype = GetType( var )
	if ( itype == TYPE_NONE ) then return end
	
	save:StartBlock( type(var) )
	
		if (itype == TYPE_FLOAT) then 
			save:WriteFloat( var )
		elseif (itype == TYPE_BOOL) then 
			save:WriteBool( var )
		elseif (itype == TYPE_STRING) then 
			save:WriteString( var )
		elseif (itype == TYPE_ENTITY) then 
			save:WriteEntity( var )
		elseif (itype == TYPE_ANGLE) then 
			save:WriteAngle( var )
		elseif (itype == TYPE_VECTOR) then 
			save:WriteVector( var )	
		elseif (itype == TYPE_TABLE) then 
			WriteTable( var, save )
		else 
			Msg("Error! Saving unsupported Type: "..type(var).."\n");
		end
	
	save:EndBlock()

end


/*---------------------------------------------------------
   Name: ReadVar
   Desc: Reads a single variable
---------------------------------------------------------*/
function ReadVar( restore )

	local retval = 0
	
	local typename = restore:StartBlock()
	
		itype = GetTypeStr( typename )
	
		if (itype == TYPE_FLOAT) then 
			retval = restore:ReadFloat()
		elseif (itype == TYPE_BOOL) then 
			retval = restore:ReadBool()
		elseif (itype == TYPE_STRING) then 
			retval = restore:ReadString()
		elseif (itype == TYPE_ENTITY) then 
			retval = restore:ReadEntity()
		elseif (itype == TYPE_ANGLE) then 
			retval = restore:ReadAngle()
		elseif (itype == TYPE_VECTOR) then 
			retval = restore:ReadVector()	
		elseif (itype == TYPE_TABLE) then 
			retval = ReadTable( restore )
		else 
			Msg("Error! Loading unsupported Type: "..typename.."\n");
		end
	
	restore:EndBlock()
	
	return retval
	
end

/*---------------------------------------------------------
   Name: WriteTable
   Desc: Writes a table to the save
---------------------------------------------------------*/
function WriteTable( tab, save )

	// Write a blank table (because read will be expecting one)
	if ( tab == nil ) then
	
		save:StartBlock( "Table" )
		save:EndBlock()

	end

	// We have already saved this table
	if ( TableRefs[ tab ] ) then
	
		save:StartBlock( "TableRef" )
			save:WriteInt( TableRefs[ tab ] )
		save:EndBlock()

		return 
		
	end
	
	TableRefs[ tab ] = TableRef
	
	save:StartBlock( "Table" )
	
		local iCount = WritableKeysInTable( tab )
	
		save:WriteInt( TableRef )
		TableRef = TableRef + 1
		
		save:WriteInt( iCount )
	
		for k, v in pairs( tab ) do

			if ( IsWritable( k, v ) ) then
	
				WriteVar( k, save )
				WriteVar( v, save )
		
			end
	
		end
	
	save:EndBlock()

end

/*---------------------------------------------------------
   Name: ReadTable
   Desc: Assumes a table is waiting to be read, returns a table
---------------------------------------------------------*/
function ReadTable( restore )

	local name = restore:StartBlock()
	local tab = {}
	
	if (name == "TableRef") then
	
		local ref = restore:ReadInt()	
		if ( !TableRefs[ ref ] ) then
			TableRefs[ ref ] = {}
			return
		end

		tab = TableRefs[ ref ]

	else

		local iRef	 = restore:ReadInt()
		local iCount = restore:ReadInt()
		
		if ( TableRefs[ iRef ] ) then
			tab = TableRefs[ iRef ]
		end
		
		for i=0, iCount-1 do
			
			local k = ReadVar( restore )
			local v = ReadVar( restore )
			tab[ k ] = v
			
		end
		
		TableRefs[ iRef ] = tab
	
	end
	
	restore:EndBlock()
	
	return tab
	
end


/*---------------------------------------------------------
   Name: SaveEntity
   Desc: Called by the engine for each entity
---------------------------------------------------------*/
function SaveEntity( ent, save )

	save:StartBlock( "EntityTable" )
	
		WriteTable( ent:GetTable(), save )
	
	save:EndBlock()

end

/*---------------------------------------------------------
   Name: LoadEntity
   Desc: Called by the engine for each entity
---------------------------------------------------------*/
function LoadEntity( ent, restore )

	local EntTable = ent:GetTable()
	
	local name = restore:StartBlock()
		
		if ( name == "EntityTable" ) then
		
			table.Merge( EntTable, ReadTable( restore ) )
			
		end
		
	restore:EndBlock()
	
	ent:SetTable( EntTable )

end


/*---------------------------------------------------------
   Name: AddSaveHook
   Desc: Adds a hook enabling something to save something..
---------------------------------------------------------*/
function AddSaveHook( name, func )

	local h = {}
		h.Name = name
		h.Func = func
	SaveHooks[ name ] = h

end


/*---------------------------------------------------------
   Name: AddRestoreHook
   Desc: Adds a hook enabling something to restore something..
---------------------------------------------------------*/
function AddRestoreHook( name, func )

	local h = {}
		h.Name = name
		h.Func = func
	RestoreHooks[ name ] = h 

end



/*---------------------------------------------------------
   Name: SaveGlobal
   Desc: Should save any Lua stuff that isn't entity based.
---------------------------------------------------------*/
function SaveGlobal( save )

	save:StartBlock( "GameMode" )
		WriteTable( gmod.GetGamemode(), save )
	save:EndBlock()
	
	for k, v in pairs( SaveHooks ) do
	
		save:StartBlock( v.Name )
		
			v.Func( save )
		
		save:EndBlock()
	
	end
	
end


/*---------------------------------------------------------
   Name: LoadGlobal
   Desc: ...
---------------------------------------------------------*/
function LoadGlobal( restore )

	local name = restore:StartBlock()
	
		if ( name == "GameMode" ) then
			table.Merge( gmod.GetGamemode(), ReadTable( restore ) )
		end
		
	restore:EndBlock()
	
	
	while ( name != "EndGlobal" ) do
	
		name = restore:StartBlock()
		
			local tab = RestoreHooks[ name ]
			if (tab) then
			
				tab.Func( restore )
			
			end
			
		restore:EndBlock()
		
	end

end

