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

WFCatSearchAndSort.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains 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 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 Systems Incorporated.

----------------------------------------------------------------------------]]--
	-- WF.core.os
local KSDate = require 'KSDate'

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

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

local WFTableArchiving = require 'WFTableArchiving' 

local WFCatAssetInfoListUtils = require 'WFCatAssetInfoListUtils'
local WFCatSortHandler = require 'WFCatSortHandler'
local WFCatSearch = require 'WFCatSearch'
local WFCatSearchSortOnlineHandler = require 'WFCatSearchSortOnlineHandler'
local WFCatSearchAndSortConstants = require "WFCatSearchAndSortConstants"
 
	-- WF.core.logging
local WFLogger = require 'WFLogger'
local logger = WFLogger( 'SearchAndSort' )

local WFSearchDebugLogging = require 'WFSearchDebugLogging'

local WFCatAssetInfoListUtils_getPackedAssetInfoDetails = assert( WFCatAssetInfoListUtils.getPackedAssetInfoDetails )
local WFCatAssetInfoListUtils_getPackedAssetInfoDetailsForArray = assert( WFCatAssetInfoListUtils.getPackedAssetInfoDetailsForArray )

--==============================================================================
-------------------------------------------------------------
local WFCatSearchAndSort = {}
local internalData = {}
--=========================Logger Debug Methods ========================================
local KSDate_timeNowForAnimation = KSDate.timeNowForAnimation
local requestCounter = 0
local startTime = 0
local endTime = 0
local expectedMaxTime = 2 -- sec

-- we want to prevent data collection if the logger is not active
local debugDetailedLoggingNeeded = logger._priority >= 5 or false -- 5 is for the debug priority in WFLogger


--------------------------------------------------------------------------------
local function processItemDetails(  stringValue, details, combinigTerm )  
	local currentTermVal = "("
	for k,v in pairs ( details ) do
		currentTermVal = string.format("%s %s:%s ", currentTermVal, k,v )
	end
	currentTermVal = currentTermVal .. ")" .. combinigTerm
	stringValue = stringValue .. currentTermVal
	return stringValue
end

--------------------------------------------------------------------------------
local function processItems(  stringValue, details )  
	local terms = details and details.terms
	if terms then
		stringValue = string.format("%s [ ",stringValue )
		local combinigTerm = details and details.type
		if combinigTerm then
			local count = 1
			for _, termDetails in ipairs( terms ) do
				if count > 1 then
					stringValue = stringValue .. combinigTerm
				end
				count = count + 1
				-- check whether it has term
				if termDetails and details.terms then
					stringValue =  processItems( stringValue, termDetails )
				else
					stringValue = processItemDetails( stringValue, termDetails, combinigTerm )
				end
				
			end
		end
		stringValue = string.format("%s ] ",stringValue )
	else
		stringValue = processItemDetails( stringValue, details, "")
	end
	
	return stringValue
	
end

--------------------------------------------------------------------------------
local function getStringRepresentationOfSearchData( searchDescription )
	local predicates = searchDescription and searchDescription.predicates
	local stringValue = ""
	if predicates then
		stringValue = processItems(  stringValue, predicates )  
	end
	return stringValue
end

--------------------------------------------------------------------------------
local function search_debug_getCurrentTime()
	if debugDetailedLoggingNeeded then
		return KSDate_timeNowForAnimation()
	else
		return 0
	end
end
--------------------------------------------------------------------------------
local function getFormattedTimeForSearchDebug()
	if debugDetailedLoggingNeeded then
		return WFSearchDebugLogging.getFormattedTimeForSearchDebug( "WFCatSearchAndSort" )
	else
		return ""
	end
end

--------------------------------------------------------------------------------
local function search_debugPrint( messageType, ... )
	if debugDetailedLoggingNeeded then
		WFSearchDebugLogging.search_debugPrint( "WFCatSearchAndSort", logger, messageType, ... )
	end
end

--------------------------------------------------------------------------------
local function search_debugPrintTimeConsumed( operationIndetifier, sessionIdentifier , 
												maxTimeExpected , myStartTime, myEndTime, 
												additionalData )
	if debugDetailedLoggingNeeded then											
		WFSearchDebugLogging.search_debugPrintTimeConsumed( 
			"WFCatSearchAndSort", logger,
			operationIndetifier, sessionIdentifier,maxTimeExpected, 
			myStartTime, myEndTime, additionalData )
	end
end

--------------------------------------------------------------------------------
local function markSearchEnd()	
	if debugDetailedLoggingNeeded then	
		endTime = search_debug_getCurrentTime() --os.time()
		local searchData = internalData._currentSearchDescription 
		searchData = getStringRepresentationOfSearchData( searchData )
		search_debugPrintTimeConsumed( "Search : WFSearchAndSort: SearchTime taken for", 
				requestCounter, expectedMaxTime, startTime, endTime, searchData )
	end
end

--------------------------------------------------------------------------------
local function markSearchStart()	
	if debugDetailedLoggingNeeded then	
		startTime = search_debug_getCurrentTime() --os.time()
		-- logging
		search_debugPrint( "i", "Search : WFSearchAndSort: Start of Search and sort request : ", 
						requestCounter, getFormattedTimeForSearchDebug() )
	end
end
	

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

--[[!

	\brief Combining search and sort  togather and this class stores the intermediate search result
	so that only when sorting is changed, the result need not be recalculated
	
--]]

--------------------------------------------------------------------------------
internalData._assetInfoMap = false
internalData._orderedRows = false
internalData._assetIds = false
internalData._supportsStacks = false

internalData._lastSearchDescription = false
internalData._lastExecutedSortOrder = "DESC"

local searchSortMethods = {}

local assetProcessing = {}
assetProcessing.currentResultIdPos = 1
assetProcessing.assetInfoIdPos = 1

local MultipleEntryPerAssetHandling =
{	
	useFirst = "UF",
	useLast = "UL",
	noCheck = "NC",
}

local SHOW_GRID_LABELS = WFConfiguration.Shared.showGridLabels



--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--[[!
	\brief Utility method- if the id is present the map passed, return true
]]--
local function skipElement( id, idsToBeSkipped )
	if idsToBeSkipped ~= nil and idsToBeSkipped[ id ] == true then
		return true
	else
		return false
	end
end

--------------------------------------------------------------------------------
--[[!
	\brief Utility method- requiredPositionInfoMap has the required Position of and id
	If the current position and required position matches returns true
]]--
local function isAssetInRequiredPosition( id, requiredPositionInfoMap, currentPosition)
	local result = true
	if requiredPositionInfoMap ~= nil then
		local expectedPos = requiredPositionInfoMap[ id ]
		if expectedPos ~= nil and  expectedPos ~= currentPosition then
			result = false
		end
	end
	
	return result
end

--------------------------------------------------------------------------------
--[[!
	\brief Gets the ids of the assets present in the sourcedata and returns a map
]]--
function assetProcessing.getIdsOfAssets( sourceData, idPos )
	local assetsInfoMap = {}
		-- get the info of the assets whihc has relevant data
	for _, data in ipairs( sourceData ) do
		local id =  data[ idPos ]
		if id ~= nil then
			assetsInfoMap[ id ] = true
		end
	end
	
	return assetsInfoMap
end


--------------------------------------------------------------------------------
--[[!
	\brief If the data source has more than one entry for an asset, the position of the last 
	occurrence is considered
]]--
function assetProcessing.getLastPositionOfAssets( dataSource , index, idPos )
	local lastIndexOfIds =  {}
	-- we need to use the last enrtry
	-- so we are getting the last index of the id and we will process only that
	local position = index 
	for _, assetInfo in ipairs( dataSource ) do
		local id =  assetInfo[ idPos ]
		if id ~= nil then	
			lastIndexOfIds[ id ] = position
			position = position + 1
		end
	end
	
	return lastIndexOfIds
end

--------------------------------------------------------------------------------
--[[!
	\brief This method will take each element from the source and add the same to destination
	considerations :
		If elementIdsToBeSkipped is given, it will skip the assets which are present in ot
		If expectedPositionOfAssets is given , if the current position matches with the value present in that map only considered
		If mapToStoreProcessedAssetId is given, it will store the id of the processed assets in this list
]]--

function assetProcessing.addElementsInOrder( dataSource, destination, index, elementIdsToBeSkipped, 
													mapToStoreProcessedAssetId, expectedPositionOfAssets, idPositionInInput )
	local sourcePosition  = 1
	for _, asset in ipairs( dataSource ) do
		local id =  asset[ idPositionInInput ]
		if id ~= nil then	
			if skipElement( id , elementIdsToBeSkipped ) == false then
				if isAssetInRequiredPosition( id, expectedPositionOfAssets, sourcePosition ) then
					local currentAssetInfo = internalData._assetInfoMap [ id ] 
					if currentAssetInfo ~= nil then
						local additionalDataForAsset = SHOW_GRID_LABELS and searchSortMethods.getAdditionalDataForAsset( asset )  or nil
						destination[ index ] = WFCatAssetInfoListUtils_getPackedAssetInfoDetails( currentAssetInfo, additionalDataForAsset )
						if mapToStoreProcessedAssetId then
							mapToStoreProcessedAssetId[ id ] = true
						end
						index = index + 1
					end
				end
			end
			sourcePosition = sourcePosition + 1
		end
	end
	return index
end


--------------------------------------------------------------------------------
--[[!
	\brief Processes the source elements based on multipleEntryHandling
		if 'multipleEntryHandling' is 
			"noCheck" elements are added using the given requirements
			"useFirst" ids once processed should not be considered again.
				So elementIdsToBeSkipped and 
				mapToStoreProcessedAssetId is used as the same asset
			"useLast" -last occurrence of assets to be considered. 
							So gets the last position of assets and uses that in addElementsInOrder
]]--
function assetProcessing.addElements( dataSource, destination, index , elementsToBeSkippedFromSource, 
						mapToStoreProcessedAssetId,  multipleEntryHandling, idPositionInInput )

	-- Some sort handlers expect multiple data for an asset, in some cases we need to use the first entry (Desc ) and 
	-- in some cases we need to use the last entry (Asc)
	local isMultipleDataExpectedFromAsset = searchSortMethods.isMultipleDataExpectedFromAsset()
	
	if multipleEntryHandling == nil or multipleEntryHandling == MultipleEntryPerAssetHandling.noCheck 
						or isMultipleDataExpectedFromAsset == false then
		index = assetProcessing.addElementsInOrder ( dataSource, destination, index, 
							elementsToBeSkippedFromSource, mapToStoreProcessedAssetId,nil, idPositionInInput )
	else
		if multipleEntryHandling ==  MultipleEntryPerAssetHandling.useFirst then
			-- We need to skip the further presence of and asset if the asset was already processed.
			-- so both elementIdsToBeSkipped and mapToStoreProcessedAssetId is same
			local elementIdsToBeSkipped = {}
			index =  assetProcessing.addElementsInOrder( dataSource, destination, index, elementIdsToBeSkipped, 
						elementIdsToBeSkipped, nil, idPositionInInput )
			
			-- copy the elementIdsToBeSkipped to mapToStoreProcessedAssetId
			for id, _ in pairs( elementIdsToBeSkipped ) do
				mapToStoreProcessedAssetId[ id ] = true
			end
		else
			local lastIndexOfIds = assetProcessing.getLastPositionOfAssets( dataSource,1, idPositionInInput )
			-- addElementsInOrder ( dataSource, destination, index, elementIdsToBeSkipped, 
					-- mapToStoreProcessedAssetId, expectedPositionOfAssets, idPositionInInput )
			index =  assetProcessing.addElementsInOrder( dataSource, destination, index, nil, 
													mapToStoreProcessedAssetId, lastIndexOfIds, idPositionInInput )
		end
	end
	
	return index
end

--------------------------------------------------------------------------------
function searchSortMethods.processSearchData()
	local assetInfos = WFCatAssetInfoListUtils_getPackedAssetInfoDetailsForArray( internalData._orderedRows or {} )

	search_debugPrint( "d", "Search : WFSearchAndSort: end of  processSearchData" , requestCounter, getFormattedTimeForSearchDebug() ) 
	markSearchEnd()
	return assetInfos, internalData._supportsStacks
	
end

--------------------------------------------------------------------------------
function searchSortMethods.storeSearchResultData( ordereredRows, currentRequestedSortOrder, searchDescription, supportsStacks )
	local assetIds = {}
	local assetInfoMap = {}
	for i, row in ipairs( ordereredRows ) do
		local id =  row[ 1 ]
		assetIds[ i ] = id
		assetInfoMap[ id ] = row
	end
	internalData._assetInfoMap = assetInfoMap
	internalData._orderedRows = ordereredRows
	internalData._assetIds = assetIds
	internalData._supportsStacks = supportsStacks
	internalData._lastExecutedSortOrder = currentRequestedSortOrder

	if searchDescription ~= nil then
		-- search description nil indicates dont change the last search details
		internalData._lastSearchDescription = searchDescription
	end
	search_debugPrint( "d", "Search : WFSearchAndSort: end of  storeSearchResultData" , 
						requestCounter, getFormattedTimeForSearchDebug() ) 
end

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

function searchSortMethods.initSortHandlerDetails ( sortDescription )
	-- this method will analyse the sort details and find the handler, get the required sort order etc.
	WFCatSortHandler.initSortHandlerDetails( sortDescription ) 
	assetProcessing.currentResultIdPos = WFCatSortHandler.getAssetIdPos() or 1
end

--------------------------------------------------------------------------------
function searchSortMethods.isSearchingNeeded( searchDescription, currentRequestedSortOrder )
	-- if we get a new search request we need to perform the search
	if searchDescription ~= nil then
		return true
	end
	
	-- If the sort order is changed also, we need to perform the search as the search 
	-- has the order by capture date which is the defaultSortOrder. Other sorting is done 
	-- on the top of the default. So when sort order is changed also, search needs to be performed
	if currentRequestedSortOrder ~= internalData._lastExecutedSortOrder then
		return true
	end
	return false
end

--------------------------------------------------------------------------------
function searchSortMethods.isSortingNeeded()
	return WFCatSortHandler.isSortingNeeded()
end
	

--------------------------------------------------------------------------------
function searchSortMethods.isMultipleDataExpectedFromAsset()
	return  WFCatSortHandler.isMultipleDataExpectedFromAsset()
end

--------------------------------------------------------------------------------
function searchSortMethods.getAdditionalDataForAsset( assetAfterSearchAndSort )
	return  WFCatSortHandler.getAdditionalDataForAsset( assetAfterSearchAndSort )
end

--------------------------------------------------------------------------------
--[[!
	\brief This method is called when the data needs to be stored in the ascending order
	Here we are placing the assets which does not have any info used for sorting first
	and then the assets which have the relevant data in the ascending order.
	
	e.g when sorting based on rating, all assets which does not have rating information comes first
	and then the assets with the rating information in ascending order.
	
	in the case of rating the order will be
	-- unrated images, images with 1- 5 rating in that order
]]--

function searchSortMethods.handleAscendingData( totalAssetInfos, sortedAssetsInfo )
	local totalAssetsCount = #internalData._assetIds 
	local index = 1
	local relevantAssetsMap = assetProcessing.getIdsOfAssets( sortedAssetsInfo, assetProcessing.currentResultIdPos )
	local processedAssetIdMap = {}
	
	-- add Assets which are not present in relevantAssetsMap
	-- addElements( dataSource,  destination, index, elementsNotNeeded, 
				-- mapToStoreProcessedAssetId , MultipleEntryHandler)
	index = assetProcessing.addElements( internalData._orderedRows, totalAssetInfos, index, relevantAssetsMap, 
					processedAssetIdMap, MultipleEntryPerAssetHandling.noCheck,
					assetProcessing.assetInfoIdPos)
	
	-- now add the assets which have the info
	-- addElements( dataSource,  destination, index, elementsNotNeeded, 
					-- mapToStoreProcessedAssetId , MultipleEntryHandler)
	index = assetProcessing.addElements( sortedAssetsInfo, totalAssetInfos, index, processedAssetIdMap, nil,
			MultipleEntryPerAssetHandling.useLast, assetProcessing.currentResultIdPos )

	local processedCount = index-1
	if processedCount <  totalAssetsCount then
		logger:error( "handleAscendingData proccessed < totalAssetCount" )
	elseif processedCount >  totalAssetsCount then
		logger:error( "handleAscendingData proccessed > totalAssetCount" )
	end

	return totalAssetInfos
end

--------------------------------------------------------------------------------
--[[!
	\brief This method is called when the data is to be stored in Descending order.
	SortedAssetsInfo has the data corresponding to the assets which have relevant data.
	e.g if the sorting is based on rating, all assets may not have the relevant data.
	So we will place the assets which have relevant data in the first and then add the assets which
	do not have the relevant data.
	
	in the case of rating the order will be
	--  images with 5- 1 rating in that order, unrated images
	
	Among the assets which have relevant data also can have more than one entry. e.g sort by relevany
	So assets will have a keyword and tag. keyword will be of the high priority and that needs to be treated for ordering
	
	So in cased of Descending data, if the sort data has more than one entry per asset, we need to consider the first
]]--

function searchSortMethods.handleDescendingData( totalAssetInfos, sortedAssetsInfo )
	local totalAssetsCount = #internalData._assetIds 
	local processedAssetIdMap = {}
	local index = 1
	
	-- addElements( dataSource,  destination, index, elementsNotNeeded, processedAssetIdMap , MultipleEntryHandler)
	index = assetProcessing.addElements( sortedAssetsInfo, totalAssetInfos, index, nil, processedAssetIdMap, 
				MultipleEntryPerAssetHandling.useFirst, assetProcessing.currentResultIdPos )	

	-- in the sorted data, there may not be data for all the required asset Ids, 
	if index ~= ( totalAssetsCount+1 ) then
		-- addElements( dataSource, destination, index , elementsToBeSkippedFromSource, 
						--mapToStoreProcessedAssetId,  multipleEntryHandling, idPositionInInput 
		index = assetProcessing.addElements( internalData._orderedRows, totalAssetInfos, index, 
					processedAssetIdMap, nil, nil,
					assetProcessing.assetInfoIdPos )
	end

	local processedCount = index-1
	if processedCount <  totalAssetsCount then
		logger:error( "handleDescendingData proccessed < totalAssetCount" )
	elseif processedCount >  totalAssetsCount then
		logger:error( "handleDescendingData proccessed > totalAssetCount" )
	end

	return totalAssetInfos
end


--------------------------------------------------------------------------------
--[[!

	\brief Both search and sort is present. so first we will execute the search, which will result in an observable
	and the search result is stored and the sort is executed using the stored data.
	
]]--

function searchSortMethods.executeSearchAndSort( wfCatalog, searchDescription, currentRequestedSortOrder, options )

	local supportsStacks = WFCatSearch.searchSupportsStacks( searchDescription )
	
	return WFCatSearch.executeSearch( wfCatalog, searchDescription, currentRequestedSortOrder )
		:flatMapLatest( function( ordereredRows )
			
			searchSortMethods.storeSearchResultData( ordereredRows, currentRequestedSortOrder, searchDescription, supportsStacks )
			
			local searchAndSort

			if options == "searchAndSort" then
				searchAndSort = searchSortMethods.executeSort( wfCatalog )
			elseif options == "onlySearch" then
				searchAndSort = searchSortMethods.processStoredData()
			end
			
			return searchAndSort or Rx.Observable.empty()
					
		end )

end

--------------------------------------------------------------------------------
--[[!

	\brief Only when sort  is presnt. Search would have beed executed before and currently only the sort is 
	getting changed. To avoid reexecutiion of search, only sort will be performed on stored search result,
	
]]--
function searchSortMethods.executeSort( wfCatalog )
	local totalAssetInfos = {
	}
	
	local supportsStacks = internalData._supportsStacks
	-- currently support one sort operation
	return WFCatSortHandler.executeSort( wfCatalog, internalData._assetIds )
		:map( function( sortedAssetsInfo )
			if sortedAssetsInfo then 
				local order = WFCatSortHandler.getSortOrder()
				if order == "ASC" then
					-- if we want o place the assets with no relevant sorting data before the 
					-- assets with least relevant data, this method (handleAscendingData ) has to be used.
					-- if we want to place the assets with relevant data in the top in the ascending order,
					-- handleDescendingData can be used.
					searchSortMethods.handleAscendingData( totalAssetInfos, sortedAssetsInfo ) 
				else
					searchSortMethods.handleDescendingData( totalAssetInfos, sortedAssetsInfo )
				end
				
			end
			-- logging
			search_debugPrint( "d", "Search : WFSearchAndSort: end of  sort" , requestCounter, 
															getFormattedTimeForSearchDebug() ) 
			markSearchEnd()
			
			return totalAssetInfos, supportsStacks
		end )
end

--------------------------------------------------------------------------------
--[[!

	\brief Only when no search and sort is speified, (e.g sort was there before, now the sort is changed to date which is 
	--default, and since the previous search conditions were not changed, search result calculation is also not needed
	
]]--
function searchSortMethods.processStoredData()
	return Observable.just ( searchSortMethods.processSearchData() )
end

--------------------------------------------------------------------------------
--[[!
	\brief This handles the search and sort related operations. To make the operations performant, sort should be 
	done post search on the filtered set. So WFCatSearchAndSort handles the order, provision to the use the intermediate
	result etc.
]]--

function  WFCatSearchAndSort.makeModel( wfCatalogStream, 
		localSearchDescription, onlineSearchSortDescription, sortDescription )
	
	requestCounter = requestCounter + 1
	local thisRequestNumber = requestCounter

	internalData._currentSearchDescription = localSearchDescription
	markSearchStart()
	
	local onlineSearchApplicable = false
	local onlineSearchStatusObservable
	
	if onlineSearchSortDescription then
		onlineSearchApplicable = true
		--WFCatSearchOnlineHandler is the owner to communicate to the online
		-- search service, get the data and add the data to the requried 
		-- tables. When the data is active in the table, the combined(AND) data from 
		-- the local tables and this table will be used.
		-- Sorting will also be  based on this table order, but defined by a separate sort handler
		onlineSearchStatusObservable = WFCatSearchSortOnlineHandler.initializeSearchDetails( wfCatalogStream,  onlineSearchSortDescription )	
	end
		

	-- Since this is the entry point, it will now handle the delay and other related conditions which 
	-- WFCatSearch.makeModel was doing before,

	local searchAndSort = wfCatalogStream
	
		:map( function( wfCatalog )

			-- Return a default value if user has not
			-- logged in or if catalog is not available.
			-- This is required for resetting the ui.
			if not wfCatalog then
				return Observable.just( {} )
			end

			if debugDetailedLoggingNeeded then
			
				if onlineSearchApplicable then
					local localSearchDescriptionString = WFTableArchiving.tableToString( localSearchDescription or {} )
					local onlineSearchSortDescriptionString = WFTableArchiving.tableToString( onlineSearchSortDescription or {} )
					local sortDescriptionString = WFTableArchiving.tableToString( sortDescription  or {} )
					
					local totalInfo = string.format( "local search Description: \n\t %s \n localSortDescriptions: \n\t %s \n  online searchDescriptions \n\t %s \n",
						localSearchDescriptionString, sortDescriptionString, onlineSearchSortDescriptionString )
					totalInfo = string.format( "\n--------------------------------\n %s \n--------------------------------\n", 	totalInfo)
					search_debugPrint( "d", "\n\nSearch : WFSearchAndSort: Starting search and sort", 
								thisRequestNumber, getFormattedTimeForSearchDebug(), totalInfo )
				end
				
			end
			
			-- Info:
			-- default source is complete assets and default sort order is DESC based on capture date.
			-- so the default search result has the DESC order based on the capture date.
			-- so for the capture date as the sort information, the search is reexecuted as the default
			-- sort is part of search
			
			-- Only if the handler is active, sort is executed. And handler is not active if the sort is the default one
			-- TBD if online terms are present, sorting has to be redirected to the appropriate handler
			searchSortMethods.initSortHandlerDetails( sortDescription )
			
			local currentRequestedSortOrder = sortDescription and sortDescription.sortOrder
			currentRequestedSortOrder = WFCatSortHandler.getValidatedSortOrder( currentRequestedSortOrder )
			
			-- get the search and sort change details
			local searchNeeded = searchSortMethods.isSearchingNeeded( localSearchDescription, currentRequestedSortOrder )
			local sortNeeded = searchSortMethods.isSortingNeeded( sortDescription, currentRequestedSortOrder )
			
			if searchNeeded  then
				if localSearchDescription == nil then
					-- search description nil indicates that search operation details are not changed.
					-- but we would have decided to reexecute the search, as the sort details are changed.
					-- Rani [ TBD] seperate the default sorting also from the search expression
					localSearchDescription = internalData._lastSearchDescription 
				end
			end

			if searchNeeded and  sortNeeded then

				search_debugPrint( "d", "Search : WFSearchAndSort: requesting search and sort ", 
								thisRequestNumber, getFormattedTimeForSearchDebug() )
								
				return searchSortMethods.executeSearchAndSort( wfCatalog, localSearchDescription, 
							currentRequestedSortOrder, "searchAndSort" )
							
			elseif searchNeeded then

				search_debugPrint( "d", "Search : WFSearchAndSort: requesting only search ", 
							thisRequestNumber, getFormattedTimeForSearchDebug() )
							
							
				return searchSortMethods.executeSearchAndSort( wfCatalog, localSearchDescription, 
							currentRequestedSortOrder, "onlySearch" )
							
			elseif sortNeeded then

				search_debugPrint( "d", "Search : WFSearchAndSort: requesting only sort ", 
							thisRequestNumber, getFormattedTimeForSearchDebug() )
							
				return searchSortMethods.executeSort( wfCatalog )
				
			else 
				search_debugPrint( "d","Search : WFSearchAndSort: requesting default options ", 
							thisRequestNumber, getFormattedTimeForSearchDebug() )
							
				return searchSortMethods.processStoredData()
				
			end
			
		end )
		
		:switch()
	
	-- NBM: 5205 
	-- Issue : Empty screen flash before the results are available.
	-- Reason : As per the above code we were sending the latest results of 
	-- searchAndSort and onlineSearchStatusObservable
	-- So, soon after the results are received the observable triggered the result 
	-- and the assetInfos were not ready. (i.e search results were received and data added to the table)
	-- but table update was not ready.
	
	-- So instead of triggering the changes on searchAndSort and onlineSearchStatusObservable 
	-- with the changes below, we plan to send the value when the searchAndSort changes (i.e when the table updates).
	
	-- Online Search tables get cleared when the search is started and hence searchAndSort
	-- gets triggered. But this should be avoided to prevent we reporting the empty screen
	-- in the start of the search itself.
	-- So if online search is active, till we get a search status, we will be sending nil
	
	-- And hence the searchResultObject sent as nil in the client side it is 
	-- handled as "not to respond case", hence the grid does not
	-- clear. So no empty state, till we get the first result.
	
	-- But this does handle the case of empty result as searchAndSort does not trigger
	-- as the table does not change when there are no results (compared to the initial clreaed status)
	-- So to handle this we use the emptyValSubject.
	-- When the search status is receieved, if the result is empty, this trigger
	-- and makes the newSearchAndSortObservable send a non nil object with the 
	-- assetInfos as nil.
	-- Since the result is not nil and since the assetInfo is nil grid accepts this 
	-- as no result/empty result case.

	--================ no online search ================
	-- if there are no online search, just listen to the search tables and return
	
	if not onlineSearchApplicable then
	
		searchAndSort = searchAndSort

			:map( function( assetInfos, supportsStacks )

				assetInfos = assetInfos or {}

				search_debugPrint( "d", "Search : WFSearchAndSort: No Online search : table update received ", 
						thisRequestNumber, getFormattedTimeForSearchDebug() )
				search_debugPrint( "d", "***********************************************\n",
						thisRequestNumber, getFormattedTimeForSearchDebug() )
						
				return { 
					assetInfos = assetInfos,
					supportsStacks = supportsStacks,
					onlineSearchStatus = WFCatSearchAndSortConstants.OnlineSearchStatus.NoOnlineRequest
				}
					
			end )

		return { value = searchAndSort }
		
	end
	
	--================  online search is present.================
	--  We need to wait for the table result after the search results are 
	-- available
	
	local latestOnlineSearchStatusString = ""
	
	local newSearchAndSortObservable = onlineSearchStatusObservable
	
		:map( function( searchStatus )
		
			local searchAndSortWillFire = true
		
			if debugDetailedLoggingNeeded then
				latestOnlineSearchStatusString = WFTableArchiving.tableToString( searchStatus or {} )
				latestOnlineSearchStatusString = string.format("\n---------\n %s \n---------\n ", latestOnlineSearchStatusString )
				search_debugPrint( "d", "Search : WFSearchAndSort: online request results receieved with status", 
						thisRequestNumber, getFormattedTimeForSearchDebug(), latestOnlineSearchStatusString )
			end
			
			-- we are storing the details of the online search status
			
			if searchStatus.totalAvailable == 0 then
			
				-- after online search results are available,
				-- if the result count is 0, we need to trigger the
				-- newSearchAndSortObservable, ( as searchAndSort won't trigger in this case)
			
				search_debugPrint( "d", "Search : WFSearchAndSort: online request received with 0 available ", 
						thisRequestNumber, getFormattedTimeForSearchDebug() )
						
				searchAndSortWillFire = false
						
			elseif searchStatus.executionStatus == "CompletedUnexpectedly" then
			
				-- we need to trigger for the CompletedUnexpectedly  case also
				-- as this would have been the case of no result in one request 
				-- and received is not same as total available. 
				-- since no data is added to the table, searchAndSort will not trigger
				-- hence to communicate the end status to client, we trigger the emptyValSubject
				
				searchAndSortWillFire = false
				
			end
			
			return searchStatus, searchAndSortWillFire
			
		end )
	
		:flatMapLatest( function( searchStatus, searchAndSortWillFire )
		
			local searchAndSortsObs = searchAndSort
			
				:map( function( assetInfos, supportsStacks )
				
					return assetInfos, supportsStacks, searchStatus
				
				end )
	
			if not searchAndSortWillFire then
			
				-- In case searchAndSort doesn't fire, start it with an empty result set.
				searchAndSortsObs = searchAndSortsObs:startWith( nil )
					:map( function( assetInfos, supportsStacks, optSearchStatus )

						if assetInfos == nil then
							-- Use upvalue for search status here as startWith only supports single values.
							return {}, false, searchStatus
						end

						return assetInfos, supportsStacks, optSearchStatus

					end )
				
			end
			
			return searchAndSortsObs
		
		end )

		:map( function( assetInfos, supportsStacks, searchStatus )

			assetInfos = assetInfos or {}
			searchStatus = searchStatus or {}

			-- [kcarlson 2020-09-04]: Moved this if block from client in processAssetInfoStreamModelResult to
			-- suppress unneccessary sending data that will be ignored.

			if searchStatus.executionStatus == "InProgress" and #assetInfos == 0 then

				-- [anmurark]: NMB-5205 : Special case when online search has returned non-zero results but the
				-- final merged result after applying other filters/constraints has resulted in zero results.
				-- If search is still in progress, we should ignore the result and wait for further results or completion status.
				-- Example of additional filters/constraints:
				-- 1. Images are not yet downloaded
				-- 2. Search is performed on Recent Import section
				-- 3. Additional filters such as Keywords, Location, Camera are applied with text search

				return nil, nil, nil, false

			end

			return assetInfos or {}, supportsStacks, searchStatus, true

		end )

		:filter( function( _, _, _, hasChanges )

			return hasChanges

		end )

		:map( function( assetInfos, supportsStacks, searchStatus ) 

				if debugDetailedLoggingNeeded then

					local assetInfoCount = assetInfos and #assetInfos or -1
					local values = string.format( "asset Info count: %s, Search Status %s ", assetInfoCount, latestOnlineSearchStatusString )

					search_debugPrint( "d", "Search : WFSearchAndSort: table update received and  sending values",
							thisRequestNumber, getFormattedTimeForSearchDebug(), values )
					search_debugPrint( "d", "***********************************************\n",
							thisRequestNumber, getFormattedTimeForSearchDebug() )
				end

				return { 
					assetInfos = assetInfos,
					supportsStacks = supportsStacks,
					onlineSearchStatus = searchStatus
				}

		end )

	return { value = newSearchAndSortObservable }
	
end

--------------------------------------------------------------------------------
--[[!
	\brief This handles the facets values related operations. The client is responsible for the 
	onlineSearchTerms that needs to be sent to USS. It should include the following:
	* "facet_fields"/"fetch_facets" - fields on which the facet values are required
	* any search parameters to reduce the scope of the facet values such as album-ids, date-range, etc.
]]--

function  WFCatSearchAndSort.getFacetValues( wfCatalogStream, onlineSearchTerms )

	local onlineSearchStatusObservable = WFCatSearchSortOnlineHandler.searchFacetValues( wfCatalogStream,  onlineSearchTerms )	
	
	return { value = onlineSearchStatusObservable }

end

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

return WFCatSearchAndSort
