
--[[---------------------------------------------------------
	This module was primarily developed to enable toolmodes
	to share the numpad.

	Scripted Entities can add functions to be excecuted when
	a certain key on the numpad is pressed or released.
-----------------------------------------------------------]]

if ( !SERVER ) then return end

local tonumber		= tonumber
local pairs			= pairs
local unpack		= unpack
local table			= table
local saverestore	= saverestore
local math			= math
local IsValid		= IsValid
local type			= type
local ErrorNoHaltWithStack = ErrorNoHaltWithStack
local KEY_NONE		= KEY_NONE

module( "numpad" )

local functions = {}
local keys_in = {}
local keys_out = {}
local lastindex = 1
local button_fired = false

function FromButton()

	return button_fired == true

end

local function Save( save )

	saverestore.WriteTable( keys_in, save )
	saverestore.WriteTable( keys_out, save )
	saverestore.WriteVar( lastindex, save )

end

local function Restore( restore )

	keys_in = saverestore.ReadTable( restore )
	keys_out = saverestore.ReadTable( restore )
	lastindex = saverestore.ReadVar( restore )

end

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

--[[---------------------------------------------------------
	Returns a unique index based on a player.
-----------------------------------------------------------]]
local function GetPlayerIndex( ply )

	if ( !IsValid( ply ) ) then return 0 end

	return ply:SteamID64()

end

--[[---------------------------------------------------------
	Fires the impulse to the child functions
-----------------------------------------------------------]]
local function FireImpulse( tab, pl, idx )

	if ( idx == nil ) then
		idx = GetPlayerIndex( pl )
	end

	if ( !tab ) then return end
	if ( !tab[ idx ] ) then return end

	for k, v in pairs( tab[ idx ] ) do

		local func = functions[ v.name ]

		local retval = func( pl, unpack( v.arg ) )

		-- Remove hook
		if ( retval == false ) then
			tab[ idx ][ k ] = nil
		end

	end

end

--[[---------------------------------------------------------
	Console Command
-----------------------------------------------------------]]
function Activate( pl, num, bIsButton )

	local key = math.Clamp( tonumber( num ), 0, 256 )

	-- Hack. Kinda. Don't call it again until the key has been lifted.
	-- When holding down 9 or 3 on the numpad it will repeat. Ignore that.
	if ( IsValid( pl ) ) then
		pl.keystate = pl.keystate or {}
		if ( pl.keystate[ key ] ) then return end
		pl.keystate[ key ] = true
	end

	button_fired = bIsButton

		FireImpulse( keys_in[ key ], pl, nil )

		-- And fire `all`
		FireImpulse( keys_in[ key ], pl, 0 )

	button_fired = false

end

--[[---------------------------------------------------------
	Console Command
-----------------------------------------------------------]]
function Deactivate( pl, num, bIsButton )

	local key = math.Clamp( tonumber( num ) , 0, 256 )

	if ( IsValid( pl ) ) then
		pl.keystate = pl.keystate or {}
		pl.keystate[ key ] = nil
	end

	button_fired = bIsButton

		FireImpulse( keys_out[ key ], pl, nil )

		-- And fire `all`
		FireImpulse( keys_out[ key ], pl, 0 )

	button_fired = false

end

--[[---------------------------------------------------------
	Toggle
-----------------------------------------------------------]]
function Toggle( pl, num )

	local key = math.Clamp( tonumber( num ), 0, 256 )

	pl.keystate = pl.keystate or {}
	if ( pl.keystate[ key ] ) then return Deactivate( pl, num ) end

	return Activate( pl, num )

end

--[[---------------------------------------------------------
	Adds an impulse to to the specified table
-----------------------------------------------------------]]
local function AddImpulse( table, ply, impulse )

	lastindex = lastindex + 1

	local idx = GetPlayerIndex( ply )
	table[ idx ] = table[ idx ] or {}
	table[ idx ][ lastindex ] = impulse

	return lastindex

end

--[[---------------------------------------------------------
	Adds a function to call when ply presses key
-----------------------------------------------------------]]
function OnDown( ply, key, name, ... )

	if ( !key || key ~= key ) then ErrorNoHaltWithStack( "bad argument #2 to 'numpad.OnDown' (number expected, got ", type( key ), ")" ) return end
	if ( key == KEY_NONE ) then return end

	keys_in[ key ] = keys_in[ key ] or {}

	local impulse = {}
	impulse.name = name
	impulse.arg = { ... }

	table.insert( impulse.arg, GetPlayerIndex( ply ) )

	return AddImpulse( keys_in[ key ], ply, impulse )

end

--[[---------------------------------------------------------
	Adds a function to call when ply releases key
-----------------------------------------------------------]]
function OnUp( ply, key, name, ... )

	if ( !key || key ~= key ) then ErrorNoHaltWithStack( "bad argument #2 to 'numpad.OnUp' (number expected, got ", type( key ), ")" ) return end
	if ( key == KEY_NONE ) then return end

	keys_out[ key ] = keys_out[ key ] or {}

	local impulse = {}
	impulse.name = name
	impulse.arg = { ... }

	table.insert( impulse.arg, GetPlayerIndex( ply ) )

	return AddImpulse( keys_out[ key ], ply, impulse )

end

--[[---------------------------------------------------------
	Removes key from tab (by unique index)
-----------------------------------------------------------]]
local function RemoveFromKeyTable( tab, idx )

	for k, v_key in pairs( tab ) do

		for k_, v_player in pairs( v_key ) do

			if ( v_player[ idx ] != nil ) then
				v_player[ idx ] = nil
			end

		end

	end

end

--[[---------------------------------------------------------
	Removes key (by unique index)
-----------------------------------------------------------]]
function Remove( idx )

	if ( !idx ) then return end

	RemoveFromKeyTable( keys_out, idx )
	RemoveFromKeyTable( keys_in, idx )

end

--[[---------------------------------------------------------
	Register a function
-----------------------------------------------------------]]
function Register( name, func )

	functions[ name ] = func

end
