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

WFRxDocValueWatcherTest.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.

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

	-- Rx
local Rx = require 'Rx'
local Subject = Rx.Subject

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

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

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local WFCloseSupport = require 'WFCloseSupport'

	-- WF.sqlite.doc_store
local WFRxDocStore = require 'WFRxDocStore'

local coveragePath

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

local WFRxDocValueWatcherTest = {}

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

local function makeDocValueRevIdConstantIfSet( docValue )

	docValue._revId = docValue._revId and docValue._revId and "(rev ID)" or nil

	return docValue

end

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

local function makeConstRevIdDocValueStream( docStore, docIdsStream )

	return docIdsStream
		:call( WFRxDocValueWatcher.make, docStore )
		:map( makeDocValueRevIdConstantIfSet )

end

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

local function setupTestData( rxTest )

	local docStore = WFRxDocStore( ':memory:' )

	rxTest:finally( function()

		if coveragePath then
			docStore:DEBUG_dumpCoverage( coveragePath )
		end

		if docStore and not WFCloseSupport.hasBeenOrWillBeClosed( docStore ) then
			docStore:close()
			docStore = nil
		end

	end )

	local docIds = Subject()

	local docIdsValueStream = makeConstRevIdDocValueStream( docStore, docIds )

	return docStore, docIds, docIdsValueStream

end

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

local function testBasic( rxTest, idSet )

	local docStore, docIds, docIdsValueStream  = setupTestData( rxTest )

	local watcher = rxTest:observe( docIdsValueStream )

	rxTest:expectSingleValue(

		docStore:bulkSetDocValue {
			{
				_id = 'doc_1',
				rating = 3,
			},
			{
				_id = 'doc_2',
				rating = 1,
			},
			{
				_id = 'doc_3',
				rating = 2,
			},
			{
				_id = 'doc_4',
				rating = 5,
			},

		},

		':ignore:' )

	-- Put some documents into the watched set. Their values should immediately
	-- be reported by the watcher, in order of appearance in the array.

	docIds:onNext { idSet[ 4 ], idSet[ 2 ] }

	watcher:expectData {
		_changeCounter = 4,
		_id = "doc_4",
		_localId = 4,
		_revId = "(rev ID)",
		rating = 5,
	}

	watcher:expectData {
		_changeCounter = 2,
		_id = "doc_2",
		_localId = 2,
		_revId = "(rev ID)",
		rating = 1,
	}

	-- Change two documents, one in the watched set, one not in the watched set.
	-- Should only see a notif for the watched doc.

	rxTest:expectSingleValue(

		docStore:bulkApplyOverlay {
			{
				_id = 'doc_1',
				flag = 'pick',
			},
			{
				_id = 'doc_2',
				flag = 'pick',
			},
		},

		':ignore:' )

	watcher:expectData {
		_changeCounter = 6,
		_id = "doc_2",
		_localId = 2,
		_revId = "(rev ID)",
		flag = "pick",
		rating = 1,
	}

	-- Now change the watched set. Should see doc values for only the newly-
	-- included documents, in order of appearance.

	docIds:onNext { idSet[ 1 ], idSet[ 4 ], idSet[ 3 ] }

	watcher:expectData {
		_changeCounter = 5,
		_id = "doc_1",
		_localId = 1,
		_revId = "(rev ID)",
		flag = "pick",
		rating = 3,
	}

	-- doc_4 doesn't appear because it was previously sent

	watcher:expectData {
		_changeCounter = 3,
		_id = "doc_3",
		_localId = 3,
		_revId = "(rev ID)",
		rating = 2,
	}

	-- Now change some more doc values. Again, only those in the watched set
	-- should be relayed.

	rxTest:expectSingleValue(

		docStore:bulkApplyOverlay {
			{
				_id = 'doc_2',
				flag = 'reject',
			},
			{
				_id = 'doc_1',
				flag = 'reject',
			},
		},

		':ignore:' )

	watcher:expectData {
		_changeCounter = 8,
		_id = "doc_1",
		_localId = 1,
		_revId = "(rev ID)",
		flag = "reject",
		rating = 3,
	}

	-- Change a doc again, just to be sure we aren't taking only the first
	-- matching change for a doc.

	rxTest:expectSingleValue(

		docStore:bulkApplyOverlay {
			{
				_id = 'doc_2',
				flag = 'pick',
			},
			{
				_id = 'doc_1',
				flag = 'pick',
			},
		},

		':ignore:' )

	watcher:expectData {
		_changeCounter = 10,
		_id = "doc_1",
		_localId = 1,
		_revId = "(rev ID)",
		flag = "pick",
		rating = 3,
	}

end

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

function WFRxDocValueWatcherTest.rxTestBasic( rxTest )

	testBasic( rxTest, { 'doc_1', 'doc_2', 'doc_3', 'doc_4' } )

end

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

function WFRxDocValueWatcherTest.rxTestLocalIds( rxTest )

	testBasic( rxTest, { 1, 2, 3, 4 } )

end

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

function WFRxDocValueWatcherTest.rxTestTestEmptyDoc( rxTest )

	local docStore = WFRxDocStore( ':memory:' )

	rxTest:finally( function()

		if coveragePath then
			docStore:DEBUG_dumpCoverage( coveragePath )
		end

		if docStore and not WFCloseSupport.hasBeenOrWillBeClosed( docStore ) then
			docStore:close()
			docStore = nil
		end

	end )
	
	local watcher = rxTest:observe( Rx.Observable.just( { 127 } ) -- this is a non-existent local id
		:call( WFRxDocValueWatcher.make, docStore )
		:map( makeDocValueRevIdConstantIfSet ) )
		
	watcher:expectData {
		_empty = true,
		_id = 127,
	}

	
end

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

local function sortArray( array )

	local sorted = KSTableUtils.deepCopy( array )

	table.sort( sorted )

	return sorted

end

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

function WFRxDocValueWatcherTest.rxTestBatchedSourceUpdate( rxTest )

	local docStore, docIds, docIdsValueStream = setupTestData( rxTest )

	-- Add some records to the doc store.

	rxTest:expectSingleValue(

		docStore:bulkSetDocValue {
			{
				_id = 'doc_1',
				rating = 3,
			},
			{
				_id = 'doc_2',
				rating = 1,
			},
			{
				_id = 'doc_3',
				rating = 2,
			},
			{
				_id = 'doc_4',
				rating = 5,
			},

		},

		':ignore:' )

	local watchFirstTwoFetchedDocIds = rxTest:observe( docIdsValueStream
											:take( 2 )
											:pluck( '_id' )
											:toArray()
											:map( sortArray ) )

	-- Start with just one watched doc ID.

	docIds:onNext { 'doc_3' }

	-- Add another watched doc ID without having given time before we've
	-- fetched the first.

	docIds:onNext { 'doc_3', 'doc_2' }

	-- Verify we got doc values for everything in the final set
	-- (The order is sorted alphanumerically regardless of input.)

	watchFirstTwoFetchedDocIds:expectData { 'doc_2', 'doc_3' }

end

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

function WFRxDocValueWatcherTest.rxTestBatchedSourceUpdate2( rxTest )

	rxTest:setTimeLimit( 60 )

	local docStore, docIds, docIdsValueStream = setupTestData( rxTest )

	-- Add some records to the doc store.

	rxTest:expectSingleValue(

		docStore:bulkSetDocValue {
			{
				_id = 'doc_1',
				rating = 3,
			},
			{
				_id = 'doc_2',
				rating = 1,
			},
			{
				_id = 'doc_3',
				rating = 2,
			},
			{
				_id = 'doc_4',
				rating = 5,
			},

		},

		':ignore:' )

	local watchFirstTwoFetchedDocIds = rxTest:observe( docIdsValueStream
											:take( 2 )
											:pluck( '_id' )
											:toArray()
											:map( sortArray ) )

	-- Start with just one watched doc ID.

	docIds:onNext { 'doc_2' }

	-- Add another watched doc ID without having given time before we've
	-- fetched the first.

	docIds:onNext { 'doc_2', 'doc_3' }

	-- Verify we got doc values for everything in the final set
	-- (The order is sorted alphanumerically regardless of input.)

	watchFirstTwoFetchedDocIds:expectData { 'doc_2', 'doc_3' }


end

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

function WFRxDocValueWatcherTest.rxTestBufferedUpdates( rxTest )

	-- This test attempts to hit the a special case where all values being
	-- watched have already flowed through the doc value watcher.

	rxTest:setTimeLimit( 5 )

	local docStore, docIds, docIdsValueStream = setupTestData( rxTest )

	local currentWatchValues = {}

	local function emptyCurrentWatchValues()
		for i = #currentWatchValues, 1, -1 do
			currentWatchValues[ i ] = nil
		end
	end

	local watchIds = rxTest:observe( docIdsValueStream
		:scan( function( vals, docValue )

			currentWatchValues[  #currentWatchValues + 1 ] = docValue._id

			table.sort( currentWatchValues )

			return currentWatchValues

		end ), currentWatchValues )


	docIds:onNext { 'doc_1', 'doc_2' }
	watchIds:expectDataWithinTime( 2, { 'doc_1', 'doc_2' } )

	emptyCurrentWatchValues()

	-- Add some records to the doc store.

	rxTest:expectSingleValue(

		docStore:bulkSetDocValue {
			{
				_id = 'doc_1',
				rating = 3,
			},
			{
				_id = 'doc_2',
				rating = 1,
			},
			{
				_id = 'doc_3',
				rating = 2,
			},
			{
				_id = 'doc_4',
				rating = 5,
			},

		},

		':ignore:' )

	docIds:onNext { 'doc_1', 'doc_2' }
	watchIds:expectDataWithinTime( 2, { 'doc_1', 'doc_2' } )

	emptyCurrentWatchValues()

	docIds:onNext { 'doc_3', 'doc_4' }
	watchIds:expectDataWithinTime( 2, { 'doc_3', 'doc_4' } )

	emptyCurrentWatchValues()

	docIds:onNext { 'doc_1', 'doc_2', 'doc_3', 'doc_4' }
	watchIds:expectDataWithinTime( 2, { 'doc_1', 'doc_2' } )

	emptyCurrentWatchValues()

	-- Verify an empty watch id list works.
	docIds:onNext {}
	rxTest:sleep( .2 )
	rxTest:expectEqual( currentWatchValues, {} )

	emptyCurrentWatchValues()

	-- Verify a docId list still works after watching an empty list.

	docIds:onNext { 'doc_1', 'doc_2', 'doc_3', 'doc_4' }
	watchIds:expectDataWithinTime( 2, { 'doc_1', 'doc_2', 'doc_3', 'doc_4' } )

end

--==============================================================================
-- @mark setup
--==============================================================================


function WFRxDocValueWatcherTest.setUp( inCoveragePath )

	coveragePath = inCoveragePath

end

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

return WFRxTestCase.wrapSuite( 'WFRxDocValueWatcherTest', WFRxDocValueWatcherTest )
