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

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

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

	-- RxLua
local Rx = require 'Rx'

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

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

	-- WF.oz_client.managed_catalog
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatDelete = require 'WFCatDelete'

local WFCatIndexUtils = require 'WFCatIndexUtils'

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

local check = assert( WFArguments.check )
KSData.registerArgumentChecker()

local isValidAssetDocument = assert( WFCatIndexUtils.isValidAssetDocument )
local isNonDeletedStyleDocument = assert( WFCatIndexUtils.isNonDeletedStyleDocument )

local derefOpt = KSTableUtils.derefOpt

local logger = WFLogger 'WFCatStaleBinaryRemover'

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

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

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

local function generateIndexSchema( _, indexStoreSchema )

	check( '_', 'WFRxIndexStoreSchema' )
	
	indexStoreSchema:addTable {

		sql = [[CREATE TABLE staleNegatives(
				localId,
				isLocalOriginalStale,
				isLocalProxyStale )]],
				
		hasOneRowPerDocument = true,
		
		relevantTypeSubtypeTuples = {
			{ 'asset', '*' }
		},

		map = function( docValue, emit )
		
			local isStyleDocument = isNonDeletedStyleDocument( docValue )

			if isValidAssetDocument( docValue ) or isStyleDocument then

				local hasLocalOriginal = docValueHasLocalOriginal( docValue ) 
				
				local localOriginalSha256WithFallback = hasLocalOriginal 
						and WFCatFileSpec.getLocalOriginalSha256WithFallback( docValue )
						
				local currentSha256 = derefOpt( docValue, 'importSource', 'sha256' )
				
				local ozMasterUrl, ozOriginalIsInvalid = WFCatFileSpec.getOriginalLinkFromDocValue( docValue )
				
				local isLocalOriginalStale = 
						hasLocalOriginal
							-- Original must be local for it to be "stale"
						and localOriginalSha256WithFallback
							-- We must know the local original SHA256.
						and currentSha256
							-- Oz must have the original SHA256 in order for us to know if the original 
							-- is "stale"
						and localOriginalSha256WithFallback ~= currentSha256
							-- If the local original SHA256 differes from the SHA256 of the original
							-- on Oz, it's stale.
						and not ozOriginalIsInvalid
							-- If the original on Oz is invalid, leave the stale binary alone.
						and not isStyleDocument
							 -- Leave stale binaries to be overwritten on download of the
							 -- fresh one, to avoid disconcerting issues with temporarily
							 -- disapperaing items in the UI.
				
				-- NMB-10355: For a period of time we were failing to add SHA256 to assets
				-- that were first synced via Classic, and then imported into LrCC. If these
				-- assets have been uploaded to Oz, and are not marked for local use, then
				-- we consider them stale. Part 2 of the fix for NMB-10355 can be found
				-- within NmbSimpleDocStoreUpdater.lua
				
				local isLocalOriginalStaleBecauseOfNoSha256 = false
				
				if not isLocalOriginalStale then
					
					local localOriginalSha256 = hasLocalOriginal and WFCatFileSpec.getLocalOriginalSha256( docValue )
					local isMarkedForOffline = WFCatFileSpec.getMarkedForOfflineUseFromDocValue( docValue )
					
					isLocalOriginalStaleBecauseOfNoSha256 =
						hasLocalOriginal
							-- Original must be local for it to be "stale"
						and currentSha256
							-- Oz must have the sha256
						and not localOriginalSha256
							-- We must not have the local original SHA256
						and ozMasterUrl
							-- The Original must be on Oz
						and not isMarkedForOffline
							-- The Original must not be marked for offline use
							
				end
							
				isLocalOriginalStale = isLocalOriginalStale or isLocalOriginalStaleBecauseOfNoSha256
							
				----------------------------------------------------------------
							
				local hasLocalProxy = docValueHasLocalProxy( docValue )
				local localProxySha256 = hasLocalProxy and WFCatFileSpec.getLocalProxySha256( docValue )
				local _, _, ozProxyIsInvalid = WFCatFileSpec.getProxyLinkFromDocValue( docValue )
				
				local isLocalProxyStale = 
						hasLocalProxy
						and localProxySha256
						and currentSha256
						and localProxySha256 ~= currentSha256
						and not ozProxyIsInvalid
					
				if isLocalOriginalStale or isLocalProxyStale then
				
					isLocalOriginalStale = isLocalOriginalStale and 1 or 0
					isLocalProxyStale = isLocalProxyStale and 1 or 0
				
					emit( docValue._localId, isLocalOriginalStale, isLocalProxyStale )
						
				end
				
			end

		end,

	}
				
end

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

local function toBool( zeroOrOne )

	return zeroOrOne > 0

end

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

local function removeStaleBinaries( self )

	return self._catalog:indexStore():query {
			sql = [[SELECT localId, isLocalOriginalStale, isLocalProxyStale 
					FROM staleNegatives ]],
			calm = 5,
		}

		:flatMapLatest( function( rows )

			return self._continuouStaleBinaryRemoverTask:throwIfNotRunning( rows )
				-- Need to throw as this function doesn't ever complete.

		end )

		:filter( function( rows )
			return rows and #rows > 0
		end )

		:flatMap( function( rows )
		
			local staleOriginalIds = {}
			local staleProxyIds = {}
		
			for _, row in ipairs( rows ) do
			
				if not self._continuouStaleBinaryRemoverTask:isRunning() then break end
			
				local localId = row[ 1 ]
				local isLocalOriginalStale = toBool( row[ 2 ] )
				local isLocalProxyStale = toBool( row[ 3 ] )
						
				if isLocalOriginalStale then
					table.insert( staleOriginalIds, localId )
				end
				
				if isLocalProxyStale then
					table.insert( staleProxyIds, localId )
				end

			end
			
			-- Bulk delete the stale local originals and stale local proxies.

			local deletionBacklog = self._catalog:getDeletionBacklog()
			
			return WFCatDelete.deleteLocalProxiesByDocIds( self._catalog, staleProxyIds, deletionBacklog )
				:flatMap( function()
					return WFCatDelete.deleteLocalOriginalsByDocIds( self._catalog, staleOriginalIds, deletionBacklog )
				end )
				:tap( function()
				
					for _, localId in ipairs( staleOriginalIds ) do
						self._idleDownloader:clearRecentlyDownloadedStatus( localId, 'original' )
							-- Ensure that we can immediately download the fresh original from Oz.
					end
				
					for _, localId in ipairs( staleProxyIds ) do
						self._idleDownloader:clearRecentlyDownloadedStatus( localId, 'proxy' )
							-- Ensure that we can immediately download the fresh proxy from Oz.
					end
				
				end )
			
		end )

		:catch( function( errorMessage )

			return self._continuouStaleBinaryRemoverTask:throwIfNotStopped( errorMessage )

		end )

end


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

-- @mark WFCatStaleBinaryRemover namespace

local WFCatStaleBinaryRemover = {

	_catalog = false,
		-- (WFCatalog)
		
	_idleDownloader = false,
		-- (WFRxIdleDownloader)
		
	_continuouStaleBinaryRemoverTask = false,
		-- (WFRxContinuousTask)

	class = {
		generateIndexSchema = generateIndexSchema,
	},

	WFCloseSupport.addCloseSupportMethods,

}

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

function WFCatStaleBinaryRemover:__init( catalog, idleDownloader )

	check( 'WFCatStaleBinaryRemover', 'WFCatalog', 'WFCatIdleDownloader' )

	logger:infof( "Constructing idle binary downloader for catalog %s", tostring( catalog ) )

	self:setOwned {
		_catalog = catalog:relayObject()
	}

	self._idleDownloader = idleDownloader

	local continuousTaskDelays = {
		progressiveBackoff = true,
		errorDelay = 5,
		maxDelay = 10,
	}
	
	local continuousTask = function() return removeStaleBinaries( self ) end
	
	local taskName = "WFCatStaleBinaryRemover"
	
	self._continuouStaleBinaryRemoverTask = WFRxContinuousTask( taskName, continuousTask, continuousTaskDelays )
	self._continuouStaleBinaryRemoverTask:startContinuousTask()
	
end

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

function WFCatStaleBinaryRemover:close()

	check( 'WFCatStaleBinaryRemover' )
	
	local continuouStaleBinaryRemoverTask = self._continuouStaleBinaryRemoverTask
	self._continuouStaleBinaryRemoverTask = nil
	
	if continuouStaleBinaryRemoverTask then
		continuouStaleBinaryRemoverTask:close()
	end

end

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

return Rx.Class 'WFCatStaleBinaryRemover'( WFCatStaleBinaryRemover )
