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

WFCatFileSpec.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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 KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'
local WFFileUtils = require 'WFFileUtils'
local WFArguments = require 'WFArguments'
local _ = require 'WFArguments_uuid'
local WFCloseSupport = require 'WFCloseSupport'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.oz_client.catalog.index
local WFOzContentTypes = require 'WFOzContentTypes'

	-- WF.oz_client.managed_catalog
local WFCatTrashUtils = require 'WFCatTrashUtils'

	-- WF.oz_client.managed_catalog.pixel_provider
local WFCatPixelPathGenerator = require 'WFCatPixelPathGenerator'

local NmbFileManagementSettings = require 'NmbFileManagementSettings'

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

local check = assert( WFArguments.check )

local derefOpt = assert( KSTableUtils.derefOpt )

local WFCatTrashUtils_getOriginalAssetMasterLink = assert( WFCatTrashUtils.getOriginalAssetMasterLink )
local WFCatTrashUtils_getOriginalAssetLinkForKey = assert( WFCatTrashUtils.getOriginalAssetLinkForKey )
local WFCatTrashUtils_getOriginalAssetXmpDevelopLink = assert( WFCatTrashUtils.getOriginalAssetXmpDevelopLink )
local WFCatTrashUtils_getOriginalAssetXmpLink = assert( WFCatTrashUtils.getOriginalAssetXmpLink )
local WFCatTrashUtils_getOriginalAssetUrlForLinkKey = assert( WFCatTrashUtils.getOriginalAssetUrlForLinkKey )
local WFCatTrashUtils_getOriginalAssetLinks = assert( WFCatTrashUtils.getOriginalAssetLinks )

local logger = WFLogger 'WFCatFileSpec'

local kRemoveMe = "remove_me"

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

local function useTrashLink()

	return true

	-- Let's try always returning true as we probably want to use this if we
	-- are paused as well. The downfall may be that_localOnly.binaryDownloadErrors
	-- may get set if we try to download from an original asset that is no longer
	-- there.

	-- If the above approach is identified as problematic use this:
	--	return WFCatOzErrors.hasStorageExceededError( docValue )
	--		or WFCatOzErrors.hasTrialOrSubscriptionExpiredError

end

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

local function buildAssetPath( docValue, ... )

	local segments = { ... }
	local nSegments = select( '#', ... )

	-- SPAM [jandrew 2017-04-27]: This is a little bit hacky, but it
	-- works around a parameter passing issue. buildAssetPath is called
	-- below by a helper function, and the last param was sometimes nil.
	-- In the event the last param was nil, the segment count was 1
	-- greater than the actual number of segments, and so the following
	-- derefOpt would always return nil. To avoid this issue, we now pass
	-- in kRemoveMe as the last param, instead of nil, and remove it 
	-- from the segment list prior to calling derefOpt.
	
	if segments[ nSegments ] == kRemoveMe then
		table.remove( segments )
	end
	
	local maybeAsset = derefOpt( docValue, unpack( segments ) ) 
	local path = maybeAsset and maybeAsset.path

	if maybeAsset and not path then
	
		local relativePath = maybeAsset.relativePath
		
		local volumeGuid = maybeAsset.volumeGuid
		
		if relativePath and volumeGuid then
		
			local success, assetPath = pcall( 
					WFFileUtils.createFullPathFromRelativePathAndGuid,
					relativePath, volumeGuid )
					
			if success and assetPath then
			
				return assetPath
			
			end
			
		end
	
	end

	return path

end

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

local function buildOriginalPathFromDocValue( _, docValue )

	return buildAssetPath( docValue, '_localOnly', 'files', 'original', 'master' )

end

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

-- The following function fetches the absolute path of the proxy. We no longer
-- use this annotation, since we instead store assets via relative paths. However,
-- we use this function to see which assets need to be updated to use the new
-- relative path semantics.

local function getAbsoluteOriginalPathFromDocValue( _, docValue )

	return derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'path' )

end

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

local function getOriginalVolumeGuidFromDocValue( _, docValue )

	return derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'volumeGuid' )

end

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

local function getOriginalRelativePathFromDocValue( _, docValue )

	return derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'relativePath' )

end

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

local function getLocalOriginalSha256( _, docValue )

	local sha256 = derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'sha256' )

	return sha256

end

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

local function getLocalOriginalSha256WithFallback( _, docValue )

	local sha256 = getLocalOriginalSha256( _, docValue )

	if not sha256 then

		-- SPAM [jandrew 2017-06-08]: if there's no local SHA256, it means the asset was
		-- downloaded or imported prior to this date. In this case, we assume that the
		-- local asset SHA256 matches the asset payload SHA256.

		sha256 = derefOpt( docValue, 'importSource', 'sha256' )

	end

	return sha256

end

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

local function isVideo( subtype )
	return subtype == 'video' or subtype == 'deleted_video'
end

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

local function extractProxyStorageParamFromDocValue( _, extractionFunc, docValue, param, optProxySize )

	if optProxySize then

		return extractionFunc( docValue, '_localOnly', 'files', 'proxy', optProxySize, param ), optProxySize

	end
	
	if isVideo( docValue.subtype ) then

		return extractionFunc( docValue, '_localOnly', 'files', 'proxy', '720p', param ), '720p'

	end

	local maybe2560 = extractionFunc( docValue, '_localOnly', 'files', 'proxy', '2560', param ) 
	if maybe2560 then
		
		return maybe2560, '2560'
	
	end
	
	local maybePano

	if derefOpt( docValue, '_localOnly', 'files', 'proxy', 'panorama' ) then
		maybePano = extractionFunc( docValue, '_localOnly', 'files', 'proxy', 'panorama', param )
	else -- check old location (pano is deprecated)
		maybePano = extractionFunc( docValue, '_localOnly', 'files', 'proxy', 'pano', param )
	end

	if maybePano then
	
		return maybePano, 'panorama'
	
	end
	
	return nil
	
end

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

local function buildProxyPathFromDocValue( self, docValue, optProxySize )

	return extractProxyStorageParamFromDocValue( self, buildAssetPath, docValue, kRemoveMe, optProxySize )
	
end

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

-- The following function fetches the absolute path of the proxy. We no longer
-- use this annotation, since we instead store assets via relative paths. However,
-- we use this function to see which assets need to be updated to use the new
-- relative path semantics.

local function getAbsoluteProxyPathFromDocValue( self, docValue, optProxySize )

	return extractProxyStorageParamFromDocValue( self, derefOpt, docValue, 'path', optProxySize )
	
end

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

local function getProxyVolumeGuidFromDocValue( self, docValue, optProxySize )

	return extractProxyStorageParamFromDocValue( self, derefOpt, docValue, 'volumeGuid', optProxySize )
	
end

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

local function getProxyRelativePathFromDocValue( self, docValue, optProxySize )

	return extractProxyStorageParamFromDocValue( self, derefOpt, docValue, 'relativePath', optProxySize )
	
end

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

local function getLocalProxySha256( self, docValue, optProxySize )

	local sha256 = extractProxyStorageParamFromDocValue( self, derefOpt, docValue, 'sha256', optProxySize )
	
	if not sha256 then

		-- SPAM [jandrew 2017-06-08]: if there's no local SHA256, it means the asset was
		-- downloaded or imported prior to this date. In this case, we assume that the
		-- local asset SHA256 matches the asset payload SHA256.

		sha256 = derefOpt( docValue, 'importSource', 'sha256' )

	end

	return sha256
	
end

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

local function getOriginalLinkFromDocValue( _, docValue )

	local sha256Error = derefOpt( docValue,
								  'importSource',
								  'status',
								  'error',
								  'all',
								  'reason',
								  'sha256' )

	if sha256Error then
		-- File on oz is corrupt, we shouldn't try to use it anywhere, so
		-- pretend like it doesn't exist so we don't try to download corrupt files.
		--
		-- Scenario: User imports a file and it is a valid file but the
		-- uploaded bits get corrupted during transfer, so the local copy is good
		-- but the file on oz is bad. In this case we don't want to download the oz
		-- copy and replace the valid file with a corrupted one.
		return nil, true
	end

	local original = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/master' )
		or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetMasterLink( docValue )

	local originalLink = derefOpt( original, 'href' )
	local isInvalid = derefOpt( original, 'invalid' )

	-- File on Oz might not exist even though it presents a link to it (NMB-3111) and 
	-- multiple download attempts have failed.  Pretend like it doesn't exist so 
	-- we don't try to download something we know doesn't exist	
	local permanentOriginalDownloadErrorForCurrentLink = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/master', originalLink )
	
	-- Upload may be corrupt, but still the link exists on Oz and we've removed the sha256 error from the main payload
	-- See NMB-4862
	local permanentOriginalUploadErrorForCurrentLink = derefOpt( docValue, '_localOnly', 'binaryUploadErrors', '/rels/master', originalLink )
	
	return ( not permanentOriginalDownloadErrorForCurrentLink and 
				not permanentOriginalUploadErrorForCurrentLink ) and
					originalLink or nil, isInvalid

end

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

local function getProxyLinkFromDocValue( _, docValue, optProxySize )
	
	local link, proxySize, isInvalid

	if isVideo( docValue.subtype ) then
	
		proxySize = optProxySize or '720p'
		
		local proxyLinkKey = '/rels/rendition_type/' .. proxySize

		local videoProxy = derefOpt( docValue, '_serverProvidedMetadata', 'links', proxyLinkKey )
			or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetLinkForKey( docValue, proxyLinkKey )

		local videoProxyLink = derefOpt( videoProxy, 'href' )
		isInvalid = derefOpt( videoProxy, 'invalid' )
		
		local permanentProxyDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', proxyLinkKey, videoProxyLink )
		
		link = not permanentProxyDownloadError and videoProxyLink or nil 
		
	else
				
		if optProxySize then
		
			-- if the image proxy size was specified, check for that link	
			local proxyLinkKey = '/rels/proxy_type/' .. optProxySize
			
			local imageProxy = derefOpt( docValue, '_serverProvidedMetadata', 'links', proxyLinkKey )
				or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetLinkForKey( docValue, proxyLinkKey )

			local imageProxyLink = derefOpt( imageProxy, 'href' )
			isInvalid = derefOpt( imageProxy, 'invalid' )
			
			local permanentProxyDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', proxyLinkKey, imageProxyLink )
			
			link = not permanentProxyDownloadError and imageProxyLink or nil
			proxySize = optProxySize
			
		else
			
			proxySize = '2560' -- default proxy size
			
			local maybeProxy = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/2560' )
				or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetLinkForKey( docValue, '/rels/proxy_type/2560' )

			local maybeProxyLink = derefOpt( maybeProxy, 'href' )
			local maybeProxyExists = derefOpt( maybeProxy, 'exists' )
			local maybeProxyInvalid = derefOpt( maybeProxy, 'invalid' )
			local maybeProxyDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/proxy_type/2560', maybeProxyLink )
			
			local maybePanoProxy = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/proxy_type/panorama' )
				or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetLinkForKey( docValue, '/rels/proxy_type/panorama' )

			local maybePanoProxyLink = derefOpt( maybePanoProxy, 'href' )
			local maybePanoProxyExists = derefOpt( maybePanoProxy, 'exists' )
			local maybePanoProxyInvalid = derefOpt( docValue, 'invalid' )
			local maybePanoProxyDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/proxy_type/panorama', maybePanoProxyLink )
			
			
			-- check for pano and 2560 proxies and whether they have binary download errors
			if ( maybePanoProxyLink or maybePanoProxyExists ) and not maybePanoProxyDownloadError then
				link, proxySize, isInvalid = maybePanoProxyLink, 'panorama', maybePanoProxyInvalid
			elseif ( maybeProxyLink or maybeProxyExists ) and not maybeProxyDownloadError then
				link, isInvalid = maybeProxyLink, maybeProxyInvalid
			end
			
		end
	
	end

	return link, proxySize, isInvalid

end

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

local function getProxySizeFromDocValue( self, docValue, optProxySize )

	return extractProxyStorageParamFromDocValue( self, derefOpt, docValue, 'size', optProxySize )
	
end

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

local function getOriginalSizeFromDocValue( _, docValue )

	return derefOpt( docValue, '_localOnly', 'files', 'original', 'master', 'size' )
		or derefOpt( docValue, 'importSource', 'fileSize' )
		
end

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

local function getRenditionLinksFromDocValue( _, docValue )

	local renditionLinks = {}
	
	local links = derefOpt( docValue, '_serverProvidedMetadata', 'links' )
	
	renditionLinks[ 'thumbnail' ] = derefOpt( links, '/rels/rendition_type/144h', 'href' )
	renditionLinks[ 'thumbnail2x' ]= derefOpt( links, '/rels/rendition_type/thumbnail2x', 'href' )
	renditionLinks[ '640' ] = derefOpt( links, '/rels/rendition_type/640', 'href' )
	renditionLinks[ '1280' ] = derefOpt( links, '/rels/rendition_type/1280', 'href' )
	renditionLinks[ '2048' ] = derefOpt( links, '/rels/rendition_type/2048', 'href' )
	renditionLinks[ 'fullsize' ] = derefOpt( links, '/rels/rendition_type/fullsize', 'href' )
	renditionLinks[ 'panorama' ] = derefOpt( links, '/rels/rendition_type/panorama', 'href' )
	
	if isVideo( docValue.subtype )  then
		renditionLinks[ '360p' ] = derefOpt( links, '/rels/rendition_type/360p', 'href' )
		renditionLinks[ '720p' ] = derefOpt( links, '/rels/rendition_type/720p', 'href' )
		renditionLinks[ 'hls' ] = derefOpt( links, '/rels/rendition_type/hls', 'href' )
	end
	
	return renditionLinks
	
end

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

local function checkOzForNonThumbnailRendition( self, docValue )

	local renditionLinks = getRenditionLinksFromDocValue( self, docValue )
	local sufficientRenditionFoundOnOz = false
	
	local assetIsAVideo = isVideo( docValue.subtype )
	
	for renditionSize, link in pairs( renditionLinks ) do
	
		-- For images, Oz must contain at least a 640 rendition that we can download.
		-- For videos, any rendition will do. ( though the name of this function is 
		-- bit of a misnomer ).
	
		if assetIsAVideo or not string.find( renditionSize, 'thumbnail' ) then
	
			if link then 
				sufficientRenditionFoundOnOz = true
				break 
			end
	
		end
	
	end
	
	return sufficientRenditionFoundOnOz
	
end

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

local function getXmpDevelopLinkFromDocValue( _, docValue )

	local maybeDevelop = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop' )
		or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetXmpDevelopLink( docValue )

	local xmpDevelopLink = derefOpt( maybeDevelop, 'href' )
	local isXmpDevelopInvalid = derefOpt( maybeDevelop, 'invalid' )
	
	local xmpDevelopDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/xmp/develop', xmpDevelopLink )
	
	if isXmpDevelopInvalid or xmpDevelopDownloadError then
		return nil
	end
	
	return xmpDevelopLink
	
end

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

local function getXmpLinkFromDocValue( _, docValue )
	
	local maybeXmp = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp' )
	local xmpLink = derefOpt( maybeXmp, 'href' )

	if xmpLink == nil and useTrashLink( docValue ) then
		maybeXmp = WFCatTrashUtils_getOriginalAssetXmpLink( docValue )
		xmpLink = derefOpt( maybeXmp, 'href' )
	end

	local isXmpInvalid = derefOpt( maybeXmp, 'invalid' )

	local xmpDownloadError = derefOpt( docValue, '_localOnly', 'binaryDownloadErrors', '/rels/xmp', xmpLink )
	
	if isXmpInvalid or xmpDownloadError then
		return nil
	end
	
	return xmpLink
	
end

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

local function getUrlForLinkKey( _, docValue, linksKey )

	return derefOpt( docValue, '_serverProvidedMetadata', 'links', linksKey, 'href' )
		or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetUrlForLinkKey( docValue, linksKey )

end

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

local function getLinksFromDocValue( _, docValue )

	return derefOpt( docValue, '_serverProvidedMetadata', 'links' )
		or useTrashLink( docValue ) and WFCatTrashUtils_getOriginalAssetLinks( docValue )

end

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

local function getWatermarkContentType( docValue )
	
	local fileName = derefOpt( docValue, 'importSource', 'fileName' )
	local extension = fileName:match( "%.([^%.]+)$" ):lower()
	-- All the allowed extensions validated already.
	return extension == "png" and 'image/png' or 'image/jpeg'
	
end

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

local function getContentTypeFromDocValue( _, docValue )

	if( docValue.type == "watermark" ) then
		return getWatermarkContentType( docValue )
	end
	
	local contentType = derefOpt( docValue, 'importSource', 'contentType' )

	if not contentType then
		return WFOzContentTypes.contentTypeForSubtype( derefOpt( docValue, 'subtype' ) )
	end

	return contentType

end

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

local function getMarkedForOfflineUseFromDocValue( _, docValue )

	return derefOpt( docValue, '_localOnly', 'isMarkedForOfflineUse' )

end

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

local function getXmpDevelopInfoFromDocValue( _, docValue )
	
	return derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' )
	
end

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

local function getXmpSidecarPathFromDocValue( _, docValue )
	return buildAssetPath( docValue, '_localOnly', 'files', 'original', 'xmp' )
end

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

local function getXmpSidecarFileSizeFromDocValue( _, docValue )
	return derefOpt( docValue, '_localOnly', 'files', 'original', 'xmp', 'fileSize' )
end

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

local function getXmpSidecarExistsFromDocValue( _, docValue )
	-- returns true, false, or nil for uknown
	return derefOpt( docValue, '_localOnly', 'xmpSidecarExists' )
end

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

local function getCameraProfileLinkFromDocValue( _, docValue )

	return derefOpt( docValue, 'develop', 'profiles', 'camera', 'href' )

end

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

local function getCameraProfileFilenameFromDocValue( _, docValue )

	return derefOpt( docValue, 'develop', 'profiles', 'camera', 'filename' )
	
end

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

local function getOriginalCannotBeRendered( _, docValue )

	local codec = derefOpt( docValue, 'video', 'videoCodec' )
	
	if codec == 'HEVC' then

		return true

	end

	local fileName = derefOpt( docValue, "importSource", "fileName" )

	if type( fileName ) ~= "string" then

		-- can't check extension, but be optimistic and assume we *can* render
		-- original file
		return false

	end

	if string.match( string.lower( fileName ), '.avi$' ) then

		-- 2018.08.27 bkruse
		-- Our video decoder (DLMS v12) only supports AVI files on Windows
		return not WFPlatformInfo.isWindows

	end

	return false
	
end

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

local function maybeAddParentFoldersToPath( maybePathInfo, defaultPathInfo, ozCatalogId )

	if maybePathInfo and
		maybePathInfo.relativePath and
		maybePathInfo.volumeGuid then

		-- If the user specified destination is the same as the base catalog dir,
		-- there's no need to make the path more distinct.
		
		if maybePathInfo.relativePath == defaultPathInfo.relativePath then
			return maybePathInfo
		end

		local appDir = KSPathUtils.child( maybePathInfo.relativePath, 'Lightroom CC' )

		maybePathInfo.relativePath = KSPathUtils.child( appDir, ozCatalogId )

		return maybePathInfo

	end 
	
	return defaultPathInfo

end

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

local _docStoreFilename = "Managed Catalog.mcat"

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

local _endsWithDocStoreFileNamePattern = 
		string.format( "%s$", _docStoreFilename )

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

local function isDocStoreFilePath( _, path )
	
	return string.find( path, _endsWithDocStoreFileNamePattern )

end

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

local function getDefaultDocstorePathForFolder( baseCatalogDir )

	return KSPathUtils.child( baseCatalogDir, _docStoreFilename )

end

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

local function getDefaultDocstorePath( _, baseFolder )
	
	return getDefaultDocstorePathForFolder( baseFolder )

end

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

-- @mark WFCatFileSpec class

--------------------------------------------------------------------------------
--[[!
	\class WFCatFileSpec

	\brief A \c WFCatFileSpec gathers together information about where data associated with a
		catalog should live in the local file system
]]--

local WFCatFileSpec = {

	_rootDir = false,
	
	_catalogPath = false,
	
	_docStorePath = false,
	
	_pixelsPath = false,
	
	_catalogPathInfo = false,
	
	_originalsPathInfo = false,
	
	_proxiesPathInfo = false,
	
	_fileAccessDbPath = false,
	
	_crUserPathInfo = false,
	
	class = {
		buildOriginalPathFromDocValue = buildOriginalPathFromDocValue,
		getAbsoluteOriginalPathFromDocValue = getAbsoluteOriginalPathFromDocValue,
		getCameraProfileLinkFromDocValue = getCameraProfileLinkFromDocValue,
		getCameraProfileFilenameFromDocValue = getCameraProfileFilenameFromDocValue,
		getOriginalVolumeGuidFromDocValue = getOriginalVolumeGuidFromDocValue,
		getOriginalRelativePathFromDocValue = getOriginalRelativePathFromDocValue,
		getLocalOriginalSha256 = getLocalOriginalSha256,
		getLocalOriginalSha256WithFallback = getLocalOriginalSha256WithFallback,
		buildProxyPathFromDocValue = buildProxyPathFromDocValue,
		getAbsoluteProxyPathFromDocValue = getAbsoluteProxyPathFromDocValue,
		getProxyVolumeGuidFromDocValue = getProxyVolumeGuidFromDocValue,
		getProxyRelativePathFromDocValue = getProxyRelativePathFromDocValue,
		getLocalProxySha256 = getLocalProxySha256,
		getOriginalLinkFromDocValue = getOriginalLinkFromDocValue,
		getProxyLinkFromDocValue = getProxyLinkFromDocValue,
		getXmpDevelopLinkFromDocValue = getXmpDevelopLinkFromDocValue,
		getXmpLinkFromDocValue = getXmpLinkFromDocValue,
		getUrlForLinkKey = getUrlForLinkKey,
		getLinksFromDocValue = getLinksFromDocValue,
		getContentTypeFromDocValue = getContentTypeFromDocValue,
		getMarkedForOfflineUseFromDocValue = getMarkedForOfflineUseFromDocValue,
		getXmpDevelopInfoFromDocValue = getXmpDevelopInfoFromDocValue,
		getProxySizeFromDocValue = getProxySizeFromDocValue,
		getOriginalSizeFromDocValue = getOriginalSizeFromDocValue,
		getXmpSidecarPathFromDocValue = getXmpSidecarPathFromDocValue,
		getXmpSidecarFileSizeFromDocValue = getXmpSidecarFileSizeFromDocValue,
		getXmpSidecarExistsFromDocValue = getXmpSidecarExistsFromDocValue,
		getRenditionLinksFromDocValue = getRenditionLinksFromDocValue,
		checkOzForNonThumbnailRendition = checkOzForNonThumbnailRendition,
		isDocStoreFilePath = isDocStoreFilePath,
		getDefaultDocstorePath = getDefaultDocstorePath,
		getOriginalCannotBeRendered = getOriginalCannotBeRendered,
	},
	
	WFCloseSupport.addCloseSupportMethods,
}

--------------------------------------------------------------------------------
--[[!
	\brief Constructs the WFCatFileSpec with the specified root dir as the base. All managed data will reside underneath this directory
	
	\param rootDir (string) Fully qualified path to directory that should be treated as the base directory
		for all managed content
]]--

function WFCatFileSpec:__init( rootDir, ozCatalogId, optDocstorePathOverride )
	
	check( 'WFCatFileSpec', 'string', 'string', '?string' )
	
	self._rootDir = rootDir
	
	local baseCatalogDir 
		
	baseCatalogDir = KSPathUtils.child( rootDir, ozCatalogId )
		
	self._catalogPath = baseCatalogDir
		
	self._docStorePath = optDocstorePathOverride or 
						getDefaultDocstorePathForFolder( baseCatalogDir )
			
	self._pixelsPath = baseCatalogDir
	
	self._catalogPathInfo = {
		volumeGuid = WFFileUtils.getVolumeGuidForPath( self._catalogPath ),
		relativePath = WFFileUtils.removeVolumeFromPath( self._catalogPath )
	}
	
	local originalsPathInfo = NmbFileManagementSettings.originalStorageLocationInfo()

	-- Check to see if the volumeGuid is saved as a device identifier, if it is get the new version of
	-- the volumeGuid which is saved as the mount point.  See NMB-19641
	if derefOpt( originalsPathInfo, 'volumeGuid' ) and string.match( originalsPathInfo.volumeGuid, "^/dev/disk" ) then

		local volumeGuid = originalsPathInfo.volumeGuid
		local relativePath = originalsPathInfo.relativePath
		local basePath = WFFileUtils.createFullPathFromRelativePathAndGuid( relativePath, volumeGuid )
		local newVolumeGuid = WFFileUtils.getVolumeGuidForPath( basePath )
		if newVolumeGuid ~= volumeGuid then
			NmbFileManagementSettings.setOriginalStorageLocation( basePath )
		end

	end

	self._originalsPathInfo = maybeAddParentFoldersToPath( originalsPathInfo, self._catalogPathInfo, ozCatalogId )

	local proxiesPathInfo = NmbFileManagementSettings.proxyStorageLocationInfo()
	
	self._proxiesPathInfo = maybeAddParentFoldersToPath( proxiesPathInfo, self._catalogPathInfo, ozCatalogId )
	
	self._fileAccessDbPath = KSPathUtils.child( baseCatalogDir, 'Managed Catalog.mru' )
	
end

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

function WFCatFileSpec:rootPath()
	
	return self._rootDir

end

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

function WFCatFileSpec:catalogPath()
	
	return self._catalogPath

end

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

function WFCatFileSpec:catalogPathInfo()
	
	return self._catalogPathInfo

end

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

function WFCatFileSpec:docStorePath()
	
	return self._docStorePath

end

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

function WFCatFileSpec:pixelsPath()
	
	return self._pixelsPath
	
end

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

function WFCatFileSpec:originalsPathInfo()
	
	return self._originalsPathInfo
	
end

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

function WFCatFileSpec:proxiesPathInfo()
	
	return self._proxiesPathInfo
	
end

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

function WFCatFileSpec:fileAccessDbPath()
	
	return self._fileAccessDbPath
	
end

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

function WFCatFileSpec:externalSettingsPath()

	return self._externalSettingsPath 

end

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

function WFCatFileSpec:crUserPathInfo()

	return self._crUserPathInfo

end

--------------------------------------------------------------------------------
--[[!
	\brief Fetches the temporary folder path for a specific volume.
	
	Since we leverage the temp folder path as a staging area when we are
	downloading assets from Oz, it's important that the temp folder
	exists on the same volume as the final destination of the asset.

	\param volumeGuid (uuid) The GUID of the volume.

	\return (String, local file path) A file path to the temporary folder
	on the volume.
]]--

function WFCatFileSpec:tempFolderPathForVolume( volumeGuid )

	local tempFolder = KSPathUtils.getStandardFilePath( "temp" )
	
	if self._catalogPathInfo.volumeGuid ~= volumeGuid then
	
		if self._originalsPathInfo.volumeGuid == volumeGuid then
		
			local basePath = WFFileUtils.createFullPathFromRelativePathAndGuid(
					self._originalsPathInfo.relativePath, 
					self._originalsPathInfo.volumeGuid )

			if basePath then
				tempFolder = KSPathUtils.child( basePath, 'temp' )
			else
				logger:warnf( "Failed to build temp folder path %s", basePath )
			end
				
		elseif self._proxiesPathInfo.volumeGuid == volumeGuid then
		
			local basePath = WFFileUtils.createFullPathFromRelativePathAndGuid(
					self._proxiesPathInfo.relativePath, 
					self._proxiesPathInfo.volumeGuid )

			if basePath then
				tempFolder = KSPathUtils.child( basePath, 'temp' )
			else
				logger:warnf( "Failed to build temp folder path %s", basePath )
			end
		
		end
		
	end
	
	local success = pcall( WFFileUtils.mkdir, tempFolder )
	
	if not success then
		logger:warnf( "Failed to create temp folder at path %s", tempFolder )
	end
	
	return tempFolder

end

--------------------------------------------------------------------------------
--[[!
	\brief Calculate the extension-less file path for a preview.

	\param uuid (uuid) The asset id. It must be a 32-character lowercase
		hexadecimal string

	\param digest (md5 digest) The md5 digest as represented as a 32-character
		lowercase hexadecimal string

	\param longEdge (non-zero positive integer|"full") Must be an even value
		greater than 1 and less than 65535 or the string "full". The string full
		means the longEdge will be math.max( croppedWidth, croppedHeight ) for
		the dev settings matching the digest. If the image is not cropped, "full"
		means math.max( fullImageW, fullImageH )

	\param optCreateDirectory (optional boolean) If true (or omitted) then the
		directory for this preview will be created. Otherwise the directory
		is not created (useful when the caller is only trying to find existing
		files and is not preparing to write one).

	\return (String, local file path) A file path where the preview may be found
		or should be stored. The directory for this preview is guaranteed to
		exist.
]]--

function WFCatFileSpec:pathForPreviewDigestAndLongEdge( uuid, digest,
																  longEdge,
																  optCreateDirectory )

	check( 'WFCatFileSpec', 'uuid', 'uuid', '_', '?boolean' )

	return WFCatPixelPathGenerator.pathForPreviewDigestAndLongEdge( self._pixelsPath,
																uuid,
																digest,
																longEdge,
																optCreateDirectory )

end

--------------------------------------------------------------------------------
--[[!
	\brief Calculate the .jpg file path for a preview.

	\param uuid (uuid) The asset id. It must be a 32-character lowercase
		hexadecimal string

	\param digest (md5 digest) The md5 digest as represented as a 32-character
		lowercase hexadecimal string

	\param longEdge (non-zero positive integer|"full") Must be an even value
		greater than 1 and less than 65535 or the string "full". The string full
		means the longEdge will be math.max( croppedWidth, croppedHeight ) for
		the dev settings matching the digest. If the image is not cropped, "full"
		means math.max( fullImageW, fullImageH )

	\param optCreateDirectory (optional boolean) If true (or omitted) then the
		directory for this preview will be created. Otherwise the directory
		is not created (useful when the caller is only trying to find existing
		files and is not preparing to write one).

	\return (String, local file path) A file path where the preview may be found
		or should be stored. The directory for this preview is guaranteed to
		exist.
]]--

function WFCatFileSpec:pathForPreviewDigestAndLongEdgeJpg( uuid, digest,
																  longEdge,
																  optCreateDirectory )

	check( 'WFCatFileSpec', 'uuid', 'uuid', '_', '?boolean' )

	return WFCatPixelPathGenerator.pathForPreviewDigestAndLongEdgeJpg( self._pixelsPath,
																uuid,
																digest,
																longEdge,
																optCreateDirectory )

end

--------------------------------------------------------------------------------
--[[!
	\brief Extract preview info from a path to a preview file.

	\param previewPath (string) A local file path where the preview
		was found.

	\return (uuid) The asset id. It will be a 32-character lowercase
		hexadecimal string
	\return (md5 digest) The md5 digest as represented as a 32-character
		lowercase hexadecimal string
	\return (non-zero positive integer|"full") the long edge dimension associated with
		this preview or the string "full". "full" means the path represents a
		1:1 render at the current devSettings. In that case, longEdge =
		math.max( croppedWidth, croppedHeight ) for the dev settings in the digest.
		If the image is not cropped, "full" means
		math.max( fullImageW, fullImageH )
]]--

WFCatFileSpec.assetIdDigestAndLongEdgeFromPreviewPath = function( _, previewPath )

	check( 'WFCatFileSpec', '+string' )

	return WFCatPixelPathGenerator.assetIdDigestAndLongEdgeFromPreviewPath( previewPath )

end

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

function WFCatFileSpec:pathForOriginal( fileName, captureDate, useAlternatePathIfDriveOffline, skipCreatePath )

	check( 'WFCatFileSpec', '+string', 'KSDate', '?boolean', '?boolean' )

	local alternatePath

	if useAlternatePathIfDriveOffline then
		alternatePath = self._catalogPath
	end

	local volumeGuid = self._originalsPathInfo.volumeGuid
	local relativePath = self._originalsPathInfo.relativePath

	local basePath = WFFileUtils.createFullPathFromRelativePathAndGuid( relativePath, volumeGuid )

	if not basePath then
		basePath = alternatePath
	end

	return WFCatPixelPathGenerator.pathForOriginal( basePath, fileName, captureDate, alternatePath, skipCreatePath )

end

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

function WFCatFileSpec:pathForProxy( assetId, proxyType )

	check( 'WFCatFileSpec', 'uuid', 'string' )

	local volumeGuid = self._proxiesPathInfo.volumeGuid
	local relativePath = self._proxiesPathInfo.relativePath

	local basePath = WFFileUtils.createFullPathFromRelativePathAndGuid( relativePath, volumeGuid )

	if not basePath then
		basePath = self._catalogPath
	end
	
	if proxyType == "720p" then
		return WFCatPixelPathGenerator.pathForProxyTypeAndUuidWithExtension( basePath, proxyType, assetId )
	else
		return WFCatPixelPathGenerator.pathForProxyTypeAndUuid( basePath, proxyType, assetId )
	end

end

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

function WFCatFileSpec:folderForSettingsXmp()
	
	check( 'WFCatFileSpec' )
	
	return WFCatPixelPathGenerator.folderForSettingsXmp( self._pixelsPath )
	
end

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

function WFCatFileSpec:pathForSettingsXmp( sha256 )
	
	check( 'WFCatFileSpec', 'string' )
	
	return WFCatPixelPathGenerator.pathForSettingsXmp( self._pixelsPath, sha256 )
	
end


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

function WFCatFileSpec:pathForXmpSidecar( assetId )

	check( 'WFCatFileSpec', 'string' )

	return WFCatPixelPathGenerator.pathForXmpSidecar( self._pixelsPath, assetId )

end

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

function WFCatFileSpec:pathForStyle( assetId )

	check( 'WFCatFileSpec', 'string' )

	return WFCatPixelPathGenerator.pathForStyle( self._pixelsPath, assetId )

end

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

function WFCatFileSpec:pathForPerson( personId )

	check( 'WFCatFileSpec', 'string' )

	return WFCatPixelPathGenerator.pathForPerson( self._pixelsPath, personId )

end

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

function WFCatFileSpec:maybeStylePath( path )

	check( 'WFCatFileSpec', 'string' )

	return WFCatPixelPathGenerator.maybeStylePath( self._pixelsPath, path )
end

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

function WFCatFileSpec:pathForDCP( fileName )

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

	return WFCatPixelPathGenerator.pathForDCP( self._pixelsPath, fileName )

end
--------------------------------------------------------------------------------

function WFCatFileSpec:pathForStandardProfile( relativePath )

	check( 'WFCatFileSpec', '+string' )

	return WFCatPixelPathGenerator.pathForStandardProfile( self._pixelsPath, relativePath )

end
 
--============================================================================--

return Rx.Class 'WFCatFileSpec'( WFCatFileSpec )
