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

WFRxLuaExerciseTest.lua

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

ADOBE CONFIDENTIAL
___________________

Copyright 2021 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 Exercise
-- Author: [jandrew 2021-1-25]

	-- RxLua
local Rx = require 'Rx'

local WFRxScheduler = require 'WFRxScheduler'

local WFRxLuaExerciseTest = {}

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

--==============================================================================
INTRODUCTION
--------------------------------------------------------------------------------

Welcome to the Wichita Foundation RxLua Exercise! The purpose of the following
exercise is to help you write your first RxLua code. It's a learning exercise,
and the only thing that matters is whether or not you learn anything; please
take your time, and ask lots of questions!

--==============================================================================
PRE-EXERCISE REQUIRED READING
--------------------------------------------------------------------------------

Before attempting this exercise, please make sure that you've read the lua
manual and have completed the Lua coding exercise located in the file:

WFLuaExerciseTest.lua

You should also read and work through the WF RxLua Tutorial before attempting
this exercise. The tutorial can be found within the file:

WFRxLuaTutorialTest.lua

--==============================================================================
GETTING STARTED
--------------------------------------------------------------------------------

Below you'll find some simple tasks. The goal of this exercise is to complete
these tasks, and ensure that the unit test associated with this test file
is passing.

If you're stuck and unsure about a step, please reach out to a WF engineer.

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

--------------------------------------------------------------------------------
-- STEP 1: SET THE FOLLOWING VARIABLE TO TRUE.
--------------------------------------------------------------------------------

local setToTrue = false
	-- Setting this to true enables the unit test associated with this exercise.

--------------------------------------------------------------------------------
-- STEP 2: Create a new git branch inside of the wf submodule for this exercise.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 3: Open WFRxLuaExerciseTest.lua inside of Halide.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 4: Build and run the test runner. Please use either the Mac, Windows,
-- or iOS test runner for the purpose of this exercise.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 5: Within the test runner, search for WFRxLuaExerciseTest and run the
-- test suite. It should fail.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 6: Complete the coding exercises below. Once completed, the test within
-- the WFRxLuaExerciseTest test suite should pass.
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 7: Once you've completed the coding exercise, commit your changes and
-- push your branch git git.corp.adobe.com. Create a Pull Request containing
-- your changes, and mark it with a "DO NOT MERGE" label. 
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- STEP 8: Notify engineers of your PR within the #wf-standup channel. 
--------------------------------------------------------------------------------

--==============================================================================
-- START CODING EXERCISE
--------------------------------------------------------------------------------

-- Please read through and implement the following functions according to the
-- directions within:

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

local function exerciseOne( televisionShowsStream )

	-- Given a stream that fires with tables containing information about television
	-- shows, create an observable sequence that fires with a single data event containing
	-- the title of shows whose ratings are greater than  or equal to 7.
	-- User the following Rx operators in your solution: map, scan, filter

	-- Example:
	--[[ 

	Input: 

	Observable
	< 
		{ 
			title = "Avatar the Last Airbender",
			rating = 10
		}
	>,
	< 
		{ 
			title = "How I Met Your Mother",
			rating = 5
		}
	>,
	< 
		{ 
			title = "The Expanse",
			rating = 9
		}
	>

	Output: 

	Observable< { "Avatar the Last Airbender", "The Expanse" } >

	]]--

end

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

local function exerciseTwo( arr )

	-- Given an array containing N numbers, create an observable sequence that doubles
	-- all odd numbers within the array. You should not use any loops.
	-- Use the following Rx operators in your solution: map, from, toArray

	-- Example:
	-- Input: { 3, 2, 6, 5, .. N }
	-- Output: Observable< { 6, 2, 6, 10, .. N } >

end

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

local function exerciseThree( errorObservable )

	-- Given an observable sequence that throws an error, catch the error
	-- and return an Observable that emits the error message as a data event.
	
	-- Input: Observable.Error< "WARNING WARNING!" >
	-- Output: Observable< "WARNING WARNING!" >
end

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

local function exerciseFour()

	

	-- Create an observable sequence that emits one letter of the English alphabet
	-- every 0.1 seconds, until all 26 letters have been emitted.
	-- You will need to use an observable time in your solution:
	-- Observable.timer( <afterTime>, <period>, WFRxScheduler.sharedScheduler() )

	-- Use the following Rx operators in your solution: map, take

	-- Example:
	-- Input: nil
	-- Output: Observable< 'a' >, < 'b' >, < 'c' >..< 'z' >
	
end

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

local function exerciseFive( numberStream, letterStream )
	
	-- You are given two observables: numberStream and letterStream.
	
	-- numberStream emits the numbers 1 through N, and then completes.
	
	-- letterStream emits the letters a through z, and then completes.
	
	-- Create an observable sequence that combines the output from numberStream and 
	-- letterStream, and emits the concatenated string. The new sequence should only
	-- emit 5 data events before completing.
	-- Use the following Rx features in your solution: zip, take

	-- Example:
	-- Input: Observable< 1 >, < 2 >, < 3 >..< N >, Observable< 'a' >, < 'b' >, < 'c' >..< 'z' >
	-- Output: Observable< '1a' >, < '2b' >, < '3c' >..>

end


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

local function getRandomAnimalSoundObs( number )

	local sound

	if number == 1 then
	
		sound = 'meow'
	
	elseif number == 2 then
	
		sound = 'woof'
	
	elseif number == 3 then

		sound = 'oink'
	
	elseif number == 4 then
	
		sound = 'roar'
	
	else
		sound = 'moo'
	end
	
	return Rx.Observable.just( sound )
	
end

local randNum = {}
local getRandomNumberCount = 0

local function getRandomNumber()

	getRandomNumberCount = getRandomNumberCount + 1
	return randNum[ getRandomNumberCount ]
	
end

local function exerciseSix( trueStream )
	
	-- Every time trueStream fires, get a random number by calling getRandomNumber,
	-- and then generate a random animal sound by passing that number to getRandomAnimalSoundObs().
	-- 
	-- Use the following Rx operators in your solution: map, flatMap
	--
	-- Example:
	-- Input: Observable< true >,< true >,< true >
	-- Output: Observable< 'meow', 'woof', 'oink' >
	
end


--==============================================================================
-- END CODING EXERCISE: PLEASE DO NOT MODIFY ANY OF THE CODE BELOW THIS LINE
--------------------------------------------------------------------------------

function WFRxLuaExerciseTest.rxTestVerifyExercise1( rxTest ) 

	if not setToTrue then
		return
	end
	
	-- Given a stream that fires with tables containing information about television
	-- shows, create an observable sequence that fires with a single data event containing
	-- the title of shows whose ratings are greater then 7.
	
	local televisionShowsStream = Rx.Observable.from( {
	
		{ 
			title = "Friends",
			rating = 7
		},
		{ 
			title = "Mad About You",
			rating = 5
		},
		{ 
			title = "Seinfeld",
			rating = 9
		},
		{ 
			title = "Frasier",
			rating = 6
		},
		{ 
			title = "Cheers",
			rating = 8
		}
	} )
 
 	local expectedResult = { "Friends", "Seinfeld", "Cheers" }
 
	local resultStream = exerciseOne( televisionShowsStream )
	
	local observer = rxTest:observe( resultStream )
	
	observer:expectDataWithinTime( 1, expectedResult )
	
	observer:dispose()
	
end

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

function WFRxLuaExerciseTest.rxTestVerifyExercise2( rxTest ) 

	if not setToTrue then
		return
	end

	-- Given an array containing N numbers, create an observable sequence that doubles
	-- all odd numbers within the array.

	local arr = { 1, 3, 6, 2, 6, 3, 8, 42, 2, 5, 7, 8, 8, 1 }
	
	local resultStream = exerciseTwo( arr )
	
	local expectedResult = { 2, 6, 6, 2, 6, 6, 8, 42, 2, 10, 14, 8, 8, 2 }
	
	rxTest:expectSingleValue( resultStream, expectedResult )

end

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

function WFRxLuaExerciseTest.rxTestVerifyExercise3( rxTest ) 

	if not setToTrue then
		return
	end

	-- Given an observable sequence that throws an error, catch the error
	-- and return an Observable that emits the error message as a data event.
	
	local errorObservable = Rx.Observable.throw( "Foo" )
	
	local resultStream = exerciseThree( errorObservable )
	
	rxTest:expectSingleValue( resultStream, "Foo" )

end

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

function WFRxLuaExerciseTest.rxTestVerifyExercise4( rxTest ) 

	local KSDate = require 'KSDate'

	rxTest:setTimeLimit( 10 )

	if not setToTrue then
		return
	end

	-- Create an observable sequence that emits one letter of the English alphabet
	-- every second, until all 26 letters have been emitted.
	-- You will need to use an observable time in your solution:
	-- Observable.timer( <afterTime>, <period>, WFRxScheduler.sharedScheduler() )

	-- Example:
	-- Input: nil
	-- Output: Observable< 'a' >, < 'b' >, < 'c' >..< 'z' >
	
	assert( WFRxScheduler )

	local lastLetterReceivedTimestamp
	
	local alphabetStream = exerciseFour()
	
		:map( function( letter )
		
			local letterReceivedTimestamp = KSDate.timeNow()
			
			if lastLetterReceivedTimestamp then
			
				if letterReceivedTimestamp - lastLetterReceivedTimestamp < 0.1 then
					assert( false, "Letters received too quickly. Should be one second apart." )
				elseif letterReceivedTimestamp - lastLetterReceivedTimestamp > 2 then
					assert( false, "Letters received too slowly. Should be one second apart." )
				end 
			
			end
			
			lastLetterReceivedTimestamp = letterReceivedTimestamp
			
			return letter
			
		end )
		:toArray()
	
	local expected =
		{ 
			'a', 'b', 'c', 'd', 
			'e', 'f', 'g', 'h', 
			'i', 'j', 'k', 'l', 
			'm', 'n', 'o', 'p', 
			'q', 'r', 's', 't', 
			'u', 'v', 'w', 'x', 
			'y', 'z'
		}
	
	rxTest:expectSingleValue( alphabetStream, expected )

end

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

function WFRxLuaExerciseTest.rxTestVerifyExercise5( rxTest ) 

	if not setToTrue then
		return
	end

	-- You are given two observables: numberStream and letterStream.
	
	-- numberStream emits the numbers 1 through N, and then completes.
	
	-- letterStream emits the letters a through z, and then completes.
	
	-- Create an observable sequence that combines the output from numberStream and 
	-- letterStream, and emits the concatenated string. The new sequence should only
	-- emit 5 data events before completing.

	local numberStream = Rx.Observable.from( { 1, 2, 3, 4, 5, 6, 7 } )

	local letterStream = Rx.Observable.from( { 'a', 'b', 'c', 'd', 'e', 'f', 'g' } )

	local resultStream = exerciseFive( numberStream, letterStream )

	local observer = rxTest:observe( resultStream )

	observer:expectData( '1a' )
	observer:expectData( '2b' )
	observer:expectData( '3c' )
	observer:expectData( '4d' )
	observer:expectData( '5e' )
	observer:expectCompleted()
	
	observer:dispose()
	
end

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

function WFRxLuaExerciseTest.rxTestVerifyExercise6( rxTest ) 

	if not setToTrue then
		return
	end
	
	-- Every time trueStream fires, get a random number by calling getRandomNumber,
	-- and then generate a random animal sound by passing that number to getRandomAnimalSoundObs().

	local trueStream = Rx.Observable.from( { 'true', 'true', 'true', 'true' } )

	randNum = {}
	getRandomNumberCount = 0

	randNum = {
		math.random( 1, 5 ),
		math.random( 1, 5 ),
		math.random( 1, 5 ),
		math.random( 1, 5 )
	}

	local resultStream = exerciseSix( trueStream ):toArray()

	local firstSound = rxTest:getFirstValue( getRandomAnimalSoundObs( randNum[ 1 ] ) )
	local secondSound = rxTest:getFirstValue( getRandomAnimalSoundObs( randNum[ 2 ] ) )
	local thirdSound = rxTest:getFirstValue( getRandomAnimalSoundObs( randNum[ 3 ] ) )
	local fourthSound = rxTest:getFirstValue( getRandomAnimalSoundObs( randNum[ 4 ] ) )

	rxTest:expectSingleValue( resultStream, { firstSound, secondSound, thirdSound, fourthSound } )

end

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

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

return WFRxTestCase.wrapSuite( 'WFRxLuaExerciseTest', WFRxLuaExerciseTest )
