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

NmbOfflineAssets.lua

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

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

NOTICE: All information contained herein is, and remains the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

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

local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatDelete = require 'WFCatDelete'
local WFCatAlbums = require 'WFCatAlbums'
local WFCatVolumeAgent = require 'WFCatVolumeAgent'

	-- WF.core.utils
local WFFileUtils = require 'WFFileUtils'
local WFRxThrottledBatchedBulkApplyOverlay = require "WFRxThrottledBatchedBulkApplyOverlay"
local WFRxThrottledBatchedBulkDocValue = require "WFRxThrottledBatchedBulkDocValue"

local runUntilDone = WFRxRunUntilDone


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

local NmbOfflineAssets = {}

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

--[[!

	\brief Marks the given asset ids to be kept locally, meaning we will download
	and store the originals to the users machine. This flag is machine-specific,
	and does not sync.

	\param wfCatalog (WFCatalog) 
	\param imageIds (table) a table of asset ids to be marked for offline use.

	\return <code>Observable</code> that completes when the assets have been marked
		for offline use.
	
--]]

function NmbOfflineAssets.enableAssetsOfflineUse( wfCatalog, imageIds )

	local docStore = wfCatalog:docStore()
	
	local deletionBacklog = wfCatalog:getDeletionBacklog()

	return WFRxThrottledBatchedBulkDocValue( "NmbOfflineAssets_enableAssetsOfflineUse", docStore, imageIds, 100, 10 )
	
		:flatMap( function( docValues )
			return Observable.from( docValues )
		end )
	
		:flatMap( function( docValue )
		
			local hasLocalOriginalPath = WFCatFileSpec.buildOriginalPathFromDocValue( docValue )
		
			-- hasLocalOriginalPath will equal `nil` if the volume is offline,
			-- so we don't have to worry about deleting paths for offline assets.
		
			if hasLocalOriginalPath and not WFFileUtils.exists( hasLocalOriginalPath ) then
			
				-- If the local path is valid, but the asset doesn't exist, then delete
				-- that path annotation. This is to cover the case where the asset is
				-- manually deleted from disk and then the user chooses to mark the asset
				-- for local use.

				return WFCatDelete.deleteLocalOriginalsByDocValues( wfCatalog, { docValue }, deletionBacklog )
					:map( function()
						return docValue._id
					end )
			
			end
		
			return Observable.just( docValue._id )
		
		end )
		
		:map( function( docId )
		
			local baseOverlay = { _id = docId }
		
			baseOverlay._localOnly = {
				isMarkedForOfflineUse = true,
			}
			
			return baseOverlay
			
		end )
		
		:toArray()
		
		:flatMap( function( overlays )
			return WFRxThrottledBatchedBulkApplyOverlay( "NmbOfflineAssets_enableAssetsOfflineUse", wfCatalog:docStore(), overlays, 100, 10 )
		end )
		
		:takeLast( 0 )

end

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

--[[!

	\brief Remove the marked-for-offline-use flag from the given asset ids. Once removed, the
	originals associated with the assets will become purgeable.

	\param wfCatalog (WFCatalog) 
	\param imageIds (table) a table of asset ids
	
	\return <code>Observable</code> that completes when the marked-for-offline-use
		flag has been removed from the given assets.
	
--]]

function NmbOfflineAssets.disableAssetsOfflineUse( wfCatalog, imageIds )

	return Observable.from( imageIds )

		:map( function( docId )
		
			local baseOverlay = { _id = docId }
		
			baseOverlay._localOnly = {
				isMarkedForOfflineUse = { _delete = true },
			}
		
			return baseOverlay
			
		end )
		
		:toArray()
		
		:flatMapLatest( function( overlays )
			return WFRxThrottledBatchedBulkApplyOverlay( "NmbOfflineAssets_disableAssetsOfflineUse", wfCatalog:docStore(), overlays, 100, 10 )
		end )
		
		:takeLast( 0 )

end

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

--[[!

	\brief Ask the volume agent if we have enough space to download the provided
		albums to disk. If there's not enough space, the purge agent will try
		to reclaim space by purging existing local assets.
	
	\param wfCatalog (WFCatalog) 
	\param albumIds (table) a table of album ids
	
	\return <code>Observable</code> that fires with an integer representing the number
		of bytes needed to be freed.
		
		If this value is 0, then the space check succeeded and we can download the albums
		to disk.
		
		If this value is greater than 0, the user may need free up additional space for
		the album downloads to complete.
	
--]]

function NmbOfflineAssets.checkAndReclaimSpaceForAlbums( wfCatalog, albumIds )

	local fileSpec = wfCatalog:fileSpec()
	local originalsVolumeGuid = fileSpec:originalsPathInfo().volumeGuid

	if not WFFileUtils.isVolumeGuidOnline( originalsVolumeGuid ) then
	
		return Observable.just( 0 )
	
	end

	local volumeAgent = wfCatalog:getVolumeAgentByVolumeGuid( originalsVolumeGuid )

	local listOfAlbumIds = {}
	
	for albumId, _ in pairs( albumIds ) do
	
		table.insert( listOfAlbumIds, albumId )
	
	end

	local volumePath = WFFileUtils.getVolumeFromGuid( originalsVolumeGuid )

	local availableSpace = volumePath and WFCatVolumeAgent.checkAvailableSpaceOnVolume( volumePath ) or 0

	return Observable.combineLatest( 
		WFCatAlbums.getSizeOfAlbumsForLocalUse( wfCatalog, listOfAlbumIds, true ),
		WFCatAlbums.getSizeOfAlbumsForLocalUse( wfCatalog, listOfAlbumIds ) )
		
		:flatMapLatest( function( albumSize, spaceNeeded )
		
			if spaceNeeded > 0 then
			
				return volumeAgent:tryToClaimSpace( 'NmbOfflineAssets_checkAndReclaimSpaceForAlbums', spaceNeeded )
				
					:map( function( freeSpace )
					
						local totalSpaceNeeded = spaceNeeded - freeSpace
						totalSpaceNeeded =  totalSpaceNeeded > 0 and totalSpaceNeeded or 0
					
						return {
							albumSize = albumSize,
							availableSpace = availableSpace,
							spaceNeeded = totalSpaceNeeded
						}
					
					end )
					
			else
			
				return Observable.just( {
					albumSize = albumSize,
					availableSpace = availableSpace,
					spaceNeeded = spaceNeeded,
				} )
				
			end
			
		end )
	
end

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

--[[!

	\brief Marks the given albums ids to be kept locally, meaning we will download
		and store the originals within to the users machine. This flag is machine-specific,
		and does not sync.
		
	\param wfCatalog (WFCatalog) 
	\param albumIds (table) a table of album ids
	\param debugDontUpdateAssets (boolean) If true, don't update the assets marked-for-
		offline status. Only used by unit tests.
	
	\return <code>Observable</code> that completes when all of the albums, and assets within,
		are marked for offline use.
		
--]]

function NmbOfflineAssets.enableLocalAlbumStorage( wfCatalog, albumIds, debugDontUpdateAssets )

	return Observable.from( albumIds )

		:map( function( albumId )
		
			local baseOverlay = { _id = albumId }
		
			baseOverlay._localOnly = {
				isMarkedForOfflineUse = true
			}
		
			return baseOverlay
			
		end )
		
		:toArray()
		
		:flatMapLatest( function( overlays )
		
			return WFRxThrottledBatchedBulkApplyOverlay( "NmbOfflineAssets_enableLocalAlbumStorage", wfCatalog:docStore(), overlays, 100, 10 )
			
		end )
		
		:takeLast( 0 )

end

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

--[[!

	\brief Removes the marked-for-offline-use flag from the given albums.
		
	\param wfCatalog (WFCatalog) 
	\param albumIds (table) a table of album ids
	\param debugDontUpdateAssets (boolean) If true, don't update the assets marked-for-
		offline status. Only used by unit tests.
	
	\return <code>Observable</code> that completes when all of the albums, and assets within,
		no longer have the marked-for-offline-use flag.
		
--]]

function NmbOfflineAssets.disableLocalAlbumStorage( wfCatalog, albumIds, debugDontUpdateAssets )

	return Observable.from( albumIds )

		:map( function( albumId )
		
			local baseOverlay = { _id = albumId }
		
			baseOverlay._localOnly = {
				isMarkedForOfflineUse = { _delete = true }
			}
		
			return baseOverlay
			
		end )
		
		:toArray()
		
		:flatMapLatest( function( overlays )
		
			return WFRxThrottledBatchedBulkApplyOverlay( "NmbOfflineAssets_disableLocalAlbumStorage", wfCatalog:docStore(), overlays, 100, 10 )
			
		end )
		
		:takeLast( 0 )

end

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

function NmbOfflineAssets.offlineAlbumsModel( wfCatalog, albumIds, enableOfflineMode )

	local listOfAlbumIds = {}
	
	for albumId, _ in pairs( albumIds ) do
	
		table.insert( listOfAlbumIds, albumId )
	
	end

	local value = wfCatalog
		:filter( function( wfCatalog )
			return wfCatalog ~= nil
		end )
		:take( 1 )
		:selectMany( function( wfCatalog )
			if enableOfflineMode then
				return NmbOfflineAssets.enableLocalAlbumStorage( wfCatalog, listOfAlbumIds )
			else
				return NmbOfflineAssets.disableLocalAlbumStorage( wfCatalog, listOfAlbumIds )
			end
		end )
		:call( runUntilDone )

	return {
		value = value,
	}

end

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

return NmbOfflineAssets
