local PreloadGarbage = collectgarbage("count")

if not CLIENT then return end
math.randomseed(os.time())

local gmod = gmod
local pairs = pairs
local isfunction = isfunction
local isstring = isstring
local IsValid = IsValid

local function tablecopy(t, lookup_table)
	if (t == nil) then return nil end
	
	local copy = {}
	for i,v in pairs(t) do
		if ( !istable(v) ) then
			copy[i] = v
		else
			lookup_table = lookup_table or {}
			lookup_table[t] = copy
			if lookup_table[v] then
				copy[i] = lookup_table[v]
			else
				copy[i] = table.Copy(v,lookup_table)
			end
		end
	end
	return copy
end

local G = tablecopy(_G)



G.net.WriteEntity = function( ent )
	if ( !G.IsValid( ent ) ) then 
		G.net.WriteUInt( 0, 16 )
	else
		G.net.WriteUInt( ent:EntIndex(), 16 )
	end
end

G.net.WriteTable = function( tab )
	for k, v in pairs( tab ) do
		G.net.WriteType( k )
		G.net.WriteType( v )
	end
	G.net.WriteUInt( 0, 8 )
end

local WriteVars = 
{
	[TYPE_NIL]			= function ( t, v )	G.net.WriteUInt( t, 8 )								end,
	[TYPE_STRING]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteString( v )		end,
	[TYPE_NUMBER]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteFloat( v )			end,
	[TYPE_TABLE]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteTable( v )			end,
	[TYPE_BOOL]			= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteBit( v )			end,
	[TYPE_ENTITY]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteEntity( v )		end,
	[TYPE_VECTOR]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteVector( v )		end,
	[TYPE_ANGLE]		= function ( t, v )	G.net.WriteUInt( t, 8 )	G.net.WriteAngle( v )			end,	
}

G.net.WriteType = function( v )
	local typeid = G.TypeID( v )
	local wv = WriteVars[ typeid ]
	if ( wv ) then return wv( typeid, v ) end
end













local function GetRandomString()
	local str = ""
	
	for i=1, math.random(7, 10) do
		str = str .. string.char(math.random(1, 64))
	end

	return str
end

local QueuedTimers = {}
local function QueueTimerCreation(name, interval, amount, func)
	table.insert(QueuedTimers, {name, interval, amount, func})
end

local function ReportToServer(msg)
	net.Start("DuckStats")
		net.WriteString("Alert")
		net.WriteString(msg)
	net.SendToServer()
end

local function PostInitGarbage()
	local postinitgarbage = collectgarbage("count")
	net.Start("DuckStats")
		net.WriteString("Garbage")
		net.WriteDouble(PreloadGarbage)
		net.WriteDouble(postinitgarbage)
	net.SendToServer()
end

local QueuedHooks = {}
local function QueueHookVerification(name, type, info)
	if not QueuedHooks[type] then
		QueuedHooks[type] = {}
	end
	QueuedHooks[type][name] = info
end

local function UploadQueuedHooks()
	net.Start("DuckStats")
		net.WriteString("HookVerify")
		net.WriteTable(QueuedHooks)
	net.SendToServer()
	QueuedHooks = {}
end

local postinit = false
local function GetPostInit()
	return postinit
end

local function TriggerPostInit()
	postinit = true
	PostInitGarbage()
	UploadQueuedHooks()
end

local function GetHookAdditionsAllowed()
	return !postinit
end




















local Hooks = {
	["InitPostEntity"] = {
		["InternalAC"] = TriggerPostInit
	}
}

local function hookGetTable()
	local tbl = tablecopy(Hooks)
	tbl["InitPostEntity"]["InternalAC"] = nil
	return tbl
end

local function hookAdd( event_name, name, func )

	print(name)

	if not GetHookAdditionsAllowed() then
		ReportToServer("Hook addition on postinit blocked: [" .. event_name .. "] " .. name)
		return
	end

	if ( !isfunction( func ) ) then return end
	if ( !isstring( event_name ) ) then return end

	if (Hooks[ event_name ] == nil) then
			Hooks[ event_name ] = {}
	end

	if event_name == "InitPostEntity" and name == "InternalAC" then
		ReportToServer("Addition of ac hook blocked")
		return 
	end

	print("slerpenherpenamingit")

	QueueHookVerification(name, event_name, debug.getinfo(2))

	Hooks[ event_name ][ name ] = func

end

local function hookRemove( event_name, name )

	if ( !isstring( event_name ) ) then return end
	if ( !Hooks[ event_name ] ) then return end

	if event_name == "InitPostEntity" and name == "InternalAC" then
		ReportToServer("Removal of ac hook blocked")
		return
	end

	Hooks[ event_name ][ name ] = nil

end

local function hookCall( name, gm, ... )

	local HookTable = Hooks[ name ]
	if ( HookTable != nil ) then
	
		local a, b, c, d, e, f;

		for k, v in pairs( HookTable ) do 
			
			if ( isstring( k ) ) then
				
				a, b, c, d, e, f = v( ... )

			else

				if ( IsValid( k ) ) then
					a, b, c, d, e, f = v( k, ... )
				else
					HookTable[ k ] = nil
				end
			end

			if ( a != nil ) then
				return a, b, c, d, e, f
			end
				
		end
	end
	
	if ( !gm ) then return end
	
	local GamemodeFunction = gm[ name ]
	if ( GamemodeFunction == nil ) then return end
			
	return GamemodeFunction( gm, ... )     
	
end

local function hookRun( name, ... )
	return hookCall( name, gmod and gmod.GetGamemode() or nil, ... )
end



local origrawset = G.rawset
_G.rawset = nil
local function acrawset(t, n, v)
	if t == _G then
		return
	end
	return origrawset(t, n, v)
end


setmetatable(_G, {

	__index = function(t, k)
		if k == "hook" then
			return {Call = hookCall, GetTable = hookGetTable, Add = hookAdd, Remove = hookRemove, Run = hookRun}
		elseif k == "require" then
			return 
		elseif k == "rawset" then
			return acrawset
		end
	end,

	__metatable = true,

})

require("hook")
hook = nil