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

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

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

LrmLibraryCacheModelImp.lua

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

	-- WF.lrmobile
local LrmConfig = require 'LrmConfig'

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.oz_client.managed_catalog
local WFCatLocalStorage = require 'WFCatLocalStorage'
local WFCatVolumeAgentQueryHelper = require 'WFCatVolumeAgentQueryHelper'

local WFRxPromiseBridge = require 'WFRxPromiseBridge'

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

local Model = {}

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

local check = assert( WFArguments.check )

local runUntilDone = require 'WFRxRunUntilDone'

local asKSPromise = WFRxPromiseBridge

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

function Model:purgeableCacheSizeForAlbum( albumId, binaryDownloadStrategy )

	check( 'table', 'string', '?binaryStrategy' )

	-- FIXME [jandrew 2022-05-25]: The following preferOriginalSql query should 
	-- include proxies without originals when calculating the purgeable cache 
	-- size for shared-with-you albums where the user's role is editor. This would
	-- require a lot of additional work (maybe a new index store table column on
	-- the cacheReferences table) and it's not important enough right now to
	-- warrant the work.

	local preferOriginalSql = [[
		SELECT SUM(size)
		FROM cacheReferences
			INNER JOIN album_asset ON album_asset.albumId == ? AND album_asset.assetId == cacheReferences.assetId
			INNER JOIN albums ON albums.docId = album_asset.albumId
			LEFT JOIN space s ON s.docId = albums.spaceId
			LEFT JOIN space_user su ON s.spaceUserId = su.fullDocId
		WHERE cacheReferences.hasUploaded == 1
			AND (
				( albums.spaceId IS NULL OR su.role == 'editor' )
					AND renditionName IN ( 'preview', 'fullsize', 'original', 'latestOriginal', 'xmp_develop' )
				OR
				( albums.spaceId IS NOT NULL AND ( su.role IS NULL OR su.role != 'editor' )
					AND renditionName IN ( 'preview', 'fullsize', 'proxy', 'xmp_develop' ) )
			)
			AND cacheReferences.assetId NOT IN (

				SELECT
					album_asset.assetId
				FROM
					album_asset
				INNER JOIN
					albums
				ON
					album_asset.albumId == albums.docId
				WHERE
					albums.useOffline == 1 AND
					album_asset.albumId != ?
			) ]]

	local preferProxySql = [[
		SELECT SUM(size)
		FROM cacheReferences
			INNER JOIN album_asset ON album_asset.albumId == ? AND album_asset.assetId == cacheReferences.assetId
		WHERE cacheReferences.hasUploaded == 1
			AND renditionName IN ( 'preview', 'fullsize', 'proxy', 'xmp_develop' )
			AND cacheReferences.assetId NOT IN (

				SELECT
					album_asset.assetId
				FROM
					album_asset
				INNER JOIN
					albums
				ON
					album_asset.albumId == albums.docId
				WHERE
					albums.useOffline == 1 AND
					album_asset.albumId != ?
			) ]]

	local sql

	if binaryDownloadStrategy == LrmConfig.StrategyPreferMaster then
		sql = preferOriginalSql
	else
		sql = preferProxySql
	end

	local indexStore = self.catalog:indexStore()

	return {

		value =	indexStore:queryP( sql, albumId, albumId )

			:mapDataP_new( function( size )

				return size and size[ 1 ] and size[ 1 ][ 1 ] or 0

			end )

			:mapDataP_new( function( size )

				return WFCatVolumeAgentQueryHelper.getSizeOfPurgeableAuxInAlbum( indexStore, albumId )

					:call( asKSPromise )

					:mapDataP_new( function( auxSize )

						return auxSize + size

					end )

			end )

			:mapDataP_new( function( size )

				if WFPlatformInfo.isAndroid then
                    return {
						size = size,
						albumId = albumId,		-- android expects albumid as it does not maintain state
					}
				end

				return size

			end )

	}

end

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

function Model:purgeableCacheSize()

	check( 'table' )

	return {

		value = self._binaryDownloadStrategy

			:flatMapLatest( function( binaryStrategy )

				return WFCatLocalStorage.getLocalStorageSize( self._wfCatalog, binaryStrategy )

					:map( function( buckets )
						return buckets.photoCache
					end )

			end )

	}

end

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

function Model:getStoredLocallySizeForAlbum( albumId )

	check( 'table', '+string' )

	return {

		value = self._binaryDownloadStrategy

			:flatMapLatest( function( binaryStrategy )

				return WFCatLocalStorage.getStoredLocallySizeForAlbum_Mobile( self._wfCatalog, albumId, binaryStrategy )

					:map( function( size )

						if WFPlatformInfo.isAndroid then

							return {
								size = size,
								albumId = albumId, -- android expects albumid as it does not maintain state
							}

						end

						return size

					end )

			end )

	}

end

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

function Model:purgeImmediately( noPurgeLimit )

	check( "table", "?boolean" )
	
	local purgeImmediatelyObs = self.catalog:getSyncClient():purgeImmediately()
	
	return {
		value = purgeImmediatelyObs:call( runUntilDone )
	}

end

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

--[[!

	\brief Get the disk space usage of Lr.
		
	\return <code>Observable< table ></code> Fires once with a table of sizes. Example:
	
		{

			photoCache: The size of the purgeable cache of binaries.
				
			storedLocally: The size of binaries that have been marked for local use,
				either globally or through a parent album.
				
			appAndUserData: The size of the application & required files.
			
			freeSpace: The amount of free space on the main volume.
			
			addlStorageUsedOnDevice: LrA-Only: If an SD card is being used, it's the
				size of the catalog data on the device.
			
		}
	
--]]

function Model:getLocalStorageSize()

	check( "table" )

	return {

		value = self._binaryDownloadStrategy

			:flatMapLatest( function( binaryStrategy )

				return WFCatLocalStorage.getLocalStorageSize( self._wfCatalog, binaryStrategy )

			end )

	}

end

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

--[[!

	\brief Returns a table containing cache size recommendations, as well as
		minimum and maximum cache size values.

	\return <code>Observable< table></code> A table of cache size values.

--]]

function Model:getRecommendedCacheSizes()

	check( "table" )
	
	return {
		value = WFCatLocalStorage.getRecommendedCacheSizes( self._wfCatalog )
	}

end

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

--[[!

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

--]]

function Model:trytoClaimSpace( desc, bytes, forcePurge )

	check( "table", 'string', 'number', '?boolean' )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:trytoClaimSpace( desc, bytes, forcePurge )
	}

end

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

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

function Model:setPhotoCacheSize( targetCacheSize )

	check( "table", 'number' )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:setPhotoCacheSize( targetCacheSize )
	}

end

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

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

function Model:getMaximumPhotoCacheSize()

	check( "table" )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:getMaximumPhotoCacheSize()
	}

end

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

--[[!
	\brief Adds an asset to a ring buffer. Entries within the ring buffer are
		protected from purge. The ring buffer has a fixed size, and the oldest
		entries are cleared when the ring buffer grows too large.
	
	The primary use case for this API is to protect recently accessed and/or
		downloaded assets from purge.
	
	\return Observable<boolean> Fires with true once complete.
--]]

function Model:addToProtectedRingBuffer( assetId )

	check( "table", "+string|number" )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:addToProtectedRingBuffer( assetId )
	}

end

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

--[[!
	\brief Returns a stream that fires periodically with the status of the disk.
	\return (Rx ReplaySubject) Each event is a table containing the following:
	
		{
			isOutOfSpace = <boolean>,
			isOverTargetDiskSpaceUsage = <boolean>,
			remainingSpace = <bytes>
		}
--]]

function Model:getVolumeStatusStream()
	
	check( "table" )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:getVolumeStatusStream()
	}

end

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

--[[!
	\brief Protects an asset from being purged. This API can be used
	during operations such as export to prevent certain required assets from
	being removed from the system.
	
	\param assetId (+string|number) The asset doc Id or local Id.
	
	\return Observable<boolean> Fires with true once complete.
--]]

function Model:lockAssetForPurge( assetId )

	check( "table", "+string|number" )

	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:lockAssets( { assetId } )
	}

end

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

--[[!
	\brief Releases an asset from protection from the volume agent. Once,
	released, the asset is subject to standard purging.
	
	\param assetId (+string|number) The asset doc Id or local Id.
	
	\return Observable<boolean> Fires with true once complete.
--]]

function Model:unlockAssetForPurge( assetId )

	check( "table", "+string|number" )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:unlockAssets( { assetId } )
	}

end

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

function Model:lockAssetsForPurge( assetIds )

	check( "table", "table" )

	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:lockAssets( assetIds )
	}

end

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

function Model:unlockAssetsForPurge( assetIds )

	check( "table", "table" )
	
	local syncClient = self.catalog:getSyncClient()
	
	return {
		value = syncClient:unlockAssets( assetIds )
	}

end

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

return Model
