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

WFCatSyncStatus.lua

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

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

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

	-- Rx
local Rx = require 'Rx'

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

	-- WF.oz_client.managed_catalog
local WFCatIndexUtils = require 'WFCatIndexUtils'
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatOzErrors = require 'WFCatOzErrors'
local WFCatTrashUtils = require 'WFCatTrashUtils'
local NmbFileManagementSettings = require 'NmbFileManagementSettings'

	-- WF.oz_client.shared.utils
local LrAssetUtils = require 'LrAssetUtils'

	-- WF.sqlite.database
local WFSQLiteUtils = require 'WFSQLiteUtils'

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

local isAssetDocument = assert( WFCatIndexUtils.isAssetDocument )
local isValidDisplayableAssetDocument = assert( WFCatIndexUtils.isValidDisplayableAssetDocument )
local isFileBasedAssetDocument = assert( WFCatIndexUtils.isFileBasedAssetDocument )
local isVideoDocument = assert( WFCatIndexUtils.isVideoDocument )

local WFCatTrashUtils_isDeleteTrash = assert( WFCatTrashUtils.isDeleteTrash )

local derefOpt = KSTableUtils.derefOpt

local isLatestMetadataUploaded = assert( LrAssetUtils.isLatestMetadataUploaded )

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

local WFCatSyncStatus = {}

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

local kAssetNeedsMetadataUpload = 'NeedsMetadataUploaded'
local kAssetNeedsOriginalUpload = 'NeedsOriginalUploaded'
local kAssetNeedsOriginalDownload = 'NeedsOriginalDownloaded'
local kAssetNeedsOriginalDownloadForOffline = 'NeedsOriginalDownloadedForOffline'
local kAssetNeedsProxyDownloadForOffline = 'NeedsProxyDownloadedForOffline'
local kAssetNeedsProxyDownload = 'NeedsProxyDownloaded'
local kAssetNeedsDevelopXmpDownload = 'NeedsDevelopXmpDownloaded'
local kAssetComplete = 'Completed'
local kAssetCompleteNoMaster = 'CompletedNoMaster'
local kAssetCompleteNoNegatives = 'CompletedNoNegatives'
local kAssetCompleteForOffline = 'CompletedForOffline'
local kAssetStorageLimitExceeded = 'StorageLimitExceeded'
local kAssetInvalidOriginal = 'InvalidLocalOriginal'
local kAssetOriginalIsOnOfflineVolume = 'OriginalIsOnOfflineVolume'
local kAssetProxyIsOnOfflineVolume = 'ProxyIsOnOfflineVolume'
local kAssetNeedsOriginalDownloadToOfflineVolume = 'NeedsOriginalDownloadToOfflineVolume'
local kAssetNeedsProxyDownloadToOfflineVolume = 'NeedsProxyDownloadToOfflineVolume'
local kAssetNeedsOriginalUploadFromOfflineVolume = 'NeedsOriginalUploadedButOnOfflineVolume'
local kAssetNeedsOriginalMigration = 'NeedsOriginalMigration'
local kAssetNeedsProxyMigration = 'NeedsProxyMigration'
local kAssetOriginalDownloadError = 'OriginalDownloadError'
local kAssetProxyDownloadError = 'ProxyDownloadError'
local kAssetDevelopXmpDownloadError = 'DevelopXmpDownloadError'
local kAssetNeedsOriginalDownloadToOutOfSpaceVolume = 'NeedsOriginalDownloadToOutOfSpaceVolume'
local kAssetNeedsProxyDownloadToOutOfSpaceVolume = 'NeedsOriginalProxyToOutOfSpaceVolume'
local kAssetSyncPaused = 'SyncPaused'
local kAssetSyncNoConnection = 'SyncNoConnection'
local kAssetTrialOrSubscriptionExpired = 'TrialOrSubscriptionExpired'
local kAssetStackComplete = 'StackComplete'
local kAssetInsufficientPermissionError = 'InsufficientPermissionError'

local kAssetSyncStatusById = {
	kAssetNeedsMetadataUpload,						-- 1
	kAssetNeedsOriginalUpload,						-- 2
	kAssetNeedsOriginalDownload,					-- 3
	kAssetNeedsOriginalDownloadForOffline,			-- 4
	kAssetNeedsProxyDownload,						-- 5
	kAssetNeedsDevelopXmpDownload,					-- 6
	kAssetComplete,									-- 7
	kAssetCompleteNoMaster,							-- 8
	kAssetStorageLimitExceeded,						-- 9
	kAssetInvalidOriginal,							-- 10
	kAssetOriginalIsOnOfflineVolume,            	-- 11
	kAssetProxyIsOnOfflineVolume,            		-- 12
	kAssetNeedsOriginalDownloadToOfflineVolume, 	-- 13
	kAssetNeedsProxyDownloadToOfflineVolume,		-- 14
	kAssetNeedsOriginalMigration,					-- 15
	kAssetNeedsProxyMigration,						-- 16
	kAssetNeedsOriginalUploadFromOfflineVolume,		-- 17
	kAssetOriginalDownloadError, 					-- 18
	kAssetProxyDownloadError,						-- 19
	kAssetDevelopXmpDownloadError,					-- 20
	kAssetNeedsOriginalDownloadToOutOfSpaceVolume, 	-- 21
	kAssetNeedsProxyDownloadToOutOfSpaceVolume, 	-- 22
	kAssetSyncPaused,								-- 23
	kAssetSyncNoConnection,							-- 24
	kAssetNeedsProxyDownloadForOffline,				-- 25
	kAssetTrialOrSubscriptionExpired,				-- 26
	kAssetStackComplete,							-- 27
	kAssetCompleteForOffline,						-- 28
	kAssetCompleteNoNegatives,						-- 29
	kAssetInsufficientPermissionError,				-- 30
}

local kAssetSyncStatus = KSTableUtils.invert( kAssetSyncStatusById )

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

local kAssetHasLocalOriginal = 'OriginalOnDisk'
local kAssetHasLocalOriginalForOffline = 'OriginalOnDiskForOffline'
local kAssetHasLocalProxy = 'ProxyOnDisk'
local kAssetHasNothing = 'NothingOnDisk'

local kAssetLocalFileTypeById = {
	kAssetHasLocalOriginal,				-- 1
	kAssetHasLocalOriginalForOffline,	-- 2
	kAssetHasLocalProxy,				-- 3
	kAssetHasNothing,					-- 4
}

local kAssetLocalFileType = KSTableUtils.invert( kAssetLocalFileTypeById )

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

local kAlbumSyncing = 'syncing'
local kAlbumComplete = 'synced'
local kAlbumSyncPaused = 'paused'
local kAlbumSyncNoConnection = 'offline'
local kAlbumSyncingForOfflineUse = 'syncingForOfflineUse'
local kAlbumCompleteForOfflineUse = 'syncedForOfflineUse'
local kAlbumSyncingForOfflineUseToOfflineVolume = 'syncingForOfflineUseToOfflineVolume'
local kAlbumSyncingForOfflineUseToOutOfSpaceVolume = 'syncingForOfflineUseToOutOfSpaceVolume'

local kAlbumSyncStatusById = {
	kAlbumSyncing,									-- 1
	kAlbumComplete,									-- 2
	kAlbumSyncPaused,   							-- 3
	kAlbumSyncNoConnection, 						-- 4
	kAlbumSyncingForOfflineUse, 					-- 5
	kAlbumCompleteForOfflineUse,					-- 6
	kAlbumSyncingForOfflineUseToOfflineVolume, 		-- 7
	kAlbumSyncingForOfflineUseToOutOfSpaceVolume 	-- 8
}

local kAlbumSyncStatus = KSTableUtils.invert( kAlbumSyncStatusById )

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

local kOzHasOriginal = 'OzHasOriginal'
local kOzHasProxy = 'OzHasSmartPreview'
local kOzHasNothing = 'OzHasNothing'

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

local function docValueHasLocalOriginal( docValue )

	local localOriginalVolumeGuid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue )
	local localOriginalRelativePath = WFCatFileSpec.getOriginalRelativePathFromDocValue( docValue )
	return localOriginalVolumeGuid and localOriginalRelativePath

end

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

local function docValueHasLocalProxy( docValue )

	local localProxyVolumeGuid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue )
	local localProxyRelativePath = WFCatFileSpec.getProxyRelativePathFromDocValue( docValue )
	return localProxyVolumeGuid and localProxyRelativePath

end

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

function WFCatSyncStatus.whatToDownload(
		keepAllOriginals,
		keepAllProxies,
		needsOriginalDownload, 
		hasLocalOriginal,
		needsProxyDownload, 
		hasLocalProxy,
		needsDevelopXmpDownload,
		isMarkedForOffline,
		originalVolumeOfflineOrFull,
		proxyVolumeOfflineOrFull,
		ozHasNonThumbnailRendition,
		waitingForProxyOnOz )

	local maybeBinaryType

	local shouldDownloadOriginal = needsOriginalDownload and not originalVolumeOfflineOrFull
	local shouldDownloadProxy = needsProxyDownload and not proxyVolumeOfflineOrFull and not waitingForProxyOnOz
	local needNegativeForRender = not ozHasNonThumbnailRendition and not hasLocalOriginal and not hasLocalProxy
	
	if keepAllOriginals or isMarkedForOffline then

		if needsDevelopXmpDownload then
		
			-- 1st Download the xmp develop settings.
		
			maybeBinaryType = 'xmp_develop'

		elseif shouldDownloadOriginal then
		
			-- 2nd Download the original, if we can.
		
			maybeBinaryType = 'original'
			
		elseif shouldDownloadProxy then
		
			-- 3rd Download the proxy
			
			maybeBinaryType = 'proxy'
			
		end

	elseif needNegativeForRender then
	
		if needsDevelopXmpDownload then
		
			-- 1st Download the xmp develop settings.
	
			maybeBinaryType = 'xmp_develop'

		elseif shouldDownloadProxy then
		
			-- 2nd First try to download the proxy. 
			
			maybeBinaryType = 'proxy'
			
		elseif shouldDownloadOriginal then
		
			-- If we can't download it, download the original.
		
			maybeBinaryType = 'original'
		
		end
	
	else
	
		if needsDevelopXmpDownload then

			-- 1st Download the xmp develop settings.
	
			maybeBinaryType = 'xmp_develop'

		elseif needsProxyDownload then
		
			if not proxyVolumeOfflineOrFull and not waitingForProxyOnOz then
	
				-- 2nd Download the proxy, if we can.
		
				maybeBinaryType = 'proxy'
			
			end
	
		elseif not hasLocalProxy and shouldDownloadOriginal then
		
			-- If we can't download the proxy because it's not on Oz, then
			-- download the original.
		
			maybeBinaryType = 'original'
		
		end
		
	end
	
	-- A binary is "required" if:
	-- it is marked for offline use.
	-- a negative is needed to render
	-- it is an xmp develop settings and we're keeping all originals or proxies.
	-- it is an original and we're keeping all originals.
	-- it is a proxy and we're keeping all originals, but there's no original on Oz.
	-- it is a proxy and we're keeping all proxies.
	-- it is an original and we're keeping all proxies, but there's no proxy on Oz.
	
	local isBinaryRequired = false
	
	if isMarkedForOffline or
		needNegativeForRender or
		( maybeBinaryType == 'xmp_develop' and ( keepAllOriginals or keepAllProxies ) ) or
		( maybeBinaryType == 'original' and keepAllOriginals ) or
		( maybeBinaryType == 'proxy' and keepAllProxies ) or
		( maybeBinaryType == 'proxy' and keepAllOriginals and not hasLocalOriginal and not shouldDownloadOriginal ) or
		( maybeBinaryType == 'original' and keepAllProxies and not hasLocalProxy and not shouldDownloadProxy ) then
			isBinaryRequired = true
	end
	
	return maybeBinaryType, isBinaryRequired

end 

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

local function assetNeeded( docValue, keepAllProxies, keepAllOriginals )

	local ozHasURIForOriginal = WFCatFileSpec.getOriginalLinkFromDocValue( docValue ) ~= nil
	local haveLocalPathForOriginal = docValueHasLocalOriginal( docValue )
	local needsOriginalDownload = not haveLocalPathForOriginal and ozHasURIForOriginal 

	local ozHasURIForProxy = WFCatFileSpec.getProxyLinkFromDocValue( docValue ) ~= nil
	local haveLocalPathForProxy = docValueHasLocalProxy( docValue )
	local needsProxyDownload = not haveLocalPathForOriginal and not haveLocalPathForProxy and ozHasURIForProxy
	
	local haveLocalPathForXmp = WFCatFileSpec.getXmpDevelopInfoFromDocValue( docValue ) ~= nil
	local ozHasURIForXmp = WFCatFileSpec.getXmpDevelopLinkFromDocValue( docValue ) ~= nil
	local needsDevelopXmpDownload = not haveLocalPathForXmp and ozHasURIForXmp 
	
	local isMarkedForOffline = WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue )

	local sufficientRenditionFoundOnOz = WFCatFileSpec.checkOzForNonThumbnailRendition( docValue )

	local waitingForProxyOnOz = derefOpt( docValue, '_serverProvidedMetadata', 'startedWaitingForProxyTimestamp' )
	
	-- SPAM [jandrew 2017-09-12]: We pass `nil` for two of the parameters because they should be
	-- ignored when calculating what we NEED to sync as opposed to what we SHOULD try to sync;
	-- the difference between "need" and "should" in this context is that an asset that needs
	-- syncing still needs to be synced even if there's no disk space available. On the other hand,
	-- an asset that needs to be synced SHOULD NOT be synced if there's no disk space available.

	local binaryType, isBinaryRequired = WFCatSyncStatus.whatToDownload(
		keepAllOriginals,
		keepAllProxies,
		needsOriginalDownload,
		haveLocalPathForOriginal,
		needsProxyDownload,
		haveLocalPathForProxy,
		needsDevelopXmpDownload,
		isMarkedForOffline,
		nil, -- originalVolumeOfflineOrFull.
		nil, -- proxyVolumeOfflineOrFull
		sufficientRenditionFoundOnOz,
		waitingForProxyOnOz )

	-- Sync status is not affected by purely idle downloads. It's only affected by idle
	-- downloads that are required. 
	
	if not isBinaryRequired then 
		binaryType = nil
	end

	return binaryType

end 

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

local function isStack( docValue )
	
	return not not ( docValue.subtype == 'stack' )

end 

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

local function doesAssetNeedOriginalUploaded( docValue )
	
	local ozMasterUrl, isOzOriginalInvalid = WFCatFileSpec.getOriginalLinkFromDocValue( docValue )
	local hasLocalOriginal = docValueHasLocalOriginal( docValue )
	local hasOriginalSourceLinkForCopy = derefOpt( docValue, '_localOnly', 'sourceOriginalLink' )
	
	local imageSha256 = derefOpt( docValue, 'importSource', 'sha256' )
	local localOriginalSha256 = hasLocalOriginal and WFCatFileSpec.getLocalOriginalSha256( docValue )
	local isLocalOriginalValid = localOriginalSha256 == imageSha256
	
	local isSpaceAsset = not not docValue._spaceId
	local isSpaceAssetWithImpliedMaster = isSpaceAsset and derefOpt( docValue, '_serverProvidedMetadata',
											 'links',
											 '/rels/master',
											 'exists' ) == true
	
	local ozHasMaster = ozMasterUrl or isSpaceAssetWithImpliedMaster
	
	local needsUpload = false
	
	if ( hasLocalOriginal or hasOriginalSourceLinkForCopy ) and not ozHasMaster then
	
		needsUpload = true
		
	elseif hasLocalOriginal and ozMasterUrl and isOzOriginalInvalid and isLocalOriginalValid then
	
		needsUpload = true
	
	end
	
	return needsUpload
	
end

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

local function isMarkedForOfflineUse( docValue )
	
	return WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue )

end

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

local function doesAssetNeedOriginalDownloaded( docValue, keepAllProxies, keepAllOriginals )
	
	return assetNeeded( docValue, keepAllProxies, keepAllOriginals ) == 'original'

end

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

local function doesAssetNeedProxyDownloaded( docValue, keepAllProxies, keepAllOriginals )

	return assetNeeded( docValue, keepAllProxies, keepAllOriginals ) == 'proxy'
	
end

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

local function doesAssetNeedDevelopXmpDownloaded( docValue, keepAllProxies, keepAllOriginals )
	
	return assetNeeded( docValue, keepAllProxies, keepAllOriginals ) == 'xmp_develop'
	
end

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

local function hasInvalidOriginalError( docValue )
	return KSTableUtils.derefOpt( docValue, '_localOnly', 'localOriginalError' )
end

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

local function hasOriginalDownloadError( docValue )

	local originalHref = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
	
	local permanentOriginalDownloadErrorForCurrentHref = KSTableUtils.derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/master', originalHref )
	
	return not not permanentOriginalDownloadErrorForCurrentHref
	
end

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

local function hasProxyDownloadError( docValue )
	
	local proxyRel
	if isVideoDocument( docValue ) then
		proxyRel = '/rels/rendition_type/720p'
	else
		proxyRel = '/rels/proxy_type/2560'
	end
	
	local proxyHref = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', proxyRel, 'href' )
	
	local permanentProxylDownloadErrorForCurrentHref = KSTableUtils.derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', proxyRel, proxyHref )
	
	return not not permanentProxylDownloadErrorForCurrentHref
end

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

local function hasXmpDevelopDownloadError( docValue )

	local xmpDevelopHref = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
	
	local permanentXmpDevelopDownloadErrorForCurrentHref = KSTableUtils.derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', 'rels/xmp/develop', xmpDevelopHref )
	
	return not not permanentXmpDevelopDownloadErrorForCurrentHref
	
end

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

function WFCatSyncStatus.getAssetStatus( docValue, keepAllProxies, keepAllOriginals )

	local assetSyncStatusId

	if docValue._deleted then

		local links = KSTableUtils.derefOpt( docValue,
									 '_serverProvidedMetadata',
									 'links' )

		if not links then
			-- This gets executed if you delete an asset that hasn't been
			-- uploaded yet, it is called by the _map function in asset_status_table().
			return nil
		end

		assetSyncStatusId = kAssetNeedsMetadataUpload

	elseif isStack( docValue ) then
		
		if not isLatestMetadataUploaded( docValue ) then
			assetSyncStatusId = kAssetNeedsMetadataUpload
		else
			assetSyncStatusId = kAssetStackComplete
		end
	
	else
	
		if not isLatestMetadataUploaded( docValue ) then

			if WFCatOzErrors.hasStorageExceededError( docValue ) then
				assetSyncStatusId = kAssetStorageLimitExceeded
			elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue ) 
					or WFCatOzErrors.hasEntitlementExpiredError( docValue ) then
				assetSyncStatusId = kAssetTrialOrSubscriptionExpired
			elseif WFCatOzErrors.hasInsufficientPermissionError( docValue ) then
				assetSyncStatusId = kAssetInsufficientPermissionError
			elseif hasInvalidOriginalError( docValue ) then
				assetSyncStatusId = kAssetInvalidOriginal
			else
				assetSyncStatusId = kAssetNeedsMetadataUpload
			end

		elseif doesAssetNeedOriginalUploaded( docValue ) then

			if hasInvalidOriginalError( docValue ) then
				assetSyncStatusId = kAssetInvalidOriginal
			elseif WFCatOzErrors.hasStorageExceededError( docValue ) then
				assetSyncStatusId = kAssetStorageLimitExceeded
			elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue )
					or WFCatOzErrors.hasEntitlementExpiredError( docValue ) then
				assetSyncStatusId = kAssetTrialOrSubscriptionExpired
			elseif WFCatOzErrors.hasInsufficientPermissionError( docValue ) then
				assetSyncStatusId = kAssetInsufficientPermissionError
			else
				assetSyncStatusId = kAssetNeedsOriginalUpload
			end

		elseif doesAssetNeedOriginalDownloaded( docValue, keepAllProxies, keepAllOriginals ) then
			
			if isMarkedForOfflineUse( docValue ) or keepAllOriginals then
				assetSyncStatusId = kAssetNeedsOriginalDownloadForOffline
			else
				assetSyncStatusId = kAssetNeedsOriginalDownload
			end

		elseif doesAssetNeedProxyDownloaded( docValue, keepAllProxies, keepAllOriginals ) then
			
			if isMarkedForOfflineUse( docValue ) or keepAllProxies then
				assetSyncStatusId = kAssetNeedsProxyDownloadForOffline
			else
				assetSyncStatusId = kAssetNeedsProxyDownload
			end
			
		elseif doesAssetNeedDevelopXmpDownloaded( docValue, keepAllProxies, keepAllOriginals ) then
			
			assetSyncStatusId = kAssetNeedsDevelopXmpDownload
		
		else
			
			if hasOriginalDownloadError( docValue ) then
				
				assetSyncStatusId = kAssetOriginalDownloadError
				
			elseif hasProxyDownloadError( docValue ) then
			
				assetSyncStatusId = kAssetProxyDownloadError
			
			elseif hasXmpDevelopDownloadError( docValue ) then
			
				assetSyncStatusId = kAssetDevelopXmpDownloadError
				
			elseif WFCatFileSpec.getOriginalLinkFromDocValue( docValue ) and 
				( isMarkedForOfflineUse( docValue ) or keepAllOriginals ) then
				
				assetSyncStatusId = kAssetCompleteForOffline
				
			elseif WFCatFileSpec.getOriginalLinkFromDocValue( docValue ) then
				
				assetSyncStatusId = kAssetComplete
			
			elseif WFCatFileSpec.getProxyLinkFromDocValue( docValue ) then
			
				if docValue._spaceId and docValue.subtype == 'video' then
				
					-- This addresses bug https://jira.corp.adobe.com/browse/WF-1611
					-- The kAssetCompleteNoMaster sync status maps to "Synced from Lr Classic"
					-- in client UI. This is not true for shared-with-you video assets.
					-- Shared-with-you video assets will not have an accessible original
					-- or a true proxy. Videos, in general, use the 720p as the 'proxy'. This
					-- 720p rendition is available for all shared-with-you album members.
					--
					-- In order to prevent shared-with-you videos from showing up as 
					-- "Synced from Lr Classic", we do not treat the presence of the 720p 
					-- rendition as if the asset has a proxy for shared videos.
					--
					-- Note: The bug only applies to shared-with-you viewers/contributors
					-- and not editors because editors have access to the original.
				
					assetSyncStatusId = kAssetCompleteNoNegatives
					
				else
				
					assetSyncStatusId = kAssetCompleteNoMaster
					
				end
			
			else
			
				assetSyncStatusId = kAssetCompleteNoNegatives
			
			end

		end

	end

	return kAssetSyncStatus[ assetSyncStatusId ]

end

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

function WFCatSyncStatus.getVolumeInfoForSyncStatus( wfCatalog )

	local allVolumeAgents = wfCatalog:getAllVolumeAgents()

	local volumeAgentStatusStreams = {}
	
	local originalsPathInfo = wfCatalog:fileSpec():originalsPathInfo()
	local volumeGuidForOriginals = originalsPathInfo.volumeGuid
	
	local proxiesPathInfo = wfCatalog:fileSpec():proxiesPathInfo()
	local volumeGuidForProxies = proxiesPathInfo.volumeGuid
	
	-- Build up a table of volume agent status streams.

	for _, agent in pairs( allVolumeAgents ) do
		table.insert( volumeAgentStatusStreams, agent:getVolumeStatusStream() )
	end

	table.insert( volumeAgentStatusStreams, Rx.Observable.just( nil ) )
		-- Add a nil observable in case there are no volume streams. This can
		-- happen in unit tests.
		
	return Rx.Observable.combineLatest( unpack( volumeAgentStatusStreams ) )
	
		:map( function( ... )
		
			local knownVolumes = {}
			local onlineVolumes = {}
			local outOfSpaceVolumes = {}
			
			for _, volumeStatus in ipairs( arg ) do

				local volumeGuid = volumeStatus.volumeGuid
				local isOnline = volumeStatus.isOnline
				local outOfSpace = volumeStatus.isOutOfSpace

				knownVolumes[ volumeGuid ] = true
				
				onlineVolumes[ volumeGuid ] = isOnline
				
				outOfSpaceVolumes[ volumeGuid ] = outOfSpace
				
			end
			
			return volumeGuidForOriginals, volumeGuidForProxies,
					onlineVolumes, outOfSpaceVolumes, knownVolumes
		
		end )

end

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

function WFCatSyncStatus.getCombinedAssetStatusAsStr( wfCatalog, syncStatusAndVolumeInfos )

	--[[ 
	
		True asset sync status is made up 3 separate data points.
		
		1. The current state of the asset as generated by "getAssetStatus". This
			is the same status that is stored in sync status index store table.
	
		2. The current state of the volumes where the assets are stored. If an asset
		 	needs to be downloaded, but the volume we need to download it to is
			offline, that's a new unique state.
		
		3. The current global state of sync; is sync paused and/or offline.

	]]--
	
	local mappedVolumeStatuses = WFCatSyncStatus.getVolumeInfoForSyncStatus( wfCatalog )
	
	local volumeAgents = wfCatalog:getAllVolumeAgents()
	local volumeAgent = volumeAgents[ next( volumeAgents ) ]
	local syncPausedStream = volumeAgent and volumeAgent:isSyncPausedStream() or Rx.Observable.just( false )
	local internetConnectionStream = volumeAgent and volumeAgent:hasInternetConnectionStream() or Rx.Observable.just( true )
	
	-- syncStatusAndVolumeInfos is an Observable that produces an array of rows containing
	-- 1: sync status constant mapping to WFCatSyncStatus.getAssetStatus
	-- 2: originalVolumeGuid
	-- 3: proxyVolumeGuid
	-- 4: markedAsOffline
	
	return Rx.Observable.combineLatest(
			syncStatusAndVolumeInfos,
			mappedVolumeStatuses )
		:map( function( syncStatusAndVolumeInfoRows, volumeGuidForOriginals, volumeGuidForProxies, 
				onlineVolumes, outOfSpaceVolumes, knownVolumes )
			
			local combinedSyncStatus
			
			for _, syncStatusAndVolumeInfoRow in ipairs( syncStatusAndVolumeInfoRows ) do
			
				local syncStatus = syncStatusAndVolumeInfoRow[ 1 ]
				local volumeGuidUsedByOriginal = syncStatusAndVolumeInfoRow[ 2 ]
				local volumeGuidUsedByProxy = syncStatusAndVolumeInfoRow[ 3 ]
				
				local isOriginalOnKnownVolume = volumeGuidUsedByOriginal and knownVolumes[ volumeGuidUsedByOriginal ]
				local isProxyOnKnownVolume = volumeGuidUsedByProxy and knownVolumes[ volumeGuidUsedByProxy ]
				
				local isOriginalOnOnlineVolume = volumeGuidUsedByOriginal and onlineVolumes[ volumeGuidUsedByOriginal ] 
				local isProxyOnOnlineVolume = volumeGuidUsedByProxy and onlineVolumes[ volumeGuidUsedByProxy ]
				
				local isVolumeForOriginalsOnline = onlineVolumes[ volumeGuidForOriginals ]
				local isVolumeForProxiesOnline = onlineVolumes[ volumeGuidForProxies ]
			
				local isVolumeForOriginalsOutOfSpace = outOfSpaceVolumes[ volumeGuidForOriginals ]
				local isVolumeForProxiesOutOfSpace = outOfSpaceVolumes[ volumeGuidForProxies ]
				
				-- Calculate the sync status.
				
				local currentDocSyncStatus = WFCatSyncStatus.calculateSyncStatusWithVolumeStatus( 
						syncStatus, volumeGuidUsedByOriginal, volumeGuidUsedByProxy,
						isOriginalOnKnownVolume, isProxyOnKnownVolume,
						isVolumeForOriginalsOnline, isVolumeForProxiesOnline,
						isVolumeForOriginalsOutOfSpace, isVolumeForProxiesOutOfSpace,
						isOriginalOnOnlineVolume, isProxyOnOnlineVolume )
				
				if not combinedSyncStatus then
					combinedSyncStatus = currentDocSyncStatus
				elseif combinedSyncStatus ~= currentDocSyncStatus then
					combinedSyncStatus = nil
					break
				end
					
			end
					
			return combinedSyncStatus
					
		end )
		
		:flatMap( function( syncStatus )
		
			return Rx.Observable.combineLatest( 
				Rx.Observable.just( syncStatus ),
				syncPausedStream,
				internetConnectionStream )
				
		end)
		
		:map( function( syncStatus, isSyncPaused, isOnline )
		
			return WFCatSyncStatus.calculateSyncStatusWithGlobalStatus( syncStatus, isSyncPaused, not isOnline )
		
		end )
		
		:map( WFCatSyncStatus.translateAssetSyncStatus )

end

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

function WFCatSyncStatus.getCombinedOzStatusFromIdsAsStr( wfCatalog, assetIds )
	
	return wfCatalog:indexStore():query {
			sql = [[SELECT ozHasOriginal, ozHasProxy FROM localStatusAndOzStatus JOIN CARRAY( ?, ? , 'int32' ) AS ai ON localStatusAndOzStatus.docId = ai.value ]],
			calm = 2,
			assetIds,
			#assetIds,
		}:map( function( ozHasRows )
			
			local combinedOzStatusStr
			
			local previousOzHasOriginal
			local previousOzHasProxy
			
			-- returns kOzHasOriginal if all assets have original on Oz, kOzHasProxy if all assets have only a proxy on Oz
			-- kOzHasNothing if all assets have nothing on Oz and nil if the assets are mixed
			for _, ozHas in ipairs( ozHasRows ) do
			
				local ozHasOriginal = ozHas[ 1 ] == 1
				local ozHasProxy = ozHas[ 2 ] == 1
			
				if not combinedOzStatusStr then
				
					if ozHasOriginal then
					
						combinedOzStatusStr = kOzHasOriginal
					
					elseif ozHasProxy then
					
						combinedOzStatusStr = kOzHasProxy
					
					else
					
						combinedOzStatusStr = kOzHasNothing
					
					end
					
				elseif previousOzHasOriginal ~= ozHasOriginal
					or previousOzHasProxy ~= ozHasProxy then
				
					combinedOzStatusStr = nil
					break
					
				end
				
				previousOzHasProxy = ozHasProxy
				
				previousOzHasOriginal = ozHasOriginal
				
			end
		
			return combinedOzStatusStr
		
		end )

end

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

local function localFileType( hasOriginal, isOriginalOnOfflineVolume, hasOriginalError, isMarkedForOffline, hasProxy, isProxyOnOfflineVolume )

	local assetLocalFileTypeId = kAssetHasNothing
	
	if hasOriginal and not isOriginalOnOfflineVolume and not hasOriginalError then
		if isMarkedForOffline then
			assetLocalFileTypeId = kAssetHasLocalOriginalForOffline
		else
			assetLocalFileTypeId = kAssetHasLocalOriginal
		end
	elseif hasProxy and not isProxyOnOfflineVolume then
		assetLocalFileTypeId = kAssetHasLocalProxy
	end
	
	return kAssetLocalFileType[ assetLocalFileTypeId ]
end

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

function WFCatSyncStatus.getLocalFileTypeForAsset( docValue, isOriginalOnOfflineVolume, isProxyOnOfflineVolume )
	
	return localFileType( 
		docValueHasLocalOriginal( docValue ),
		isOriginalOnOfflineVolume,
		hasInvalidOriginalError( docValue ),
		WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue ),
		docValueHasLocalProxy( docValue ),
		isProxyOnOfflineVolume )
		
end

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

function WFCatSyncStatus.getCombinedLocalFileTypeAsStr( wfCatalog, syncStatusAndVolumeInfos )
	-- syncStatusAndVolumeInfos is an Observable that produces an array of rows containing
	-- 1: sync status constant mapping to WFCatSyncStatus.getAssetStatus
	-- 2: originalVolumeGuid
	-- 3: proxyVolumeGuid
	-- 4: markedAsOffline
	local mappedVolumeStatuses = WFCatSyncStatus.getVolumeInfoForSyncStatus( wfCatalog )
	
	local volumeAgents = wfCatalog:getAllVolumeAgents()
	local volumeAgent = volumeAgents[ next( volumeAgents ) ]
	
	return Rx.Observable.combineLatest(
		syncStatusAndVolumeInfos,
		mappedVolumeStatuses )

		:map( function( syncStatusAndVolumeInfoRows, volumeGuidForOriginals, 
				volumeGuidForProxies, onlineVolumes )
					
			local combinedLocalFileType
			
			for _, syncStatusAndVolumeInfoRow in ipairs( syncStatusAndVolumeInfoRows ) do
					
				local volumeGuidUsedByOriginal = syncStatusAndVolumeInfoRow[ 2 ]
				local volumeGuidUsedByProxy = syncStatusAndVolumeInfoRow[ 3 ]
				local isMarkedForOffline = syncStatusAndVolumeInfoRow[ 4 ] == 1
			
				local isOriginalOnOfflineVolume = volumeGuidUsedByOriginal and not onlineVolumes[ volumeGuidUsedByOriginal ] 
				local isProxyOnOfflineVolume = volumeGuidUsedByProxy and not onlineVolumes[ volumeGuidUsedByProxy ]
				
				local docLocalFileType = localFileType(
					not not volumeGuidUsedByOriginal,
					isOriginalOnOfflineVolume,
					nil, -- need to fix this has InvalidOriginalError shouldn't be empty
					isMarkedForOffline,
					not not volumeGuidUsedByProxy,
					isProxyOnOfflineVolume )
					
				if not combinedLocalFileType then
				
					combinedLocalFileType = docLocalFileType
					
				elseif combinedLocalFileType ~= docLocalFileType then
				
					combinedLocalFileType = nil
					break
				
				end
				
			end
					
			return combinedLocalFileType
					
		end )

		:map( WFCatSyncStatus.translateAssetSyncLocalFileType )

end

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

function WFCatSyncStatus.getCombinedMarkedForOfflineUse( wfCatalog, syncStatusAndVolumeInfos )
	-- syncStatusAndVolumeInfos is an Observable that produces an array of rows containing
	-- 1: sync status constant mapping to WFCatSyncStatus.getAssetStatus
	-- 2: originalVolumeGuid
	-- 3: proxyVolumeGuid
	-- 4: markedAsOffline
	return syncStatusAndVolumeInfos
		:map( function( isMarkedForOfflineRows )
			
			-- returns true if all assets are marked for offline, false if no assets are marked for offline
			-- and nil if the assets are mixed
			local combinedMarkedForOfflineUse

			for _, row in ipairs( isMarkedForOfflineRows ) do

				local markedForOfflineUse = row[ 4 ] == 1

				if combinedMarkedForOfflineUse then
				
					combinedMarkedForOfflineUse = markedForOfflineUse
					
				elseif combinedMarkedForOfflineUse ~= markedForOfflineUse then
				
					combinedMarkedForOfflineUse = nil
					break
				
				end

			end

			return combinedMarkedForOfflineUse

		end )

end

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

function WFCatSyncStatus.getSyncStatusColumnName()

	local keepAllOriginals = NmbFileManagementSettings.keepAllOriginals()
		
	local keepAllProxies = NmbFileManagementSettings.keepAllProxies()
	
	local statusColumn
	
	if keepAllOriginals and keepAllProxies then
		statusColumn = 'keepEverythingStatus'
	elseif keepAllOriginals then
		statusColumn = 'keepAllOriginalsStatus'
	elseif keepAllProxies then
		statusColumn = 'keepAllProxiesStatus'
	else
		statusColumn = 'keepNothingStatus'
	end

	return statusColumn

end

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

local function asset_status_table()

	local mapFn = function( docValue, emit )

		if docValue._deleted and not WFCatTrashUtils_isDeleteTrash( docValue ) then

			-- Don't count deleted assets unless they are delete trash as Cloud
			-- Trash generates another asset for each deleted or restored asset.
			return

		end

		if isAssetDocument( docValue ) then

			local keepNothingStatus = WFCatSyncStatus.getAssetStatus( docValue, false, false )
			local keepAllProxiesStatus = WFCatSyncStatus.getAssetStatus( docValue, true, false )
			local keepAllOriginalsStatus = WFCatSyncStatus.getAssetStatus( docValue, false, true )
			local keepEverythingStatus = WFCatSyncStatus.getAssetStatus( docValue, true, true )
			
			if keepNothingStatus
					and keepAllProxiesStatus
					and keepAllOriginalsStatus 
					and keepEverythingStatus then

				local originalVolumeGuid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue )
				local proxyVolumeGuid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue )

				local assetId = docValue._id

				-- searchGridStatus only holds the status of searchable assets (it has NULL for
				-- assets that are in the trash or in a shared with you space album. This is done
				-- so the cloudy indicator doesn't show errors related to these as the search will
				-- always come up empty.
				
				local searchGridStatus = not docValue._spaceId 
						and not WFCatIndexUtils.isTrashAssetDocument( docValue ) 
						and keepNothingStatus
						
					-- Note that searchGridStatus is only used when searching for assets in error states, so 
					-- it's safe to use the data from keepNothingStatus (the revelant errors should be identical
					-- across keepNothingStatus, keepAllProxiesStatus, etc).
					
				emit( assetId, keepNothingStatus, keepAllProxiesStatus, 
						keepAllOriginalsStatus, keepEverythingStatus, 
						searchGridStatus, originalVolumeGuid, proxyVolumeGuid )
				
			end
			
		end
		
	end

	return {
		
		sql = [[CREATE TABLE asset_sync_status_ui( 
					assetId, 
					keepNothingStatus,
					keepAllProxiesStatus,
					keepAllOriginalsStatus,
					keepEverythingStatus,
					searchGridStatus, 
					volumeGuidUsedByOriginal, 
					volumeGuidUsedByProxy )]],
					
		useLocalId = true,
		
		hasOneRowPerDocument = true,
		
		map = mapFn,
		
		relevantTypeSubtypeTuples = {
			{ 'asset', '*' }
		},
		
		docValueFilter = function( docValue )
			return isAssetDocument( docValue )
		end,
		
	}
	
end

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

local function asset_status_by_assetId_index()
	return [[CREATE INDEX asset_sync_status_ui_by_assetId_keepNothingStatus ON asset_sync_status_ui( assetId, keepNothingStatus )]]
end

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

local function asset_status_by_keepNothingStatus_index()
	return [[CREATE INDEX asset_sync_status_ui_by_keepNothingStatus ON asset_sync_status_ui( keepNothingStatus, assetId )]]
end

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

local function asset_status_by_keepAllProxiesStatus_index()
	return [[CREATE INDEX asset_sync_status_ui_by_keepAllProxiesStatus ON asset_sync_status_ui( keepAllProxiesStatus, assetId )]]
end

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

local function asset_status_by_keepAllOriginalsStatus_index()
	return [[CREATE INDEX asset_sync_status_ui_by_keepAllOriginalsStatus ON asset_sync_status_ui( keepAllOriginalsStatus, assetId )]]
end

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

local function asset_status_by_keepEverythingStatus_index()
	return [[CREATE INDEX asset_sync_status_ui_by_keepEverythingStatus ON asset_sync_status_ui( keepEverythingStatus, assetId )]]
end

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

local function asset_status_by_searchGridStatus_index()
	return [[CREATE INDEX asset_sync_status_ui_by_searchGridStatus ON asset_sync_status_ui( searchGridStatus, assetId )]]
end

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

local function asset_local_file_type_table()

	return {

		sql = [[CREATE TABLE asset_sync_local_file_type_ui( assetId, localFileType INTEGER )]],
		
		useLocalId = true,
		
		hasOneRowPerDocument = true,
		
		relevantTypeSubtypeTuples = {
			{ 'asset', 'image' },
			{ 'asset', 'video' },
			{ 'asset', 'deleted_image' },
			{ 'asset', 'deleted_video' }
		},
		
		docValueFilter = isFileBasedAssetDocument,
		
		map = function( docValue, emit )
		
			if isValidDisplayableAssetDocument( docValue ) and 
				isFileBasedAssetDocument( docValue ) then
			
				local TABLE_VERSION
				TABLE_VERSION = TABLE_VERSION or 2  -- a dummy change to force the reindexing

				local localFileType = WFCatSyncStatus.getLocalFileTypeForAsset( docValue )

				-- SPAM [jandrew 2018-09-05]: The assets table does not emit for assets
				-- that do not contain a capture date. In order to maintain consistency
				-- between the tables, I've added that here as well.

				local captureDate = derefOpt( docValue, 'captureDate' )

				if localFileType and captureDate then

					local assetId = docValue._id	
					emit( assetId, localFileType )

				end

			end
		end,

	}

end

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

local function asset_local_file_type_by_assetId_index()
	return [[CREATE INDEX asset_sync_local_file_type_ui_by_assetId_status ON asset_sync_local_file_type_ui( assetId, localFileType )]]
end

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

local function asset_local_file_type_by_localFileType_index()
	return [[CREATE INDEX asset_sync_local_file_type_ui_by_status ON asset_sync_local_file_type_ui( localFileType )]]
end

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

local function album_status_table()

	return {

		sql = [[CREATE TABLE album_sync_status_ui( albumId, status INTEGER )]],
		
		useLocalId = true,
		
		hasOneRowPerDocument = true,
		
		relevantTypeSubtypeTuples = {
			{ 'album', '*' }
		},
		
		map = function( docValue, emit )
			if docValue and docValue.type == 'album' then
				
				local TABLE_VERSION
				TABLE_VERSION = TABLE_VERSION or 2  -- a dummy change to force the reindexing

				local albumId = docValue._id

				local status = kAlbumSyncStatus[ kAlbumComplete ]
				
				if docValue._deleted and not derefOpt( docValue, '_serverProvidedMetadata', 'links' )  then
					-- this deleted album is either not yet up on Oz or has been on Oz and was deleted
					-- so we can avoid emitting for this album
					return
				end
				
				local isMarkedForOffline = WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue )

				if isLatestMetadataUploaded( docValue ) then
				
					if isMarkedForOffline then
				
						status = kAlbumSyncStatus[ kAlbumCompleteForOfflineUse ]
					
					else
					
						status = kAlbumSyncStatus[ kAlbumComplete ]
						
					end
					
				else
				
					if isMarkedForOffline then
				
						status = kAlbumSyncStatus[ kAlbumSyncingForOfflineUse ]
					
					else
						
						status = kAlbumSyncStatus[ kAlbumSyncing ]
					
					end
					
				end

				emit( albumId, status )

			end
		end,	
	}

end

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

function WFCatSyncStatus.generateIndexSchema( indexStoreSchema )
	
	indexStoreSchema:addTable( asset_status_table() )
	
	indexStoreSchema:addIndex( asset_status_by_assetId_index() )
	
	indexStoreSchema:addIndex( asset_status_by_keepNothingStatus_index() )
	
	indexStoreSchema:addIndex( asset_status_by_keepAllProxiesStatus_index() )
	
	indexStoreSchema:addIndex( asset_status_by_keepAllOriginalsStatus_index() )

	indexStoreSchema:addIndex( asset_status_by_keepEverythingStatus_index() )

	indexStoreSchema:addIndex( asset_status_by_searchGridStatus_index() )
	
	indexStoreSchema:addTable( asset_local_file_type_table() )
	
	indexStoreSchema:addIndex( asset_local_file_type_by_assetId_index() )
	
	indexStoreSchema:addIndex( asset_local_file_type_by_localFileType_index() )
	
	indexStoreSchema:addTable( album_status_table() )
	
end

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

local function toBool( int )

	return int and int > 0

end

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

local kNonSyncingStates = {
	kAssetComplete,
	kAssetCompleteNoMaster,
	kAssetCompleteNoNegatives,
	kAssetCompleteForOffline,
	kAssetInvalidOriginal,
	kAssetOriginalDownloadError,
	kAssetProxyDownloadError,
	kAssetDevelopXmpDownloadError,
	kAssetStackComplete,
	kAssetInsufficientPermissionError,
	kAssetStorageLimitExceeded,
	kAssetTrialOrSubscriptionExpired
}

local kNonSyncingStateSet = KSTableUtils.mapArrayToSet( kNonSyncingStates )

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

local function getSyncingStatusIds( allSyncStatuses, isStatusForAlbums )
	
	-- Certain statuses (kAssetComplete, kAssetCompleteNoMaster, kAssetInvalidOriginal, etc.) 
	-- would indicate complete asset. All other assets are considered 'syncing'.
	
	
	local nonSyncingStates = KSTableUtils.shallowcopy( kNonSyncingStates )
	local keepAllOriginals = NmbFileManagementSettings.keepAllOriginals()

	if isStatusForAlbums and not keepAllOriginals then
		table.insert( nonSyncingStates, kAssetNeedsOriginalDownloadForOffline )
		table.insert( nonSyncingStates, kAssetNeedsProxyDownloadForOffline )
	end

	local nonSyncingStateSet = KSTableUtils.mapArrayToSet( nonSyncingStates )

	-- Copy status ids into a table that can be unpacked.
	
	local syncingStatusIds = {}

	for state, statusId in pairs( allSyncStatuses ) do

		if not nonSyncingStateSet[ state ] then

			table.insert( syncingStatusIds, statusId )

		end

	end

	return syncingStatusIds

end

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

local function getErrorStatusIds( allSyncStatuses )
	
	local errorSyncStates = {
		kAssetInvalidOriginal,
		kAssetOriginalDownloadError,
		kAssetProxyDownloadError,
		kAssetDevelopXmpDownloadError,
	}
	
	local errorStatusIds = {}
	
	for _, errorState in ipairs( errorSyncStates ) do
		
		if allSyncStatuses[ errorState ] then
			table.insert( errorStatusIds, allSyncStatuses[ errorState ] )
		end
		
	end
	
	return errorStatusIds
	
end

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

local function basicAlbumStatusStream( wfCatalog )
	
	return wfCatalog:indexStore():query {
			sql = [[SELECT albumId, status
						FROM album_sync_status_ui ]],
			calm = 0.25
		}
		:map( function( albumStatuses )
			
			local statusForAlbum = {}
			
			for _, albumStatus in ipairs( albumStatuses ) do
			
				local albumId = albumStatus[ 1 ]
				local status = albumStatus[ 2 ]
				
				statusForAlbum[ albumId ] = kAlbumSyncStatusById[ status ]
					
			end
			
			return statusForAlbum
			
		end )
end

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

local function getSyncingAlbumsQuerySQL( numberOfSyncingStatuses )
	
		local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
	
		return string.format( 
				[[SELECT DISTINCT albumId
					FROM album_asset_v2
					WHERE assetId IN
						(
							SELECT assetId 
								FROM asset_sync_status_ui
								WHERE %s IN ( %s )
						) ]], syncStatusColumnName, WFSQLiteUtils.createVariableArgList( numberOfSyncingStatuses ) )
						
	end
	
--------------------------------------------------------------------------------

function WFCatSyncStatus.getAllAlbumsFinishedUploading( wfCatalog )
	
	local albumSyncStatus = basicAlbumStatusStream( wfCatalog )
	
	local pendingAssetUploadStatuses = {
		-- these statuses indicate we're still syncing the 
		-- base asset data UP to Oz and therefore cannot
		-- send the data to the third party service yet
		kAssetNeedsMetadataUpload,	
		kAssetNeedsOriginalUpload,
	}
	
	local assetsPendingUpload = getSyncingAlbumsQuerySQL( #pendingAssetUploadStatuses )
	
	local albumsPendingAssetUpload = wfCatalog:indexStore():query {
			sql = assetsPendingUpload,
			calm = 3,
			unpack( pendingAssetUploadStatuses )
		}
		:map( function( syncingAlbums )
		
			local statusForAlbum = {}
			
			for _, syncingAlbum in ipairs( syncingAlbums ) do
				statusForAlbum[ syncingAlbum[ 1 ] ] = kAlbumSyncing
			end
			
			return statusForAlbum
			
		end )
		
	return Rx.Observable.combineLatest( albumSyncStatus, albumsPendingAssetUpload )
		:map( function( albums, albumsWithAssetsPendingUpload )
			local completed = {}
			for albumId, status in pairs( albums ) do
				if ( status == kAlbumComplete or
					status == kAlbumCompleteForOfflineUse ) and 
					not albumsWithAssetsPendingUpload[ albumId ] then
					completed[ albumId ] = true
				end
			end
			return completed
		end )
end

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

function WFCatSyncStatus.getSyncStatusForAlbums( wfCatalog, optIgnoreAlbumSyncStatus )
	
	local albumStatusesStream
	
	local keepAllOriginals = NmbFileManagementSettings.keepAllOriginals()
	
	if optIgnoreAlbumSyncStatus then 
	
		-- Don't consider the sync status of the actual album document when determining 
		-- the status to show for the album. Essentially this means that we only consider
		-- the sync status of the underlying asset documents since this also ignores the 
		-- album_asset documents.
		albumStatusesStream = wfCatalog:indexStore():query {
			sql = [[SELECT albumId, isMarkedForOffline FROM albums ]],
			calm = 0.25
		}
		:map( function( albums )
		
			local statusForAlbum = {}
			
			for _, album in ipairs( albums ) do
			
				local albumId = album[ 1 ]
				local isMarkedForOfflineUse = toBool( album[ 2 ] )
				
				statusForAlbum[ albumId ] = isMarkedForOfflineUse and kAlbumCompleteForOfflineUse or kAlbumComplete

			end
			
			return statusForAlbum
			
		end )
		
	else 
	
		albumStatusesStream = basicAlbumStatusStream( wfCatalog )
	
	end 
	
	local syncingStatusIds = getSyncingStatusIds( kAssetSyncStatus, true )
	
	local query = getSyncingAlbumsQuerySQL( #syncingStatusIds )
	
	local syncingAlbumsFromAssets = wfCatalog:indexStore():query {
			sql = query,
			calm = 3,
			unpack( syncingStatusIds )
		}
		
		:map( function( syncingAlbums )
		
			local statusForAlbum = {}
			
			for _, syncingAlbum in ipairs( syncingAlbums ) do
				statusForAlbum[ syncingAlbum[ 1 ] ] = kAlbumSyncing
			end
			
			return statusForAlbum
			
		end )
	
	local syncingAlbumsMarkedForOfflineUseFromAssets = Rx.Observable.just( {} )
	
	if not keepAllOriginals then
	
		local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
	
		syncingAlbumsMarkedForOfflineUseFromAssets = wfCatalog:indexStore():query {
				sql = string.format( [[ SELECT DISTINCT albumId
							FROM album_asset_v2
							INNER JOIN albums USING( albumId )
							WHERE albums.isMarkedForOffline = 1
							AND assetId IN
							(
								SELECT assetId 
									FROM asset_sync_status_ui
									WHERE %s IN ( ?, ? ) 		  									
								UNION
								SELECT assetId 
									FROM assets 
									WHERE isMarkedForOffline = 0
							) ]], syncStatusColumnName ),
				calm = 3,
				kAssetSyncStatus[ kAssetNeedsOriginalDownloadForOffline ],
				kAssetSyncStatus[ kAssetNeedsProxyDownloadForOffline ]
			}
			
			:map( function( syncingAlbums )
			
				local statusForAlbum = {}
				
				for _, syncingAlbum in ipairs( syncingAlbums ) do
					statusForAlbum[ syncingAlbum[ 1 ] ] = kAlbumSyncingForOfflineUse
				end
				
				return statusForAlbum
				
			end )
			
	end
	
	local sharedWithYouAlbumIdsStream = wfCatalog:indexStore():query {
				sql = [[ SELECT DISTINCT albumId
							FROM albums
							WHERE spaceId IS NOT NULL]],
				calm = 3,
			}
			
			:map( function( rows )
			
				local sharedWithYouAlbums = {}
				
				for _, row in ipairs( rows ) do
					sharedWithYouAlbums[ row[ 1 ] ] = true
				end
				
				return sharedWithYouAlbums
				
			end )
	
	local volumeAgents = wfCatalog:getAllVolumeAgents()
	local volumeAgent = volumeAgents[ next( volumeAgents ) ]
	local syncPausedStream = volumeAgent and volumeAgent:isSyncPausedStream() or Rx.Observable.just( false )
	local internetConnectionStream = volumeAgent and volumeAgent:hasInternetConnectionStream() or Rx.Observable.just( true )
	local mappedVolumeStatuses = WFCatSyncStatus.getVolumeInfoForSyncStatus( wfCatalog )
	
	return Rx.Observable.combineLatest( albumStatusesStream, syncingAlbumsFromAssets, 
		syncingAlbumsMarkedForOfflineUseFromAssets, syncPausedStream, 
		internetConnectionStream, sharedWithYouAlbumIdsStream, mappedVolumeStatuses )

		:calm( 0.5, WFRxScheduler.sharedScheduler() )
		
		:map( function( albumStatusMap, albumStatusFromAssetsMap, albumMarkedForOfflineStatusFromAssetsMap,
			isSyncPaused, isSyncOnline, sharedWithYouAlbumIds, volumeGuidForOriginals, _, onlineVolumes, 
			outOfSpaceVolumes )

			local combinedAlbumStatuses = {}
			
			local isVolumeForOriginalsOnline = onlineVolumes[ volumeGuidForOriginals ]
			local isVolumeForOriginalsOutOfSpace = outOfSpaceVolumes[ volumeGuidForOriginals ]
			
			for albumId, albumStatus in pairs( albumStatusMap ) do
			
				local combinedStatus
				
				local albumStatusFromAssets = albumMarkedForOfflineStatusFromAssetsMap[ albumId ] 
						or albumStatusFromAssetsMap[ albumId ]
				
				if not sharedWithYouAlbumIds[ albumId ]
					and ( albumStatus == kAlbumSyncingForOfflineUse 
					or albumStatusFromAssets == kAlbumSyncingForOfflineUse
					or ( albumStatus == kAlbumCompleteForOfflineUse and albumStatusFromAssets == kAlbumSyncing )
					or ( keepAllOriginals and ( albumStatus == kAlbumSyncing or albumStatusFromAssets == kAlbumSyncing ) ) ) then
				
					if isSyncPaused then
					
						combinedStatus = kAlbumSyncPaused
						
					elseif not isSyncOnline then
					
						combinedStatus = kAlbumSyncNoConnection
					
					elseif not isVolumeForOriginalsOnline then
	
						combinedStatus = kAlbumSyncingForOfflineUseToOfflineVolume
					
					elseif isVolumeForOriginalsOutOfSpace then
				
						combinedStatus = kAlbumSyncingForOfflineUseToOutOfSpaceVolume
					
					else
					
						combinedStatus = kAlbumSyncingForOfflineUse
					
					end
				
				elseif albumStatus == kAlbumSyncing or albumStatusFromAssets == kAlbumSyncing then
				
					if isSyncPaused then
					
						combinedStatus = kAlbumSyncPaused
						
					elseif not isSyncOnline then
					
						combinedStatus = kAlbumSyncNoConnection
						
					else
					
						combinedStatus = kAlbumSyncing
						
					end
					
				elseif not sharedWithYouAlbumIds[ albumId ]
					and ( ( albumStatus == kAlbumCompleteForOfflineUse and albumStatusFromAssets == kAlbumComplete )
					or ( keepAllOriginals and ( albumStatus == kAlbumComplete or albumStatusFromAssets == kAlbumComplete ) ) ) then
					
					combinedStatus = kAlbumCompleteForOfflineUse
					
				elseif albumStatus == kAlbumComplete or albumStatusFromAssets == kAlbumComplete then
				
					combinedStatus = kAlbumComplete
				
				else
				
					combinedStatus = albumStatus
				
				end
				
				combinedAlbumStatuses[ albumId ] = combinedStatus
				
			end
			
			return combinedAlbumStatuses
			
		end )
	
end

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

function WFCatSyncStatus.getSyncStatusOfParentCollectionOfAlbums( wfCatalog, albumIds )
	
	return wfCatalog:indexStore():query {
			sql = [[SELECT album_sync_status_ui.albumId, album_sync_status_ui.status
						FROM album_sync_status_ui
						INNER JOIN albums ON albums.parentId == album_sync_status_ui.albumId
						INNER JOIN CARRAY( ?, ?, 'int32' ) AS ai ON ai.value = albums.docId ]],
			calm = 2,
			albumIds,
			#albumIds,
		}
		
		:map( function( albumStatuses )
			
			local syncedAlbumsById = {}
			
			for _, albumStatus in ipairs( albumStatuses ) do
			
				local albumId = albumStatus[ 1 ]
				local statusId = albumStatus[ 2 ]
				
				syncedAlbumsById[ albumId ] = WFCatSyncStatus.isAlbumCompleted( statusId )
					
			end
			
			return syncedAlbumsById
			
		end )
	
end

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

function WFCatSyncStatus.getSyncStatusOfAlbumsOfAlbumAssets( wfCatalog, albumAssetIds )
	
	return wfCatalog:indexStore():query {
			sql = [[SELECT album_sync_status_ui.albumId, album_sync_status_ui.status
						FROM album_sync_status_ui
						INNER JOIN album_asset_v2 ON album_asset_v2.albumId == album_sync_status_ui.albumId
						INNER JOIN CARRAY( ?, ?, 'int32' ) AS aai ON aai.value = album_asset_v2.docId ]],

			calm = 2,
			albumAssetIds,
			#albumAssetIds,
		}
		
		:map( function( albumStatuses )
			
			local syncedAlbumsById = {}
			
			for _, albumStatus in ipairs( albumStatuses ) do
			
				local albumId = albumStatus[ 1 ]
				local statusId = albumStatus[ 2 ]
				
				syncedAlbumsById[ albumId ] = WFCatSyncStatus.isAlbumCompleted( statusId )
					
			end
			
			return syncedAlbumsById
			
		end )
	
end

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

-- [jandrew 2022-01-04]: WF-1644:

-- An ongoing task within `WFCatErrorHandler` constantly monitors asset-dependent
-- documents (Versions, Comments, Favorites, etc) that are blocked from upload due
-- to issues syncing the referenced Asset document. This task will clear the errors
-- from the asset-dependent documents if it determines that the referenced Asset 
-- document has been successfully synced to Oz. It determines this by calling the
-- WFCatSyncStatus.getSyncStatusOfAssetsOf* family of functions. Unfortunately, the 
-- old implementations of these functions determined the sync-status of the 
-- referenced Asset document using WFCatSyncStatus.isAssetComplete which incorrectly
-- returns true for assets that are in error states. The new implementation relies
-- on kFullySyncedStates, which contains a reduced set of states and only considers
-- the true "complete" states. 

local kFullySyncedStates = {
	kAssetComplete,
	kAssetCompleteNoMaster,
	kAssetCompleteNoNegatives,
	kAssetCompleteForOffline,
	kAssetStackComplete
}

local kFullySyncedStatesSet = KSTableUtils.mapArrayToSet( kFullySyncedStates )

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

local function reduceSyncStatuses( assetStatuses )

	local isDocumentSyncedByAssetId = {}
			
	for _, assetStatus in ipairs( assetStatuses ) do
	
		local assetId = assetStatus[ 1 ]
		local statusId = assetStatus[ 2 ]
		
		if type( statusId ) ~= 'string' then
			statusId = kAssetSyncStatusById[ statusId ]
		end

		isDocumentSyncedByAssetId[ assetId ] = not not kFullySyncedStatesSet[ statusId ]
			
	end
	
	return isDocumentSyncedByAssetId

end

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

--[[!

	\brief Given a list of album-asset IDs, this function returns a map of the asset
		IDs referenced by the album-asset documents, along with a boolean indicating
		the sync status of the associated assets.

	\param wfCatalog (WFCatalog)
	\param albumAssetIds (table) A table of album-asset IDs.

	\return (void) Returns a map whose keys are asset IDs, and whose values are
		booleans representing the whether or not the asset is considered fully synced.

--]]

function WFCatSyncStatus.getSyncStatusOfAssetsOfAlbumAssets( wfCatalog, albumAssetIds )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	return wfCatalog:indexStore():query {
			sql = string.format( 
					[[SELECT asset_sync_status_ui.assetId, asset_sync_status_ui.%s
						FROM asset_sync_status_ui
						INNER JOIN album_asset_v2 ON album_asset_v2.assetId == asset_sync_status_ui.assetId
						INNER JOIN CARRAY( ?, ?, 'int32' ) AS aai ON aai.value = album_asset_v2.docId ]], 
						syncStatusColumnName ),
			calm = 2,
			albumAssetIds,
			#albumAssetIds,
		}
		
		:map( reduceSyncStatuses )

end

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

--[[!

	\brief Given a list of version IDs, this function returns a map of the asset
		IDs referenced by the version documents, along with a boolean indicating
		the sync status of the associated assets.

	\param wfCatalog (WFCatalog)
	\param versionIds (table) A table of version IDs.

	\return (void) Returns a map whose keys are asset IDs, and whose values are
		booleans representing the whether or not the asset is considered fully synced.

--]]

function WFCatSyncStatus.getSyncStatusOfAssetsOfVersions( wfCatalog, versionIds )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	return wfCatalog:indexStore():query {
			sql = string.format( 
					[[SELECT asset_sync_status_ui.assetId, asset_sync_status_ui.%s
						FROM asset_sync_status_ui
						INNER JOIN versions ON versions.assetId == asset_sync_status_ui.assetId
						INNER JOIN CARRAY( ?, ?, 'int32' ) AS vi ON vi.value = versions.localId ]],
						syncStatusColumnName ),
			calm = 2,
			versionIds,
			#versionIds,
		}
		
		:map( reduceSyncStatuses )

end

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

--[[!

	\brief Given a list of favorite IDs, this function returns a map of the asset
		IDs referenced by the favorite documents, along with a boolean indicating
		the sync status of the associated assets.

	\param wfCatalog (WFCatalog)
	\param favoriteIds (table) A table of favorite IDs.

	\return (void) Returns a map whose keys are asset IDs, and whose values are
		booleans representing the whether or not the asset is considered fully synced.

--]]

function WFCatSyncStatus.getSyncStatusOfAssetsOfFavorites( wfCatalog, favoriteIds )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	return wfCatalog:indexStore():query {
			sql = string.format(
					[[SELECT asset_sync_status_ui.assetId, asset_sync_status_ui.%s
						FROM asset_sync_status_ui
						INNER JOIN spacefavorite ON spacefavorite.assetId == asset_sync_status_ui.assetId
						INNER JOIN CARRAY( ?, ?, 'char*' ) AS fi ON fi.value = spacefavorite.docId ]],
						syncStatusColumnName ),
			calm = 2,
			favoriteIds,
			#favoriteIds,
		}
		
		:map( reduceSyncStatuses )

end

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

--[[!

	\brief Given a list of comment IDs, this function returns a map of the asset
		IDs referenced by the comment documents, along with a boolean indicating
		the sync status of the associated assets.

	\param wfCatalog (WFCatalog)
	\param commentIds (table) A table of comment IDs.

	\return (void) Returns a map whose keys are asset IDs, and whose values are
		booleans representing the whether or not the asset is considered fully synced.

--]]

function WFCatSyncStatus.getSyncStatusOfAssetsOfComments( wfCatalog, commentIds )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	return wfCatalog:indexStore():query {
			sql = string.format( 
					[[SELECT asset_sync_status_ui.assetId, asset_sync_status_ui.%s
						FROM asset_sync_status_ui
						INNER JOIN comment ON comment.assetId == asset_sync_status_ui.assetId
						INNER JOIN CARRAY( ?, ?, 'char*' ) AS ci ON ci.value = comment.docId ]],
						syncStatusColumnName ),
			calm = 2,
			commentIds,
			#commentIds,
		}
		
		:map( reduceSyncStatuses )

end

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

function WFCatSyncStatus.syncPendingForAnyAsset( wfCatalog )

	local syncingStatusIds = getSyncingStatusIds( kAssetSyncStatus )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	local isSyncPendingSQL = string.format( 
			[[SELECT %s 
				FROM asset_sync_status_ui 
				WHERE %s IN ( %s )
				LIMIT 1]], 
				syncStatusColumnName, syncStatusColumnName, 
				WFSQLiteUtils.createVariableArgList( #syncingStatusIds ) )

	return wfCatalog:indexStore():query {
			sql = isSyncPendingSQL,
			unpack( syncingStatusIds )
		}
		:map( function( results )
			return KSTableUtils.derefOpt( results, 1, 1 ) ~= nil
		end )
		:distinctUntilChanged()

end

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

function WFCatSyncStatus.getLocalIdsWithSyncPendingOrError( wfCatalog )
	
	local syncingStatusIds = getSyncingStatusIds( kAssetSyncStatus )
	
	local errorStatusIds = getErrorStatusIds( kAssetSyncStatus )
	
	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
	
	local syncingOrErrorStatusIds = KSTableUtils.appendArray( syncingStatusIds, errorStatusIds )
	
	local assetsWithSyncPendingOrErrorSQL = string.format(
		[[SELECT docId 
			FROM asset_sync_status_ui 
			WHERE %s IN ( %s )]], syncStatusColumnName,
		WFSQLiteUtils.createVariableArgList( #syncingOrErrorStatusIds ) )
	
	return wfCatalog:indexStore():query {
			sql = assetsWithSyncPendingOrErrorSQL,
			unpack( syncingOrErrorStatusIds )
		}
		:map( function( rows )
			local docIds = {}
			for _, row in ipairs( rows ) do
				table.insert( docIds, row[ 1 ] )
			end
			return docIds
		end )
end

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

-- Allows for identification and count of assets pending restoration, which is
-- needed for 'pancake' messaging purposes when a restore happens while the user
-- is out of Oz storage or has an expired subscription.

function WFCatSyncStatus.getNumAssetsWithRestorationPending( wfCatalog )
	
	local assetsWithRestorationPendingSQL = 
		[[SELECT COUNT(1), MIN(purgeDate) FROM assetsPendingRestore]]
	
	return wfCatalog:indexStore():query {
			sql = assetsWithRestorationPendingSQL,
			calm = 2,
		}
		:map( function( rows )
			local row = rows[ 1 ]
			return { numAssets = row[ 1 ], firstPurgeDate = row[ 2 ] }
		end )
end

--------------------------------------------------------------------------------
function WFCatSyncStatus.getDocIdsAndStatusWithSyncPendingOrError( wfCatalog )

	local syncingStatusIds = getSyncingStatusIds( kAssetSyncStatus )
	
	local errorStatusIds = getErrorStatusIds( kAssetSyncStatus )
	
	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
	
	local syncingOrErrorStatusIds = KSTableUtils.appendArray( syncingStatusIds, errorStatusIds )
	
	local assetsWithSyncPendingOrErrorSQL = string.format(
		[[SELECT assetId, %s
			FROM asset_sync_status_ui 
			WHERE %s IN ( %s )]], syncStatusColumnName, syncStatusColumnName,
		WFSQLiteUtils.createVariableArgList( #syncingOrErrorStatusIds ) )
		
	return wfCatalog:indexStore():query {
			sql = assetsWithSyncPendingOrErrorSQL,
			unpack( syncingOrErrorStatusIds )
		}
		:map( function( rows )
			local results = {}
			for _, row in ipairs( rows ) do
				table.insert( results, { assetId = row[ 1 ], status = row[ 2 ], statusString = kAssetSyncStatusById[ row[ 2 ] ] } )
			end
			return results
		end )
end

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

function WFCatSyncStatus.persistentSyncErrorForAnyAsset( wfCatalog )
	
	local errorStatusIds = getErrorStatusIds( kAssetSyncStatus )

	-- Because search eliminates assets in spaces and does not include assets
	-- that are in the trash use assetGridStatus for this search so that the
	-- cloudy icon won't show an error state when errors exist in those
	-- areas.  The respect album / trash grid views will show the badges with
	-- error there so leave the existing status as is.

	return wfCatalog:indexStore():query {
			sql = string.format( 
				[[SELECT searchGridStatus 
					FROM asset_sync_status_ui 
					WHERE searchGridStatus IN ( %s )  LIMIT 1]],
				WFSQLiteUtils.createVariableArgList( #errorStatusIds ) ),
			singleColumnQuery = true,
			unpack( errorStatusIds )
		}
		:map( function( results )
			return KSTableUtils.derefOpt( results, 1 ) ~= nil
		end )
		:distinctUntilChanged()

end

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

function WFCatSyncStatus.getSyncStatusForAssets( wfCatalog )
	
	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
	
	-- For the query, searchGridStatus != 0 means it is in the trash or a shared with you
	-- album and we don't want to include it in the sync status counts.
	
	return wfCatalog:indexStore():query {
			sql = string.format( 
				[[SELECT %s, count( %s ) 
					FROM asset_sync_status_ui
					WHERE searchGridStatus > 0
					GROUP BY %s]],
					syncStatusColumnName, syncStatusColumnName, syncStatusColumnName )
		}
		:map( function( rows )
			local statusCounts = {}
			
			for _, row in ipairs( rows ) do
				local statusId = row[ 1 ]
				local statusCount = row[ 2 ]
				
				if statusId and statusCount then
					local status = kAssetSyncStatusById[ statusId ]
					if status then
						statusCounts[ status ] = tonumber( statusCount ) or 0
					end
				end
			end
			
			for _, status in pairs( kAssetSyncStatusById ) do
				if not statusCounts[ status ] then 
					statusCounts[ status ] = 0
				end
			end
			
			return statusCounts
		end )

end

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

function WFCatSyncStatus.isDoingInitialSync( wfCatalog )

	return wfCatalog:docStore():ongoingLocalDocValue( 'Oz-isDoingInitialSync' )
	
		:map( function( docValue )
		
			local isDoingInitialSync = true
			
			if docValue.isDoingInitialSync ~= nil then
			 
				isDoingInitialSync = docValue.isDoingInitialSync
				
			end
			
			return isDoingInitialSync
			
		end )
		
end

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

function WFCatSyncStatus.translateAssetSyncStatus( status )
	
	return kAssetSyncStatusById[ status ]

end

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

function WFCatSyncStatus.translateAssetSyncLocalFileType( localFileType )
	
	return kAssetLocalFileTypeById[ localFileType ]

end

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

function WFCatSyncStatus.completeNoErrorSyncStatus()
	
	return require 'WFCatSearchBySyncStatus'.completeNoErrorSyncStatus()()()

end

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

function WFCatSyncStatus.isAssetCompleted( statusOrId )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAssetSyncStatusById[ statusOrId ]
	end

	return not not kNonSyncingStateSet[ statusOrId ] 
		
end

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

function WFCatSyncStatus.isAlbumCompleted( statusOrId )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAlbumSyncStatusById[ statusOrId ]
	end

	return statusOrId == kAlbumComplete
		or statusOrId == kAlbumCompleteForOfflineUse

end

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

function WFCatSyncStatus.calculateSyncStatusWithVolumeStatus( 
		statusOrId, hasLocalOriginal, hasLocalProxy, 
		isOriginalOnKnownVolume, isProxyOnKnownVolume,
		isVolumeForOriginalsOnline, isVolumeForProxiesOnline,
		isVolumeForOriginalsOutOfSpace, isVolumeForProxiesOutOfSpace,
		isOriginalOnOnlineVolume, isProxyOnOnlineVolume )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAssetSyncStatusById[ statusOrId ]
	end

	if statusOrId == kAssetNeedsOriginalDownload
		or statusOrId == kAssetNeedsOriginalDownloadForOffline then
		
		if not isVolumeForOriginalsOnline then
		
			-- Original needs to be downloaded to an offline volume.
			statusOrId = kAssetNeedsOriginalDownloadToOfflineVolume
			
		elseif isVolumeForOriginalsOutOfSpace then
		
			-- Original needs to be downloaded to an out-of-space volume.
			statusOrId = kAssetNeedsOriginalDownloadToOutOfSpaceVolume
		
		end
		
	elseif statusOrId == kAssetNeedsProxyDownload then
	
		if not isVolumeForProxiesOnline then
		
			-- Proxy needs to be downloaded to an offline volume.
			statusOrId = kAssetNeedsProxyDownloadToOfflineVolume
			
		elseif isVolumeForProxiesOutOfSpace then
		
			-- Proxy needs to be downloaded to an out-of-space volume.
			statusOrId = kAssetNeedsProxyDownloadToOutOfSpaceVolume
		
		end
	
	elseif statusOrId == kAssetNeedsOriginalUpload then

		if hasLocalOriginal and not isOriginalOnKnownVolume then
		
			-- Original needs to be migrated to the original volume.
			statusOrId = kAssetNeedsOriginalMigration
		
		elseif hasLocalOriginal and not isOriginalOnOnlineVolume then
		
			-- Original needs to be uploaded, but is on an offline volume.
			statusOrId = kAssetNeedsOriginalUploadFromOfflineVolume
			
		end
		
	elseif kNonSyncingStateSet[ statusOrId ] then
		
		if hasLocalProxy and not isProxyOnKnownVolume then
		
			-- Proxy needs to be migrated to the proxy volume.
			statusOrId = kAssetNeedsProxyMigration
			
		elseif hasLocalProxy and not isProxyOnOnlineVolume then
		
			-- Proxy is downloaded, but on an offline volume.
			statusOrId = kAssetProxyIsOnOfflineVolume
			
		end
		
		if hasLocalOriginal and not isOriginalOnKnownVolume then
		
			-- Original needs to be migrated to the original volume.
			statusOrId = kAssetNeedsOriginalMigration
		
		elseif hasLocalOriginal and not isOriginalOnOnlineVolume then
		
			-- Original is downloaded, but on an offline volume.
			statusOrId = kAssetOriginalIsOnOfflineVolume
			
		end
		
	end

	return kAssetSyncStatus[ statusOrId ]

end

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

function WFCatSyncStatus.calculateSyncStatusWithGlobalStatus( 
		statusOrId, isSyncPaused, isOffline )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAssetSyncStatusById[ statusOrId ]
	end

	if isSyncPaused or isOffline then
	
		if statusOrId == kAssetNeedsMetadataUpload
			or statusOrId == kAssetNeedsOriginalUpload
			or statusOrId == kAssetNeedsOriginalDownload
			or statusOrId == kAssetNeedsOriginalDownloadForOffline
			or statusOrId == kAssetNeedsProxyDownload
			or statusOrId == kAssetNeedsProxyDownloadForOffline
			or statusOrId == kAssetNeedsDevelopXmpDownload
			or statusOrId == kAssetNeedsOriginalDownloadToOfflineVolume
			or statusOrId == kAssetNeedsProxyDownloadToOfflineVolume
			or statusOrId == kAssetNeedsOriginalUploadFromOfflineVolume
			or statusOrId == kAssetNeedsOriginalDownloadToOutOfSpaceVolume
			or statusOrId == kAssetNeedsProxyDownloadToOutOfSpaceVolume then
			
			statusOrId = isOffline and kAssetSyncNoConnection or kAssetSyncPaused
			
		end
		
	end

	return kAssetSyncStatus[ statusOrId ]

end

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

function WFCatSyncStatus.offlineVolumesRequiringSyncing( wfCatalog )

	local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()

	local syncStatusQuery = wfCatalog:indexStore():query {
		sql = string.format( 
				[[SELECT DISTINCT %s, volumeGuidUsedByOriginal, volumeGuidUsedByProxy
					FROM asset_sync_status_ui
					WHERE %s IN ( ?, ?, ?, ?, ? )]],
				syncStatusColumnName, syncStatusColumnName ),
		kAssetSyncStatus[ kAssetNeedsOriginalDownload ],
		kAssetSyncStatus[ kAssetNeedsOriginalDownloadForOffline ],
		kAssetSyncStatus[ kAssetNeedsProxyDownload ],
		kAssetSyncStatus[ kAssetNeedsProxyDownloadForOffline ],
		kAssetSyncStatus[ kAssetNeedsOriginalUpload ],
		calm = 1,
	}
	
	-- Combine the asset sync status query with the volume info status stream. We
	-- use this information to map the asset sync status to a new status based
	-- on the connection status of the volumes.
	
	local mappedVolumeStatuses = WFCatSyncStatus.getVolumeInfoForSyncStatus( wfCatalog )
	
	local volumesRequiringSyncingStream 
		= Rx.Observable.combineLatest( syncStatusQuery, mappedVolumeStatuses )
		
			:map( function( rows, volumeGuidForOriginals, volumeGuidForProxies, 
					onlineVolumes, outOfSpaceVolumes, knownVolumes )
		
				local volumesRequiringSync = {}
			
				local isVolumeForOriginalsOnline = onlineVolumes[ volumeGuidForOriginals ]
				local isVolumeForProxiesOnline = onlineVolumes[ volumeGuidForProxies ]
			
				local isVolumeForOriginalsOutOfSpace = outOfSpaceVolumes[ volumeGuidForOriginals ]
				local isVolumeForProxiesOutOfSpace = outOfSpaceVolumes[ volumeGuidForProxies ]
			
				for _, row in ipairs( rows ) do
				
					local syncStatusId = row[ 1 ]
					local volumeGuidUsedByOriginal = row[ 2 ]
					local volumeGuidUsedByProxy = row[ 3 ]
					
					local isOriginalOnKnownVolume = knownVolumes[ volumeGuidUsedByOriginal ]
					local isProxyOnKnownVolume = knownVolumes[ volumeGuidUsedByProxy ]
					
					local isOriginalOnOnlineVolume = onlineVolumes[ volumeGuidUsedByOriginal ] 
					local isProxyOnOnlineVolume = onlineVolumes[ volumeGuidUsedByProxy ]
					
					-- Calculate the new sync status.
					
					syncStatusId = WFCatSyncStatus.calculateSyncStatusWithVolumeStatus( 
							syncStatusId, volumeGuidUsedByOriginal, volumeGuidUsedByProxy,
							isOriginalOnKnownVolume, isProxyOnKnownVolume,
							isVolumeForOriginalsOnline, isVolumeForProxiesOnline,
							isVolumeForOriginalsOutOfSpace, isVolumeForProxiesOutOfSpace,
							isOriginalOnOnlineVolume, isProxyOnOnlineVolume )
					
					local syncStatus = kAssetSyncStatusById[ syncStatusId ]
					
					if syncStatus == kAssetNeedsOriginalUploadFromOfflineVolume then
					
						volumesRequiringSync[ volumeGuidUsedByOriginal ] = true
					
					elseif syncStatus == kAssetNeedsOriginalDownloadToOfflineVolume then
						
						volumesRequiringSync[ volumeGuidForOriginals ] = true
						
					elseif syncStatus == kAssetNeedsProxyDownloadToOfflineVolume then
					
						volumesRequiringSync[ volumeGuidForProxies ] = true
					
					end
					
				end
			
				return volumesRequiringSync
			
			end )
			
	return volumesRequiringSyncingStream	
		
end

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

function WFCatSyncStatus.getAssetSyncStatusList()

	return kAssetSyncStatus

end

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

function WFCatSyncStatus.getNonSyncingAssetSyncStatusList()

	return kNonSyncingStates

end

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

return WFCatSyncStatus
