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

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

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

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.utils
local WFCloseSupport = require 'WFCloseSupport'
local WFArguments = require 'WFArguments'
local KSTableUtils = require 'KSTableUtils'
local WFDeviceInfo = require "WFDeviceInfo"
local WFAnalytics = require "WFAnalytics"
local WFCapabilities = require 'WFCapabilities'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFProgressReporter = require 'WFProgressReporter'
local WFRxThrottledBatchedApplyOverlayToIds = require 'WFRxThrottledBatchedApplyOverlayToIds'

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

	-- WF.oz_client.managed_catalog
local WFCatBinaryUploader = require 'WFCatBinaryUploader'
local WFCatConflictResolver = require 'WFCatConflictResolver'
local WFCatErrorHandler = require 'WFCatErrorHandler'
local WFCatOzMaintenanceTracker = require 'WFCatOzMaintenanceTracker'
local WFCatStorage = require 'WFCatStorage'
local WFCatStuckSyncUtils = require 'WFCatStuckSyncUtils'
local WFCatSyncAutoDeleter = require 'WFCatSyncAutoDeleter'
local WFCatSyncClientBase = require 'WFCatSyncClientBase'
local WFCatUserSynchronizer = require 'WFCatUserSynchronizer'
local WFCatVolumeAgent = require 'WFCatVolumeAgent'
local WFCatVideo = require 'WFCatVideo'
local WFCatPendingCommitHandler = require 'WFCatPendingCommitHandler'

	-- WF.oz_client.lrmobile
local WFOzSyncConstants = require 'WFOzSyncConstants'
local OzSyncMetadataMapper = require 'OzSyncMetadataMapper'
local LrmCatSyncStatus = require 'LrmCatSyncStatus'
local LrmCatXmpDevelop = require 'LrmCatXmpDevelop'

	-- WF.sqlite.doc_store
local WFRxDocStoreMessageProcessor = require "WFRxDocStoreMessageProcessor"

	-- WF.oz_client.doc_store
 local OzCatalogServiceViaWFCatalog = require "OzCatalogServiceViaWFCatalog"

	-- WF trash
local WFCatTrash = require 'WFCatTrash'

local LrmConfig = require "LrmConfig"

local LrmCatAlbum = require "LrmCatAlbum"

local WFLogger = require 'WFLogger'

local logger = WFLogger( 'LrmSyncClientImp' )
--	:setLevel( 'debug' )

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

LrmConfig.registerBinaryStrategyCheck()

local runUntilDone = assert( WFRxRunUntilDone )
local check = assert( WFArguments.check)
local derefOpt = assert( KSTableUtils.derefOpt )

local Observable = Rx.Observable

-- The kinds of sync which can be run off this class
local kStoppedSync = 'stopped' -- This is only set when the client does it
local kStandardSync = 'standard'
local kStandardNoUploaderSync = 'standardNoUploader'
local kAccountsSync = 'accounts'
local kResidualModeSync = 'residual'
local kSpaceNoOzCatalogSync = 'spaceNoOzCatalog'

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

local function trackDocStoreSyncInitError()

    local errorEvent = {
	[ 'event.appWorkflow' ] = 'LAUNCH',
	[ 'event.subcategory' ] = 'SyncClient:init',
	[ 'event.error_type' ] = 'DocStore:SchemaConflict'
    }

    WFAnalytics.trackEvent( errorEvent )

end

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

local function trackSha256MasterUploadError( puller, docValue )

	if docValue then

		local docType = derefOpt( docValue, 'type' )

		if docType == 'asset' then

			local hasSha256Error = derefOpt( docValue, 'importSource', 'status', 'error', 'all', 'reason', 'sha256' ) == "does not match"

			if hasSha256Error then

				local uniqueDeviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )

				if uniqueDeviceId and ( uniqueDeviceId == WFDeviceInfo.uniqueDeviceId() ) then

					local errorEvent = {}

					errorEvent['event.type'] = 'error'
					errorEvent['event.appWorkflow'] = 'SYNC'
					errorEvent['event.subcategory'] = 'Error_handler'
					errorEvent['event.error_type'] = 'Sha256MismatchError'

					errorEvent['content.id'] = docValue._id
					errorEvent['content.type'] = derefOpt( docValue, 'subtype' )
					errorEvent['content.mimetype'] = derefOpt( docValue, 'importSource', 'contentType' )
					errorEvent['content.size'] = derefOpt( docValue, 'importSource', 'fileSize' )
					errorEvent['event.error_desc'] = derefOpt( docValue, 'importSource', 'sha256' )

					WFAnalytics.trackEvent( errorEvent )

				end

			end

		end

	end


end

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

local function albumAssetsForFilteredAssetsQueryFn( catalog, assetIds )

	return catalog:indexStore():queryOnce {
				sql = [[SELECT album_asset.docId, album_asset.assetId FROM album_asset WHERE album_asset.assetId IN CARRAY(?, ?, 'char*') ]],

				assetIds,
				#assetIds
		}

end

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

local function deleteAssets( catalog, assetIds )

	local overlay = {
		docType = 'asset',
		_deleted = true
	}

	return WFRxThrottledBatchedApplyOverlayToIds( "deleteAssets", catalog:docStore(), overlay, assetIds, 100, 10 )
		:call( runUntilDone )

end
--==============================================================================

-- @mark LrmSyncClientImp namespace

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

local syncClientImpMethods = {

	_syncClientBase = false,
		-- (WFCatSyncClientBase)

	_userId = false,
		--(string)

	_storageMonitor = false,
		-- (WFCatStorage)

	_binaryUploader = false,
		-- (WFCatBinaryUploader)

	_progressReporter = false,
		-- (WFProgressReporter) Reports back processed and total bytes of files
		-- currently being uploaded.

	_purgeAgent = false,
		-- (WFCatVolumeAgent)

	_errorHandler = false,
		-- (WFCatErrorHandler)

	_userSynchronizer = false,
		-- (WFCatUserSynchronizer)

	_conflictResolver = false,
		-- (WFCatConflictResolver)

	_catalogServiceViaWFCatalog = false,
		-- (OzCatalogServiceViaWFCatalog)

	_autoPurgeSubscription = false,
		-- (Rx Disposable)

	_videoGenerateProxiesForStoreLocallySubscription = false,
		-- (Rx Disposable)

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

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

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

	_purgeAgentStream = false,
		-- (ReplaySubject) Fires with the purge agent, once it has been initialized.

	_autoDeleter = false,
		-- (WFCatSyncAutoDeleter)

	_pendingCommitHandler = false,
		-- (WFCatPendingCommitHandler)

	_clientSyncAction = false,
		-- (string) Simple label to determine what the sync strategy has been specified by client interaction

	_lockedByWFAction = false,
		-- (sring) When set, the value of this property is the name of the lock source,
		--         either "client" or "wf". Locks the transiton from sync-off to sync-on.

	attachMessageHandlers = false,

	WFCloseSupport.addCloseSupportMethods,

}

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

function syncClientImpMethods:__init( options )

	check( 'LrmSyncClientImp', 'table' )

	-- Additional options for WFCatSyncClientBase

	options.noCatalogSearch = true
	options.pullerPrefetch = true
	options.pusherDelayBeforeSendingUpdates = 3
	options.albumAssetsForFilteredAssetsQueryFn = albumAssetsForFilteredAssetsQueryFn

	local syncClientBase = WFCatSyncClientBase( options )

	local catalog = syncClientBase:getCatalog()

	-- ConflictResolver

	local conflictResolver = WFCatConflictResolver( catalog:docStore(), catalog:indexStore() )

	-- Error Handler

	local errorHandler = WFCatErrorHandler( catalog, {
			externalizeSettings = LrmCatXmpDevelop.externalizeDevelopSettings,
			deleteAssets = deleteAssets,
			deleteIndividualAlbum = LrmCatAlbum.deleteAlbum,
			getSyncStatusOfAssetsOfAlbumAssets = LrmCatSyncStatus.getSyncStatusOfAssetsOfAlbumAssets,
			getSyncStatusOfAlbumsOfAlbumAssets = LrmCatSyncStatus.getSyncStatusOfAlbumsOfAlbumAssets,
			getSyncStatusOfParentCollectionOfAlbums = LrmCatSyncStatus.getSyncStatusOfParentCollectionOfAlbums,
			getSyncStatusOfAssetsOfVersions = LrmCatSyncStatus.getSyncStatusOfAssetsOfVersions,
			getSyncStatusOfAssetsOfComments = LrmCatSyncStatus.getSyncStatusOfAssetsOfComments,
			getSyncStatusOfAssetsOfFavorites = LrmCatSyncStatus.getSyncStatusOfAssetsOfFavorites,
		} )

	-- Custom doc handling

	local catalogPuller = syncClientBase:getCatalogPuller()

	catalogPuller:addAssetTypeHandler( 'asset', trackSha256MasterUploadError )

	local catalogPusher = syncClientBase:getCatalogPusher()

	self._userId = assert( options.userId )

	local function remapAssetDocForUpdate( _, localDoc, payload )

		OzSyncMetadataMapper.remapGlobalDocumentId( self._userId, payload )

	end

	if catalogPusher then
		catalogPusher:addUpdateDocRemapFunction( 'asset', remapAssetDocForUpdate )
	end

	-- Cloud Trash Auto-Purge

	local isAuthenticatedUser = self._userId ~= WFOzSyncConstants.kGlobalDocumentId

	local catalogId = assert( options.catalogId )

	local user = catalog:ozUser()

	local hasValidConnectionToOz = (isAuthenticatedUser and
									catalogId ~= WFOzSyncConstants.kGlobalDocumentId) and	-- CatalogId check is needed for Created users as they have valid connection to Oz but no catalog on Oz.
									user:hasValidConnectionToOz() or
									Rx.Observable.just( false )

	self._autoPurgeSubscription = Rx.Observable.combineLatest(

			function( isSubscriptionExpired, hasValidConnectionToOz )

				if isSubscriptionExpired or not hasValidConnectionToOz then
					return true
				end

				return false

			end,
			user:isSubscriptionExpired(), hasValidConnectionToOz
		)
		:distinctUntilChanged()
		:flatMap( function( autoPurgeAll )

			local autoPurgeOnlyIfNotOnOz = not autoPurgeAll
			return WFCatTrash.autoPurge( catalog, autoPurgeOnlyIfNotOnOz )

		end )
		:subscribe()

	self._isNormalSyncRunningStream = Rx.BehaviorSubject( false )

	-- User Synchronizer

	local userSynchronizer = WFCatUserSynchronizer( user )

	-- Binary Uploader

	local ozCatalog = OzCatalogServiceViaWFCatalog( Rx.Observable.just( catalog ) )

	local progressReporter = WFProgressReporter( "Binary Upload Progress Report" )
	self._progressReporter = progressReporter

	local uploadOptions = {
		deriveCatalogBasePath = true,
		progressReporter = progressReporter,
	}

	local catSpec = syncClientBase:getCatSpec()

	-- Binary Uploader
	local binaryUploader = WFCatBinaryUploader( catalog, nil, nil, catSpec, uploadOptions )

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

	self._checkOzForCatalogUpdatesSubscription = binaryUploader:binaryUploadCompletionStream()
		:subscribe( function()
			syncClientBase:checkOzForUpdates()
		end )

	-- Quota Monitoring

	local storageMonitor = WFCatStorage( catalog )

	if catalogPusher then

		-- monitor quota

		local quotaMonitorSubscription = storageMonitor:isOverStorage()
			:distinctUntilChanged()
			:subscribe( function( isOverStorage )
				catalogPusher:setUserIsOverQuota( isOverStorage )
			end )

		-- monitor expiration

		local userExpiredSubscription = user:isSubscriptionExpired()
			:distinctUntilChanged()
			:subscribe( function( isExpired )
				catalogPusher:setUserIsExpired( isExpired )
			end )

		ozCatalog:addCloseAction( function()

			quotaMonitorSubscription:dispose()
			userExpiredSubscription:dispose()

		end )

	end

	self._purgeAgentStream = Rx.ReplaySubject( 1 )
	self._purgeAgentStream:onNext( false )

	local function trackPurgeAgent( purgeAgent )

		self._purgeAgentStream:onNext( purgeAgent )

		self:setOwned {
			_purgeAgent = purgeAgent,
		}

	end

	local catalogPath = catalog:fileSpec():catalogPath()
	local purgeAgent
	local pendingCommitHandler

	if WFPlatformInfo.isAndroid then

		ozCatalog:getDataCache()

			:map( function( dataCache )

				purgeAgent = WFCatVolumeAgent( catalog, catalogPath, nil, nil, dataCache )
				trackPurgeAgent( purgeAgent )

				pendingCommitHandler = WFCatPendingCommitHandler( catalog, purgeAgent )
				pendingCommitHandler:startCleanTask()

				-- Setup the Auto Deleter for Cascade Document Deletions

				local autoDeleter = WFCatSyncAutoDeleter( catalog, nil, dataCache )

				self:setOwned {
					_autoDeleter = autoDeleter,
				}

			end )

			:call( runUntilDone )

	else

		purgeAgent = WFCatVolumeAgent( catalog, catalogPath )
		trackPurgeAgent( purgeAgent )

		pendingCommitHandler = WFCatPendingCommitHandler( catalog, purgeAgent )
		pendingCommitHandler:startCleanTask()

		-- Setup the Auto Deleter for Cascade Document Deletions

		local autoDeleter = WFCatSyncAutoDeleter( catalog )

		self:setOwned {
			_autoDeleter = autoDeleter,
		}

	end

	self:setOwned {
		_syncClientBase = syncClientBase,
		_storageMonitor = storageMonitor,
		_binaryUploader = binaryUploader,
		_errorHandler = errorHandler,
		_userSynchronizer = userSynchronizer,
		_conflictResolver = conflictResolver,
		_catalogServiceViaWFCatalog = ozCatalog,
		_pendingCommitHandler = pendingCommitHandler,
	}

	-- Stuck Sync Analytics

	local kStuckSyncAnalyticsInterval = 600 -- seconds

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

				:calm( kStuckSyncAnalyticsInterval, WFRxScheduler.sharedScheduler() )

				:flatMapLatest( function( stuckDocIds )

					return WFCatStuckSyncUtils.resolveStuckSync( catalog, stuckDocIds )

				end )

				:subscribe()

	-- Register a tracking event for schema conflicts
	WFRxDocStoreMessageProcessor.onSchemaConflictWFRxDocStoreImp( trackDocStoreSyncInitError )

end

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

function syncClientImpMethods:__index( k )

	-- When code attempts to call an undefined method on a LrmSyncClientImp object,
	-- this function is called instead. If the below code determines that the
	-- WFCatSyncClientBase class has such a method, then that method is called. With
	-- this, all the class methods defined within WFCatSyncClientBase are automatically
	-- exposed as class methods of LrmSyncClientImp.

	-- Some of the WFCatSyncClientBase methods, such as startSync, require
	-- special handling within LrmSyncClientImp. You'll see these methods
	-- defined later in this file. All manually-defined methods take precedent
	-- over the metamethod mappings below.

	if not k then
		return
	end

	if self._syncClientBase:hasMethod( k ) then

		local syncClientBaseFn = function( _, ... )

			logger:infof( "Now calling WFCatSyncClientBase:%s via LrmSyncClientImp", k )

			return self._syncClientBase[ k ]( self._syncClientBase, ... )

		end

		return syncClientBaseFn

	else

		local errorFn = function( _, ... )

			local warning = string.format( "Unable to call unknown method: %s", k  )

			logger:warn( warning )

			return Rx.Observable.throw( warning )

		end

		return errorFn

	end

end

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

function syncClientImpMethods:getSyncClientBaseMethodList()

	check( 'LrmSyncClientImp' )

	-- This function returns a list of methods on the WFCatSyncClientBase object.
	-- It is used to build up the message handlers within LrmSyncClientMessageProcessor.

	return self._syncClientBase.listMethods()

end

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

function syncClientImpMethods:close()

	check( 'LrmSyncClientImp' )

	if self._autoPurgeSubscription then

		self._autoPurgeSubscription:dispose()
		self._autoPurgeSubscription = nil

	end

	if self._checkOzForCatalogUpdatesSubscription then

		self._checkOzForCatalogUpdatesSubscription:dispose()
		self._checkOzForCatalogUpdatesSubscription = nil

	end

	if self._videoGenerateProxiesForStoreLocallySubscription then

		self._videoGenerateProxiesForStoreLocallySubscription:dispose()
		self._videoGenerateProxiesForStoreLocallySubscription = nil

	end

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

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

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

end

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

local function getPurgeAgent( self )

	return self._purgeAgentStream

		:filter( function( purgeAgent )
			return not not purgeAgent
		end )

		:take( 1 )

		:replay( nil, 1 )

		:refCount()

end

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

local function startVideoGenerateProxyForStoreLocally( self )

	local enableVideoStoreLocally = WFCapabilities.getCapability( "WFCatVideo.enableVideoStoreLocally" ) == 'true'

	if enableVideoStoreLocally and not self._videoGenerateProxiesForStoreLocallySubscription then

		local wfCatalog = self._syncClientBase:getCatalog()

		self._videoGenerateProxiesForStoreLocallySubscription =
			WFCatVideo.generateProxiesForStoreLocally( wfCatalog )
			:subscribe()

	end

end

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

local function stopVideoGenerateProxyForStoreLocally( self )

	if self._videoGenerateProxiesForStoreLocallySubscription then

		-- self._videoGenerateProxiesForStoreLocallySubscription is set to false
		-- because if it's nil, self._videoGenerateProxiesForStoreLocallySubscription will
		-- return errorFn at line 537

		self._videoGenerateProxiesForStoreLocallySubscription:dispose()
		self._videoGenerateProxiesForStoreLocallySubscription = false

	end

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

function syncClientImpMethods:isPushingMetadata()

	-- Though this method calls a WFCatSyncClientBase method directly without
	-- any special handling, we must define it manually because the method
	-- names don't match. This is also true for isPullingMetadata.

	check( 'LrmSyncClientImp' )

	return self._syncClientBase:isUploadingMetadata()

end

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

function syncClientImpMethods:isPullingMetadata()

	check( 'LrmSyncClientImp' )

	return self._syncClientBase:isDownloadingMetadata()

end

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

function syncClientImpMethods:getDeleteFeedCallback()

    check( 'LrmSyncClientImp' )

    if self._purgeAgent then

		return self._purgeAgent:getPresetProfileDeletionStream()

    end

    return Observable.empty()

end

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

function syncClientImpMethods:startSync()

	check( 'LrmSyncClientImp' )

	self._clientSyncAction = kStandardSync

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	self._isNormalSyncRunningStream:onNext( true )

	self._binaryUploader:startContinuousUploads()

	startVideoGenerateProxyForStoreLocally( self )

	return self._syncClientBase:startSync():concat( Observable.just( true ) )

end

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

function syncClientImpMethods:startSyncWithoutUploader()

	check( 'LrmSyncClientImp' )

	self._clientSyncAction = kStandardNoUploaderSync

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	self._isNormalSyncRunningStream:onNext( false )

	startVideoGenerateProxyForStoreLocally( self )

	return self._syncClientBase:startSync():concat( Observable.just( true ) )

end

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

function syncClientImpMethods:setBinaryStrategy( strategy )

	check( 'LrmSyncClientImp', '+string' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:setBinaryStrategy( strategy )
		end )

end

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

function syncClientImpMethods:startAccountsSync()

	check( 'LrmSyncClientImp' )

	self._clientSyncAction = kAccountsSync

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	local isAuthenticatedUser = self._userId ~= WFOzSyncConstants.kGlobalDocumentId

	if isAuthenticatedUser then
		self._userSynchronizer:startContinuousUpdates()
	end

	return Observable.just( true )

end

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

function syncClientImpMethods:stopSync_INTERNAL()

	check( 'LrmSyncClientImp' )

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	self._isNormalSyncRunningStream:onNext( false )

	self._binaryUploader:stopContinuousUploads()

	stopVideoGenerateProxyForStoreLocally( self )

	return self._syncClientBase:stopSync()
end

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

function syncClientImpMethods:stopSync()

	check( 'LrmSyncClientImp' )

	-- Set the client directed action - this is the exernal API
	self._clientSyncAction = kStoppedSync

	return self:stopSync_INTERNAL()
end


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

function syncClientImpMethods:stopAccountsSync()

	check( 'LrmSyncClientImp' )

	self._userSynchronizer:stopContinuousUpdates()

	return Observable.just( true )

end

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

function syncClientImpMethods:startSyncInResidualMode()

	check( 'LrmSyncClientImp' )

	self._clientSyncAction = kResidualModeSync

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	self._isNormalSyncRunningStream:onNext( false )

	self._binaryUploader:startContinuousUploadOfOnlySpaceAssets()

	startVideoGenerateProxyForStoreLocally( self )

	return self._syncClientBase:startSyncInResidualMode()

end

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

function syncClientImpMethods:startSpaceSyncWithoutOzCatalog()

	check( 'LrmSyncClientImp' )

	self._clientSyncAction = kSpaceNoOzCatalogSync

	if self._lockedByWFAction then

		return Observable.just( false )

	end

	self._isNormalSyncRunningStream:onNext( false )

	self._binaryUploader:startContinuousUploadOfOnlySpaceAssets()

	startVideoGenerateProxyForStoreLocally( self )

	return self._syncClientBase:startSpaceMetadataSyncInFreemiumMode()

end

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

function syncClientImpMethods:enableBackgroundUpload( enable )

	check( 'LrmSyncClientImp', '?boolean' )

	enable = enable or false
	self._binaryUploader:enableBackgroundUpload( enable )

	return Observable.just( true )

end

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

function syncClientImpMethods:getDetailedUploadProgress()

	check( 'LrmSyncClientImp' )

	local function distinctTableCompare( _, tbl1, _, tbl2 )
		return KSTableUtils.deepEquals( tbl1, tbl2 )
	end

	return self._progressReporter:status()

		:distinctUntilChanged( nil, distinctTableCompare )

end

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

function syncClientImpMethods:getOzMaintenanceNotificationStream( optArgs )

	check( 'LrmSyncClientImp', '?table' )

	if not self._ozMaintenanceNotificationStream then

		local catalog = self._syncClientBase:getCatalog()

		local catalogOzMaintenanceNotificationStream
				= WFCatOzMaintenanceTracker.getOzMaintenanceNotificationStream( catalog, optArgs )

		local spaceFeedsMaintenanceNotificationStream
				= self._syncClientBase:spaceFeedsMaintenanceNotificationStream()

		self:addCloseAction( function()

			catalogOzMaintenanceNotificationStream:dispose()

		end )

		self._ozMaintenanceNotificationStream =
				catalogOzMaintenanceNotificationStream
					:merge( spaceFeedsMaintenanceNotificationStream )

	end

	return self._ozMaintenanceNotificationStream

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

	check( 'LrmSyncClientImp', '?number' )

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

		:map( function( metadataSyncDiagnostics,
						binaryUploaderSyncDiagnostics )

			return {

				metadataSyncDiagnostics = metadataSyncDiagnostics,
				binaryUploaderSyncDiagnostics = binaryUploaderSyncDiagnostics,

			}

		end )

end

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

--[[!
	\brief Fetch a stream that fires true when sync is running, false otherwise.

	\return Observable< boolean >
--]]

function syncClientImpMethods:isNormalSyncRunningStream()

	check( 'LrmSyncClientImp' )

	return self._isNormalSyncRunningStream

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 syncClientImpMethods:getEstimatedTimeRemainingForUploads()

	return self._binaryUploader:getEstimatedTimeRemaining()

end

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

--[[!
	\brief Starts the purge agent.
	\return Observable<boolean> Fires with true once complete.
--]]

function syncClientImpMethods:startContinuousPurge()

	check( 'LrmSyncClientImp' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:start()
		end )

end

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

--[[!
	\brief Stops the purge agent.
	\return Observable<boolean> Fires with true once complete.
--]]

function syncClientImpMethods:stopContinuousPurge()

	check( 'LrmSyncClientImp' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:stop()
		end )

end

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

--[[!

	\brief Force the purge agent to purge all purgeable binaries within the
			photo cache.

	\return Observable<true> Fires with true upon completion.

--]]

function syncClientImpMethods:purgeImmediately()

	check( 'LrmSyncClientImp' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:clearCache()
		end )

end

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

--[[!

	\brief Ask the volume agent to provide n bytes of free space.

	\param desc (string) description of operation to use for debugging and diagnostic logs
	\param bytes (number) The number of bytes needed.
	\param forcePurge (boolean) If true, this API will purge binaries to make
		space, even if the resulting amount of free space is less than
		the requested bytes. If false or nil, this API will _only_ perform any
		purging in the event that the amount of space able to reclaimed
		is the equal to or greater than the requested bytes.

	\return Observable<number> The amount of free space, in bytes, that can be provided.

--]]

function syncClientImpMethods:tryToClaimSpace( desc, bytes, forcePurge )

	check( 'LrmSyncClientImp', 'string', 'number', '?boolean' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:tryToClaimSpace( desc, bytes, forcePurge )
		end )

end

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

--[[!
	\brief Updates the target cache size.
	\param targetCacheSize (number)
	\return Observable<boolean> Fires with true once complete.
--]]

function syncClientImpMethods:setPhotoCacheSize( targetCacheSize )

	check( 'LrmSyncClientImp', 'number' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:setPhotoCacheSize( targetCacheSize )
		end )

end

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

--[[!
	\brief Get the maximum size of the photo cache.
	\return Observable<number> Size in bytes.
--]]

function syncClientImpMethods:getMaximumPhotoCacheSize()

	check( 'LrmSyncClientImp' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:getMaximumPhotoCacheSize()
		end )

end

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

--[[!

	\brief Purge an external develop settings file from disk if it has no
		references.

	\param externalDevelopSettingsFilename (string) File name.

	\return Observable<boolean> Fires with true once complete.

--]]

function syncClientImpMethods:maybePurgeExternalDevelopSettingsFile( externalDevelopSettingsFilename )

	check( 'LrmSyncClientImp', '+string' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:maybePurgeExternalDevelopSettingsFile( externalDevelopSettingsFilename )
		end )

end

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

--[[!

	\brief Adds an asset to a ring buffer. Entries within the ring buffer are
		protected from purge. The ring buffer has a fixed size, and the oldest
		entries are cleared when the ring buffer grows too large.

	The primary use case for this API is to protect recently accessed and/or
		downloaded assets from purge.

	\param assetId (+string|number) The asset doc Id or local Id.

	\return Observable<boolean> Fires with true once complete.

--]]

function syncClientImpMethods:addToProtectedRingBuffer( assetId )

	check( 'LrmSyncClientImp', '+string|number' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:addToProtectedRingBuffer( assetId )
		end )

end

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

--[[!

	\brief Protect a list of assets from various WF operations. This includes
		purge, as well as other tasks that should only operate on assets that
		are not currently in use.

		This API should be used during edit, and with operations such as export
		to prevent certain required binaries from being removed from the system.

	\param assetIds (table) A table containing asset ids or local ids.

	\return Observable<boolean> Fires with true once complete.

--]]

function syncClientImpMethods:lockAssets( assetIds )

	check( 'LrmSyncClientImp', 'table' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:protectFromPurge( assetIds )
		end )

end

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

--[[!

	\brief Releases protection for the specific assets.
		See syncClientImpMethods.lockAssets for more info.

	\param assetIds (table) A table containing asset ids or local ids.

	\return Observable<boolean> Fires with true once complete.

--]]

function syncClientImpMethods:unlockAssets( assetIds )

	check( 'LrmSyncClientImp', 'table' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:releaseProtectionFromPurge( assetIds )
		end )

end

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

--[[!

	\brief Returns a stream that fires periodically with the status of the disk.
	\return (Rx ReplaySubject) Each event is a table containing the following:

		{
			isOutOfSpace = <boolean>,
			isOverTargetDiskSpaceUsage = <boolean>,
			remainingSpace = <bytes>
		}

--]]

function syncClientImpMethods:getVolumeStatusStream()

	check( 'LrmSyncClientImp' )

	return getPurgeAgent( self )
		:flatMap( function( purgeAgent )
			return purgeAgent:getVolumeStatusStream()
		end )

end

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

--[[

	\brief Resumes the sync client to continue whatever sync
	action was in progress when the sync client was last
	stopped. If there was no previous action, start standard sync.

]]
function syncClientImpMethods:resume()

	check( 'LrmSyncClientImp' )

	if self._clientSyncAction == kStandardNoUploaderSync then
		return self:startSyncWithoutUploader()
	end

	if self._clientSyncAction == kAccountsSync then
		return self:startAccountsSync()
	end

	if self._clientSyncAction == kResidualModeSync then
		return self:startSyncInResidualMode()
	end

	if self._clientSyncAction == kSpaceNoOzCatalogSync then
		return self:startSpaceSyncWithoutOzCatalog()
	end

	if self._clientSyncAction == kStoppedSync then
		-- if previously stopped, stay stopped
		return Observable.just( false )
	end

	-- Base case kStandardSync

	return self:startSync()

end

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

function syncClientImpMethods:lockSync()

	self._lockedByWFAction = true

	return Observable.just( self._lockedByWFAction )

end

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

function syncClientImpMethods:unlockSync()

	self._lockedByWFAction = false

	return Observable.just( self._lockedByWFAction )

end

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

function syncClientImpMethods:getSQLiteFatalErrorCodeStream()

	check( 'LrmSyncClientImp' )

	return self._syncClientBase:getSQLiteFatalErrorCodeStream()

end

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

function syncClientImpMethods:_DEBUG_SyncClientReturnsFoo()

	check( 'LrmSyncClientImp' )

	return Observable.just( 'foo' )

end

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

function syncClientImpMethods:_DEBUG_getClientSyncAction()

	check( 'LrmSyncClientImp' )

	return self._clientSyncAction

end

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

return Rx.Class( 'LrmSyncClientImp', syncClientImpMethods )
