--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


local meta = FindMetaTable( "Entity" )

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

function meta:GetShouldPlayPickupSound()
	return self.m_bPlayPickupSound or false
end

function meta:SetShouldPlayPickupSound( bPlaySound )
	self.m_bPlayPickupSound = tobool( bPlaySound ) or false
end

--
-- Entity index accessor. This used to be done in engine, but it's done in Lua now because it's faster
--
function meta:__index( key )

	--
	-- Search the metatable. We can do this without dipping into C, so we do it first.
	--
	local val = meta[ key ]
	if ( val != nil ) then return val end

	--
	-- Search the entity table
	--
	local tab = self:GetTable()
	if ( tab ) then
		local tabval = tab[ key ]
		if ( tabval != nil ) then return tabval end
	end

	--
	-- Legacy: sometimes use self.Owner to get the owner.. so lets carry on supporting that stupidness
	-- This needs to be retired, just like self.Entity was.
	--
	if ( key == "Owner" ) then return meta.GetOwner( self ) end

	return nil

end

--[[---------------------------------------------------------
	Name: Short cut to add entities to the table
-----------------------------------------------------------]]
function meta:GetVar( name, default )

	local Val = self:GetTable()[ name ]
	if ( Val == nil ) then return default end

	return Val

end

if ( SERVER ) then

	function meta:SetCreator( ply --[[= NULL]] )
		if ( ply == nil ) then
			ply = NULL
		elseif ( !isentity( ply ) ) then
			error( "bad argument #1 to 'SetCreator' (Entity expected, got " .. type( ply ) .. ")", 2 )
		end

		self.m_PlayerCreator = ply
	end

	function meta:GetCreator()
		return self.m_PlayerCreator or NULL
	end

end

--[[---------------------------------------------------------
	Name: Returns true if the entity has constraints attached to it
-----------------------------------------------------------]]
function meta:IsConstrained()

	if ( CLIENT ) then return self:GetNWBool( "IsConstrained" ) end

	local c = self:GetTable().Constraints
	local bIsConstrained = false

	if ( c ) then

		for k, v in pairs( c ) do
			if ( IsValid( v ) ) then bIsConstrained = true break end
			c[ k ] = nil
		end

	end

	self:SetNWBool( "IsConstrained", bIsConstrained )
	return bIsConstrained

end

--[[---------------------------------------------------------
	Name: Short cut to set tables on the entity table
-----------------------------------------------------------]]
function meta:SetVar( name, value )

	self:GetTable()[ name ] = value

end

--[[---------------------------------------------------------
	Name: CallOnRemove
	Desc: Call this function when this entity dies.
	Calls the function like Function( <entity>, <optional args> )
-----------------------------------------------------------]]
function meta:CallOnRemove( name, func, ... )

	local mytable = self:GetTable()
	mytable.OnDieFunctions = mytable.OnDieFunctions or {}

	mytable.OnDieFunctions[ name ] = { Name = name, Function = func, Args = { ... } }

end

--[[---------------------------------------------------------
	Name: RemoveCallOnRemove
	Desc: Removes the named hook
-----------------------------------------------------------]]
function meta:RemoveCallOnRemove( name )

	local mytable = self:GetTable()
	mytable.OnDieFunctions = mytable.OnDieFunctions or {}
	mytable.OnDieFunctions[ name ] = nil

end

--[[---------------------------------------------------------
	Simple mechanism for calling the die functions.
-----------------------------------------------------------]]
local function DoDieFunction( ent )

	if ( !ent or !ent.OnDieFunctions ) then return end

	for k, v in pairs( ent.OnDieFunctions ) do

		-- Functions aren't saved - so this could be nil if we loaded a game.
		if ( v && v.Function ) then

			v.Function( ent, unpack( v.Args ) )

		end

	end

end

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

function meta:PhysWake()

	local phys = self:GetPhysicsObject()
	if ( !IsValid( phys ) ) then return end

	phys:Wake()

end

local GetColorOriginal4 = meta.GetColor4Part  -- Do not use me! I will be removed
local GetColorOriginal = meta.GetColor
function meta:GetColor()

	-- Backwards comp slower method
	if ( !GetColorOriginal4 ) then
		return GetColorOriginal( self )
	end

	return Color( GetColorOriginal4( self ) )

end

local SetColorOriginal4 = meta.SetColor4Part  -- Do not use me! I will be removed
local SetColorOriginal = meta.SetColor
function meta:SetColor( col )

	-- Backwards comp slower method
	if ( !SetColorOriginal4 ) then
		return SetColorOriginal( self, col )
	end

	-- Even more backwards compat
	if ( !col ) then
		return SetColorOriginal4( self, 255, 255, 255, 255 )
	end

	SetColorOriginal4( self, col.r, col.g, col.b, col.a )

end

function meta:GetChildBones( bone )

	local bonecount = self:GetBoneCount()
	if ( bonecount == 0 or bonecount < bone ) then return end

	local bones = {}

	for k = 0, bonecount - 1 do
		if ( self:GetBoneParent( k ) != bone ) then continue end
		table.insert( bones, k )
	end

	return bones

end

function DTVar_ReceiveProxyGL( ent, name, id, val )
	if ( ent.CallDTVarProxies ) then
		ent:CallDTVarProxies( name, id, val )
	end
end

function meta:InstallDataTable()

	self.dt = {}
	local typetable = {}
	local datatable = {}
	local keytable = {}
	local dtmeta = {}
	local editing = {}

	dtmeta.__index = function ( ent, key )

		local dt = datatable[ key ]
		if ( dt == nil ) then return end

		return dt.GetFunc( self, dt.index, key )

	end

	dtmeta.__newindex = function( ent, key, value )

		local dt = datatable[ key ]
		if ( dt == nil ) then return end

		dt.SetFunc( self, dt.index, value )

	end

	local function FindUnusedIndex( typename )

		local tbl = typetable[ typename ]
		if ( !tbl ) then return 0 end

		for i = 0, 31 do
			if ( !tbl[i] ) then return i end
		end

	end

	self.IsDTVarSlotUsed = function( ent, typename, index )

		local tbl = typetable[ typename ]
		if ( !tbl or !tbl[index] ) then return false end
		return true

	end

	self.DTVar = function( ent, typename, index, name )

		if ( isstring( index ) && !name ) then
			name = index
			index = FindUnusedIndex( typename )
		elseif ( !index && isstring( name ) ) then
			index = FindUnusedIndex( typename )
		end

		local SetFunc = ent[ "SetDT" .. typename ]
		local GetFunc = ent[ "GetDT" .. typename ]

		if ( !SetFunc or !GetFunc ) then
			MsgN( "Couldn't addvar ", name, " - type ", typename, " is invalid!" )
			return
		end

		local data = {
			index = index,
			name = name,
			SetFunc = SetFunc,
			GetFunc = GetFunc,
			typename = typename,
			Notify = {}
		}

		typetable[ typename ] = typetable[ typename ] or {}
		typetable[ typename ][ index ] = data
		datatable[ name ] = data

		return data

	end

	--
	-- Access to the editing table
	--
	self.GetEditingData = function()
		return editing
	end

	--
	-- Adds an editable variable.
	--
	self.SetupEditing = function( ent, name, keyname, data )

		if ( !data ) then return end

		if ( !data.title ) then data.title = name end

		editing[ keyname ] = data

	end

	self.SetupKeyValue = function( ent, keyname, kvtype, setfunc, getfunc, other_data )

		keyname = keyname:lower()

		keytable[ keyname ] = {
			KeyName		= keyname,
			Set			= setfunc,
			Get			= getfunc,
			Type		= kvtype
		}

		if ( other_data ) then

			table.Merge( keytable[ keyname ], other_data )

		end

	end

	local CallProxies = function( ent, tbl, name, oldval, newval )

		for i = 1, #tbl do
			tbl[ i ]( ent, name, oldval, newval )
		end

	end

	self.CallDTVarProxies = function( ent, typename, index, newVal )

		local t = typetable[ typename ] && typetable[ typename ][ index ] or nil
		if ( t ) then
			CallProxies( ent, t.Notify, t.name, t.GetFunc( ent, index ), newVal )
		end

	end

	self.NetworkVar = function( ent, typename, index, name, other_data )

		if ( isstring( index ) && ( istable( name ) or !name ) ) then
			other_data = name
			name = index
			index = FindUnusedIndex( typename )
		elseif ( !index && isstring( name ) ) then
			index = FindUnusedIndex( typename )
		end

		local t = ent.DTVar( ent, typename, index, name )

		-- Some addons call these on the entity table, and that used to work, so we keep that
		ent[ "Set" .. name ] = function( selfent, value )
			CallProxies( ent, t.Notify, name, t.GetFunc( ent, index ), value )
			t.SetFunc( ent, index, value )
		end

		ent[ "Get" .. name ] = function( selfent )
			return t.GetFunc( ent, index )
		end

		if ( !other_data ) then return end

		-- This KeyName stuff is absolutely unnecessary, there's absolutely no reason for it to exist
		-- But we cannot remove it now because dupes will break. It should've used the "name" variable
		if ( other_data.KeyName ) then
			ent:SetupKeyValue( other_data.KeyName, typename, ent[ "Set" .. name ], ent[ "Get" .. name ], other_data )
			ent:SetupEditing( name, other_data.KeyName, other_data.Edit )
		end

	end

	--
	-- Add a function that gets called when the variable changes
	-- Note: this doesn't work on the client yet - which drastically reduces its usefulness.
	--
	self.NetworkVarNotify = function( ent, name, func )

		if ( !datatable[ name ] ) then error( "calling NetworkVarNotify on missing network var " .. name ) end

		table.insert( datatable[ name ].Notify, func )

	end

	--
	-- Create an accessor of an element. This is mainly so you can use spare
	-- network vars (vectors, angles) to network single floats.
	--
	self.NetworkVarElement = function( ent, typename, index, element, name, other_data )

		if ( isstring( index ) && isstring( element ) ) then
			other_data = name
			name = element
			element = index
			index = FindUnusedIndex( typename )
		elseif ( !index && isstring( name ) ) then
			index = FindUnusedIndex( typename )
		end

		local t = ent.DTVar( ent, typename, index, name )
		t.element = element

		ent[ "Set" .. name ] = function( selfent, value )
			local old = t.GetFunc( selfent, index )
			old[ element ] = value
			t.SetFunc( selfent, index, old )
		end

		ent[ "Get" .. name ] = function( selfent )
			return t.GetFunc( selfent, index )[ element ]
		end

		if ( !other_data ) then return end

		-- This KeyName stuff is absolutely unnecessary, there's absolutely no reason for it to exist
		-- But we cannot remove it now because dupes will break. It should've used the "name" variable
		if ( other_data.KeyName ) then
			ent:SetupKeyValue( other_data.KeyName, "float", ent[ "Set" .. name ], ent[ "Get" .. name ], other_data )
			ent:SetupEditing( name, other_data.KeyName, other_data.Edit )
		end

	end

	self.SetNetworkKeyValue = function( ent, key, value )

		key = key:lower()

		local k = keytable[ key ]
		if ( !k ) then return end

		local v = util.StringToType( value, k.Type )
		if ( v == nil ) then return end

		k.Set( ent, v )
		return true

	end

	self.GetNetworkKeyValue = function( ent, key )

		key = key:lower()

		local k = keytable[ key ]
		if ( !k ) then return end

		return k.Get( ent )

	end

	--
	-- Called by the duplicator system to get the network vars
	--
	self.GetNetworkVars = function( ent )

		local dt = {}

		for k, v in pairs( datatable ) do

			-- Don't try to save entities (yet?)
			if ( v.typename == "Entity" ) then continue end

			if ( v.element ) then
				dt[ k ] = v.GetFunc( ent, v.index )[ v.element ]
			else
				dt[ k ] = v.GetFunc( ent, v.index )
			end

		end

		--
		-- If there's nothing in our table - then return nil.
		--
		if ( table.IsEmpty( dt ) ) then return nil end

		return dt

	end

	--
	-- Called by the duplicator system to restore from network vars
	--
	self.RestoreNetworkVars = function( ent, tab )

		if ( !tab ) then return end

		-- Loop this entities data table
		for k, v in pairs( datatable ) do

			-- If it contains this entry
			if ( tab[ k ] == nil ) then continue end

			-- Support old saves/dupes with incorrectly saved data
			if ( v.element && ( isangle( tab[ k ] ) or isvector( tab[ k ] ) ) ) then
				tab[ k ] = tab[ k ][ v.element ]
			end

			-- Set it.
			if ( ent[ "Set" .. k ] ) then
				ent[ "Set" .. k ]( ent, tab[ k ] )
			else
				v.SetFunc( ent, v.index, tab[ k ] )
			end

		end

	end

	setmetatable( self.dt, dtmeta )

	--
	-- In sandbox the client can edit certain values on certain entities
	-- we implement this here incase any other gamemodes want to use it
	-- although it is of course deactivated by default.
	--

	--
	-- This function takes a keyname and a value - both strings.
	--
	--
	-- Called serverside it will set the value.
	--
	self.EditValue = function( ent, variable, value )

		if ( !isstring( variable ) ) then return end
		if ( !isstring( value ) ) then return end

		--
		-- It can be called clientside to send a message to the server
		-- to request a change of value.
		--
		if ( CLIENT ) then

			net.Start( "editvariable" )
				net.WriteEntity( ent )
				net.WriteString( variable )
				net.WriteString( value )
			net.SendToServer()

		end

		--
		-- Called serverside it simply changes the value
		--
		if ( SERVER ) then

			ent:SetNetworkKeyValue( variable, value )

		end

	end

end

if ( SERVER ) then

	util.AddNetworkString( "editvariable" )

	net.Receive( "editvariable", function( len, client )

		local ent = net.ReadEntity()

		if ( !IsValid( ent ) ) then return end
		if ( !isfunction( ent.GetEditingData ) ) then return end
		if ( ent.AdminOnly && !( client:IsAdmin() or game.SinglePlayer() ) ) then return end

		local key = net.ReadString()

		-- Is this key in our edit table?
		local editor = ent:GetEditingData()[ key ]
		if ( !istable( editor ) ) then return end

		local val = net.ReadString()
		hook.Run( "VariableEdited", ent, client, key, val, editor )

	end )

	function meta:GetUnFreezable()
		return self.m_bUnFreezable or false
	end

	function meta:SetUnFreezable( bFreeze )
		self.m_bUnFreezable = tobool( bFreeze ) or false
	end

end

--
-- Networked var proxies
--
function meta:SetNetworked2VarProxy( name, func )

	if ( !self.NWVarProxies ) then
		self.NWVarProxies = {}
	end

	self.NWVarProxies[ name ] = func

end

function meta:GetNetworked2VarProxy( name )

	if ( self.NWVarProxies ) then
		local func = self.NWVarProxies[ name ]
		if ( isfunction( func ) ) then
			return func
		end
	end

	return nil

end

meta.SetNW2VarProxy = meta.SetNetworked2VarProxy
meta.GetNW2VarProxy = meta.GetNetworked2VarProxy

hook.Add( "EntityNetworkedVarChanged", "NetworkedVars", function( ent, name, oldValue, newValue )

	if ( ent.NWVarProxies ) then
		local func = ent.NWVarProxies[ name ]

		if ( isfunction( func ) ) then
			func( ent, name, oldValue, newValue )
		end
	end

end )

--
-- Vehicle Extensions
--
local vehicle = FindMetaTable( "Vehicle" )

--
-- We steal some DT slots by default for vehicles
-- to control the third person view. You should use
-- these functions if you want to play with them because
-- they might eventually be moved into the engine - so manually
-- editing the DT values will stop working.
--
function vehicle:SetVehicleClass( s )

	self:SetDTString( 3, s )

end

function vehicle:GetVehicleClass()

	return self:GetDTString( 3 )

end

function vehicle:SetThirdPersonMode( b )

	self:SetDTBool( 3, b )

end

function vehicle:GetThirdPersonMode()

	return self:GetDTBool( 3 )

end

function vehicle:SetCameraDistance( dist )

	self:SetDTFloat( 3, dist )

end

function vehicle:GetCameraDistance()

	return self:GetDTFloat( 3 )

end
