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

WFRxLuaTutorialTest.lua

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

ADOBE CONFIDENTIAL
___________________

Copyright 2019 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

-- WF RxLua Tutorial
-- Author: [jandrew 2018-12-08]
-- Updated: [jandrew 2021-01-14] 

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

Welcome to the Wichita Foundation RxLua tutorial!

RxLua is a port of the well-known Reactive Extensions for JavaScript (RxJS*).
RxJS is widely documented and discussed on the Internet. This means that the
users of WF and RxLua are able to draw on the large body of existing RxJS
Version 4.0 documentation.

The version of RxLua that we use is closed-source, and can be found here:
https://git.corp.adobe.com/lua/RxLua

This file contains a WF-based RxLua tutorial in the form of a WF unit test suite.
Each test is an example of how to use different RxLua operators, work with the 
various WF components, and provides general knowledge on WF RxLua development
techniques.

Throughout this tutorial please remember that RxLua is a port of RxJS. If you're
stuck and there's not enough information in this guide, then just google it!

* WF RxLua is based off of Version 4.0 of RxJS. Versions 5, 6 and 7 remove
operation chaining and introduce different concepts that are not compatible with
Version 4.

--==============================================================================
PRE-TUTORIAL REQUIRED READING
--------------------------------------------------------------------------------

If you're completely new to Reactive Programming and Rx, then you should first
take a look at our RxLua documentation inside of the lua/wf repository:

WF RxLua Introduction - https://git.corp.adobe.com/pages/lua/wf/doc/rxlua/

If you're looking for information on specific RxLua operators or APIs, see the 
following:

RxLua API Documentation - https://git.corp.adobe.com/lua/RxLua/tree/master/doc/

Having trouble? Observable not firing? Checkout our troubleshooting guide:

https://git.corp.adobe.com/lua/wf/tree/doc/rxlua/troubleshooting.md

Remember that if you're ever stuck you can always just google it! There's a 
vibrant Rx community on the internet and your question has most likely already 
been asked and answered. 

Other Resources:

RxJS 4.0 Tutorial: https://doc.ebichu.cc/RxJS/

The Major Concepts of RxJS: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/exploring.md

--==============================================================================
BASIC RXLUA Q&A
--------------------------------------------------------------------------------

Q: What are the different RxLua operators?

A: The different RxLua operators, with thorough API documentation and examples, can 
be found within the RxLua repository:

RxLua Operators - https://git.corp.adobe.com/lua/RxLua/tree/master/doc/api/core/operators

Please note that not all RxJS operators have been ported to RxLua. 

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

Q: Which RxLua operator should I use?

A: That depends. Check the existing source code, or the tutorial below, for 
examples which may help you decide. If you're still stuck, consult the RxJS which-
operator-to-use guide:

Which Operator to Use? - https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/which-instance.md

Rx Marbles can also be useful for figuring out which operator to use:

Rx Marbles - http://rxmarbles.com/

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

Q: Is it okay to use up-values in observable sequences?

A: In most cases yes, but it's cleaner to avoid them.

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

Q: In RxLua does everything happening on different threads?

A: No, there are not multiple threads involved; just an event loop which allows
for cooperative single-threaded multitasking.

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

Q: How do I debug RxLua?

A: Right now the best way to debug RxLua is via print statements, and temporary
map/tap operators. See the Debugging section of the tutorial below for best practices.


--==============================================================================
WF RXLUA TUTORIAL
--------------------------------------------------------------------------------

The following tests are meant to teach you about RxLua within the realm of
Wichita Foundation. These tests were built to be interactive, so fire up your
unit test runner! For assistance in running the Wichita Foundation unit tests,
see following guide or reach out to the folks in the #wf slack channel.

https://git.corp.adobe.com/lua/wf-wrapper/blob/master/README.md

You are encouraged to make changes to the source code of these RxLua tests; 
comment-out lines, add Rx operators, and modify the tests in order to gain a
better understanding of the observable sequences. Just make sure to revert any 
changes to this file before you make your next commit.

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

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

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

	-- WF.core.logger
local WFLogger = require 'WFLogger'

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

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

local logger = WFLogger 'WFRxLuaTutorialTest'


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

local WFRxLuaTutorialTest = {}

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

function WFRxLuaTutorialTest.rxTest_HowTo_CreateObservables( rxTest )

	-- In WF, we use a number of techniques to create new observables. Below you'll
	-- find examples of the most common techniques. A comprehensive list of
	-- observables can be found here:
	
	-- https://git.corp.adobe.com/lua/RxLua/tree/master/doc/api/core/observables
	
	----------------------------------------------------------------------------

	-- If you want to convert a Lua variable into an Observable that emits that
	-- item, use the Just operator. This works with strings, booleans, numbers, 
	-- tables, etc.
	
	local justObservable = Observable.just( "Hello" )
	rxTest:expectSingleValue( justObservable, "Hello" )
		-- justObservable fires with 1 value - "Hello" - and then completes.
		
	justObservable = Observable.just( true )
	rxTest:expectSingleValue( justObservable, true )
		-- justObservable fires with 1 value - true - and then completes.
		
	local tableValue = { 1, 2, 3 }
	justObservable = Observable.just( tableValue )
	rxTest:expectSingleValue( justObservable, tableValue )
		-- Note: Observable.just( table ) will create an observable that produces
		-- a single data event containing the table; it will not produce an event
		-- for each entry within the table. See the next example.
		
	----------------------------------------------------------------------------
	
	-- Sometimes you have a table of values and you want to create an observable 
	-- that fires once for each value within the table. This is common when you
	-- have an array docIds or docValues and need to process the entries one
	-- at a time. For this, use From.
	
	local fromObservable = Observable.from( { 1, 2, 3 } )
		-- This produces an observable that fires once for each table entry:
		-- 1
		-- 2
		-- 3
		-- Completes
	
	local observer = rxTest:observe( fromObservable )
	
	observer:expectData( 1 )
	observer:expectData( 2 )
	observer:expectData( 3 )
	observer:expectCompleted()
	
	----------------------------------------------------------------------------
	
	-- If you want to create a completed observable that never fires with data, 
	-- use the Empty operator.
		
	local emptyObservable = Observable.empty()
	rxTest:expectOnlyCompleted( emptyObservable )
	
	----------------------------------------------------------------------------
	
	-- You can also create an Observable from scratch by using the Create operator.
		
	local createObservable = Observable.create(

		-- This example observable fires with 1 data event - 42 - before completing.

		function( observer )

			logger:infof( "%s: Now creating createObservable.", rxTest:testName() )

			observer:onNext( 42 )
			observer:onCompleted()

			return Rx.Disposable.create(

				function()

					logger:infof( "%s: Now closing createObservable.", rxTest:testName() )

				end )

			end )
	
	rxTest:expectSingleValue( createObservable, 42 )
	
end

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

function WFRxLuaTutorialTest.rxTest_HowTo_CreateSubjects( rxTest )

	-- In WF, we commonly use Subjects. Subjects are both observables and 
	-- observers. For more info on Subjects, checkout:
	
	-- http://reactivex.io/documentation/subject.html
	
	-- We use subjects when we want hot observables. Hot observables do not wait 
	-- for any subscription to start firing; they start emitting items when created.
	-- For more info on Hot vs Cold observables, see:
	
	-- https://alligator.io/rxjs/hot-cold-observables/
	
	----------------------------------------------------------------------------
	
	-- Replay Subjects: every event is broadcasted to all subscribed and future 
	-- observers. This means that if you subscribe to a ReplaySubject after N
	-- events have already fired, you'll still receive those N events. 
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/subjects/replaysubject.md
	
	-- NOTE: In WF we commonly use a buffer size of 1 with ReplaySubjects. This 
	-- effectively means that an observer will receive the latest value when
	-- subscribing, along with all subsequent values. 
	
	local numberOfEventsToReplay = 2 -- buffer size
	local replaySubject = Rx.ReplaySubject( numberOfEventsToReplay )
	
	replaySubject:onNext( 'a' )
	replaySubject:onNext( 'b' )
	replaySubject:onNext( 'c' )
	replaySubject:onCompleted()

	local observer = rxTest:observe( replaySubject )
	
	observer:expectData( 'b' ) -- We received the 2 latest values (buffer size).
	observer:expectData( 'c' ) 
	observer:expectCompleted()
	
	-- Behavior Subjects: once subscribed an observer will receive the last 
	-- (or initial) value and then all subsequent notifications.
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/subjects/behaviorsubject.md
	
	local initialValue = false
	local behaviorSubject = Rx.BehaviorSubject( initialValue )
	
	observer = rxTest:observe( behaviorSubject )
	
	behaviorSubject:onNext( true )
	behaviorSubject:onCompleted()
	
	observer:expectData( false ) -- We received the initial value.
	observer:expectData( true )
	observer:expectCompleted()
	
end

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

function WFRxLuaTutorialTest.rxTest_HowTo_ListenToAnObservable( rxTest )

	-- Now that you know how to create an observable, let's quickly cover how
	-- to subscribe to an observable.
	
	-- First let's create an observable that will fire with three different events.
	
	local observable = Observable.from( { 'a', 'b', 'c' } )
		-- This produces an observable that fires once for each table entry:
		-- 'a'
		-- 'b'
		-- 'c'
		-- Completes
		
	-- Now we want to create an observer; the thing that sees the items being emitted
	-- from the observable. To do this, we use Subscribe.
	
	-- http://reactivex.io/documentation/operators/subscribe.html
	
	local gotHereCount = 0
	local completedObservable = Rx.Subject()
	
	-- The following parameters to Subscribe are optional.
	
	local subscription = observable:subscribe(
	
		function( value )
		
			-- The first parameter is the onNext handler. This function is
			-- called on every data event.
			
			gotHereCount = gotHereCount + 1
			
		end,
		
		function( err )
		
			-- The second parameter is the onError handler. This function is 
			-- called when an error is thrown.
		
		end,
		
		function()
		
			-- The third parameter is the onCompleted handler. This function is
			-- called when the observable is complete.
		
			completedObservable:onCompleted()
				-- completedObservable only exists here for the purpose of this unit test.
		end )
			
	rxTest:expectOnlyCompleted( completedObservable )
	rxTest:expectEqual( 3, gotHereCount )
			
	-- It's IMPORTANT to dispose your subscription once you're done with it.
	
	subscription:dispose()
			
end

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

function WFRxLuaTutorialTest.rxTest_HowTo_FireAndForget( rxTest )

	-- Sometimes we want to trigger to an observable but we really don't care
	-- about the response. This is common when we're calling certain WF APIs.
	
	-- Though you can accomplish this using Subscribe, you can also use
	-- WFRxRunUntilDone. Note: WFRxRunUntilDone is not standard Rx.
	
	local WFRxRunUntilDone = require 'WFRxRunUntilDone'
	
	local gotHereCount = 0
	local completedObservable = Rx.Subject()
	
	-- Even though we don't subscribe to the following observable, or keep a 
	-- reference to it, the appended WFRxRunUntilDone will guarantee that
	-- the observable sequence is activated.
	
	Observable.from( { 'a', 'b', 'c' } )
		:map( function()
		
			gotHereCount = gotHereCount + 1
			
			if gotHereCount == 3 then
				completedObservable:onCompleted()
			end
			
		end )
		:call( WFRxRunUntilDone ) -- "call" is also a custom WF RxLua operator.
		
	rxTest:expectOnlyCompleted( completedObservable )
	rxTest:expectEqual( 3, gotHereCount )
		
end

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

function WFRxLuaTutorialTest.rxTest_HowTo_ScheduleObservables( rxTest )

	-- In Rx you can create observables that produce a value after a specified
	-- delay, or periodically produce values.

	local initialDelay = 0.1
		-- In WF, all time values are measured in seconds. This is not standard
		-- for Rx.

	-- The following observable will fire with a single timer event after initialDelay
	-- seconds have elapsed.
	
	local timerObservable = Observable.timer( initialDelay, WFRxScheduler.sharedScheduler() )
	
			:map( function( timerValue )
				return "Timer Fired"
			end )
				
	rxTest:expectSingleValue( timerObservable, "Timer Fired" )
				
	-- You can also create a recurring timer by specifying a period parameter. This timer
	-- will first fire after initialDelay seconds have elapsed, and then will fire every
	-- recurringDelay seconds.
	
	local recurringDelay = 0.01
	
	local recurringTimerObservable = 
	
			Observable.timer( initialDelay, recurringDelay, WFRxScheduler.sharedScheduler() )
			
				:map( function( timerValue )
					return "Timer Fired"
				end )
				
				:take( 3 ) -- Limit the observable to three data events.

	local observer = rxTest:observe( recurringTimerObservable )
			
	observer:expectData( "Timer Fired" )
	observer:expectData( "Timer Fired" )
	observer:expectData( "Timer Fired" )
	observer:expectCompleted()

	-- You can add a delay into a reactive chain using the Delay operator.
	
	local delayObservable = Observable.just( false ) -- This fires immeadiately
	
		:delay( 0.01, WFRxScheduler.sharedScheduler() ) -- We delay the 'false' event by 0.01 seconds.
		
		:map( function( value )
		
			return not value 
			
		end )
		
	rxTest:expectSingleValue( delayObservable, true )

end

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

function WFRxLuaTutorialTest.rxTest_HowTo_CombineObservables( rxTest )

	-- In WF we often care about multiple observables and need to combine them
	-- into a single observable sequence. For this we often use the operator
	-- CombineLatest. 
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/observables/combinelatest.md
	
	-- With CombineLatest, the sequence is triggered once all combined observables
	-- have values. There onwards, any change to a single observable value will
	-- trigger the observable sequence.
	
	local streamOfLetters = Observable.from( { 'a', 'b', 'c' } )
		-- This produces an observable that fires once for each table entry:
		-- 'a'
		-- 'b'
		-- 'c'
		-- Completes
		
	local streamOfNumbers = Observable.from( { 1, 2, 3, 4, 5 } )
		-- This produces an observable that fires once for each table entry:
		-- 1
		-- 2
		-- 3
		-- 4
		-- 5
		-- Completes
		
	local combinedObservable = 
		
		Observable.combineLatest( streamOfLetters, streamOfNumbers )
	
			:map( function( letter, number ) 
			
				-- By using combineLatest, we now have access to the latest values for
				-- each of the combined observables.
				
				-- Note that there's no limit on the number of observables you can combine.
			
				return string.format( "%s%d", letter, number )
			
			end )
	
	local observer = rxTest:observe( combinedObservable )
			
	observer:expectData( 'a1' )
	observer:expectData( 'b1' )
	observer:expectData( 'b2' )
	observer:expectData( 'c2' )
	observer:expectData( 'c3' )
	observer:expectData( 'c4' )
	observer:expectData( 'c5' )
	observer:expectCompleted()
	
end

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

function WFRxLuaTutorialTest.rxTest_HowTo_ManipulateData( rxTest )

	-- This test demonstrates some common patterns for manipulating data within
	-- an observable sequence.
	
	----------------------------------------------------------------------------
	
	-- The most basic way to manipulate data is by using Map. This is the simplest
	-- and most powerful Rx operator. 
	
	local mapObservable = Observable.just( 1 )
	
		:map( function( value )
		
			return value * 2
				
		end )
		
	rxTest:expectSingleValue( mapObservable, 2 )
	
	----------------------------------------------------------------------------
	
	-- Other common operators are flatMap and flatMapLatest. These operators
	-- allow us to switch to a new observable within an existing observable
	-- sequence. 
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/operators/selectmany.md
	
	local flatMapObservable = Observable.from( { 1, 2, 3 } )
	
		:flatMap( function( value )
		
			return Observable.just( value * 2 )
				:delay( 0.01, WFRxScheduler.sharedScheduler() )
				
		end )

	local observer = rxTest:observe( flatMapObservable )
			
	observer:expectData( 2 )
	observer:expectData( 4 )
	observer:expectData( 6 )
	observer:expectCompleted()
	
	-- The flatMapLatest operator is similar to the flatMap, except that rather 
	-- than emitting all of the items emitted by all of the Observables, flatMapLatest
	-- instead emits items from each such transformed Observable only until the 
	-- next such Observable is emitted, then it ignores the previous one and begins
	-- emitting items emitted by the new one.
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/operators/flatmaplatest.md
	
	local flatMapLatestObservable = Observable.from( { 1, 2, 3 } )
	
		:flatMapLatest( function( value )
		
			return Observable.just( value * 2 )
				:delay( 0.01, WFRxScheduler.sharedScheduler() )
				
		end )

	observer = rxTest:observe( flatMapLatestObservable )
			
	observer:expectData( 6 )
	observer:expectCompleted()
	
	----------------------------------------------------------------------------
	
	-- Use filter to restrict the data events that are allowed to continue through
	-- the observable sequence.
	
	local filterObservable = Observable.from( { 1, 2, 3, 4, 5 } )
	
		:filter( function( value )
		
			return value % 2 == 0
				-- If the number is even, return true.
				
		end )
		
	observer = rxTest:observe( filterObservable )
			
	observer:expectData( 2 )
	observer:expectData( 4 )
	observer:expectCompleted()
	
	----------------------------------------------------------------------------
	
	-- Extract data from a table. This is commonly used when working with
	-- docValues.
	
	local animals = {
		pets = {
			cats = {
				sound = "meow"
			},
		},
		notPets = {
			bears = {
				sound = "roar"
			},
		}
	}
	
	local extractDataObservable = Observable.just( animals )
	
		:pluck( 'pets', 'cats', 'sound' )
		
	rxTest:expectSingleValue( extractDataObservable, "meow" )

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

	-- Create a table from an observable sequence.
	
	local arrayObservable = Rx.Observable.timer( 0, 0.01, WFRxScheduler.sharedScheduler() )
	
		:take( 5 )
		
		:toArray()
		
	rxTest:expectSingleValue( arrayObservable, { 1, 2, 3, 4, 5 } )

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

	-- Sometimes you want to ignore duplicate data events in order to reduce
	-- downstream processing. 
	
	local distinctObservable = Observable.from( { 1, 1, 2, 3, 3 } )
	
		:distinctUntilChanged()

	observer = rxTest:observe( distinctObservable )
			
	observer:expectData( 1 )
	observer:expectData( 2 )
	observer:expectData( 3 )
	observer:expectCompleted()

	-- If the values firing on the stream are tables, then you must provide a
	-- function to distinctUntilChanged in order to compare the tables.
	
	local KSTableUtils = require 'KSTableUtils'
	
	local function distinctTableCompare( _, tbl1, _, tbl2 )

		return KSTableUtils.deepEquals( tbl1, tbl2 )

	end
	
	local distinctTableObservable = Observable.from( { { 1 }, { 1 }, { 2 }, { 3 }, { 3 } } )
	
		:distinctUntilChanged( nil, distinctTableCompare )
		
	observer = rxTest:observe( distinctTableObservable )
			
	observer:expectData( { 1 } )
	observer:expectData( { 2 } )
	observer:expectData( { 3 } )
	observer:expectCompleted()

end

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

function WFRxLuaTutorialTest.rxTest_HowTo_ControlTheRateAtWhichAnObservableFires( rxTest )

	-- You can reduce the rate at which an observable will fire using a number of
	-- RxLua operators. Most of the time when we rate limit observable streams
	-- we do so for performance reasons. The most common operators we use to
	-- accomplish this are Debounce and Calm.
	
	-- Debounce: Discards values that occur within the specific time.
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/operators/debounce.md
	
	-- In this example, all values but the last will be omitted.
	
	local debounceObservable = Rx.Observable.from( { 1, 2, 3, 4, 5 } )
	
		:debounce( 0.01, WFRxScheduler.sharedScheduler() )
		
	local observer = rxTest:observe( debounceObservable )
			
	observer:expectData( 5 )
	observer:expectCompleted()
	
	-- Calm: Calm is specific to our implementation of RxLua and is not standard. 
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/operators/calm.md
	
	-- Calm is similar to Debounce except that Calm will relay the first notification
	-- immediately upon receipt. 
	
	local calmObservable = Rx.Observable.from( { 1, 2, 3, 4, 5 } )
	
		:calm( 0.01, WFRxScheduler.sharedScheduler() )

	observer = rxTest:observe( calmObservable )
			
	observer:expectData( 1 )
	observer:expectData( 5 )
	observer:expectCompleted()

end

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

function WFRxLuaTutorialTest.rxTest_Tip_WhenToUseTakeOne( rxTest )

	-- In WF we somewhat excessively use the Take operator to guarantee that
	-- we don't keep observables open longer than we need to. This is especially
	-- important when working with the index store APIs.
	
	-- In general, it's good practice to append take( 1 ) to an observable sequence
	-- if you're calling an API and you only need a single value and not an
	-- ongoing stream of values.
	
	-- Many of the WF APIs produce ongoing data streams. For example, here's a 
	-- very simple mock API that produces data events every 0.01 seconds. 
	-- This is obviously useless and simpler than what you'd find in the codebase, 
	-- but there are many APIs that periodically produce data events.

	local ongoingAPIExample = function()
	
		return Rx.Observable.timer( 0, 0.01, WFRxScheduler.sharedScheduler() )
	
			:map( function( value )
			
				return value * 2
			
			end )
	
	end
	
	local observer = rxTest:observe( ongoingAPIExample() )
			
	observer:expectData( 2 )
	observer:expectData( 4 )
	observer:expectData( 6 )
	--... and on and on and on and on...
	--observer:expectCompleted()
		-- This will fail, because the observable sequence contains no logic
		-- to tell it went to complete. It's an ongoing sequence.
	
	-- We'll often call such APIs and only need a snapshot of the current data,
	-- rather than continuous updates. This is sometime true for UI callers, as
	-- well as code that drives the business logic. For these cases, we use
	-- take( 1 ).
	
	observer = rxTest:observe( ongoingAPIExample():take( 1 ) )
			
	observer:expectData( 2 )
	observer:expectCompleted()
	
end

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

function WFRxLuaTutorialTest.rxTest_Tip_BeCarefulWithTap( rxTest )

	-- The Rx Tap operator, at least in RxLua, can cause hard-to-find bugs if
	-- used incorrectly. Unlike most, if not all, of the other Rx operators
	-- Tap is not guaranteed to execute in order of the observable sequence.
	
	-- https://git.corp.adobe.com/lua/RxLua/blob/master/doc/api/core/operators/tap.md
	
	local tapTriggered = false
	
	local streamOfNumbers = Observable.from( { 1, 2, 3 } )
	
		:tap( function( number )
		
			logger:infof( "%s Tap is firing with value %d", rxTest:testName(), number )
			tapTriggered = true
		
		end )
		
		:map( function( number )
		
			-- assert( tapTriggered )
			-- This is commented out, because it's not guaranteed to be true.
		
			return number * 2
		
		end )

	local observer = rxTest:observe( streamOfNumbers )
			
	observer:expectData( 2 )
	observer:expectData( 4 )
	observer:expectData( 6 )
	observer:expectCompleted()

	-- In the above chain, even though the Tap operator is above the map operator, the
	-- map function may execute before the tap function ever executes.
	
	-- Because of this it's smart to avoid Tap, even if you're only adding logging.
	-- Instead you should use map, and only modify the value if needed:
	
	local safeLoggingObservable = Observable.from( { 1, 2, 3 } )
	
		:map( function( number )
		
			logger:infof( "%s Map is firing with value %d", rxTest:testName(), number )
				-- Add your logging.
				
			return number 
				-- Return the value unchanged.
		
		end )
		
		:map( function( number )
		
			return number * 2
		
		end )

	observer = rxTest:observe( safeLoggingObservable )
			
	observer:expectData( 2 )
	observer:expectData( 4 )
	observer:expectData( 6 )
	observer:expectCompleted()
	
end

--------------------------------------------------------------------------------
-- ADVANCED SECTION: Working with RxLua and the Doc Store / Index Store
--------------------------------------------------------------------------------

function WFRxLuaTutorialTest.rxTest_HowTo_DocStoreAPIs( rxTest )
	
	-- There aren't really any notable "gotcha"-type subtleties that you need
	-- to be aware of when working with the doc store; the Rx APIs are straight-
	-- forward. The one big caveat is when working with the bulk APIs you
	-- should take advantage of some of our handy Rx utility functions instead
	-- of calling the APIs directly.
	
	----------------------------------------------------------------------------
	
	-- Instead of calling the bulkApplyOverlay API, use WFRxThrottledBatchedBulkApplyOverlay
	
	-- docStore:bulkApplyOverlay( overlays ) <-- Don't do this.

	-- WFRxThrottledBatchedBulkApplyOverlay( "description", docStore, overlays, 100, 10 ) <-- Do this.
	
	----------------------------------------------------------------------------
	
	-- Instead of calling the bulkDocValue API, use WFRxThrottledBatchedBulkDocValue.

	-- docStore:bulkDocValue( assetIds ) <-- Don't do this.

	-- WFRxThrottledBatchedBulkDocValue( "description", docStore, assetIds, 100, 10 ) <-- Do this.
	
	----------------------------------------------------------------------------
	
	-- Instead of calling the bulkSetDocValue API, use WFRxBatchedBulkSetDocValue

	-- docStore:bulkSetDocValue( docValues ) <-- Don't do this.

	-- WFRxBatchedBulkSetDocValue( "description", docStore, docValues ) <-- Do this.

end

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

local mockIndexStore = {

	query = function( args )
	
		return Rx.Observable.timer( 0, 0.01, WFRxScheduler.sharedScheduler() )
			:map( function( value )
			
				if args.singleColumnQuery then
				
					return { value * 100, value * 100 + 1 }
				
				end
			
				return ( { 
					{ value * 100 }, 
					{ value * 100 + 1 }, 
				} )
			
			end )
			
	end,
	
	queryOnce = function( args )
	
		return Rx.Observable.timer( 0, 0.01, WFRxScheduler.sharedScheduler() )
			:map( function( value )
			
				if args.singleColumnQuery then
				
					return { value * 100, value * 100 + 1 }
				
				end
			
				return ( { 
					{ value * 100 }, 
					{ value * 100 + 1 }, 
				} )
			
			end )
			:take( 1 )
			
	end,
	
}

function WFRxLuaTutorialTest.rxTest_HowTo_IndexStoreAPIs( rxTest )

	local KSTableUtils = require 'KSTableUtils'
	
	local function distinctTableCompare( _, tbl1, _, tbl2 )

		return KSTableUtils.deepEquals( tbl1, tbl2 )

	end

	-- If you're working with the index store, you're most likely doing so
	-- through the use of the query API. Here is an example of to best call
	-- query.
	
	----------------------------------------------------------------------------
	
	-- Ongoing Queries: If you need to create an ongoing query that fires updates
	-- over time, then it's usually a good idea to calm the query and provide
	-- distinctUntilChanged to reduce the amount of downstream work.
	
	local ongoingQuery = mockIndexStore.query {
	
			sql = [[SELECT foo FROM bar]],
								
			calm = 1, -- (value in seconds)
				-- For ongoing queries, we often want to calm the data events; 
				-- index store tables can update very quickly, and we often don't
				-- need to process every single update. Instead, we calm the
				-- query so that it only updates once every N seconds.
				
			quiesceUntilUpToDate = false,
				-- Because this is an ongoing query, it's okay if the index store
				-- table is a little bit behind at first; it will come up to date 
				-- once the query fires a few times. For ongoing queries, we
				-- almost never include "quiesceUntilUpToDate = true".
		}
		
		:distinctUntilChanged( nil, distinctTableCompare )
			-- To prevent us from reprocessing query results when nothing has changed,
			-- it can sometimes be a good idea to include `distinctUntilChanged`. This
			-- operator will prevent data events from continuing through the observable
			-- sequence if the data hasn't changed.
			
	local observer = rxTest:observe( ongoingQuery )
			
	observer:expectData( {
		{
			100,
		},
		{
			101,
		},
	} )
	
	observer:expectData( {
		{
			200,
		},
		{
			201,
		},
	} )
	
	observer:expectData( {
		{
			300,
		},
		{
			301,
		},
	} )
	
	--observer:expectCompleted()
	
	----------------------------------------------------------------------------
	
	-- Snapshot Queries: For queries that are not ongoing, it's usually important
	-- to include quiesceUntilUpToDate so that the data is up to date before
	-- the query runs. It's also important to use queryOnce instead of query:
	-- it's more performant for one-time queries.
	
	local snapshotQuery = mockIndexStore.queryOnce {
	
			sql = [[SELECT foo FROM bar]],
								
			--calm = 0,
				-- Because we're using queryOnce, there's no need to calm this.
								
			quiesceUntilUpToDate = true,
				-- In most snapshot style queries, we want to make sure the index store
				-- table is up to date with all the latest and greatest info within
				-- the doc store before running the query. Including quiesceUntilUpToDate
				-- will ensure that.
		}

	observer = rxTest:observe( snapshotQuery )
			
	observer:expectData( {
		{
			100,
		},
		{
			101,
		},
	} )
	
	observer:expectCompleted()

	----------------------------------------------------------------------------
	
	-- Single Column Queries: For queries where you are selecting only a single
	-- column, use the `singleColumnQuery` flag. If present, the flag will cause
	-- the query will run via gatherArray, instead of gatherArrayOfArray, which 
	-- is slightly more performant. The result will be a single array instead of
	-- an array of arrays, which should be more desirable for the calling code.
	
	local singleColumnQuery = mockIndexStore.queryOnce {
			sql = [[SELECT foo FROM bar]],		
			singleColumnQuery = true,
		}

	observer = rxTest:observe( singleColumnQuery )
			
	observer:expectData(
		{
			100,
			101,
		}
	)
	
	observer:expectCompleted()

end

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

function WFRxLuaTutorialTest.rxTest_HowTo_MeasurePerformance( rxTest )

	-- To measure the performance of an Rx API, see WFPerformanceUtils.
	
	-- Use the wrapInLogLongExecutionInDiagnosticLogWithStaticDescription function 
	-- for wrapping an API w/ performance instrumentation that will show up within
	-- the diagnostic log.
	
	local WFPerformanceUtils = require 'WFPerformanceUtils'
	
	local simpleRxAPI = function()
		return Observable.just( true )
	end
	
	local category = 'WFRxLuaTutorialTest'
	local threshold = 0.1
	local description = rxTest:testName()
	
	simpleRxAPI = WFPerformanceUtils.wrapInLogLongExecutionInDiagnosticLogWithStaticDescription(
			category, threshold, description, simpleRxAPI )

	-- Now that simpleRxAPI is wrapped, if the execution time ever exceeds the defined
	-- threshold, we will receive a warning in the diagnostic log.
	
	rxTest:expectSingleValue( simpleRxAPI(), true )

end

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

function WFRxLuaTutorialTest.rxTest_HowTo_Debugging( rxTest )

	-- Unfortunately, the simplest way to debug Rx is by using print statements. If
	-- you can't even get your print statements to fire, then likely you have not
	-- subscribed to the observable. See the troubleshooting guide for more info:
	
	-- https://git.corp.adobe.com/lua/wf/tree/doc/rxlua/troubleshooting.md

	-- If you're looking for more info on Rx Debugging, go ahead and google it; 
	-- you'll be disappointed. :(

	-- Tips to help debugging:
		-- Make sure your observable has an observer! Have you subscribed?
		-- Avoid up-values in observable sequences.
		-- Don't use tap. See the 'BeCarefulWithTap' test for more info.
		
	----------------------------------------------------------------------------
		
	-- Logging Example
		
	local safeLoggingObservable = Observable.from( { 1, 2, 3 } )
		
		
		:map( function( input )
		
			-- Even though this map function is not modifying the input, we still prefer
			-- to use map instead of tap because it guarantees the order of the
			-- logging.
			
			logger:info( "Use Map for logging, not tap!" )
		
			return input
		
		end )
		
		:map( function( number )
		
			logger:infof( "%s Map is firing with value %d", rxTest:testName(), number )
		
			return number * 2
		
		end )
		
		:map( function( number )
		
			logger:infof( "%s Map is firing with value %d", rxTest:testName(), number )
		
			return number / 2
		
		end )
		

	local observer = rxTest:observe( safeLoggingObservable )
			
	observer:expectData( 1 )
	observer:expectData( 2 )
	observer:expectData( 3 )
	observer:expectCompleted()
	
	----------------------------------------------------------------------------
	
	-- Common Problems:
	
	----------------------------------------------------------------------------
	
	-- 1. My observable isn't firing:
	
	local fired = false
		
	local observable = Observable.just( true )
		:map( function()
			fired = true
		end )
		
	-- assert( fired == true )
		-- This will fail because the observable has not yet been subscribed to.
		
	-- In order for the observable to fire, it needs to have an observer.
	
	observer = observable:subscribe()
	
	assert( fired == true )
		-- Now that we have an observer, fired should be true.

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

	-- 2. My observable is returning nonsense.
	
	-- This often happens if you use Map instead of flatMap, or if you fail to
	-- include a Switch Operator.
	
	local newObservable = function()
		return Observable.just( 42 )
	end
		
	observable = Observable.just( true )
		:map( function()
			return newObservable()
		end )
		
	--rxTest:expectSingleValue( observable, 42 )
		-- This will fail because you didn't Switch to the new observable stream.
		
	-- Here is the correct observable sequence:
	
	observable = Observable.just( true )
		:map( function()
			return newObservable()
		end )
		:switch()
		
	rxTest:expectSingleValue( observable, 42 )
		
	-- Which is the same as this:
	
	observable = Observable.just( true )
		:flatMap( function()
			return newObservable()
		end )

	rxTest:expectSingleValue( observable, 42 )
	
end

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

return WFRxTestCase.wrapSuite( 'WFRxLuaTutorialTest', WFRxLuaTutorialTest )
