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

NmbPurgeDbUpdater.lua

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

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

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

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

	-- WF.oz_client.managed_catalog
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatIndexUtils = require 'WFCatIndexUtils'
local WFCatPhotoImportance = require 'WFCatPhotoImportance'

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

local derefOpt = KSTableUtils.derefOpt

local check = assert( WFArguments.check )

local isValidAssetDocument = assert( WFCatIndexUtils.isValidAssetDocument )

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

local kNullState = 'null'

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

local function docValueHasLocalOriginal( docValue )

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

end

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

local function didLocalNegativeStatusChange( assetInfo, docValue )

	local previousLocalOriginalGuid = assetInfo.localOriginalVolumeGuid
	local previousLocalProxyGuid = assetInfo.localProxyVolumeGuid
	
	local localNegativeInfo = {

		originalVolumeGuid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue ),
		originalRelativePath = WFCatFileSpec.getOriginalRelativePathFromDocValue( docValue ),
		proxyVolumeGuid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue ),
		proxyRelativePath = WFCatFileSpec.getProxyRelativePathFromDocValue( docValue )
		
	}

	assetInfo.localOriginalVolumeGuid = localNegativeInfo.originalVolumeGuid
	assetInfo.localProxyVolumeGuid = localNegativeInfo.proxyVolumeGuid
	
	local hasLocalOriginalUpdate = localNegativeInfo.originalVolumeGuid ~= previousLocalOriginalGuid 
	local hasLocalProxyUpdate = localNegativeInfo.proxyVolumeGuid ~= previousLocalProxyGuid
	
	return assetInfo, ( hasLocalOriginalUpdate or hasLocalProxyUpdate ) and localNegativeInfo

end

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

local function didImportanceChange( assetInfo, docValue )

	local previousImportance = assetInfo.importance

	local currentImportanceInfo = {
		isImportant = WFCatPhotoImportance.isPhotoImportant( docValue )
	}
	
	assetInfo.importance = currentImportanceInfo.isImportant
	
	local hasImportanceUpdate = currentImportanceInfo.isImportant ~= previousImportance 
	
	return assetInfo, hasImportanceUpdate and currentImportanceInfo

end

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

local function didSizeChange( assetInfo, docValue )

	local previousProxySize = assetInfo.proxySize
	local previousOriginalSize = assetInfo.originalSize
	local previousXmpSidecarSize = assetInfo.xmpSidecarSize

	local hasLocalSidecar = WFCatFileSpec.getXmpSidecarFileSizeFromDocValue( docValue )
	local xmpSidecarSize = hasLocalSidecar and WFCatFileSpec.getXmpSidecarFileSizeFromDocValue( docValue ) or 0

	local currentSizeInfo = {
		proxySize = WFCatFileSpec.getProxySizeFromDocValue( docValue ) or 0,
		originalSize = WFCatFileSpec.getOriginalSizeFromDocValue( docValue ) or 0,
		xmpSidecarSize = xmpSidecarSize
	}
	
	assetInfo.proxySize = currentSizeInfo.proxySize
	assetInfo.originalSize = currentSizeInfo.originalSize
	assetInfo.xmpSidecarSize = currentSizeInfo.xmpSidecarSize
	
	local hasProxyUpdate = currentSizeInfo.proxySize ~= previousProxySize
	
	if not hasProxyUpdate then
		currentSizeInfo.proxySize = nil
			-- Remove proxy size since it doesn't need to be updated.
	end
	
	local hasOriginalUpdate = currentSizeInfo.originalSize ~= previousOriginalSize
	
	if not hasOriginalUpdate then
		currentSizeInfo.originalSize = nil
			-- Remove original size since it doesn't need to be updated.
	end
	
	local hasXmpSidecarUpdate = currentSizeInfo.xmpSidecarSize ~= previousXmpSidecarSize
	
	if not hasXmpSidecarUpdate then
		currentSizeInfo.xmpSidecarSize = nil
			-- Remove sidecar size since it doesn't need to be updated.
	end
	
	return assetInfo, ( hasProxyUpdate or hasOriginalUpdate or hasXmpSidecarUpdate ) and currentSizeInfo

end

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

local function didOzStatusChange( assetInfo, docValue )

	local previousOzOriginalStatus = assetInfo.ozHasOriginal
	local previousOzProxyStatus = assetInfo.ozHasProxy
	
	local _, isOzOriginalInvalid = WFCatFileSpec.getOriginalLinkFromDocValue( docValue )
	local validSha256 = derefOpt( docValue, 'importSource', 'sha256' )
	local hasLocalOriginal = docValueHasLocalOriginal( docValue )
	local localOriginalSha256 = hasLocalOriginal and WFCatFileSpec.getLocalOriginalSha256WithFallback( docValue )
	
	local isOriginalSafeToPurge = false
	
	-- If the original on Oz is invalid, then we should only purge the local original
	-- if it's also invalid. If the local original is valid, we shouldn't purge it
	-- in case it's the only copy of the valid original.

	if isOzOriginalInvalid then
		
		local localOriginalIsInvalid = validSha256 ~= localOriginalSha256

		if localOriginalIsInvalid then
			isOriginalSafeToPurge = true
		end
		
	else
		isOriginalSafeToPurge = true
	end
	
	local currentOzStatusInfo = {
		ozHasOriginal = isOriginalSafeToPurge and not not WFCatFileSpec.getOriginalLinkFromDocValue( docValue ),
		ozHasProxy = not not WFCatFileSpec.getProxyLinkFromDocValue( docValue )
	}
	
	assetInfo.ozHasOriginal = currentOzStatusInfo.ozHasOriginal
	assetInfo.ozHasProxy = currentOzStatusInfo.ozHasProxy
	
	local hasOzOriginalStatusUpdate = currentOzStatusInfo.ozHasOriginal ~= previousOzOriginalStatus 

	if not hasOzOriginalStatusUpdate then
		currentOzStatusInfo.ozHasOriginal = nil
			-- Remove oz original status since it doesn't need to be updated.
	end

	local hasOzProxyStatusUpdate = currentOzStatusInfo.ozHasProxy ~= previousOzProxyStatus

	if not hasOzProxyStatusUpdate then
		currentOzStatusInfo.ozHasProxy = nil
			-- Remove oz proxy status since it doesn't need to be updated.
	end

	return assetInfo, ( hasOzOriginalStatusUpdate or hasOzProxyStatusUpdate ) and currentOzStatusInfo

end

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

local function didMarkedForOfflineUseChange( assetInfo, docValue )

	local previousMarkedForOfflineUse = assetInfo.isMarkedForOfflineUse

	local currentMarkedForOfflineUseInfo = {
		isMarkedForOfflineUse = WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue )
	}
	
	assetInfo.isMarkedForOfflineUse = currentMarkedForOfflineUseInfo.isMarkedForOfflineUse
	
	local hasMarkedForOfflineUseUpdate = 
			currentMarkedForOfflineUseInfo.isMarkedForOfflineUse ~= previousMarkedForOfflineUse 
	
	return assetInfo, hasMarkedForOfflineUseUpdate and currentMarkedForOfflineUseInfo

end

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

local function didRenditionInfoChange( assetInfo, docValue )

	local previousRenditionFoundOnOz = assetInfo.sufficientRenditionFoundOnOz
	
	local currentRenditionFoundOnOz = {
		sufficientRenditionFoundOnOz = WFCatFileSpec.checkOzForNonThumbnailRendition( docValue )
	}
	
	assetInfo.sufficientRenditionFoundOnOz = currentRenditionFoundOnOz.sufficientRenditionFoundOnOz
	
	local hasRenditionUpdate = currentRenditionFoundOnOz.sufficientRenditionFoundOnOz ~= previousRenditionFoundOnOz 
	
	return assetInfo, hasRenditionUpdate and currentRenditionFoundOnOz

end

--============================================================================--
--[[!

	\namespace NmbPurgeDbUpdater
	
	\brief This namespace is used to monitor the doc store and update the
		NmbFileAccessDatabase db used by the purge agent.
	
--]]

-- @mark NmbPurgeDbUpdater namespace

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

local NmbPurgeDbUpdater = {}

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

function NmbPurgeDbUpdater.scanForAssetUpdates( assetInfoCache, docValue )
	
	-- WARNING [jandrew 2017-10-25]:
	-- If the implemenation changes in this function, or in any of the called
	-- functions, we may need to manually increment dbUpdaterVersion found in
	-- NmbFileAccessDatabase.lua so that the MRU table is rebuilt.
	
	local docId = docValue._id
	local localId = docValue._localId
	local changeCounter = docValue._changeCounter
	
	if not isValidAssetDocument( docValue ) then
		return assetInfoCache, localId, docId, changeCounter
	end
	
	local assetInfo = assetInfoCache[ localId ] or {
		localOriginalVolumeGuid = false,
		localProxyVolumeGuid  = false,
			-- `nil` is a valid value for these fields. Init'ing to false ensures the fields update on
			-- the first value change.
	}
	
	-- Check to see if there have been any changes to the local negative status.
	
	local maybeLocalNegativeInfo

	assetInfo, maybeLocalNegativeInfo = didLocalNegativeStatusChange( assetInfo, docValue )

	-- Check to see if there have been any changes to the asset importance.
	
	local maybeImportanceInfo
	
	assetInfo, maybeImportanceInfo = didImportanceChange( assetInfo, docValue )
	
	-- Check to see if there have been any changes to file size.
	
	local maybeSizeInfo
	
	assetInfo, maybeSizeInfo = didSizeChange( assetInfo, docValue )
	
	-- Check to see if there have been changes to the oz status.
	
	local maybeOzStatusInfo
	
	assetInfo, maybeOzStatusInfo = didOzStatusChange( assetInfo, docValue )
	
	-- Check to see if there have been any changes to the renditions on Oz.
	
	local maybeRenditionInfo
	
	assetInfo, maybeRenditionInfo = didRenditionInfoChange( assetInfo, docValue )
	
	-- Check to see if there have been any changes to marked for offline use status.
	
	local maybeMarkedForOfflineInfo
	
	assetInfo, maybeMarkedForOfflineInfo = didMarkedForOfflineUseChange( assetInfo, docValue )
	
	assetInfoCache[ localId ] = assetInfo

	return assetInfoCache, localId, docId, changeCounter,
			maybeLocalNegativeInfo, maybeImportanceInfo,
			maybeSizeInfo, maybeOzStatusInfo, maybeRenditionInfo, maybeMarkedForOfflineInfo

end

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

function NmbPurgeDbUpdater.trackAssetUpdates( docStore, fileAccessDb, deletionBacklog, optBufferTime )

	check( 'WFRxDocStore', 'NmbFileAccessDatabase', 'NmbDeletionBacklog', '?number' )

	local assetInfoCache = {}

	local changeCounterStream = fileAccessDb:getContinueFromChangeCounter()

	local updateFileAccessDatabaseFileStatuses = changeCounterStream
	
		:flatMapLatest( function( changeCounter )
			return docStore
				:scannedFilteredMappedChangesFeedSince( 
						'NmbPurgeDbUpdater', 
						'scanForAssetUpdates', 
						assetInfoCache,
						changeCounter )
			
		end )
		:map( function( localAssetId, docId, changeCounter, maybeLocalNegativeInfo, maybeImportanceInfo, 
				maybeSizeInfo, maybeOzStatusInfo, maybeRenditionInfo, maybeMarkedForOfflineInfo )
			
			return {
				localAssetId = localAssetId,
				docId = docId,
				changeCounter = changeCounter,
				maybeLocalNegativeInfo = maybeLocalNegativeInfo,
				maybeImportanceInfo = maybeImportanceInfo,
				maybeSizeInfo = maybeSizeInfo,
				maybeOzStatusInfo = maybeOzStatusInfo,
				maybeRenditionInfo = maybeRenditionInfo,
				maybeMarkedForOfflineInfo = maybeMarkedForOfflineInfo
			}
			
		end )
		:bufferWithTimeOrCount( optBufferTime or 5, 100, require 'WFRxScheduler'.sharedScheduler() )
		:subscribe( function( bufferedResults ) 
			
			if WFCloseSupport.hasBeenOrWillBeClosed( fileAccessDb ) or
				WFCloseSupport.hasBeenOrWillBeClosed( deletionBacklog ) then
				return
			end
			
			local latestChangeCounter
			
			for _, row in ipairs( bufferedResults ) do
				
				local localAssetId = row.localAssetId
				local docId = row.docId
				local changeCounter = row.changeCounter
				local maybeLocalNegativeInfo = row.maybeLocalNegativeInfo
				local maybeImportanceInfo = row.maybeImportanceInfo
				local maybeSizeInfo = row.maybeSizeInfo
				local maybeOzStatusInfo = row.maybeOzStatusInfo
				local maybeRenditionInfo = row.maybeRenditionInfo
				local maybeMarkedForOfflineInfo = row.maybeMarkedForOfflineInfo
				
				local assetInfo = {
					_localId = localAssetId,
					docId = docId
				}
				
				local hasUpdate = false
				
				if maybeLocalNegativeInfo then
				
					hasUpdate = true
					
					assetInfo.volumeGuidUsedByOriginal = maybeLocalNegativeInfo.originalVolumeGuid or kNullState

					if maybeLocalNegativeInfo.originalVolumeGuid and maybeLocalNegativeInfo.originalRelativePath then
						
						deletionBacklog:removeFromDeletionBacklog( maybeLocalNegativeInfo.originalVolumeGuid, maybeLocalNegativeInfo.originalRelativePath )
								-- If the original has been previously added to the deletion backlog, clear it out
								-- so we don't delete it.
								
					end
					
					assetInfo.volumeGuidUsedByProxy = maybeLocalNegativeInfo.proxyVolumeGuid or kNullState
					
					if maybeLocalNegativeInfo.proxyVolumeGuid and maybeLocalNegativeInfo.proxyRelativePath then
					
						deletionBacklog:removeFromDeletionBacklog( maybeLocalNegativeInfo.proxyVolumeGuid, maybeLocalNegativeInfo.proxyRelativePath )
								-- If the proxy has been previously added to the deletion backlog, clear it out
								-- so we don't delete it.
								
					end
				
				end
				
				if maybeImportanceInfo then
				
					hasUpdate = true
					
					assetInfo.isImportant = maybeImportanceInfo.isImportant or false
				
				end
				
				if maybeSizeInfo then
				
					hasUpdate = true
					
					if maybeSizeInfo.proxySize then
						assetInfo.proxyFileSize = maybeSizeInfo.proxySize or kNullState
					end
					
					if maybeSizeInfo.originalSize then
						assetInfo.originalFileSize = maybeSizeInfo.originalSize or kNullState
					end
					
					if maybeSizeInfo.xmpSidecarSize then
						assetInfo.xmpSidecarSize = maybeSizeInfo.xmpSidecarSize or kNullState
					end
					
				end
				
				if maybeOzStatusInfo then
				
					hasUpdate = true
					
					if maybeOzStatusInfo.ozHasOriginal ~= nil then
						assetInfo.ozHasOriginal = maybeOzStatusInfo.ozHasOriginal or false
					end
					
					if maybeOzStatusInfo.ozHasProxy ~= nil then
						assetInfo.ozHasProxy = maybeOzStatusInfo.ozHasProxy or false
					end
					
				end
			
				if maybeRenditionInfo then

					hasUpdate = true
					
					assetInfo.sufficientRenditionFoundOnOz = maybeRenditionInfo.sufficientRenditionFoundOnOz or false
				
				end
				
				if maybeMarkedForOfflineInfo then
				
					hasUpdate = true
					
					assetInfo.isMarkedForOfflineUse = maybeMarkedForOfflineInfo.isMarkedForOfflineUse or false
				
				end
			
				latestChangeCounter = changeCounter
				
				if hasUpdate then
					fileAccessDb:updateAssetInfo( assetInfo )
				end
				
			end
			
			if latestChangeCounter then
				fileAccessDb:updateChangeCounter( latestChangeCounter )
			end
			
		end )
		
	docStore:addCloseAction( function()
		updateFileAccessDatabaseFileStatuses:dispose()
		updateFileAccessDatabaseFileStatuses = nil
	end )
	
end

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

return NmbPurgeDbUpdater
