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

WFCatSynchronizer.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2016 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.

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

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

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

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

	-- WF.oz_client.managed_catalog
local WFCatBinaryUploader = require 'WFCatBinaryUploader'
local WFCatConflictResolver = require 'WFCatConflictResolver'
local WFCatIdleDownloader = require 'WFCatIdleDownloader'
local WFCatOzMaintenanceTracker = require 'WFCatOzMaintenanceTracker'
local WFCatPersonBinaryDownloader = require 'WFCatPersonBinaryDownloader'
local WFCatStaleBinaryRemover = require 'WFCatStaleBinaryRemover'
local WFCatStorage = require 'WFCatStorage'
local WFCatStuckSyncUtils = require 'WFCatStuckSyncUtils'
local WFCatSyncAutoDeleter = require 'WFCatSyncAutoDeleter'
local WFCatSyncClient = require 'WFCatSyncClient'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSPrefs = require 'KSPrefs'
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFCloseSupport = require 'WFCloseSupport'
local WFConfiguration = require 'WFConfiguration'

	-- Lightroom CC
local NmbConnectors = require 'NmbConnectors'
local NmbErrorHandler = require 'NmbErrorHandler'
local NmbOfflineAssetsTask = require 'NmbOfflineAssetsTask'
local NmbFileManagementSettings = require 'NmbFileManagementSettings'

local WFPlatformInfo = require 'WFPlatformInfo'
local WFCatStyleSynchronizer
local isNimbus = WFPlatformInfo.isNimbus()
if isNimbus then
	WFCatStyleSynchronizer = require 'WFCatStyleSynchronizer'
end

local logger = WFLogger 'WFCatSynchronizer'
--	:setLevel 'debug'

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

local debug_disableSync = WFConfiguration.Sync.disable or false

local check = assert( WFArguments.check )

local runUntilDone = WFRxRunUntilDone

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

local WFCatSynchronizerMethods = {

	_wfCatalog = false,
		-- (WFCatalog)

	_pixelProviderProcessor = false,
		-- (PixelProviderProcessor)

	_metadataSyncClient = false,
		-- (WFCatSyncClient)

	_ozStorageMonitor = false,
		-- (WFCatStorage)

	_conflictResolver = false,
		-- (WFCatConflictResolver)

	_autoDeleter = false,
		-- (WFCatSyncAutoDeleter)

	_profileSyncClient = false,
		-- (WFCatSyncClient)

	_maybeNimbusIdleDownloader = false,
		-- (WFCatIdleDownloader)

	_maybeNimbusStaleBinaryRemover = false,
		-- (WFCatStaleBinaryRemover)

	_binaryUploader = false,
		-- (WFCatBinaryUploader)

	_maybeNimbusStyleSynchronizer = false,
		-- (WFCatStyleSynchronizer)

	_personThumbnailDownloader = false,
		-- (WFCatPersonBinaryDownloader)

	_nmbOfflineAssetsTask = false,
		-- (NmbOfflineAssetsTask)

	_errorHandler = false,
		-- (WFCatErrorHandler)

	_isSyncRunning = false,
		-- (boolean)

	_residualMode = false,
		-- (boolean)

	_freemiumMode = false,
		-- (boolean)

	_restrictedCountryMode = false,
		-- (boolean)

	_catalogValidationStream = false,
		-- (Observable<boolean> denoting Oz/local catalog validity for syncing)

	_maybeNimbusIdleDownloaderDelaySubscription = false,
		-- (Rx Timer Subscription)

	_isSyncRunningStream = false,
		-- (BehaviorSubject) A stream that fires true when sync is running, false otherwise.
		-- This fires true even when syncing in residual mode.

	_isNormalSyncRunningStream = false,
		-- (BehaviorSubject) A stream that fires true when sync is running, false otherwise.
		-- Note that this only fires true when sync is running it's normal mode; not in
		-- residual mode, when paused, or when there's no Oz catalog.

	_checkOzForCatalogUpdatesSubscription = false,
		-- (Disposable) A subscription that monitors for binary upload completions and triggers
		-- the puller to check for Oz catalog updates.

	_ozMaintenanceNotificationStream = false,
		-- (Rx Subject) A stream that fires with Oz outage/maintenance updates for both the
		-- users own account and all shared-with-you spaces.

	_ozMaintenanceSyncHandlerSubscription = false,
		-- (Disposable) A subscription that monitors _ozMaintenanceNotificationStream and
		-- starts/stops sync activities accordingly.

	_accountIsUnderMaintenance = false,
		-- (boolean) Keeps track of whether or not the user's Oz account is under maintenance,
		-- or if Oz is down.

	_docsStuckSyncingMetadataSubscription = false,
		-- (Rx Disposable) A subscription that monitors and reports metadata related stuck syncs.

	WFCloseSupport.addCloseSupportMethods,

}

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

local function buildDownloadBinaryStream( pixelProviderProcessor, idlePixStreamIds )

	local pixStreamIdsLut = KSTableUtils.invert( idlePixStreamIds )

	return pixelProviderProcessor
			:pixStreamIdsPixelPathStream( pixStreamIdsLut )
			:tap(

				function( pixStreamId, localAssetId )

					pixelProviderProcessor
						:runUntilDone(
							"removeRequest",
								pixStreamId, localAssetId )

				end )

end

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

local function buildDownloadBinaryFunc( pixelProviderProcessor, idlePixStreamIds )

	return function( localAssetId, binaryType, binarySize, isMarkedForOffline, hasLocalOriginal )

		local pixStreamId = idlePixStreamIds[ binaryType ]

		pixelProviderProcessor
			:runUntilDone(
				"requestBinaryWhenIdle",
					pixStreamId, localAssetId, binaryType, binarySize,
					isMarkedForOffline, hasLocalOriginal )

	end

end

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

local function buildBatchCancelDownloadsFunc( pixelProviderProcessor, idlePixStreamIds )

	return function( binaryType, localAssetIds )

		local pixStreamId = idlePixStreamIds[ binaryType ]

		pixelProviderProcessor
			:runUntilDone(
				"batchRemoveRequests", pixStreamId, localAssetIds )

	end

end

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

local function validateLocalAndOzCatalogMatch( self )

	self._wfCatalog:docStore():localDocValue( 'Oz-nextUpdateFeedLink' )
		:map( function( maybeNextFeedLinkTbl )
			local isValid = true
			local ozCatalogId = self._wfCatalog:ozCatalogId()

			if ozCatalogId and type( maybeNextFeedLinkTbl ) == 'table'  then
				if maybeNextFeedLinkTbl.ozCatalogId then
					isValid = maybeNextFeedLinkTbl.ozCatalogId == ozCatalogId
				elseif maybeNextFeedLinkTbl.link then
					isValid = not not string.find( maybeNextFeedLinkTbl.link, ozCatalogId )
				end
			end

			self._catalogValidationStream:onNext( isValid )

			return isValid
		end )
		:call( runUntilDone )

end

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

function WFCatSynchronizerMethods:__init( wfCatalog, profileCatalog, pixelProviderProcessor,
	workerThreadAgrs, syncEnabled, residualMode, freemiumMode, locale )

	check( 'WFCatSynchronizer', 'WFCatalog', '?WFCatalog', '?PixelProviderProcessor',
			'table', '?boolean', '?boolean', '?boolean', '?string' )

	self._wfCatalog = wfCatalog
	self._pixelProviderProcessor = pixelProviderProcessor

	self._conflictResolver = WFCatConflictResolver( wfCatalog:docStore(), wfCatalog:indexStore() )
	self._ozStorageMonitor = WFCatStorage( wfCatalog )

	self._metadataSyncClient = WFCatSyncClient( wfCatalog, { locale = locale } )

	self._maybeNimbusStyleSynchronizer = WFCatStyleSynchronizer and WFCatStyleSynchronizer( wfCatalog, profileCatalog, workerThreadAgrs )

	self._personThumbnailDownloader = WFCatPersonBinaryDownloader(wfCatalog)

	self._catalogValidationStream = Rx.ReplaySubject( 1 )
	validateLocalAndOzCatalogMatch( self )

	-- monitor quota
	self._ozStorageMonitor:isOverStorage()
		:distinctUntilChanged()
		:flatMap( function( isOverStorage )
			return self._metadataSyncClient:setUserIsOverQuota( isOverStorage )
		end )
		:call( runUntilDone )

	-- monitor expiration
	wfCatalog:ozUser():isSubscriptionExpired()
		:distinctUntilChanged()
		:flatMap( function( isExpired )
			return self._metadataSyncClient:setUserIsExpired( isExpired )
		end )
		:call( runUntilDone )

	self._autoDeleter = WFCatSyncAutoDeleter( wfCatalog )

	if profileCatalog then
		self._profileSyncClient = WFCatSyncClient( profileCatalog, {
																	metadataPullerArgs = {
																	 	progressiveBackoff = false,
																		noWorkDelay = 60 * 60 * 24, -- 1 day
																		errorDelay = 60 * 60, -- 1 hour
																		maxDelay = 60 * 60 * 24, -- 1 day
																	},
																	isSyncClientForProfileCatalog = true
																} )
		self._profileSyncClient:addCloseAction( function()
			profileCatalog:close()
		end )
	end

	local idlePixStreamBaseId = string.format( "%s:%s", 'WFCatSynchronizer', KSIdGenerator.uuidInLowercaseHex() )

	if isNimbus then

		local idlePixStreamIds = {}
		idlePixStreamIds[ 'original' ] = string.format( "%s:%s", idlePixStreamBaseId, 'original' )
		idlePixStreamIds[ 'proxy' ] = string.format( "%s:%s", idlePixStreamBaseId, 'proxy' )
		idlePixStreamIds[ 'xmp_develop' ] = string.format( "%s:%s", idlePixStreamBaseId, 'xmp_develop' )

		local idleDownloadStream = buildDownloadBinaryStream( pixelProviderProcessor, idlePixStreamIds )
		local idleDownloadFunc = buildDownloadBinaryFunc( pixelProviderProcessor, idlePixStreamIds )
		local batchCancelDownloadsFunc = buildBatchCancelDownloadsFunc( pixelProviderProcessor, idlePixStreamIds )

		self._maybeNimbusIdleDownloader = WFCatIdleDownloader(
				wfCatalog, idleDownloadFunc, idleDownloadStream, batchCancelDownloadsFunc,
				NmbFileManagementSettings.keepAllOriginals(),
				NmbFileManagementSettings.keepAllProxies() )

		self._maybeNimbusStaleBinaryRemover = WFCatStaleBinaryRemover( wfCatalog, self._maybeNimbusIdleDownloader )

	end

	local optArgs = {}

	self._binaryUploader = WFCatBinaryUploader( wfCatalog, nil, nil, nil, optArgs )

	-- WF-357: Reset Catalog MetadataPuller polling frequency after uploading a binary.

	self._checkOzForCatalogUpdatesSubscription = self._binaryUploader:binaryUploadCompletionStream()
		:flatMap( function()
			return self._metadataSyncClient:checkOzForUpdates()
		end )
		:subscribe()

	self._nmbOfflineAssetsTask = NmbOfflineAssetsTask( wfCatalog )

	self._errorHandler = NmbErrorHandler( wfCatalog )

	self._residualMode = residualMode

	self._freemiumMode = freemiumMode

	self._isSyncRunningStream = Rx.BehaviorSubject( syncEnabled )

	self._isNormalSyncRunningStream = Rx.BehaviorSubject( false )

	local catalogOzMaintenanceNotificationStream
			= WFCatOzMaintenanceTracker.getOzMaintenanceNotificationStream( wfCatalog )

	local spaceFeedsMaintenanceNotificationStream
			= self._metadataSyncClient:spaceFeedsMaintenanceNotificationStream()

	self:addCloseAction( function()

		catalogOzMaintenanceNotificationStream:dispose()

	end )

	self._ozMaintenanceNotificationStream =
			catalogOzMaintenanceNotificationStream
				:merge( spaceFeedsMaintenanceNotificationStream )
				:replay( nil, 1 )
				:refCount()

	self._ozMaintenanceSyncHandlerSubscription = self._ozMaintenanceNotificationStream

		:subscribe( function( ozMaintenanceInfo )

			local catalogOrSpaceId = ozMaintenanceInfo and ozMaintenanceInfo.id
			local accountIsUnderMaintenance = ozMaintenanceInfo and ozMaintenanceInfo.isUnderMaintenance

			if catalogOrSpaceId == 'catalog' and isNimbus then

				if accountIsUnderMaintenance then

					self._accountIsUnderMaintenance = true
					self:stopSyncing()

				else

					self._accountIsUnderMaintenance = false
					self:startSyncing()

				end

			end

		end )

	-- Stuck Sync Analytics

	local kStuckSyncAnalyticsInterval = 600 -- seconds

	self._docsStuckSyncingMetadataSubscription =
			WFCatStuckSyncUtils.getDocsStuckSyncingMetadataStream( wfCatalog, self._isNormalSyncRunningStream )

				:calm( kStuckSyncAnalyticsInterval, WFRxScheduler.sharedScheduler() )

				:flatMapLatest( function( stuckDocIds )

					return WFCatStuckSyncUtils.resolveStuckSync( wfCatalog, stuckDocIds )

				end )

				:subscribe()

	-- check for the latest connectors available
	Observable.timer( 10, WFRxScheduler.sharedScheduler() )
		:flatMap( function()
			return NmbConnectors.updateAvailableConnectorsCache( wfCatalog )
		end )
		:call( runUntilDone )

	if syncEnabled then

		-- delay the start of the idle downloader a bit so that we're not overaggressively pulling binaries at the
		-- start of sync when other things are likely more important to get through the sync process
		-- The purge process is setup to delay for 15 seconds on startup; it's good to have the idle downloader
		-- delay slightly longer than purge so purge can prevent the idle downloader from running if needed.

		self:startSyncing( 20 )

	end

end

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

function WFCatSynchronizerMethods:close()

	check( 'WFCatSynchronizer' )

	self:stopSyncing()

	if self._profileSyncClient then
		self._profileSyncClient:close()
	end

	if self._catalogValidationStream then
		self._catalogValidationStream:dispose()
	end

	if self._isSyncRunningStream then
		self._isSyncRunningStream:dispose()
	end

	if self._isNormalSyncRunningStream then
		self._isNormalSyncRunningStream:dispose()
	end

	if self._checkOzForCatalogUpdatesSubscription then
		self._checkOzForCatalogUpdatesSubscription:dispose()
	end

	if self._ozMaintenanceSyncHandlerSubscription then
		self._ozMaintenanceSyncHandlerSubscription:dispose()
	end

	if self._docsStuckSyncingMetadataSubscription then
		self._docsStuckSyncingMetadataSubscription:dispose()
	end

	self._autoDeleter:close()

	self._metadataSyncClient:close()
	self._ozStorageMonitor:close()
	if self._maybeNimbusIdleDownloader then
		self._maybeNimbusIdleDownloader:close()
	end
	if self._maybeNimbusStyleSynchronizer then
		self._maybeNimbusStyleSynchronizer:close()
	end
	self._personThumbnailDownloader:close()
	self._binaryUploader:close()
	self._errorHandler:close()
	if self._maybeNimbusStaleBinaryRemover then
		self._maybeNimbusStaleBinaryRemover:close()
	end
	self._conflictResolver:close()
	self._nmbOfflineAssetsTask:close()

end

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

function WFCatSynchronizerMethods:startSyncing( maybeIdleDownloaderDelay )

	if WFCloseSupport.hasBeenOrWillBeClosed( self._wfCatalog ) or
			not self:shouldSyncBeRunning() then
		-- ignore start request if catalog is closed/closing or
		-- if sync is paused/Oz is down.
		return
	end

	local ozUserId = self._wfCatalog:ozUserId()

	if not ozUserId:match( '^0+$' ) and not debug_disableSync and not self._restrictedCountryMode then

		if self._isSyncRunning then
			return
		end

		self._catalogValidationStream
			:take( 1 )
			:map( function( isValid )

				if not isValid or self._freemiumMode then
					-- catalog is not valid, don't allow syncing
					self:stopSyncing()

				else

					-- catalog is fine, start up sync
					self._isSyncRunning = true
					self._isSyncRunningStream:onNext( true )

					if not self._residualMode then
						self._isNormalSyncRunningStream:onNext( true )
					else
						self._isNormalSyncRunningStream:onNext( false )
					end

					if self._profileSyncClient then
						self._profileSyncClient:startSync():call( runUntilDone )
					end

					if self._residualMode then
						self._metadataSyncClient:startSyncInResidualMode():call( runUntilDone )
					else
						self._metadataSyncClient:startSync():call( runUntilDone )
					end

					if isNimbus then
						self._maybeNimbusIdleDownloaderDelaySubscription =
							Observable.timer( maybeIdleDownloaderDelay or 0.1, WFRxScheduler.sharedScheduler() )
								:map( function()
									self._maybeNimbusIdleDownloader:start()
								end )
								:subscribe()
					end

					if self._maybeNimbusStyleSynchronizer then
						self._maybeNimbusStyleSynchronizer:start()
					end

					if self._residualMode then
						self._binaryUploader:startContinuousUploadOfOnlySpaceAssets()
					else
						self._binaryUploader:startContinuousUploads()
					end

				end
			end )
			:call( runUntilDone )
	end

end

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

function WFCatSynchronizerMethods:stopSyncing()

	if self._profileSyncClient then
		self._profileSyncClient:stopSync():call( runUntilDone )
	end

	if self._residualMode then
		self._metadataSyncClient:stopSyncInResidualMode():call( runUntilDone )
	else
		self._metadataSyncClient:stopSync():call( runUntilDone )
	end

	if self._maybeNimbusIdleDownloaderDelaySubscription then
		self._maybeNimbusIdleDownloaderDelaySubscription:dispose()
		self._maybeNimbusIdleDownloaderDelaySubscription = nil
	end

	if self._maybeNimbusIdleDownloader then
		self._maybeNimbusIdleDownloader:stop()
	end

	if self._maybeNimbusStyleSynchronizer then
		self._maybeNimbusStyleSynchronizer:stop()
	end

	self._binaryUploader:stopContinuousUploads()

	self._isSyncRunning = false
	self._isSyncRunningStream:onNext( false )
	self._isNormalSyncRunningStream:onNext( false )

end

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

function WFCatSynchronizerMethods:enableResidualMode()

	self._freemiumMode = false
	self._residualMode = true

end

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

function WFCatSynchronizerMethods:disableResidualMode()

	self._residualMode = false

end

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

function WFCatSynchronizerMethods:enableFreemiumMode()

	self._residualMode = false
	self._freemiumMode = true

end

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

function WFCatSynchronizerMethods:disableFreemiumMode()

	self._freemiumMode = false

end

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

function WFCatSynchronizerMethods:enableRestrictedCountryMode()

	self._restrictedCountryMode = true

end

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

function WFCatSynchronizerMethods:catalogValidationStream()

	return self._catalogValidationStream

end
--------------------------------------------------------------------------------

function WFCatSynchronizerMethods:isSyncRunningNow()

	return self._isSyncRunning

end

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

function WFCatSynchronizerMethods:isSyncRunningStream()

	return self._isSyncRunningStream

end

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

function WFCatSynchronizerMethods:isNormalSyncRunningStream()

	return self._isNormalSyncRunningStream

end

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

function WFCatSynchronizerMethods:shouldSyncBeRunning()

	return not KSPrefs.syncPaused and not self._accountIsUnderMaintenance
		-- KSPrefs.syncPaused always contains the correct value for whether or
		-- not sync should be enabled.

end

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

function WFCatSynchronizerMethods:isUploadingBinaries()
	return self._binaryUploader:isDoingWork()
end

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

function WFCatSynchronizerMethods:isDownloadingRequiredOriginalsOrProxies()
	--Adding additional nil check and logger
	if not self._maybeNimbusIdleDownloader then
		logger:warnf( "isDownloadingRequiredOriginalsOrProxies was called without _maybeNimbusIdleDownloader" )
		return Observable.just( false )
	end

	return self._maybeNimbusIdleDownloader:isDownloadingRequiredBinary()
end

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

function WFCatSynchronizerMethods:isUploadingMetadata()
	return self._metadataSyncClient:isUploadingMetadata()
end

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

function WFCatSynchronizerMethods:isDownloadingMetadata()
	return self._metadataSyncClient:isDownloadingMetadata()
end

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

function WFCatSynchronizerMethods:isDownloadingStyles()
	--Adding additional nil check and logger
	if not self._maybeNimbusStyleSynchronizer then
		logger:warnf( "isDownloadingStyles was called without _maybeNimbusStyleSynchronizer" )
		return Observable.just( false )
	end

	return self._maybeNimbusStyleSynchronizer:isDownloadingStyles()
end

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

function WFCatSynchronizerMethods:getLatestDownloadedAssetIdsStream()
	return self._metadataSyncClient:getLatestDownloadedAssetIdsStream()
end

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

function WFCatSynchronizerMethods:getSQLiteFatalErrorCodeStream()
	return self._metadataSyncClient:getSQLiteFatalErrorCodeStream()
end

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

function WFCatSynchronizerMethods:isOzCatalogDeleted()
	return self._metadataSyncClient:isOzCatalogDeleted()
end

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

function WFCatSynchronizerMethods:getIdleDownloader()
	--Adding additional nil check and logger
	if not self._maybeNimbusIdleDownloader then
		logger:warnf( "getIdleDownloader was called without _maybeNimbusIdleDownloader" )
		return Observable.just( false )
	end

	return self._maybeNimbusIdleDownloader
end

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

function WFCatSynchronizerMethods:getBinaryUploader()
	return self._binaryUploader
end

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

function WFCatSynchronizerMethods:getStyleSynchronizer()
	--Adding additional nil check and logger
	if not self._maybeNimbusStyleSynchronizer then
		logger:warnf( "getStyleSynchronizer was called without _maybeNimbusStyleSynchronizer" )
		return Observable.just( false )
	end

	return self._maybeNimbusStyleSynchronizer
end

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

function WFCatSynchronizerMethods:getPersonThumbnailDownloader()
	return self._personThumbnailDownloader
end

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

function WFCatSynchronizerMethods:getRequiredUpdateStream()

	-- Listen on different threads, all of which make requests to Oz, for a required update error.

	local profileSyncClientRequiredUpdateStream
	if self._profileSyncClient then
		profileSyncClientRequiredUpdateStream = self._profileSyncClient:getRequiredUpdateStream()
	else
		profileSyncClientRequiredUpdateStream = Observable.just( false )
	end

	return Rx.Observable.combineLatest( function( maybeRequiresUpdate1, maybeRequiresUpdate2, maybeRequiresUpdate3 )
			return maybeRequiresUpdate1 or maybeRequiresUpdate2 or maybeRequiresUpdate3
		end,
		self._metadataSyncClient:getRequiredUpdateStream(),
		profileSyncClientRequiredUpdateStream,
		self._wfCatalog:ozUser():getRequiredUpdateStream() )

end

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

function WFCatSynchronizerMethods:getOzMaintenanceNotificationStream()

	return self._ozMaintenanceNotificationStream

end

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

--[[!
	\brief This API provides an estimation -- in seconds -- for the time remaining
		to upload any outstanding binaries. -1 is returned in the event there is
		nothing to upload.

	\return Observable< number > An Observable that fires with numbers representing
		the time remaining in seconds.
--]]

function WFCatSynchronizerMethods:getEstimatedTimeRemainingForUploads()

	return self._binaryUploader:getEstimatedTimeRemaining()

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 WFCatSynchronizerMethods:syncDiagnostics( optCalm )

	check( 'WFCatSynchronizer', '?number' )

	local maybeNimbusIdleDownloaderSyncDiagnostics = Observable.just( {} )

	if isNimbus then
		maybeNimbusIdleDownloaderSyncDiagnostics = self._maybeNimbusIdleDownloader
			and self._maybeNimbusIdleDownloader:syncDiagnostics( optCalm )
	end

	return Observable.combineLatest(
			self._metadataSyncClient:syncDiagnostics( optCalm ),
			self._binaryUploader:syncDiagnostics( optCalm ),
			maybeNimbusIdleDownloaderSyncDiagnostics
		)

		:map( function( metadataSyncDiagnostics,
						binaryUploaderSyncDiagnostics,
						idleDownloaderSyncDiagnostics )

			return {

				metadataSyncDiagnostics = metadataSyncDiagnostics,
				binaryUploaderSyncDiagnostics = binaryUploaderSyncDiagnostics,
				idleDownloaderSyncDiagnostics = idleDownloaderSyncDiagnostics

			}

		end )

end

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

return Rx.Class( 'WFCatSynchronizer', WFCatSynchronizerMethods )
