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

WFRxWeakObservableTable.lua

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

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

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

	-- RxLua
local Rx = require 'Rx'
local ConnectableObservable = Rx.ConnectableObservable
local CompositeDisposable = Rx.CompositeDisposable
local Disposable = Rx.Disposable
local Observable = Rx.Observable
local ReplaySubject = Rx.ReplaySubject
local Subject = Rx.Subject

	-- WF.core.utils
local WFArguments = require 'WFArguments'


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

local check = assert( WFArguments.check )

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

local fullyWeakMT = { __mode = 'kv' }

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


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

-- @mark HookableConnectableObservable class (used internally )

local HookableConnectableObservable = Rx.Class 'HookableConnectableObservable' {

	ConnectableObservable,

	_disconnectHook = false,

	connect = function( self )

		assert( not self._hasSubscription )
			-- We assume only one active subscription at a time because
			-- we'll be using :refCount().

		assert( self._disconnectHook )
			-- Should have been wired up by sourceForKey() below.

		local subscription = self:Rx_ConnectableObservable_connect()

		self._disconnectHook:disposeWith( subscription )

		return subscription

	end,

}


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

-- @mark WFRxWeakObservableTable class

local WFRxWeakObservableTable = {

	Disposable,
		-- superclass

	_observableFactory = false,
		-- (function) call this when initializing an Observable for a key

	_sources = false,
		-- (table) Observable for key

	_replayBufferSize = false,
		-- (number) size of replay buffer

}

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

function WFRxWeakObservableTable:__init( observableFactory, optReplayBufferSize )

	check( '_', 'function', '?number' )

	self._observableFactory = observableFactory

	self._sources = makeFullyWeakTable()

	if optReplayBufferSize then
		self._replayBufferSize = optReplayBufferSize
	end

	return function()
		-- No-op dispose action. See :dispose() method below instead.
		-- TODO [scouten 2015-04-28]: Patch RxLua so this isn't necessary.
	end

end

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

function WFRxWeakObservableTable:dispose()

	self._observableFactory = nil

	for _, source in pairs( self._sources ) do

		if Subject:isClassOf( source ) then
			source:onCompleted()
		end

		if Disposable:isClassOf( source ) then
			source:dispose()
		end

	end

	self._sources = nil

	return self:Rx_Disposable_dispose()

end

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

function WFRxWeakObservableTable:activeKeys()

	local result = {}

	for key, source in pairs( self._sources ) do
		result[ key ] = true
	end

	return result

end

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

local function sourceForKey( self, key )

	local source = self._sources[ key ]

	if not source then

		source = self._observableFactory( key )

		if not Observable:isClassOf( source ) then
			error( "WFRxWeakObservableTable: observableFactory must return an Observable", 3 )
		end

		local function sourceIsDone()
			if self._sources then
				self._sources[ key ] = nil
			end
		end


		if Subject:isClassOf( source ) then

			if self._replayBufferSize then
				source:dispose()
				error( "WFRxWeakObservableTable: can not use replayBufferSize with Subjects", 3 )
			end

			local hadObservers = false

			local ohoSubscription = source:observableHasObservers()
				:subscribeOnNext( function( value )
					if ( not value ) and hadObservers then
						if self._sources[ key ] then
							hadObservers = false
							self._sources[ key ]:dispose()
							self._sources[ key ] = nil
						end
					elseif value then
						hadObservers = true
					end
				end )

			ohoSubscription:disposeWith( source )

			local whenSourceIsDone = Disposable.create( sourceIsDone )
			whenSourceIsDone:disposeWith( source )

		else

			local subject

			if self._replayBufferSize then
				subject = ReplaySubject( self._replayBufferSize )
			else
				subject = Subject()
			end

			local hookable = HookableConnectableObservable(
				source:finally( sourceIsDone ),
				subject )

			hookable._disconnectHook = CompositeDisposable(
				Disposable.create( sourceIsDone ),
				subject, hookable )

			source = hookable:refCount()

		end

		self._sources[ key ] = source

	end

	return source

end

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

function WFRxWeakObservableTable:observableForKey( key )

	if key == nil or key ~= key then
		error( "WFRxWeakObservableTable:observableForKey: key must be valid table index", 2 )
	end

	return sourceForKey( self, key )

end

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

function WFRxWeakObservableTable:hasObservableForKey( key )

	if key == nil or key ~= key then
		error( "WFRxWeakObservableTable:hasObservableForKey: key must be valid table index", 2 )
	end

	local source = self._sources[ key ]

	return not not( source and not source:isDisposed() )

end

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

return Rx.Class( 'WFRxWeakObservableTable', WFRxWeakObservableTable )
