--[[----------------------------------------------------------------------------

KSWeakEventTable.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2010 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

--[[

	KSWeakEventTable: Standardized support for weak tables of events and behaviors
	
	function KSWeakEventTable.makeWeakEventTable( opt_initial_value_for_behaviors )
		A class for constructing a table to manage a weak mapping to event
		nodes -- either events or behaviors. These can be managed as follows:
		
			et =  KSWeakEventTable.makeWeakEventTable( < initial value for behaviors > )
			
			local E = et:eventE( 'foo' )
			local B = et:behaviorB( 'baz' )
			
			-- All functions do nothing if the event doesn't currently exist. These
			-- return true if the event exists and false if it doesn't.
			
			et:postToEvent( 'foo', 3 )
			et:postErrorToEvent( 'foo', "Disk full" )
			et:closeEvent( 'baz' )
			
			-- But we might also want to test for event existence
			
			et:eventExists( 'bar' )
			
			-- We can get a list of the events currently in existence.
			
			et:eventKeys()
	
]]

--============================================================================--

local assert = assert
local pairs = pairs
local setmetatable = setmetatable

local Rx = require 'Rx'

local WFCloseSupport = require 'WFCloseSupport'

local WFCloseSupport_fetchCached = assert( WFCloseSupport.fetchCached )
local WFCloseSupport_hasBeenOrWillBeClosed = assert( WFCloseSupport.hasBeenOrWillBeClosed )

local KSRPackedValues = require 'KSRPackedValues'

local KSRPackedValues_pack = KSRPackedValues.pack
local KSRPackedValues_unpack = KSRPackedValues.unpack

-- The debug hook for event nodes

local KSReactive = require 'KSReactive'

local KSReactive_sourceE = assert( KSReactive.sourceE )


--============================================================================--

-- Bypass grabbing all of KSTableUtils just to be able to make fully weak tables.

local fullyWeakMT = { __mode = 'kv' }

local function makeFullyWeakTable()
	return setmetatable( {}, fullyWeakMT )
end


--============================================================================--

local function KSWeakEventTable_getSource( self, key, makeIt )

	local srcE = WFCloseSupport_fetchCached( self._sources, key )
	
	if srcE or not makeIt then
		return srcE
	end
	
	srcE = KSReactive_sourceE()
	
	self:addCloseTarget( srcE )

	self._sources[ key ] = srcE
	
	return srcE

end

--------------------------------------------------------------------------------

local KSWeakEventTable = Rx.Class "KSWeakEventTable" {

	WFCloseSupport.addCloseSupportMethods,

	_sources = false,
	
	_relays = false,
	
	_behaviors = false,
	
	_initialValue = false,
	
	__init = function( self, ... )

		self._sources = makeFullyWeakTable()

		self._relays = makeFullyWeakTable()
		
		self._behaviors = makeFullyWeakTable()
		
		self._initialValue = KSRPackedValues_pack( ... )
		
	end,
	
	eventE = function( self, key  )
	
		local srcE = KSWeakEventTable_getSource( self, key, true )
		
		local relayE = WFCloseSupport_fetchCached( self._relays, srcE )
		
		if not relayE then
			relayE = srcE:relayE()
			self:addCloseTarget( relayE )
			self._relays[ srcE ] = relayE
		end
		
		return relayE
	
	end,
	
	behaviorB = function( self, key  )
	
		local srcE = KSWeakEventTable_getSource( self, key, true )
		
		local bhvrB = WFCloseSupport_fetchCached( self._behaviors, srcE )
		
		local behaviorDidExist = not not bhvrB
		
		if not bhvrB then
			bhvrB = srcE:holdB( KSRPackedValues_unpack( self._initialValue ) )
			self:addCloseTarget( bhvrB )
			self._behaviors[ srcE ] = bhvrB
		end
		
		return bhvrB, behaviorDidExist
	
	end,
	
	postToEvent = function( self, key, ... )
	
		local srcE = KSWeakEventTable_getSource( self, key )
		
		if not srcE then
			return false
		end
		
		srcE:post( ... )
		
		return true
	
	end,

	postErrorToEvent = function( self, key, message )
	
		local srcE = KSWeakEventTable_getSource( self, key )
		
		if not srcE then
			return false
		end
		
		srcE:postError( message )
		
		return true
	
	end,

	closeEvent = function( self, key )
	
		local srcE = KSWeakEventTable_getSource( self, key )
		
		if not srcE then
			return false
		end
		
		-- Close any event relay.

		local relayE = self._relays[ srcE ]
		if relayE then
			self._relays[ srcE ] = nil
			relayE:close()
		end
		
		-- Clear any hold behavior.

		local bhvrB = self._behaviors[ srcE ]
		if bhvrB then
			self._behaviors[ srcE ] = nil
			bhvrB:close()
		end

		self._sources[ key ] = nil
		srcE:close()
		
		-- Report that we had an event
		
		return true
	
	end,
	
	eventExists = function( self, key )
	
		return KSWeakEventTable_getSource( self, key ) ~= nil
	
	end,
	
	eventKeys = function( self )
	
		local result = { }
		
		for k, v in pairs( self._sources ) do
			if not WFCloseSupport_hasBeenOrWillBeClosed( v ) then
				result[ #result + 1 ] = k
			end
		end
		
		return result
	
	end

}


--============================================================================--

return {
	makeWeakEventTable = function( ... ) return KSWeakEventTable( ... ) end,
}
