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

NmbLocalRenditionDigest.lua

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

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.

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


	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local WFCrypto = require 'WFCrypto'
local WFTableArchiving = require 'WFTableArchiving'
local WFImageOrientation = require 'WFImageOrientation'

local NmbDocValueToRenderInfo = require 'NmbDocValueToRenderInfo'
local NmbRenditionStrategy = require 'NmbRenditionStrategy'


local logger = require 'WFLogger'( 'NmbLocalRenditionDigest' )
--------------------------------------------------------------------------------

local KSTableUtils_derefOpt = assert( KSTableUtils.derefOpt, 
										"KSTableUtils.derefOpt not defined" )

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

local md5 = assert( KSTableUtils_derefOpt( WFCrypto, "MD5", "digest" ),
						"WFCrypto.MD5.digest does not exist" )

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

local function isValidXmpCameraRaw( xmpCameraRaw ) 
	-- SPAM [matthewj 2016-08-08]: This isn't a rigorous check of the xmpCameraRaw, but it should catch 
	-- the most egregious incorrect xmpCameraRaw. xmpCameraRaw can be either a string with the xmp in it
	-- or a table containing the sha256 of the xmp string for external xmp handling
	local xmpCameraRawType = type( xmpCameraRaw )
	
	if xmpCameraRawType == 'table' then
	
		return xmpCameraRaw.sha256 ~= nil
	
	end
	
	return xmpCameraRawType == 'string'
	
end

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

local function generateDigest( renditionSha256, xmpCameraRawForDigest,
								videoAdjustmentsForDigest, 
								maybeUserOrientation,
								temporalForDigest,
								cameraProfileDigest,
								cameraProfileMissing,
								includeProfile )
		
	-- Include the orientation in the digest so changes to it will kick off new renders.
	
	-- all assets except custom camera profiles use the digest below
	
	local compositeDevSettingsForDigest =
			string.format( "%s%s%s%s%s",
					xmpCameraRawForDigest,
					videoAdjustmentsForDigest,
					maybeUserOrientation or 1,
					temporalForDigest,
					renditionSha256 )

	-- if an image has a custom camera profile, the dev settings digest gets
	-- two more seeds, the custom camera profile digest and an "empty
	-- unless missing" status string

	if cameraProfileDigest and includeProfile then

		local maybeMissingCustomCameraProfileStatus =
				cameraProfileMissing and "missing" or ""

		compositeDevSettingsForDigest =
				string.format( "%s%s%s",
						compositeDevSettingsForDigest,
						cameraProfileDigest,
						maybeMissingCustomCameraProfileStatus )

	end

    return md5( compositeDevSettingsForDigest )

end

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

local function computeTemporaryDefaultForOrientation( 
					ozAssetId, temporaryXmpCameraRaw, temporaryVideoAdjustmentsDigest, userOrientation, temporalDigest,
					cameraProfileDigest, cameraProfileMissing, includeProfile )

	return generateDigest( ozAssetId, temporaryXmpCameraRaw, temporaryVideoAdjustmentsDigest, userOrientation, temporalDigest,
		cameraProfileDigest, cameraProfileMissing, includeProfile )

end

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

local _revIdMatchPattern = "([a-f0-9]+)"

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

local WFArguments_uuid_isUuid =
		assert( require "WFArguments_uuid".isUuid,
					"missing WFArguments_uuid_isUuid" )

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

local function isValidRevId( revId )

	return type( revId ) == "string" and
			string.find( revId, _revIdMatchPattern ) ~= nil and
			WFArguments_uuid_isUuid( revId )
			

end

--------------------------------------------------------------------------------
	
local _defaultXmpCameraRaw = "<xmp />"

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

local _emptySettingsForDownloadOnlyAssetStrPrefix = "<xmp><--"

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

local _emptySettingsForDownloadOnlyAssetStrSuffix = "--></xmp>"

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

local function assembleEmptySettingsForDownloadOnlyAssetStr( 
				prefix, middle, suffix )

	return string.format( "%s%s%s", prefix, middle, suffix )

end

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

local _emptySettingsForDownloadOnlyAssetFormatStr =
		assembleEmptySettingsForDownloadOnlyAssetStr( 
			_emptySettingsForDownloadOnlyAssetStrPrefix,
			"%s",
			_emptySettingsForDownloadOnlyAssetStrSuffix )

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

local function makeUniqueEmptySettingsForDownloadOnlyAssetImp( revId )

	return string.format( _emptySettingsForDownloadOnlyAssetFormatStr, revId )

end

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

local function makeUniqueEmptySettingsForDownloadOnlyAsset( revId )

	if not isValidRevId( revId ) then

		logger:warnf( "makeUniqueEmptySettingsForDownloadOnlyAsset called with invalid revId %s, will use default xmp",
						revId )

		return _defaultXmpCameraRaw

	end

	return makeUniqueEmptySettingsForDownloadOnlyAssetImp( revId )

end

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

local function escapeSpecialCharsForMatch( str )

	return string.gsub( str, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1" )

end

--------------------------------------------------------------------------------
-- create a regex based on the format string we use to generate
-- makeUniqueEmptySettingsForDownloadOnlyAsset
--------------------------------------------------------------------------------

local _emptySettingsForDownloadOnlyAssetMatchStr =
		assembleEmptySettingsForDownloadOnlyAssetStr( 
			escapeSpecialCharsForMatch( 
				_emptySettingsForDownloadOnlyAssetStrPrefix ),
			_revIdMatchPattern,
			escapeSpecialCharsForMatch( 
				_emptySettingsForDownloadOnlyAssetStrSuffix ) )

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

local function isUniqueEmptySettingsForDownloadOnlyAsset( xmpCameraRawStr )

	if type( xmpCameraRawStr ) ~= "string" then

		return false

	end

	local didMatch, _, maybeRevId = 
			string.find( xmpCameraRawStr, 
							_emptySettingsForDownloadOnlyAssetMatchStr )

	if not didMatch or not maybeRevId then

		return false

	end

	return isValidRevId( maybeRevId )

end

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

local function extractUserOrientationForAssetSubtype( renderInfo )

	
	if renderInfo.isVideo then
	
		local videoOrientation = KSTableUtils_derefOpt( 
											renderInfo, "develop", "adjustments", "geometric", "orientation" )
		
		return videoOrientation and WFImageOrientation.vfOrientationToTiffOrientation( videoOrientation )
		
	end
	
	return KSTableUtils_derefOpt( 
						renderInfo, "develop", "userOrientation" )

end

--------------------------------------------------------------------------------
-- renderInfo comes from NmbDocValueToRenderInfo
--------------------------------------------------------------------------------

local function computeFromRenderInfo( renderInfo )

	local xmpCameraRaw = KSTableUtils_derefOpt( 
							renderInfo, "develop", "xmpCameraRaw" )
	
	-- btran 2021-07-22
	-- Account for video adjustments
	local videoAdjustmentsTable = KSTableUtils_derefOpt( 
							renderInfo, "develop", "adjustments" )
	
	local temporalAdjustmentsTable = KSTableUtils_derefOpt( 
							renderInfo, "develop", "temporal" )
	
	local userOrientation = extractUserOrientationForAssetSubtype( renderInfo )
							
	-- SPAM [matthewj 2015-12-21]: Ran into issues where the xmpCameraRaw
	-- contained an invalid, non-string value (the image's width value, in
	-- fact) and the md5 calculation would then blow up. So if it's nil or not
	-- a string, then use the default <xmp />
	
	local defaultXmp = _defaultXmpCameraRaw
	
	if renderInfo._spaceId then

		-- For all shared-with-you assets, we must uniqify the default xmp string
		-- by embedding the revision ID as a comment. This allows the pixel provider
		-- to correctly invalidate stale renditions when Oz updates the rendition links.
		
		defaultXmp = makeUniqueEmptySettingsForDownloadOnlyAsset( renderInfo._revId )
	
	end
	
	xmpCameraRaw = isValidXmpCameraRaw( xmpCameraRaw ) and xmpCameraRaw or defaultXmp

	local xmpCameraRawForDigest = type( xmpCameraRaw ) == 'table' 
								and xmpCameraRaw.sha256 
								or xmpCameraRaw
								
		
	local videoAdjustmentsForDigest = ""
	
	if type( videoAdjustmentsTable ) == "table" then
		videoAdjustmentsTable.userUpdated = nil -- remove timestamp from digest
		videoAdjustmentsForDigest = WFTableArchiving.tableToString( videoAdjustmentsTable )
		
	end
	
	local temporalForDigest = ""
	
	if type( temporalAdjustmentsTable ) == "table" then
	
		temporalAdjustmentsTable.userUpdated = nil -- remove timestamp from digest
		temporalForDigest = WFTableArchiving.tableToString( temporalAdjustmentsTable )
		
	end

	if NmbRenditionStrategy.canOnlyDownloadInvalidRenditions( renderInfo ) then

		-- it is possible for rendition download paths to change from invalid
		-- to valid when the oz revId remains unchanged. 
		--
		-- For view only assets, that means the xmpCameraRawForDigest would
		-- remain unchanged even though the rendition changes. The workaround
		-- is to add the below xmp comment so that invalid renditions
		-- associated with a revId will generate a different digest that valid
		-- ones
		xmpCameraRawForDigest = 
			string.format( "%s<!--download invalid rendition placeholder-->", xmpCameraRawForDigest )

	end

	-- Include SHA256 of the parent binary used to create the renders so that we will 
	-- generate new renders when the binary's SHA256 changes due to destructive edits. 
	
	local hasLocalOriginal = not not KSTableUtils_derefOpt( renderInfo, "originalPath" )
	local hasLocalProxy = not not KSTableUtils_derefOpt( renderInfo, "proxyPath" )
	
	local assetSha256 = KSTableUtils_derefOpt( renderInfo, "sha256" )
	local localOriginalSha256 = KSTableUtils_derefOpt( renderInfo, "localOriginalSha256" )
	local localProxySha256 = KSTableUtils_derefOpt( renderInfo, "localProxySha256" )

	local renditionSha256 = assetSha256

	-- If the asset has a local original, that the renders are generated from it.
	-- If it doesn't, but it has a local proxy, that the renders are generated from it.
	-- Otherwise we download something.
	
	if hasLocalOriginal and localOriginalSha256 then
	
		renditionSha256 = localOriginalSha256
		
	elseif hasLocalProxy and localProxySha256 then
	
		renditionSha256 = localProxySha256
		
	end
	
	logger:debugf( "LocalId:%s: profileDigest:%s isMissing:%s hasStd:%s",
		renderInfo._localId, 
		renderInfo.cameraProfileDigest,
		renderInfo.cameraProfileMissing,
		renderInfo.hasStandardProfile )
	
	local hasCustomProfile = renderInfo.cameraProfileDigest and not renderInfo.hasStandardProfile

	local digest =
			generateDigest( renditionSha256, xmpCameraRawForDigest,
							videoAdjustmentsForDigest,
							userOrientation, 
							temporalForDigest,
							renderInfo.cameraProfileDigest,
							renderInfo.cameraProfileMissing,
							hasCustomProfile )

	return digest, xmpCameraRaw, videoAdjustmentsTable, userOrientation, temporalAdjustmentsTable

end

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

local function maybeComputeFromDocValue( docValue )

	local renderInfo = 
		NmbDocValueToRenderInfo.maybeExtractRenderInfoFromDocValue( docValue )

	if not renderInfo then

		return nil

	end

	return computeFromRenderInfo( renderInfo )

end

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

return {

	computeTemporaryDefaultForOrientation = 
		computeTemporaryDefaultForOrientation,

	computeFromRenderInfo = computeFromRenderInfo,
	
	maybeComputeFromDocValue = maybeComputeFromDocValue,

	isUniqueEmptySettingsForDownloadOnlyAsset = 
		isUniqueEmptySettingsForDownloadOnlyAsset,

	privateUnitTestExports = {
		makeUniqueEmptySettingsForDownloadOnlyAssetImp =
			makeUniqueEmptySettingsForDownloadOnlyAssetImp,
	}

}
