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

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

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

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFCapabilities = require 'WFCapabilities'
local WFCloseSupport = require 'WFCloseSupport'
local WFConfiguration = require 'WFConfiguration'
local WFDiagnosticLog = require 'WFDiagnosticLog'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'

	-- WF.oz_client.managed_catalog
local WFCatUser = require 'WFCatUser'
local WFCatSpec = require 'WFCatSpec'
local WFCatStuckSyncUtils = require 'WFCatStuckSyncUtils'
local WFCatalog = require 'WFCatalog'
local WFCatMetadataPuller = require 'WFCatMetadataPuller'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatSpaceFeedsManager = require 'WFCatSpaceFeedsManager'
local WFCatNotificationSync = require 'WFCatNotificationSync'

local NmbCatalog

if WFPlatformInfo.isDesktop then
	NmbCatalog = require 'NmbCatalog'
end

local WFLogger = require 'WFLogger'
local logger = WFLogger( 'WFCatSyncClientBase' )

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

local check = assert( WFArguments.check )
local Observable = Rx.Observable
local runUntilDone = WFRxRunUntilDone

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

local pusherDocumentBatchCount = WFConfiguration.Sync.pusherDocumentBatchCount
local pullerDocumentBatchCount = WFConfiguration.Sync.pullerDocumentBatchCount

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

-- @mark WFCatSyncClientBase namespace

-- WFCatSyncClientBase contains much of the sync infrastructure that is shared
-- between all WF clients. All of the class methods defined within WFCatSyncClientBase
-- are automatically exposed within the client-facing sync client objects;
-- WFCatSyncClient on Desktop, and LrmSyncClient on Mobile.

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

local WFCatSyncClientBase = {

	_user = false,
		-- (WFCatUser)
		
	_catSpec = false,
		-- (WFCatSpec)
		
	_catalog = false,
		-- (WFCatalog)
		
	_catalogPuller = false,
		-- (WFCatMetadataPuller)
		
	_catalogPusher = false,
		-- (WFCatMetadataPusher)
		
	_spaceFeedsManager = false,
		-- (WFCatSpaceFeedsManager)
		
	_notificationSync = false,
		-- (WFCatNotificationSync)
		
	_checkOzForCatalogUpdatesSubscription = false,
		-- (Rx Disposable)
		
	WFCloseSupport.addCloseSupportMethods,
	
}

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

function WFCatSyncClientBase:__init( options )

	check( 'WFCatSyncClientBase', 'table' )
	
	-- Required settings
	
	local serverBase = assert( options.serverBase )
	local userId = assert( options.userId )
	local userInfoCacheFolder = assert( options.userInfoCacheFolder )
	local catalogId = assert( options.catalogId )
	local rootCatalogPath = assert( options.rootCatalogPath )
	local docStorePath = assert( options.docStorePath )
	local indexSpecScript = assert( options.indexSpecScript )
			
	-- Optional settings
	
	local isSyncClientForProfileCatalog = options.isSyncClientForProfileCatalog
	local pullerTaskDelays = options.metadataPullerArgs
	local noFileAccessDb = options.noFileAccessDb
	local noCatalogSearch = options.noCatalogSearch
	local pullerPrefetch = options.pullerPrefetch
	local pusherDelayBeforeSendingUpdates = options.pusherDelayBeforeSendingUpdates
	local albumAssetsForFilteredAssetsQueryFn = options.albumAssetsForFilteredAssetsQueryFn
	local locale = options.locale or "en_US"
	
	local dataCache
	if WFPlatformInfo.isAndroid then
		dataCache = options.dataCache
	end
	
	-- Set up sync infrastructure
	
	local user = options.wfCatUser or WFCatUser( serverBase, userId, userInfoCacheFolder )
	
	local catSpec = options.wfCatSpec or WFCatSpec( user, options.catalogDoc or catalogId )

	local catArgs = {
		docStorePath = docStorePath,
		indexSpecScript = indexSpecScript,
		noFileAccessDb = noFileAccessDb,
		noCatalogSearch = noCatalogSearch,
		indexStoreReadThreads = options.indexStoreReadThreads,
		syncClientBaseInstance = true
	}
	
	local wfCatalog = options.wfCatalog 
	
	if not wfCatalog then
	
		if WFPlatformInfo.isMobile() then
			wfCatalog = WFCatalog( catSpec, rootCatalogPath, catArgs )
		else
			wfCatalog = NmbCatalog( catSpec, rootCatalogPath, catArgs )
		end
		
	end
	
	self._catSpec = catSpec
	
	-- Sync Down
	
	local pullerOptions = {
	
		prefetch = pullerPrefetch,
		documentBatchCount = pullerDocumentBatchCount,
		continuousTaskDelays = pullerTaskDelays,
		isCatalogPuller = true
		
	}
	
	local pullerDescription
	
	if isSyncClientForProfileCatalog then
		pullerDescription = "Profile Catalog Puller"
	else
	 	pullerDescription = "Catalog Puller"
	end
	
	local puller = WFCatMetadataPuller( wfCatalog, pullerDescription, pullerOptions )
	
	self:setOwned {
		_user = user,
		_catalog = wfCatalog,
		_catalogPuller = puller,
	}
	
	if not isSyncClientForProfileCatalog then
		
		WFCatStuckSyncUtils.addDiagnosticLogSectionFunc( wfCatalog )
			-- Track Stuck Sync info in the diagnostic log.

		-- LrmUserSubModel:setUserAuthenticated's deviceToken parameter is optional. But WFCatNotificationSync 
		-- requires one for communicating with https://notify.adobe.io/ans/v1/notifications . 
		-- If we allow WFCatNotificationSync.startNotificationSync() then queryNotificationOnce will fail,
		-- and Oz triggers a forced logout. In order to avoid this clients and unit tests may disable
		-- WFCatNotificationSync by setting the `noNotificationSync` capability.
		local noNotificationSync = WFCapabilities.getCapability( 'WFCatSyncClient.noNotificationSync' )
		if	noNotificationSync and noNotificationSync == "true" then 
			logger:warnf("WFCatNotificationSync is disabled" )
		else
			logger:infof("WFCatNotificationSync is enabled" )
			local notificationSync = WFCatNotificationSync( wfCatalog, locale )

			self:setOwned {
				_notificationSync = notificationSync,
			}						
		end
		
	 	-- Sync Up
		
		local catalogPusherOptions = {
			delayBeforeSendingUpdates = pusherDelayBeforeSendingUpdates or 2,
			documentBatchCount = pusherDocumentBatchCount or 100,
				-- Prevents the pusher from being overly aggressive at sending updates.
			albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn
		}
	
		local catalogPusher = WFCatMetadataPusher( wfCatalog, catalogPusherOptions )
	
		self:setOwned {
			_catalogPusher = catalogPusher,
		}
	
		-- WF-256: Reset Catalog MetadataPuller polling frequency after sending data to Oz.
	
		self._checkOzForCatalogUpdatesSubscription = self._catalogPusher:workHasCompletedStream()
			:subscribe( function()
				self._catalogPuller:checkOzForUpdates()
			end )
	
		-- Space-based Metadata Sync
	
		local spaceFeedsManagerArgs = {
			taskDelays = pullerTaskDelays,
			pullerDocumentBatchCount = pullerDocumentBatchCount,
			pusherDocumentBatchCount = pusherDocumentBatchCount or 100,
			albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn,
			dataCache = dataCache, -- Only used by Android.
			testMode = options.testMode -- Only used by unit tests.
		}
	
		local spaceFeedsManager = WFCatSpaceFeedsManager( wfCatalog, spaceFeedsManagerArgs )

		self:setOwned {
			_spaceFeedsManager = spaceFeedsManager
		}

		-- WF-398: Pause space-based sync during initial sync.

		self._catalogPuller:onceInitialSyncIsComplete()
			
			:map( function()
			
				self._spaceFeedsManager:reportCatalogInitialSyncIsComplete()
				
				return true
				
			end )
			
			:call( runUntilDone )
		
		-- the catalog puller should report sync issues to the diagnostic log
		WFDiagnosticLog.addLogProvider( self )

	end
	
end

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

function WFCatSyncClientBase:close()

	check( 'WFCatSyncClientBase' )
	
	if self._checkOzForCatalogUpdatesSubscription then
	
		self._checkOzForCatalogUpdatesSubscription:dispose()
		self._checkOzForCatalogUpdatesSubscription = nil
	
	end
	
end

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

function WFCatSyncClientBase:getCatalog()

	check( 'WFCatSyncClientBase' )
	
	return self._catalog
	
end

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

function WFCatSyncClientBase:getCatSpec()

	check( 'WFCatSyncClientBase' )
	
	return self._catSpec
	
end

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

function WFCatSyncClientBase:getCatalogPuller()

	check( 'WFCatSyncClientBase' )
	
	return self._catalogPuller
	
end

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

function WFCatSyncClientBase:getCatalogPusher()

	check( 'WFCatSyncClientBase' )
	
	return self._catalogPusher
	
end

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

function WFCatSyncClientBase:getNotificationSync()

	check( 'WFCatSyncClientBase' )
	
	return self._notificationSync
	
end

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

function WFCatSyncClientBase:startSync()

	check( 'WFCatSyncClientBase' )
	
	self._catalogPuller:getUpdatesContinuously()
	
	if self._catalogPusher then
		self._catalogPusher:sendUpdatesContinuously()
	end
	
	if self._spaceFeedsManager then
		self._spaceFeedsManager:startSendingUpdates()
		self._spaceFeedsManager:startFetchingUpdates()
	end
	
	if self._notificationSync then
		self._notificationSync:startNotificationSync()
	end
	
	return Observable.empty()
	
end

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

function WFCatSyncClientBase:stopSync()

	check( 'WFCatSyncClientBase' )
	
	self._catalogPuller:stopContinuousUpdates()
	
	if self._catalogPusher then
		self._catalogPusher:stopContinuousUpdates()
	end
	
	if self._spaceFeedsManager then
		self._spaceFeedsManager:stopSendingUpdates()
		self._spaceFeedsManager:stopFetchingUpdates()
	end
	
	if self._notificationSync then
		self._notificationSync:stopNotificationSync()
	end
	
	return Observable.empty()
	
end

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

function WFCatSyncClientBase:startSyncInResidualMode()

	check( 'WFCatSyncClientBase' )
	
	self._catalogPuller:getUpdatesContinuously()

	if self._spaceFeedsManager then
		self._spaceFeedsManager:startSendingUpdates()
		self._spaceFeedsManager:startFetchingUpdates()
	end

	if self._notificationSync then
		self._notificationSync:startNotificationSync()
	end

	return Observable.empty()
	
end

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

function WFCatSyncClientBase:stopSyncInResidualMode()

	check( 'WFCatSyncClientBase' )

	self._catalogPuller:stopContinuousUpdates()

	if self._spaceFeedsManager then
		self._spaceFeedsManager:stopSendingUpdates()
		self._spaceFeedsManager:stopFetchingUpdates()
	end
	
	if self._notificationSync then
		self._notificationSync:stopNotificationSync()
	end

	return Observable.empty()

end

------------------------------------------------------------------------
	
function WFCatSyncClientBase:startSpaceMetadataSyncInFreemiumMode()

	check( 'WFCatSyncClientBase' )

	if self._spaceFeedsManager then

		self._spaceFeedsManager:reportCatalogInitialSyncIsComplete()
			-- WF-749: In Freemium mode, the catalog does not sync. In other modes, we wait
			-- for initial sync to complete before syncing the spaces; initial catalog
			-- sync will never complete in freemium mode so we must activate this trigger
			-- manually. This allows space-feed sync to work.

		self._spaceFeedsManager:startSendingUpdates()
		self._spaceFeedsManager:startFetchingUpdates()	

	end
	
	if self._notificationSync then
		self._notificationSync:startNotificationSync()
	end
	
	return Observable.empty()

end

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

function WFCatSyncClientBase:stopSpaceMetadataSyncInFreemiumMode()

	check( 'WFCatSyncClientBase' )

	if self._spaceFeedsManager then
		self._spaceFeedsManager:stopSendingUpdates()
		self._spaceFeedsManager:stopFetchingUpdates()
	end
	
	if self._notificationSync then
		self._notificationSync:stopNotificationSync()
	end

	return Observable.empty()

end

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

--[[!
	\brief Get a stream that fires with updates when a shared-with-you space's 
		availability changes due to Oz maintenance on the owner's account.

	\return Observable(spaceId, status) Status is 'true' in the event
		that the owner's account is under maintenance or down, otherwise
		it will be 'false'.
--]]

function WFCatSyncClientBase:spaceFeedsMaintenanceNotificationStream()

	check( 'WFCatSyncClientBase' )

	if self._spaceFeedsManager then
		return self._spaceFeedsManager:spaceFeedsMaintenanceNotificationStream()
	end

	return Observable.empty()

end

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

function WFCatSyncClientBase:pausePusherReturningCounter()

	-- Used for synchronizing trash operations with the pusher.
	-- Since the space-feed assets don't end up in the trash, we
	-- only have to do this for the catalog pusher.
	
	check( 'WFCatSyncClientBase' )

	local pauseCatalogPusherReturningCounter = Observable.just( 0 )

	if self._catalogPusher then
		pauseCatalogPusherReturningCounter = self._catalogPusher:pauseReturningCounter()
	end
	
	return pauseCatalogPusherReturningCounter
	
end

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

function WFCatSyncClientBase:resumePusher()

	-- Used for synchronizing trash operations with the pusher.
	-- Since the space-feed assets don't end up in the trash, we
	-- only have to do this for the catalog pusher.
	
	check( 'WFCatSyncClientBase' )

	if self._catalogPusher then
		self._catalogPusher:resume()
	end
	
	return Observable.empty()

end

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

function WFCatSyncClientBase:_DEBUG_startMetadataPusher()

	check( 'WFCatSyncClientBase' )
	
	if self._catalogPusher then
		self._catalogPusher:sendUpdatesContinuously()
	end
	
	if self._spaceFeedsManager then
		self._spaceFeedsManager:startSendingUpdates()
	end

	return Observable.empty()
	
end

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

function WFCatSyncClientBase:_DEBUG_stopMetadataPusher()

	check( 'WFCatSyncClientBase' )
	
	if self._catalogPusher then
		self._catalogPusher:stopContinuousUpdates()
	end
	
	if self._spaceFeedsManager then
		self._spaceFeedsManager:stopSendingUpdates()
	end

	return Observable.empty()

end

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

function WFCatSyncClientBase:setUserIsOverQuota( ... )

	check( 'WFCatSyncClientBase' )
	
	if self._catalogPusher then
		self._catalogPusher:setUserIsOverQuota( ... )
	end
	
	return Observable.empty()
	
end

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

function WFCatSyncClientBase:setUserIsExpired( ... )

	check( 'WFCatSyncClientBase' )
	
	if self._catalogPusher then
		self._catalogPusher:setUserIsExpired( ... )
	end
	
	return Observable.empty()
	
end

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

function WFCatSyncClientBase:isUploadingMetadata()

	check( 'WFCatSyncClientBase' )
	
	local isPushingMetadata = Observable.just( false )
	
	if self._catalogPusher then
	
		isPushingMetadata = self._catalogPusher:isDoingWork():distinctUntilChanged()
		
	end
	
	if self._spaceFeedsManager then
	
		isPushingMetadata = Observable.combineLatest( 
				function( isPushingCatalogMetadata, isPushingSpaceFeedMetadata )
					return isPushingCatalogMetadata or isPushingSpaceFeedMetadata
				end, 
				self._catalogPusher:isDoingWork():distinctUntilChanged(),
				self._spaceFeedsManager:isUploadingMetadata() )
				
	end
	
	return isPushingMetadata
	
end

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

function WFCatSyncClientBase:isDownloadingMetadata()

	check( 'WFCatSyncClientBase' )
	
	local isPullingMetadata = self._catalogPuller:isDoingWork()
	
	if self._spaceFeedsManager then
	
		isPullingMetadata = Observable.combineLatest( 
				function( isPullingCatalogMetadata, isPullingSpaceMetadata )
					return isPullingCatalogMetadata or isPullingSpaceMetadata
				end, 
				self._catalogPuller:isDoingWork(),
				self._spaceFeedsManager:isDownloadingMetadata() )
	
	end
	
	return isPullingMetadata
	
end

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

function WFCatSyncClientBase:getLatestDownloadedAssetIdsStream()

	check( 'WFCatSyncClientBase' )
	
	return self._catalogPuller:getLatestDownloadedAssetIdsStream()
	
end

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

function WFCatSyncClientBase:isOzCatalogDeleted()

	check( 'WFCatSyncClientBase' )
	
	return self._catalogPuller:isOzCatalogDeleted()
	
end

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

function WFCatSyncClientBase:getRequiredUpdateStream()

	check( 'WFCatSyncClientBase' )
	
	return self._user:getRequiredUpdateStream()
	
end

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

function WFCatSyncClientBase:checkOzForUpdates()

	check( 'WFCatSyncClientBase' )
	
	self._catalogPuller:checkOzForUpdates()
	
	return Observable.just( true )
	
end

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

function WFCatSyncClientBase:setReadNotification( notificationMarkRead, optNotificationMarkUnread )

	check( 'WFCatSyncClientBase', 'table', '?table' )

	return self._notificationSync:setReadNotification( notificationMarkRead, optNotificationMarkUnread )

end

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

function WFCatSyncClientBase:setVisitedNotification( notificationMarkVisited, optNotificationMarkUnvisited )

	check( 'WFCatSyncClientBase', 'table', '?table' )

	return self._notificationSync:setVisitedNotification( notificationMarkVisited, optNotificationMarkUnvisited )

end

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

function WFCatSyncClientBase:notificationUnreadCount()
	
	check( 'WFCatSyncClientBase' )
	
	return self._notificationSync:notificationUnreadCount()
	
end

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

function WFCatSyncClientBase:notificationList( optWaitUntilSyncCycleComplete )
	
	check( 'WFCatSyncClientBase', '?boolean' )
	
	return self._notificationSync:notificationList( optWaitUntilSyncCycleComplete )
	
end

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

function WFCatSyncClientBase:notificationKillSwitch()
	
	check( 'WFCatSyncClientBase' )
	
	return self._notificationSync:notificationKillSwitch()
	
end
--------------------------------------------------------------------------------

function WFCatSyncClientBase:haltAndDeleteNotificationDb()

	check( 'WFCatSyncClientBase' )
	
	return self._notificationSync:haltAndDeleteNotificationDb()

end

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

--[[!
	\brief This API that can be used gather real-time sync diagnostic data.

	\param optCalm (optional number) The maximum frequency at which the stream will
		fire with updates.

	\return Observable< table > An Observable whose value tracks a table of
		sync diagnostic data.
--]]

function WFCatSyncClientBase:syncDiagnostics( optCalm )

	check( 'WFCatSyncClientBase', '?number' )
	
	return Observable.combineLatest(
			self._catalogPuller:syncDiagnostics( optCalm ),
			self._catalogPusher:syncDiagnostics( optCalm ),
			self._spaceFeedsManager:syncDiagnostics( optCalm )
		)

		:map( function( catalogPullerSyncDiagnostics, 
						catalogPusherSyncDiagnostics, 
						spaceFeedsManagerSyncDiagnostics )
		
			return {
			
				catalogPullerSyncDiagnostics = catalogPullerSyncDiagnostics,
				catalogPusherSyncDiagnostics = catalogPusherSyncDiagnostics,
				spaceFeedsManagerSyncDiagnostics = spaceFeedsManagerSyncDiagnostics
				
			}
		
		end )

end

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

function WFCatSyncClientBase:_DEBUG_simulateCorruptionNotificationDb()

	check( 'WFCatSyncClientBase' )
	
	return self._notificationSync:_DEBUG_simulateCorruptionNotificationDb()

end

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

function WFCatSyncClientBase:_DEBUG_SyncClientBaseReturnsTrue()

	check( 'WFCatSyncClientBase' )
	
	return Observable.just( true )

end

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

local function wrapInTag( tag, str )
	return string.format( "<%s>%s</%s>", tag, tostring( str ), tag )
end

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

local function makeDetailedSyncStatusHTML( wfCatalog, docInfoForDetailedStatus )
	
	if #docInfoForDetailedStatus == 0 then
		return Observable.just( '' )
	end
	
	local docIds, docInfosByDocId = WFFunctionUtils.multiReturnReduce( function( docInfo, docIds, docInfosByDocId )
			docIds[ #docIds + 1 ] = docInfo.assetId
			docInfosByDocId[ docInfo.assetId ] = docInfo
			
			return docIds, docInfosByDocId
		end,
		docInfoForDetailedStatus, {}, {} )
		
		
	return wfCatalog:docStore():bulkDocValue( docIds )
		:map( function( docValues )

			local detailedDocInfos = WFFunctionUtils.reduce( {}, 
				function( acc, docValue )
					local basicDocInfo = docInfosByDocId[ docValue._id ]
					-- this should work for assets, deleted assets and cover documents
					local maybeFilename = KSTableUtils.derefOpt( docValue, 'importSource', 'fileName' ) or 
								KSTableUtils.derefOpt( docValue, '_localOnly', 'cover', 'importSource', 'fileName' )
					
					local detailedDocInfo = {}
					detailedDocInfo.assetId = docValue._id
					detailedDocInfo.filename = maybeFilename or ''
					detailedDocInfo.status = basicDocInfo.statusString
					detailedDocInfo.subtype = docValue.subtype or docValue.type or ''
					detailedDocInfo.deleted = not not ( docValue._deleted or docValue.empty )
					
					acc[ #acc + 1 ] = detailedDocInfo
					return acc
				end,
				docValues )
				
				return detailedDocInfos

		end )
		:map( function( detailedDocInfos )
		
			local detailedSyncStatusHTMLRows = 
				wrapInTag( 'tr',
					wrapInTag( 'th', 'document id' ) ..
					wrapInTag( 'th', 'sync status' ) ..
					wrapInTag( 'th', 'subtype' ) ..
					wrapInTag( 'th', 'filename') ..
					wrapInTag( 'th', 'deleted' ) )
			
			detailedSyncStatusHTMLRows = WFFunctionUtils.reduce( detailedSyncStatusHTMLRows, 
				function( acc, docInfo )
					return acc .. 
						wrapInTag( 'tr', 
							wrapInTag( 'td', docInfo.assetId ) ..
							wrapInTag( 'td', docInfo.status ) ..
							wrapInTag( 'td', docInfo.subtype ) ..
							wrapInTag( 'td', docInfo.filename ) ..
							wrapInTag( 'td', docInfo.deleted ) )
				end, 
				detailedDocInfos )

			return wrapInTag( 'table', detailedSyncStatusHTMLRows )
	
		end )
end

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

local function makeRegularSyncStatusHTML( docInfoForRegularStatus )
	
	if #docInfoForRegularStatus == 0 then
		return Observable.just( '' )
	end
	
	local regularSyncStatusHTMLRows = 
		wrapInTag( 'tr',
			wrapInTag( 'th', 'document id' ) ..
			wrapInTag( 'th', 'sync status' ) )
	
	regularSyncStatusHTMLRows = WFFunctionUtils.reduce( regularSyncStatusHTMLRows, 
		function( acc, docInfo )
			return acc .. 
				wrapInTag( 'tr', 
					wrapInTag( 'td', docInfo.assetId ) ..
					wrapInTag( 'td', docInfo.statusString ) )
		end, 
		docInfoForRegularStatus )

	return Observable.just( wrapInTag( 'table', regularSyncStatusHTMLRows ) )
	
end

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

function WFCatSyncClientBase:sendDiagnosticLogData()
	
	local docIdsAndStatusStream
	
	if WFPlatformInfo.isDesktop then
		docIdsAndStatusStream = require 'WFCatSyncStatus'.getDocIdsAndStatusWithSyncPendingOrError( self._catalog )
	elseif WFPlatformInfo.isMobile() then
		docIdsAndStatusStream = require 'LrmCatSyncStatus'.getDocIdsAndStatusWithSyncPendingOrError( self._catalog )
	else
		docIdsAndStatusStream = Observable.just({})
	end
	
	docIdsAndStatusStream
		:take( 1 )
		:map( function( results )
			local docInfoForDetailedStatus, docInfoForRegularStatus = {}, {}
			
			for i, result in ipairs( results ) do
				if i <= 10 then
					docInfoForDetailedStatus[ #docInfoForDetailedStatus + 1 ] = result
				else
					docInfoForRegularStatus[ #docInfoForRegularStatus + 1 ] = result
				end
			end
			
			return docInfoForDetailedStatus, docInfoForRegularStatus
		end )
		:flatMap( function( docInfoForDetailedStatus, docInfoForRegularStatus )
			
			return Observable.combineLatest( 
				function( detailedStatusHTML, regularStatusHTML )
					return detailedStatusHTML ..'<br>' .. regularStatusHTML
				end, 
				makeDetailedSyncStatusHTML( self._catalog, docInfoForDetailedStatus ), 
				makeRegularSyncStatusHTML( docInfoForRegularStatus ) )
		
		end )
		:map( function( syncStatusHtml )
			
			WFDiagnosticLog.addSectionToLog
				{
					name = "Syncing and Error Assets",
					id = 'SyncStatus',
					weight = 499,
					parentSection = WFDiagnosticLog.Sections.SYNC,
					body = syncStatusHtml,
				}
				
		end )
		:call( WFRxRunUntilDone )
	
	return {}
end

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

return Rx.Class( 'WFCatSyncClientBase', WFCatSyncClientBase )
