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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
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.

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

THIS CLASS IS USED BY ALL PLATFORMS. ALL NEW FUNCTIONS ADDED TO THIS CLASS MUST 
WORK ON ALL PLATFORMS AND BE VERIFIED AS SUCH BY UNIT TESTS.

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

	-- WF.core.os
local WFFileUtils = require 'WFFileUtils'
local WFPlatformInfo = require "WFPlatformInfo"
local isDesktop = WFPlatformInfo.isDesktop
local isAndroid = WFPlatformInfo.isAndroid
local isiOS = WFPlatformInfo.isiOS
local isMobile = isAndroid or isiOS

	-- WF.core.utils
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require "KSTableUtils"
local WFArguments = require 'WFArguments'

	-- WF.managed_catalog
local WFCatIndexUtils = require 'WFCatIndexUtils'

	-- WF.oz_client.utils
local LrmAssetUtils, WFCatFileSpec
local NmbLocalRenditionDigest
local OzFilePaths, OzCatalogUploadStatus

if isDesktop then

	WFCatFileSpec = require 'WFCatFileSpec'
	NmbLocalRenditionDigest = require 'NmbLocalRenditionDigest'
	
else

	LrmAssetUtils = require 'LrmAssetUtils'
	
	if isAndroid then
		
		OzCatalogUploadStatus = require 'OzCatalogUploadStatus'
		
	elseif isiOS then
		
		OzFilePaths = require 'OzFilePaths'
	
	end

end

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

local check = assert( WFArguments.check )

local derefOpt = assert( KSTableUtils.derefOpt )

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

--[[

The format at the API level adopts the conventions laid out in
https://git.corp.adobe.com/lua/wf/blob/master/doc/ozdocstore-localOnly.md

]]--

local localKeyMap_iOS = {
	relativePath = 'path',
	fileSize = 'fileSize',
	contentId = 'contentId',
}

local function derefUniversal_iOS( docValue, ... )

	if select( 1, ... ) == 'files' then

		-- special cases for old structure in iOS

		local info
		local depth = 4

		local l2 = select( 2, ... )
		local l3 = select( 3, ... )

		if l2 == 'renditions' then
			if l3 == 'thumbnail2x' then
				info = derefOpt( docValue, '_localOnly', 'files', 'thumbnail2x' )
			elseif l3 == '2048' then
				info = derefOpt( docValue, '_localOnly', 'files', 'preview' )
			end
		elseif l2 == 'proxy' then
			if l3 == '2560' then
				info = derefOpt( docValue, '_localOnly', 'files', 'proxy' )
			end
		elseif l2 == 'original' then
			if l3 == 'master' then
				info = derefOpt( docValue, '_localOnly', 'files', 'master' )
			elseif l3 == 'latestMaster' and select( 4, ... ) == 'relativePath' then
				return derefOpt( docValue, '_localOnly', 'files', 'latestMaster', 'filePath' )
			end
		elseif l2 == 'xmp_develop' then
			info = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' )
			depth = 3
		end

		if not info or not next( info ) then
			return nil
		end

		local key = localKeyMap_iOS[ select( depth, ... )  ]

		if not key then
			return nil
		else
			return info[ key ]
		end

	end

	return derefOpt( docValue, '_localOnly', 'universal', unpack( arg ) )

end

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

local function universalPath_iOS( ... )

	-- return nil for unknown paths to make errors more obviou

	if select( 1, ... ) ~= 'files' then
		return nil
	end

	local l2 = select( 2, ... )
	local l3 = select( 3, ... )

	if l2 == 'renditions' then
		if l3 == 'thumbnail' then
			return '_localOnly', 'files', 'thumbnail'
		elseif l3 == 'thumbnail2x' then
			return '_localOnly', 'files', 'thumbnail2x'
		elseif l3 == '2048' then
			return '_localOnly', 'files', 'preview'
		else
			return nil
		end
	elseif l2 == 'proxy' then
		if l3 == '2560' then
			return '_localOnly', 'files', 'proxy'
		else
			return nil
		end
	elseif l2 == 'original' then
		if l3 == 'master' then
			return '_localOnly', 'files', 'master'
		else
			return nil
		end
	elseif l2 == 'xmp_develop' then
		return '_localOnly', 'files', 'xmp_develop'
	else
		return nil
	end

end

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

local localRenditionKeyMap_Android = {
	relativePath = 'md5',
	fileSize = 'size',
	contentId = 'contentId',
}

local localXmpKeyMap_Android = {
	relativePath = 'path',
	contentId = 'sha256',
	fileSize = 'fileSize'
}

local localKeyMap_Android = {
	relativePath = 'path',
	fileSize = 'size',
}

local proxyKeyMap_Android = {
	relativePath = 'filePath',
	fileSize = 'size'
}

local function derefUniversal_Android( docValue, ... )

	if select( 1, ... ) == 'files' then

		local keyMap = localKeyMap_Android
		local depth = 4
		local info

		local l2 = select( 2, ... )
		local l3 = select( 3, ... )

		if l2 == 'renditions' then

			keyMap = localRenditionKeyMap_Android

			if l3 == 'thumbnail2x' then
				info = derefOpt( docValue, '_localOnly', 'thumbnail2x' )
			elseif l3 == '2048' then
				info = derefOpt( docValue, '_localOnly', 'preview' )
			end
		elseif l2 == 'proxy' then
		
			keyMap = proxyKeyMap_Android
		
			if l3 == '2560' then
				info = derefOpt( docValue, '_localOnly', 'proxy' )
			end
			
		elseif l2 == 'latestOriginal' then

			if l3 == 'relativePath' then

				return derefOpt( docValue, '_localOnly', 'latestOriginal', 'filePath' )

			end
		elseif l2 == 'original' then

			if l3 == 'master' then

				if select( 4, ... ) == 'relativePath' then
					return derefOpt( docValue, '_localOnly', 'originalFilePath' )
				end

				info = derefOpt( docValue, '_localOnly', 'original' )

			end
		elseif l2 == 'xmp_develop' then

			keyMap = localXmpKeyMap_Android

			info = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' )
			depth = 3
		elseif l2 == 'presetProfile' then

			keyMap = localXmpKeyMap_Android

			info = derefOpt( docValue, '_localOnly', 'files', 'presetProfile' )
			depth = 3
		end

		if not info or not next( info ) then
			return nil
		end

		local key = keyMap[ select( depth, ... ) ]
		if not key then
			return nil
		end

		return info[ key ]

	end

	return derefOpt( docValue, '_localOnly', 'universal', unpack( arg ) )

end

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

local function universalPath_Android( ... )

	if select( 1, ... ) ~= 'files' then
		return nil
	end

	local l2 = select( 2, ... )
	local l3 = select( 3, ... )

	if l2 == 'renditions' then
		if l3 == 'thumbnail' then
			return '_localOnly', 'thumbnail'
		elseif l3 == 'thumbnail2x' then
			return '_localOnly', 'thumbnail2x'
		elseif l3 == '2048' then
			return '_localOnly', 'preview'
		else
			return nil
		end
	elseif l2 == 'proxy' then
		if l3 == '2560' then
			return '_localOnly', 'proxy'
		else
			return
		end
	elseif l2 == 'original' then
		if l3 == 'master' then
			return '_localOnly', 'original'
		else
			return nil
		end
	elseif l2 == 'xmp_develop' then
		return '_localOnly', 'files', 'xmp_develop'
	else
		return nil
	end

end

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

local function derefUniversal_Desktop( docValue, ... )

	if select( 1, ... ) == 'files' then

		local keys = { ... }

		local l2 = select( 2, ... )
		local l3 = select( 3, ... )
		local l4 = select( 4, ... )

		if l2 == 'xmp_develop' then

			if l3 == 'relativePath' then
				keys[ 3 ] = 'path'
			elseif l3 == 'contentId' then
				keys[ 3 ] = 'sha256'
			end

		elseif l4 == 'fileSize' then

			keys[ 4 ] = 'size'

		elseif l4 == 'contentId' then

			keys[ 4 ] = 'sha256'
			
		end

		return derefOpt( docValue, '_localOnly', unpack( keys ) )

	end

	return derefOpt( docValue, '_localOnly', 'universal', unpack( arg ) )

end

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

local function derefUniversal( ... )
	
	if isDesktop then
		return derefUniversal_Desktop( ... )
	elseif isAndroid then
		return derefUniversal_Android( ... )
	elseif isiOS then
		return derefUniversal_iOS( ... )
	end
	
end

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

local function universalPath( ... )
	
	if isDesktop then
		return ...
	elseif isAndroid then
		return universalPath_Android( ... )
	elseif isiOS then
		return universalPath_iOS( ... )
	end
	
end

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

local function getFullProxyPathFromDocValue( docValue, optProxySize )
	
	check( 'table', '?string' )
	
	local path
	
	if isDesktop then
		path = WFCatFileSpec.buildProxyPathFromDocValue( docValue, optProxySize )
	elseif isAndroid then
		path = LrmAssetUtils.getProxyPath( docValue )
	elseif isiOS then
		path = derefOpt( docValue, '_localOnly', 'files', 'proxy', 'path' )
	end
	
	if path == "" then
		path = nil
	end
	
	return path
	
end

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

local function getProxyRelativePathFromDocValue( docValue, optProxySize )

	check( 'table', '?string' )

	local path
	
	if isDesktop then
		path = WFCatFileSpec.getProxyRelativePathFromDocValue( docValue, optProxySize )
	else
		path = getFullProxyPathFromDocValue( docValue, optProxySize )
			-- On mobile, the relative path _is_ the full path.
	end
	
	return path

end

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

local function getProxyVolumeGuidFromDocValue( docValue, optProxySize )

	check( 'table', '?string' )

	local guid
	
	if isDesktop then
		guid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue, optProxySize )
	else
		-- noop. Mobile does have volume guids.
	end
	
	return guid

end

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

local function getProxySizeFromDocValue( docValue, optProxySize )

	check( 'table', '?string' )

	local size
	
	if isDesktop then
		size = WFCatFileSpec.getProxySizeFromDocValue( docValue, optProxySize )
	elseif isAndroid then
		size = derefOpt( docValue, '_localOnly', 'proxy', 'size' )
	elseif isiOS then
		size = derefOpt( docValue, '_localOnly', 'files', 'proxy', 'fileSize' )
	end
	
	return size

end

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

local function getProxyContentIdFromDocValue( docValue, optProxySize )

	check( 'table', '?string' )

	local contentIdOrSha256
	
	if isDesktop then
		contentIdOrSha256 = WFCatFileSpec.getLocalProxySha256( docValue, optProxySize )
	elseif isAndroid then
		--- On Android, there is no SHA256 or contentId stored for the proxy.
	elseif isiOS then
		contentIdOrSha256 = derefOpt( docValue, '_localOnly', 'files', 'proxy', 'contentId' )
	end
	
	return contentIdOrSha256

end

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

local function doesWatermarkNeedBinaryUploaded( docValue )

	local isWatermarkInvalid = not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'invalid' )
	
	local href = not not derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
	 
	if not ( isWatermarkInvalid or not href ) then
		-- it's not invalid or missing, so there's nothing to upload
		return false
	end
	
	-- there is no link or the link is invalid. So Oz does need an update
	-- (though may not from this client)
	return true
	
end

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

local function getLocalWatermarkBinaryRelativePath( docValue )

	check( 'table' )
	
	local path = derefOpt( docValue, '_localOnly', 'universal', 'files', 'original', 'master', 'relativePath' )
		-- Woo, platform unity!
	
	return path

end

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

local function getLocalWatermarkBinarySize( docValue )

	check( 'table' )

	local size = derefOpt( docValue, '_localOnly', 'universal', 'files', 'original', 'master', 'fileSize' )
		-- Woo, platform unity!
	
	return size

end

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

local function getLocalWatermarkBinarySha256( docValue )

	check( 'table' )

	local sha256 = derefOpt( docValue, '_localOnly', 'universal', 'files', 'original', 'master', 'sha256' )
	-- Woo, platform unity!
	
	return sha256
	
end

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

local function getLocalWatermarkVolumeGuid( docValue )

	check( 'table' )
    local guid
    if isDesktop then
		guid = derefOpt( docValue, '_localOnly', 'universal', 'files', 'original', 'master', 'volumeGuid' )
	else
		-- noop. Mobile doesn't have volume guids.
	end
	
	return guid
	
end

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

local function getLocalWatermarkBinaryPath( docValue )

	check( 'table' )
	
	local path = getLocalWatermarkBinaryRelativePath( docValue )
	
	if WFPlatformInfo.isUnitTest() then
		path = KSPathUtils.child( KSPathUtils.getStandardFilePath( "testdata" ), path )
	end
	
	return path

end

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

local function getFullOriginalPathFromDocValue( docValue )
	
	check( 'table' )

	if( docValue.type == "watermark" ) then
		return getLocalWatermarkBinaryPath( docValue )
	end

	local path
	
	if isDesktop then
		path = WFCatFileSpec.buildOriginalPathFromDocValue( docValue )
	elseif isAndroid then
		path = LrmAssetUtils.buildOriginalPathFromDocValue( docValue )
	elseif isiOS then
		-- iOS handles "camera_profile" like a regular profile
		if docValue.subtype == "preset" or docValue.subtype == "profile" or docValue.subtype == "camera_profile" then
			path = LrmAssetUtils.getPresetPath( docValue )
		else
			path = LrmAssetUtils.getMasterPath( docValue )
		end
	end
	
	if path == "" then
		path = nil
	end
	
	return path
	
end

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

local function getOriginalRelativePathFromDocValue( docValue )

	check( 'table' )

	if docValue.type == "watermark" then
		return getLocalWatermarkBinaryRelativePath( docValue )
	end
	
	local path
	
	if isDesktop then
		path = WFCatFileSpec.getOriginalRelativePathFromDocValue( docValue )
	else
		path = getFullOriginalPathFromDocValue( docValue )
			-- On mobile, the relative path _is_ the full path.
	end
	
	return path

end


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

local function getOriginalVolumeGuidFromDocValue( docValue )

	check( 'table' )

	if( docValue.type == "watermark" ) then
		return getLocalWatermarkVolumeGuid( docValue )
	end
	
	local guid
	
	if isDesktop then
		guid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue )
	else
		-- noop. Mobile does have volume guids.
	end
	
	return guid

end

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

local function getOriginalSizeFromDocValue( docValue )

	check( 'table' )

	if ( docValue.type == "watermark" ) then
		return getLocalWatermarkBinarySize( docValue )
	end
	
	local size
	
	if isDesktop then
		size = derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'size' )
	elseif isAndroid then
		-- presets/profiles are handled like iOS presets/profiles, camera_profiles like originals
		if docValue.subtype == "preset" or docValue.subtype == "profile"  then
			size = derefOpt( docValue, '_localOnly', 'files', 'presetProfile', 'fileSize' )
		else
			size = derefOpt( docValue, '_localOnly', 'original', 'size' )
		end
	elseif isiOS then
		if docValue.subtype == "preset" or docValue.subtype == "profile" or docValue.subtype == "camera_profile" then
			size = derefOpt( docValue, '_localOnly', 'files', 'presetProfile', 'fileSize' )
		else
			size = derefOpt( docValue, '_localOnly', 'files', 'master', 'fileSize' )
		end
	end
	
	return size

end

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

local function getOriginalContentIdFromDocValue( docValue )

	check( 'table' )

	if docValue.type == "watermark" then
		return getLocalWatermarkBinarySha256( docValue )
	end
	
	local contentIdOrSha256
	
	if isDesktop then
		contentIdOrSha256 = WFCatFileSpec.getLocalOriginalSha256WithFallback( docValue )
	elseif isAndroid then
		contentIdOrSha256 = LrmAssetUtils.getlocalOriginalSha256 ( docValue )
	elseif isiOS then
		if docValue.subtype == "preset" or docValue.subtype == "profile" or docValue.subtype == "camera_profile" then
			contentIdOrSha256 = derefOpt( docValue, '_localOnly', 'files', 'presetProfile', 'sha256' )
		else
			contentIdOrSha256 = derefOpt( docValue, '_localOnly', 'files', 'master', 'contentId' )
		end
	end
	
	return contentIdOrSha256

end

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

local function getLocalExternalDevelopSettingsPath( docValue )

	check( 'table' )

	local path = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'path' )
		-- Woo, platform unity!
	
	return path

end

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

local function getLocalExternalDevelopSettingsSize( docValue )

	check( 'table' )

	local size = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'fileSize' )
		-- On Android, external develop settings size may not stored in local only for image
		-- and video assets, but it should be stored there for version documents
		
	
	return size

end

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

local function getLocalExternalDevelopSettingsContentId( docValue )

	check( 'table' )

	local contentIdOrSha256
	
	if isDesktop or isAndroid then 
	
		contentIdOrSha256 = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'sha256' )
		
	elseif isiOS then
	
		contentIdOrSha256 = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'contentId' )

	end
	
	return contentIdOrSha256

end

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

local function getXmpSidecarPathFromDocValue( docValue )
	
	if isDesktop then
		return WFCatFileSpec and WFCatFileSpec.getXmpSidecarPathFromDocValue( docValue )  -- desktop
	else
		return nil -- android and iOS don't keep local sidecar files currently
	end

end

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

local function hasLocalOriginal( docValue )

	check( 'table' )

	local result
	
	if isDesktop then
		result = WFCatIndexUtils.hasLocalOriginal( docValue )
	else
		result = LrmAssetUtils.hasMasterLocally( docValue )
	end
	
	return not not result

end

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

local function hasLocalProxy( docValue )

	check( 'table' )

	local result
	
	if isDesktop then
		result = WFCatIndexUtils.hasLocalProxy( docValue )
	else
		result = LrmAssetUtils.hasProxyLocally( docValue )
	end
	
	return not not result

end

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

local function hasLocalOriginalOrProxy( docValue )

	check( 'table' )

	local hasLocalOriginalOrProxy = hasLocalProxy( docValue ) or hasLocalOriginal( docValue )
	
	return not not hasLocalOriginalOrProxy

end

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

local function getSourceOriginalLinkFromDocValue( docValue )

	check( 'table' )
	
	return derefOpt( docValue, '_localOnly', 'sourceOriginalLink' )
	
end

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

local function getSourceProxyLinkFromDocValue( docValue )
	
	check( 'table' )
	
	return derefOpt( docValue, '_localOnly', 'sourceProxyLink' )

end

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

local function getSourceXmpLinkFromDocValue( docValue )
	
	check( 'table' )
	
	return derefOpt( docValue, '_localOnly', 'sourceXmpLink' )
	
end

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

local function getSourceXmpDevelopLinkFromDocValue( docValue )

	check( 'table' )
	
	return derefOpt( docValue, '_localOnly', 'sourceXmpDevelopLink' )

end

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

local function doesAssetDocumentHaveLocalBinary( docValue )

	check( 'table' )

	local hasLocalBinary = false
	
	if isMobile then
		hasLocalBinary = LrmAssetUtils and LrmAssetUtils.doesAssetDocumentHaveLocalBinary( docValue ) -- android and ios
	elseif isDesktop then
		hasLocalBinary = WFCatIndexUtils and WFCatIndexUtils.doesAssetDocumentHaveLocalBinary( docValue ) -- desktop
	end
	
	return not not hasLocalBinary
	
end

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

local function absolutePath( catalogId, relativePath )

	check( '+string', '?string' )

	local absolutePath 

	if isiOS then
		
		if relativePath then

			if string.sub( relativePath, 1, 1 ) == '/' then
			
				absolutePath = relativePath
				
			else
			
				absolutePath = OzFilePaths.getAbsolutePathInCatalog( catalogId, relativePath )
				
			end
			
		end

	elseif isAndroid then

		absolutePath = OzCatalogUploadStatus.checkAndCorrectLocalPath( relativePath )

	end

	return absolutePath

end

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

local function absolutePathToDevelopSettings( catalogId, maybeExternalXmpRelativePath )

	local externalXmpAbsolutePath
	
	if WFPlatformInfo.isDesktop then

		externalXmpAbsolutePath = maybeExternalXmpRelativePath

	elseif WFPlatformInfo.isMobile() and maybeExternalXmpRelativePath then

		if string.sub( maybeExternalXmpRelativePath, 1, 1 ) == '/' then
		
			externalXmpAbsolutePath = maybeExternalXmpRelativePath

		elseif WFPlatformInfo.isiOS then
		
			externalXmpAbsolutePath = OzFilePaths.getAbsolutePathInCatalog( catalogId, maybeExternalXmpRelativePath )
			
		elseif WFPlatformInfo.isAndroid then

			local documentsDir = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
		
			externalXmpAbsolutePath = KSPathUtils.child( documentsDir, maybeExternalXmpRelativePath )

		end

	end

	return externalXmpAbsolutePath

end

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

local function getLocalRenditionInfo( wfCatalog, docValue )

	local renditions = {}
	
	if isDesktop then
	
		local renditionDigest = NmbLocalRenditionDigest.maybeComputeFromDocValue( docValue )
	
		if renditionDigest then
	
			for _, longEdge in ipairs( { 320, 640, 1280, 2048 } ) do
			
				local renditionPath = wfCatalog:fileSpec():pathForPreviewDigestAndLongEdge( 
						docValue._id, renditionDigest, longEdge, false )
						
				if renditionPath and WFFileUtils.exists( renditionPath ) == "file" then
				
					table.insert( renditions, { 
							renditionPath = renditionPath,
							longEdge = longEdge
						} )
						
				end
				
			end
	
		end
	
	elseif isiOS then
	
		local catalogId = wfCatalog:ozCatalogId()
	
		for _, renditionName in ipairs( { 'thumbnail2x', 'preview' } ) do
	
			local renditionRelativePath = derefOpt( docValue, '_localOnly', 'files', renditionName, 'path' )
			local renditionContentId = derefOpt( docValue, '_localOnly', 'files', renditionName, 'contentId' )
			local renditionSize = derefOpt( docValue, '_localOnly', 'files', renditionName, 'fileSize' )
	
			local renditionPath = absolutePath( catalogId, renditionRelativePath )
	
			if renditionRelativePath then
				
				table.insert( renditions, {
					renditionPath = renditionPath,
					renditionRelativePath = renditionRelativePath,
					renditionContentId = renditionContentId,
					renditionSize = renditionSize,
					renditionName = renditionName
				} )
				
			end
			
		end
	
	elseif isAndroid then
	
		for _, renditionName in ipairs( { 'thumbnail2x', 'preview' } ) do
	
			-- On Android, renditions are stored within a DB and are referenced by an MD5
			-- hash of the rendition. This means that if multiple images have the same
			-- rendition blobs, they will exist within the same place on the DB; there's
			-- not need to copy renditions bits on Android.
	
			local renditionInfo = derefOpt( docValue, '_localOnly', renditionName )
	
			-- renditionInfo is a table containing the following:
			
			--[[ local renditionInfo = {
					md5 = < MD5 has of the rendition KSData blob >,
					revId = < the asset revision ID >,
					epoch = < epoch value >,
					size = < size of the rendition> ,
				} ]]--
				
			if renditionInfo then
			
				table.insert( renditions, { 
					renditionName = renditionName,
					renditionInfo = renditionInfo
				} )
				
			end	
			
		end
	
	end
	
	return renditions

end

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

local function binaryUploadPending( docValue )

	-- Returns true if the asset was created locally but had not finished
	-- uploading all of the associated binaries.

	if isDesktop then

		-- Desktop's binary uploader supports uploading masters, proxies,
		-- external develop settings, and xmp sidecars.
		-- Note proxies are only uploaded if the master is unavailable or a
		-- Lightroom Classic proxy-only asset was copied.

		local hasOriginalLink = WFCatFileSpec.getOriginalLinkFromDocValue( docValue ) ~= nil
		local wasCopiedLocally = derefOpt( docValue, "_localOnly", "sourceAssetId" ) ~= nil

		return not hasOriginalLink and ( hasLocalOriginal( docValue ) or wasCopiedLocally )

			or not hasOriginalLink and not WFCatFileSpec.getProxyLinkFromDocValue( docValue )
				and ( hasLocalProxy( docValue ) or wasCopiedLocally )

			or not WFCatFileSpec.getXmpDevelopLinkFromDocValue( docValue )
				and ( getLocalExternalDevelopSettingsPath( docValue ) ~= nil or wasCopiedLocally )
			
			-- if we don't have an xmp link yet but we could upload it, then consider this
			-- upload pending
			or not WFCatFileSpec.getXmpLinkFromDocValue( docValue ) 
				and ( WFCatFileSpec.getXmpSidecarExistsFromDocValue( docValue ) or 
					getSourceXmpLinkFromDocValue( docValue ) )

	else

		-- Mobile's binary uploader only supports uploading masters and external
		-- develop settings.

		return LrmAssetUtils.doesAssetNeedMasterUploaded( docValue )
			or LrmAssetUtils.doesAssetNeedDevelopXmpUploaded( docValue )

	end

end

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

local function isValidOriginalOnOz( docValue )

	local originalLink = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master' )

	local ozSha256Error = derefOpt( docValue,
			'importSource',
			'status',
			'error',
			'all',
			'reason',
			'sha256' ) == "does not match"

	return not not ( originalLink
			and originalLink.href
			and not originalLink.invalid
			and not ozSha256Error )

end

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

return {
	
	-- get functions for local originals
	
	getFullOriginalPathFromDocValue = getFullOriginalPathFromDocValue,
	getOriginalRelativePathFromDocValue = getOriginalRelativePathFromDocValue,
	getOriginalVolumeGuidFromDocValue = getOriginalVolumeGuidFromDocValue,
	getOriginalSizeFromDocValue = getOriginalSizeFromDocValue,
	getOriginalContentIdFromDocValue = getOriginalContentIdFromDocValue,
	
	-- get functions for local proxies
	
	getFullProxyPathFromDocValue = getFullProxyPathFromDocValue,
	getProxyRelativePathFromDocValue = getProxyRelativePathFromDocValue,
	getProxyVolumeGuidFromDocValue = getProxyVolumeGuidFromDocValue,
	getProxySizeFromDocValue = getProxySizeFromDocValue,
	getProxyContentIdFromDocValue = getProxyContentIdFromDocValue,
	
	-- get functions for watermark
	
	getLocalWatermarkBinaryRelativePath = getLocalWatermarkBinaryRelativePath,
	doesWatermarkNeedBinaryUploaded = doesWatermarkNeedBinaryUploaded,
	
	-- get functions for local external develop settings
	
	getLocalExternalDevelopSettingsPath = getLocalExternalDevelopSettingsPath,
	getLocalExternalDevelopSettingsSize = getLocalExternalDevelopSettingsSize,
	getLocalExternalDevelopSettingsContentId = getLocalExternalDevelopSettingsContentId,
	
	-- get functions for xmp sidecar files
	
	getXmpSidecarPathFromDocValue = getXmpSidecarPathFromDocValue,
	
	-- get functions for rendition information
	
	getLocalRenditionInfo = getLocalRenditionInfo,
	
	-- get functions for doc source links (for copies, IAC, etc.)
	getSourceOriginalLinkFromDocValue = getSourceOriginalLinkFromDocValue,
	getSourceProxyLinkFromDocValue = getSourceProxyLinkFromDocValue,
	getSourceXmpLinkFromDocValue = getSourceXmpLinkFromDocValue,
	getSourceXmpDevelopLinkFromDocValue = getSourceXmpDevelopLinkFromDocValue,
	
	-- various does-exist style functions
	
	hasLocalOriginal = hasLocalOriginal,
	hasLocalProxy = hasLocalProxy,
	hasLocalOriginalOrProxy = hasLocalOriginalOrProxy, -- Should only be used for non-trash assets.
	doesAssetDocumentHaveLocalBinary = doesAssetDocumentHaveLocalBinary,-- Works for assets and trash assets
	
	-- other local-only utilities

	binaryUploadPending = binaryUploadPending,
	derefUniversal = derefUniversal,
	universalPath = universalPath,
	absolutePathToDevelopSettings = absolutePathToDevelopSettings,

	-- other

	isValidOriginalOnOz = isValidOriginalOnOz
	
}
