
local meta = FindMetaTable( "Entity" )

// Return if there's nothing to add on to
if (!meta) then return end

local Vector_Default 	= Vector(0,0,0)
local Angle_Default		= Angle(0,0,0)

local NetworkVars 			= {}
local NetworkKeys			= {}
local NetworkKeys_Count		= 0

local NetworkFunction 	= {}
local DelayedUpdates 	= {}


if ( CLIENT ) then

local function Dump()

	Msg("Vars...\n")
	PrintTable( NetworkVars )
	
	Msg("Keys...\n")
	PrintTable( NetworkKeys )

end

concommand.Add( "networkvars_dump", Dump )

end

local function SendNetworkKey( str, int, ply )

	umsg.Start( "NetworkVarsNetworkKey", ply )
		umsg.Short( int )
		umsg.String( str )
	umsg.End()

end


local function NetworkVarsNetworkKey( m )

	local Index 	= m:ReadShort()
	local Key 		= m:ReadString()
	
	//Msg("NetworkKeys[ \""..Key.."\" ] = "..Index.."\n")
	NetworkKeys[ Key ] = Index
	
end

usermessage.Hook( "NetworkVarsNetworkKey", NetworkVarsNetworkKey )

local function TranslateNetworkKey( Key )

	// Already a number
	if ( type(Key) == "number" ) then return Key end
	if ( NetworkKeys[ Key ] ) then return NetworkKeys[ Key ] + 10000 end
	
	// We don't have this index yet.. just return -1 for now
	if (CLIENT) then 
		// It's common to have this say not found for keys that 
		// aren't yet defined on the server - so don't worry
		//Msg("TranslateNetworkKey: Key '"..Key.."' not found!\n")
		return -1 
	end
	
	//Msg("Indexing Key "..Key.."\n")
	
	NetworkKeys_Count 	= NetworkKeys_Count + 1
	NetworkKeys[ Key ] 	= NetworkKeys_Count
	SendNetworkKey( Key, NetworkKeys_Count )
	
	return NetworkKeys_Count + 10000
	
end

local function AttemptToSwitchTables( Ent, EntIndex )

	if ( NetworkVars[ EntIndex ] == nil ) then return end
		
	// We have an old entindex based entry! Move it over!
	NetworkVars[ Ent ] = NetworkVars[ EntIndex ]
	NetworkVars[ EntIndex ] = nil
	
end

local function GetNetworkTable( ent, name )
	
	if ( !NetworkVars[ ent ] ) then
	
		NetworkVars[ ent ] = {}
		
		// This is the first time this entity has been created. 
		// Check whether we previously had an entindex based table
		if ( CLIENT && type( ent ) != "number" && type( ent ) != "string" ) then
			AttemptToSwitchTables( ent, ent:EntIndex() )
		end
	
	end
	
	NetworkVars[ ent ][ name ] = NetworkVars[ ent ][ name ] or {}
	return NetworkVars[ ent ][ name ]

end

local function SendNetworkUpdate( VarType, Index, Key, Value, Player )
	
	umsg.Start( "RcvEntityVar_"..VarType, Player )
		umsg.Short( Index )
		umsg.Short( Key )
		umsg[ NetworkFunction[VarType].SetFunction ]( Value )
	umsg.End()

end

local function AddDelayedNetworkUpdate( VarType, Ent, Key, Value )
	
	DelayedUpdates[ VarType ] = DelayedUpdates[ VarType ] or {}
	DelayedUpdates[ VarType ][Ent] = DelayedUpdates[ VarType ][Ent] or {}
	DelayedUpdates[ VarType ][Ent][Key] = Value

end

local function AddNetworkFunctions( name, SetFunction, GetFunction, Default )

	NetworkFunction[ name ] = {}
	NetworkFunction[ name ].SetFunction = SetFunction
	NetworkFunction[ name ].GetFunction = GetFunction
	
	// SetNetworkedBlah
	meta[ "SetNetworked" .. name ] = function ( self, key, value, urgent )
	
		key = TranslateNetworkKey( key )
	
		// The same - don't waste our time.
		if ( value == GetNetworkTable( self, name )[key] ) then return end
		
		// Clients can set this too, but they should only really be setting it
		// when they expect the exact same result coming over the wire (ie prediction)
		GetNetworkTable( self, name )[key] = value
			
		if ( SERVER ) then
		
			local Index = self:EntIndex()
			if (Index <= 0) then return end
		
			if ( urgent ) then
				SendNetworkUpdate( name, Index, key, value )
			else
				AddDelayedNetworkUpdate( name, Index, key, value )
			end
			
		end
		
	end
	
	meta[ "SetNW" .. name ] = meta[ "SetNetworked" .. name ]
	
	// GetNetworkedBlah
	meta[ "GetNetworked" .. name ] = function ( self, key )
	
		key = TranslateNetworkKey( key )
	
		local out = GetNetworkTable( self, name )[key]
		if ( out != nil ) then return out end
		return Default
		
	end
	
	meta[ "GetNW" .. name ] = meta[ "GetNetworked" .. name ]
	
	
	// SetGlobalBlah
	_G[ "SetGlobal"..name ] = function ( key, value, urgent ) 
	
		key = TranslateNetworkKey( key )
		if ( value == GetNetworkTable( "G", name )[key] ) then return end
		GetNetworkTable( "G", name )[key] = value
			
		if ( SERVER ) then
			if ( urgent ) then
				SendNetworkUpdate( name, -1, key, value )
			else
				AddDelayedNetworkUpdate( name, -1, key, value )
			end
		end
		
	end
	
	
	// GetGlobalBlah
	_G[ "GetGlobal"..name ] = function ( key ) 
		key = TranslateNetworkKey( key )
		local out = GetNetworkTable( "G", name )[key]
		if ( out != nil ) then return out end
		return Default
	end
	
	
	if ( SERVER ) then
		// Pool the name of the function. 
		// Makes it send a number representing the string rather than the string itself.
		// Only do this with strings that you send quite a bit and always stay the same.
		umsg.PoolString( "RcvEntityVar_"..name )
	end
	
	// Client Receive Function
	if ( CLIENT ) then
	
		local function RecvFunc( m )
		
			local EntIndex 	= m:ReadShort()
			local Key		= m:ReadShort()
			local Value		= m[GetFunction]( m )
			
			local IndexKey
			
			if ( EntIndex <= 0 ) then 
			
				IndexKey = "G" 
				
			else
			
				IndexKey = Entity( EntIndex )
				
				// No entity yet - store using entindex
				if ( IndexKey == NULL ) then IndexKey = EntIndex end
				
			end
			
			GetNetworkTable( IndexKey, name )[Key] = Value	
			
			//Msg("RECV: "..EntIndex.." | "..tostring(IndexKey).." - "..tostring(Key).." - "..tostring(Value).."\n");
			
		end
		
		usermessage.Hook( "RcvEntityVar_"..name, RecvFunc )
	
	end

end


AddNetworkFunctions( "Vector", 	"Vector", 	"ReadVector", 	Vector_Default )
AddNetworkFunctions( "Angle", 	"Angle", 	"ReadAngle", 	Angle_Default )
AddNetworkFunctions( "Float", 	"Float", 	"ReadFloat", 	0 )
AddNetworkFunctions( "Int", 	"Short", 	"ReadShort", 	0 )
AddNetworkFunctions( "Entity", 	"Entity", 	"ReadEntity", 	NULL )
AddNetworkFunctions( "Bool", 	"Bool", 	"ReadBool", 	0 )
AddNetworkFunctions( "String", 	"String", 	"ReadString", 	"" )

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// Send a networkvar staggered to avoid sending too much in one tick
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
local function StaggeredNetworkUpdate( Type, Index, Key, Value, ply )

	SendNetworkUpdate( Type, Index , Key, Value, ply )

end

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// Send a full update to player that have just joined the server
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
local function FullUpdateEntityNetworkVars( ply )

	for k, v in pairs(NetworkKeys) do
	
		SendNetworkKey( k, v, ply )
		
	end

	for Ent, EntTable in pairs(NetworkVars) do
	
		for Type, TypeTable in pairs(EntTable) do
		
			for Key, Value in pairs(TypeTable) do
			
				local Index = Ent
				
				if ( type(Ent) != "string" ) then
					Index = Ent:EntIndex()
				end
			
				SendNetworkUpdate( Type, Index , Key, Value, ply )
							
			end
		end

	end


end

hook.Add( "PlayerInitialSpawn", "FullUpdateEntityNetworkVars", FullUpdateEntityNetworkVars )

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// We want our networked vars to save don't we? Yeah - we do - stupid.
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
local function Save( save )

	// Remove baggage
	for k, v in pairs(NetworkVars) do
		if ( k == NULL ) then
			NetworkVars[k] = nil
		end
	end
	
	//PrintTable(NetworkVars)
	saverestore.WriteTable( NetworkVars, save )
	saverestore.WriteTable( NetworkKeys, save )

end

local function Restore( restore )

	NetworkVars = saverestore.ReadTable( restore )
	NetworkKeys	= saverestore.ReadTable( restore )
	//PrintTable(NetworkVars)

end

saverestore.AddSaveHook( "EntityNetworkedVars", Save )
saverestore.AddRestoreHook( "EntityNetworkedVars", Restore )


///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
// We update the vars periodically instead of on demand, because we're not made of bandwidth
// Do you think it grows on trees or something? You'll see. You'll find out when you get your
// own job and you come home every night and the fire is on full blast.
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////
local function NetworkVarsSend()

	for VarType, a in pairs(DelayedUpdates) do
	
		for Index, b in pairs(a) do
		
			for Key, Value in pairs(b) do
			
				SendNetworkUpdate( VarType, Index, Key, Value )
				
			end
			
		end
		
	end
	
	// Clear the sent entries
	DelayedUpdates = {}

end

timer.Create( "NetworkVarsSend", 0.1, 0, NetworkVarsSend )


///////////////////////////////////////////////////////////////////////////////////////////////
// Listen out for dead entities so we can remove their vars
///////////////////////////////////////////////////////////////////////////////////////////////
local function NetworkVarsCleanup( ent )

	if ( SERVER ) then
		NetworkVars[ ent ] = nil
	end

end

hook.Add( "EntityRemoved", "NetworkVarsCleanup", NetworkVarsCleanup )


