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

WFRxWeakObservableTableTest.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 BehaviorSubject = Rx.BehaviorSubject
local Disposable = Rx.Disposable
local Observable = Rx.Observable
local ReplaySubject = Rx.ReplaySubject
local Subject = Rx.Subject

	-- WF.core.rxlua
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.self_test
local WFRxWeakObservableTable = require 'WFRxWeakObservableTable'


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

local WFRxWeakObservableTableTest = {}

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

local function debugDisposables( rxTest )

	Rx.config.debugDisposables = true

	Rx.Disposable.resetDebuggingStats()

	rxTest:finally( function()
		Rx.config.debugDisposables = false
	end )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_noFactory( rxTest )

	debugDisposables( rxTest )

	rxTest:expectError( function()
		return WFRxWeakObservableTable()
	end )

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_simpleObservable( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function( key )
		return Observable.just( "Simple observable for key", key )
	end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	local baz = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, Observable:isClassOf( baz ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	local o1 = rxTest:observe( baz )
	o1:expectData( "Simple observable for key", "baz" )
	o1:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	local foo = wot:observableForKey( 'foo' )
	rxTest:expectEqual( true, Observable:isClassOf( foo ) )

	rxTest:expectEqual( { foo= true }, wot:activeKeys() )

	local o2 = rxTest:observe( foo )
	o2:expectData( "Simple observable for key", "foo" )
	o2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	o1:dispose()
	o2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_behaviorSubject( rxTest )

	debugDisposables( rxTest )

	local bs

	local wot = WFRxWeakObservableTable( function( key )
		rxTest:expectEqual( 'baz', key )
		bs = BehaviorSubject( "initial", "value", "is", "tuple" )
		return bs
	end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	local s = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, Observable:isClassOf( s ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	local os = rxTest:observe( s )

	os:expectData( "initial", "value", "is", "tuple" )

	bs:onNext( "Hello, world" )
	os:expectData( "Hello, world" )

	bs:onNext( "This", "is", 1, nil, "tuple" )
	os:expectData( "This", "is", 1, nil, "tuple" )

	bs:onCompleted()
	os:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	os:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestDispose( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function( key ) end )

	rxTest:expectEqual( true, Disposable:isClassOf( wot ) )

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestMultipleObserversForSameKey( rxTest )

	debugDisposables( rxTest )

	local nTimesCreated = 0
	local s = Subject()

	local wot = WFRxWeakObservableTable( function( key )

		rxTest:expectEqual( 'baz', key )

		nTimesCreated = nTimesCreated + 1

		return s

	end )

	rxTest:expectEqual( {}, wot:activeKeys() )
	rxTest:expectEqual( 0, nTimesCreated )

	local observable1 = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, Observable:isClassOf( observable1 ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )
	rxTest:expectEqual( 1, nTimesCreated )

	local o1 = rxTest:observe( observable1 )

	s:onNext( "Hello, world" )
	o1:expectData( "Hello, world" )

	local observable2 = wot:observableForKey( 'baz' )
	local o2 = rxTest:observe( observable2 )

	rxTest:expectEqual( 1, nTimesCreated )

	s:onNext( "This", "is", 1, nil, "tuple" )
	o1:expectData( "This", "is", 1, nil, "tuple" )
	o2:expectData( "This", "is", 1, nil, "tuple" )

	o1:dispose()

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	s:onNext( "And, one more thing..." )
	o2:expectData( "And, one more thing..." )

	s:onCompleted()
	o2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	o2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestMultipleObserversForSameKey_replayBuffer( rxTest )

	debugDisposables( rxTest )

	local nTimesCreated = 0
	local s = Subject()

	local wot = WFRxWeakObservableTable( function( key )

		rxTest:expectEqual( 'baz', key )

		nTimesCreated = nTimesCreated + 1

		return s:take( 1000 ) -- to make it not a Subject any more

	end, 2 )

	rxTest:expectEqual( {}, wot:activeKeys() )
	rxTest:expectEqual( 0, nTimesCreated )

	local observable1 = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, Observable:isClassOf( observable1 ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )
	rxTest:expectEqual( 1, nTimesCreated )

	local o1 = rxTest:observe( observable1 )

	s:onNext( "Hello, world" )
	o1:expectData( "Hello, world" )

	s:onNext( "Step 2" )
	o1:expectData( "Step 2" )

	s:onNext( "And then there were", "three" )
	o1:expectData( "And then there were", "three" )

	local observable2 = wot:observableForKey( 'baz' )
	local o2 = rxTest:observe( observable2 )

	rxTest:expectEqual( 1, nTimesCreated )

	o2:expectData( "Step 2" )
	o2:expectData( "And then there were", "three" )

	s:onNext( "This", "is", 1, nil, "tuple" )
	o1:expectData( "This", "is", 1, nil, "tuple" )
	o2:expectData( "This", "is", 1, nil, "tuple" )

	o1:dispose()

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	s:onNext( "And, one more thing..." )
	o2:expectData( "And, one more thing..." )

	s:onCompleted()
	o2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	s:dispose()
	o2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestMultipleObserversForSameKey_behaviorSubject( rxTest )

	debugDisposables( rxTest )

	local nTimesCreated = 0

	local bs = BehaviorSubject( "some", "initial", "value" )

	local wot = WFRxWeakObservableTable( function( key )

		rxTest:expectEqual( 'baz', key )

		nTimesCreated = nTimesCreated + 1

		return bs

	end )

	rxTest:expectEqual( {}, wot:activeKeys() )
	rxTest:expectEqual( 0, nTimesCreated )

	local b1 = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, BehaviorSubject:isClassOf( b1 ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	local ob1 = rxTest:observe( b1 )
	rxTest:expectEqual( 1, nTimesCreated )

	ob1:expectData( "some", "initial", "value" )

	bs:onNext( "Hello, world" )
	ob1:expectData( "Hello, world" )

	local b2 = wot:observableForKey( 'baz' )

	local ob2 = rxTest:observe( b2 )
	rxTest:expectEqual( 1, nTimesCreated )

	ob2:expectData( "Hello, world" )

	bs:onNext( "This", "is", 1, nil, "tuple" )
	ob1:expectData( "This", "is", 1, nil, "tuple" )
	ob2:expectData( "This", "is", 1, nil, "tuple" )

	ob1:dispose()

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	bs:onNext( "And, one more thing..." )
	ob2:expectData( "And, one more thing..." )

	bs:onCompleted()
	ob2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	ob2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestMultipleObserversForSameKey_replaySubject( rxTest )

	debugDisposables( rxTest )

	local nTimesCreated = 0

	local rs = ReplaySubject( 1 )

	local wot = WFRxWeakObservableTable( function( key )

		rxTest:expectEqual( 'baz', key )

		nTimesCreated = nTimesCreated + 1

		return rs

	end )

	rxTest:expectEqual( {}, wot:activeKeys() )
	rxTest:expectEqual( 0, nTimesCreated )

	local r1 = wot:observableForKey( 'baz' )
	rxTest:expectEqual( true, ReplaySubject:isClassOf( r1 ) )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )
	rxTest:expectEqual( 1, nTimesCreated )

	local or1 = rxTest:observe( r1 )

	rs:onNext( "Hello, world" )
	or1:expectData( "Hello, world" )

	local r2 = wot:observableForKey( 'baz' )

	local or2 = rxTest:observe( r2 )

	rxTest:expectEqual( 1, nTimesCreated )

	or2:expectData( "Hello, world" )

	rs:onNext( "This", "is", 1, nil, "tuple" )
	or1:expectData( "This", "is", 1, nil, "tuple" )
	or2:expectData( "This", "is", 1, nil, "tuple" )

	or1:dispose()

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	rs:onNext( "And, one more thing..." )
	or2:expectData( "And, one more thing..." )

	rs:onCompleted()
	or2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	or1:dispose()
	or2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestNoCrossContaminationAmongKeys( rxTest )

	debugDisposables( rxTest )

	local baz = Subject()
	local blah = Subject()

	local wot = WFRxWeakObservableTable( function( key )

		if key == 'baz' then
			return baz
		elseif key == 'blah' then
			return blah
		end

	end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	local s1 = wot:observableForKey( 'baz' )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	local os1 = rxTest:observe( s1 )

	local s2 = wot:observableForKey( 'blah' )

	local os2 = rxTest:observe( s2 )

	rxTest:expectEqual( { baz = true, blah = true }, wot:activeKeys() )

	blah:onNext( "First blah" )
	os2:expectData( "First blah" )

	baz:onNext( "Hello, world" )
	os1:expectData( "Hello, world" )

	baz:onNext( "This", "is", 1, nil, "tuple" )
	os1:expectData( "This", "is", 1, nil, "tuple" )

	blah:onNext( "Another blah" )
	os2:expectData( "Another blah" )

	baz:onCompleted()
	os1:expectCompleted()

	rxTest:expectEqual( { blah = true }, wot:activeKeys() )

	blah:onCompleted()
	os2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	os1:dispose()
	os2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestCreateNewObserverAfterDispose( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function()
		return BehaviorSubject( "some", "initial", "value" )
	end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	repeat

		local b1 = wot:observableForKey( 'baz' )

		rxTest:expectEqual( { baz = true }, wot:activeKeys() )

		local ob1 = rxTest:observe( b1 )

		ob1:expectData( "some", "initial", "value" )

		b1:onNext( "Hello, world" ) -- kids, don't try this at home
		ob1:expectData( "Hello, world" )

		ob1:dispose()

	until true -- let b1 and ob1 fall out of scope

	collectgarbage()
	collectgarbage()
	collectgarbage()

	rxTest:expectEqual( {}, wot:activeKeys() )

	-- Create a new observer for existing key.

	local b2 = wot:observableForKey( 'baz' )

	local ob2 = rxTest:observe( b2 )

	ob2:expectData( "some", "initial", "value" )

	b2:onNext( "This", "is", 1, nil, "tuple" )
	ob2:expectData( "This", "is", 1, nil, "tuple" )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	b2:onNext( "And, one more thing..." )
	ob2:expectData( "And, one more thing..." )

	b2:onCompleted( 'baz' )
	ob2:expectCompleted()

	rxTest:expectEqual( {}, wot:activeKeys() )

	ob2:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestHasObservableForKey( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	rxTest:expectEqual( false, wot:hasObservableForKey( 'baz' ) )

	local s = wot:observableForKey( 'baz' )

	rxTest:expectEqual( true, wot:hasObservableForKey( 'baz' ) )

	local os = rxTest:observe( s )

	s:onNext( "Hello, world" )
	os:expectData( "Hello, world" )

	s:onCompleted()
	os:expectCompleted()

	rxTest:expectEqual( false, wot:hasObservableForKey( 'baz' ) )

	os:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestCleanupAfterError( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	local s = wot:observableForKey( 'baz' )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	local os = rxTest:observe( s )

	s:onNext( "Hello, world" )
	os:expectData( "Hello, world" )

	s:onError( "Something's", "not", "right" )
	os:expectError( "Something's", "not", "right" )

	rxTest:expectEqual( true, s:isDisposed() )

	rxTest:expectEqual( {}, wot:activeKeys() )

	os:dispose()
	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_errorInvalidKey( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	rxTest:expectError( function()
		return wot:observableForKey()
	end )

	rxTest:expectError( function()
		return wot:observableForKey( 0/0 )
	end )

	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_errorNotObservable( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function()
		return "not an Observable"
	end )

	rxTest:expectSpecificError(
		"WFRxWeakObservableTable: observableFactory must return an Observable",
		function()
			return wot:observableForKey( 'baz' )
		end )

	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestObservableForKey_errorReplayBufferSizeWithSubject( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function()
		return Subject()
	end, 3 )

	rxTest:expectSpecificError(
		"WFRxWeakObservableTable: can not use replayBufferSize with Subjects",
		function()
			return wot:observableForKey( 'baz' )
		end )

	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestHasObservableForKey_errorInvalidKey( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	rxTest:expectError( function()
		return wot:hasObservableForKey()
	end )

	rxTest:expectError( function()
		return wot:hasObservableForKey( 0/0 )
	end )

	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestGarbageCollection( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	rxTest:expectEqual( {}, wot:activeKeys() )

	repeat

		local s = wot:observableForKey( 'bax' )

		rxTest:expectEqual( { bax = true }, wot:activeKeys() )

		local os = rxTest:observe( s )

		s:onNext( "Hello, world" )
		os:expectData( "Hello, world" )

		s:onNext( "This", "is", 1, nil, "tuple" )
		os:expectData( "This", "is", 1, nil, "tuple" )

		os:dispose()

	until true -- let s and os fall out of scope

	collectgarbage()
	collectgarbage()
	collectgarbage()

	rxTest:expectEqual( {}, wot:activeKeys() )

	wot:dispose()

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

function WFRxWeakObservableTableTest.rxTestDispose( rxTest )

	debugDisposables( rxTest )

	local wot = WFRxWeakObservableTable( function() return Subject() end )

	local s = wot:observableForKey( 'baz' )

	rxTest:expectEqual( { baz = true }, wot:activeKeys() )

	wot:dispose()

	rxTest:expectEqual( true, wot:isDisposed() )
	rxTest:expectEqual( true, s:isDisposed() )

	rxTest:expectEqual( Rx.Disposable.numCreated(), Rx.Disposable.numDisposed() )

end

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

return WFRxTestCase.wrapSuite( 'WFRxWeakObservableTableTest', WFRxWeakObservableTableTest )
