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

WFNodeWatcher.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2013 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.

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


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

--[[

WFNodeWatcher provides a class to debug node bursts, used by KSPromises and
KSReactive.

	WFNodeWatcher( nodeTypeName, debugNodeBursts, optNotifyDelta )
		specifies parameters.
		
	nodeWatcher:watchNode( node, optKind )
		adds a node to be watched
		
	nodeWatcher:isNode( node)
		returns if node is currently being watched
		
	nodeWatcher:getNodeKind( node )
		returns the node's kind passed into watchNode

]]

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

	-- RxLua
local Rx = require 'Rx'


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

local fullyWeakMT = { __mode = 'kv' }

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

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

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

local WFCurrentThread_DEBUG_ONLY
local string_format = string.format
local WFDebugTraceback_traceback = require 'WFDebugTraceback'.traceback

local traceback = function()
	return WFDebugTraceback_traceback { "WFNodeWatcher.lua" }
end

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

local WFNodeWatcher = Rx.Class 'WFNodeWatcher' {

	_allNodes = false,
	_previousActiveNodeCount = 0,
	_nodeEpoch = 0,
	_hasDoneInit = false,
	_debugNodeBursts = false,
	_nodeTypeName = "", -- "reactive" or "promise"
	_notifyDelta = 30,
	
	__init = function( self, nodeTypeName, debugNodeBursts, optNotifyDelta )
	
		self._allNodes = makeFullyWeakTable()
		self._nodeTypeName = nodeTypeName
		self._debugNodeBursts = debugNodeBursts
		if optNotifyDelta then
			self._notifyDelta = optNotifyDelta
		end
		
	end,
	
	spinUpNodeWatcher = function( self )
	
		if ( not self._hasDoneInit ) and self._debugNodeBursts then
		
			self._hasDoneInit = true
		
			local WFTimer = require 'WFTimer'
			WFCurrentThread_DEBUG_ONLY = require 'WFCurrentThread'
			
			local nodeEpochDuration = 5 -- seconds
			
			local function kickTimer()
			
				WFTimer.afterDelayDo( nodeEpochDuration, function()
		
					local activeNodeCount = 0
					local tracebacksForThisEpoch = {}
		
					for node in pairs( self._allNodes ) do
						
						activeNodeCount = activeNodeCount + 1
						
						if node.epoch == self._nodeEpoch then
						
							local traceback = node.allocTraceback
							tracebacksForThisEpoch[ traceback ] = ( tracebacksForThisEpoch[ traceback ] or 0 ) + 1
						
						end
					
					end
		
					local activeNodeDelta = activeNodeCount - self._previousActiveNodeCount
					
					debug.print( string_format( "%s: Currently has %d %s nodes (delta = %d)\n\n",
									WFCurrentThread_DEBUG_ONLY.DEBUG_getThreadName(),
									activeNodeCount,
									self._nodeTypeName,
									activeNodeDelta ) )
		
					if activeNodeDelta >= self._notifyDelta and self._nodeEpoch > 20 then
		
						local sortedTracebacks = {}
						local stbCount = 0
		
						for tb, count in pairs( tracebacksForThisEpoch ) do
							stbCount = stbCount + 1
							sortedTracebacks[ stbCount ] = { tb = tb, count = count }
						end
						
						table.sort( sortedTracebacks, function( a, b )
							if a.count ~= b.count then
								return a.count > b.count
							else
								return a.tb < b.tb
							end
						end )
						
						local sortedTbStrings = {}
						for i = 1, #sortedTracebacks do
							local tbItem = sortedTracebacks[ i ]
							sortedTbStrings[ i ] = string_format( "%d new %s nodes :: %s", tbItem.count, self._nodeTypeName, tbItem.tb )
						end
		
						debug.print( string_format( "%s: Created net %d new %s nodes in past %d seconds\n\n\n%s\n\n----------------------------------\n",
													WFCurrentThread_DEBUG_ONLY.DEBUG_getThreadName(),
													activeNodeDelta,
													self._nodeTypeName,
													nodeEpochDuration,
													table.concat( sortedTbStrings, '\n\n' ) ) )
						
					end
		
					self._previousActiveNodeCount = activeNodeCount
					self._nodeEpoch = self._nodeEpoch + 1
		
					kickTimer()
				
				end )
			
			end
			
			kickTimer()
		
		end
		
	end,
	
	watchNode = function( self, node, optKind )
		
		self:spinUpNodeWatcher()

		if optKind == nil then
			optKind = true
		end
		
		self._allNodes[ node ] = optKind

		if self._debugNodeBursts then
			rawset( node, 'epoch', self._nodeEpoch )
			rawset( node, 'allocTraceback',
				traceback( WFCurrentThread_DEBUG_ONLY.DEBUG_getThreadName(), 4 ) )
		end

	end,

	isNode = function( self, node )
		return not not self._allNodes[ node ]
	end,

	getNodeKind = function( self, node )
		return self._allNodes[ node ]
	end

}

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

return WFNodeWatcher
