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

NmbDocValueToRenderInfo.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.

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

NmbDocValueToRenderInfo consumes a sequence of array-valued tables of asset IDs
and reports the "render info" of each corresponding document either as it first
appears in the array, or as it changes in value while still in the array.

Asset IDs may be either numeric (local) or string (global) document IDs.

Render info is expressed as a table similar to the following:

	{
		_id = "(uuid / global ID)",
		_localId = (number),
		originalPath = "(path)",
		proxyPath = "(path)",
		develop = {
			-- develop settings as from Oz (http://belt.sea.adobe.com/asset/develop.html)
		}
	}

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

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFDiagnosticLog = require "WFDiagnosticLog"
local WFTableMerge = require 'WFTableMerge'

	-- WF.oz_client
local WFLocalDocUtils = require "WFLocalDocUtils"

	-- WF.oz_client.managed_catalog
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatIndexUtils = require 'WFCatIndexUtils'
-- circular dependency, defer import
-- local WFCatAssetUtils = require 'WFCatAssetUtils'
-- local WFCatAssetAux = require 'WFCatAssetAux'

	-- WF.oz_client.managed_catalog.develop
local NmbDocValueDevelopSettingsUtils = 
		require "NmbDocValueDevelopSettingsUtils"

	-- pixel provider
local WFCatRenderCapabilities = require 'WFCatRenderCapabilities'

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

local derefOpt = assert( KSTableUtils.derefOpt )

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

local WFLocalDocUtils_currentRevIdIsOnOz =
		assert( WFLocalDocUtils.currentRevIdIsOnOz, 
				"WFLocalDocUtils.currentRevIdIsOnOz does not exist" )

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

local WFCatIndexUtils_isVideoDocument = 
		assert( WFCatIndexUtils.isVideoDocument,
				"WFCatIndexUtils.isVideoDocument is missing" )

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

local logger = require 'WFLogger'( 'NmbDocValueToRenderInfo' )

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

local function wrapInTag( tag, str )

	return string.format( "<%s>%s</%s>", tag, str, tag )

end

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

local function maybeGetSize( sizeURI )

	local maybeSize = sizeURI:gsub( ".*/(.*)", "%1" )

	if maybeSize == "" then

		return

	end

	return maybeSize

end

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

local function reduceT2( ret, f, t, prevK, ... )

	local k, v = next( t, prevK )

	if not k then

		return ret

	end

	return reduceT2( f( ret, k, v, ... ), f, t, k, ... )

end

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

local function reduceT( ret, f, t, ... )

	return reduceT2( ret, f, t, nil, ... )

end

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

local function addToURIList( downloadableBinaryInfo, uriListName, href, size )

	local uriList = downloadableBinaryInfo[ uriListName ] or {}

	uriList[ #uriList + 1 ] = 
		{
			
			href = href,

			size = size,

		}

	downloadableBinaryInfo[ uriListName ] = uriList

	return downloadableBinaryInfo

end

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

local function maybeAddDownloadableBinaryInfo( 
				downloadableBinaryInfo, uriType, uriInfoTable, 
				ozHasCurrentRevId, isVideo )

	if type( uriType ) ~= "string" or type( uriInfoTable ) ~= "table" then

		-- no valid link info, do nothing
		return downloadableBinaryInfo

	end

	local href = uriInfoTable.href

	if not href then

		-- ignore it if no link

		return downloadableBinaryInfo

	end

	local size = maybeGetSize( uriType )

	local isVideoProxyURI =
			isVideo and not not uriType:find( 'rendition_type/720p' )

	local isRenditionURI = not isVideoProxyURI and 
							uriType:find( 'rendition_type' )
	
	if isRenditionURI and size and uriInfoTable.invalid then

		-- add invalid renditions links 
		return addToURIList( downloadableBinaryInfo, 'invalid_renditions', href, size )

	end

	if uriInfoTable.invalid then

		-- ignore all other invalid links
		return downloadableBinaryInfo

	end

	if uriType:find( 'rendition_generate/fullsize' ) then

		-- add generate full size link
		downloadableBinaryInfo.createFullRenditionURITemplate = uriInfoTable.href

		return downloadableBinaryInfo

	end
		
	if not size then

		-- ignore any other links without a size 
		return downloadableBinaryInfo

	end

	if ozHasCurrentRevId and isRenditionURI then

		if size == "fullsize" then

			-- add up to date fullsize rendition link
			downloadableBinaryInfo.downloadFullRenditionURI = href

			return downloadableBinaryInfo

		end

		-- add up to date rendition links to table
		return addToURIList( downloadableBinaryInfo, 'renditions', href, size )

	end
		
	-- For legacy support, 720p hrefs should be treated as both renditions and proxies.
	-- This allows Video 1.0 requests for proxies to use the 720p renditions instead while
	-- also allowing Video 1.0 requests in Share workflows to use the 720p renditions as actual renditions for playback
	
	if isVideoProxyURI then
		downloadableBinaryInfo = addToURIList( downloadableBinaryInfo, 'renditions', href, size )
	end

	if uriType:find( 'proxy_type' ) or isVideoProxyURI then
		
		-- add up to non video proxy links to table
		return addToURIList( downloadableBinaryInfo, 'proxies', href, size )

	end

	return downloadableBinaryInfo

end

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

local function getDownloadableBinaryInfo( docValue )

	local links = WFCatFileSpec.getLinksFromDocValue( docValue )

	links = type( links ) == "table" and links or {}

	local downloadableBinaryInfo = 
			reduceT( {}, 
				maybeAddDownloadableBinaryInfo, 
					links, WFLocalDocUtils_currentRevIdIsOnOz( docValue ),
					WFCatIndexUtils_isVideoDocument( docValue ) )

	return downloadableBinaryInfo.renditions or {}, 
			downloadableBinaryInfo.invalid_renditions or {},
			downloadableBinaryInfo.proxies or {},
			downloadableBinaryInfo.createFullRenditionURITemplate,
			downloadableBinaryInfo.downloadFullRenditionURI

end

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

local function maybeGetCameraProfileInfo( docValue )

	local cameraProfileDigest = derefOpt( docValue, "_localOnly", "cameraProfileInfo", "profileDigest" )
	cameraProfileDigest = cameraProfileDigest ~= "NA" and cameraProfileDigest or nil
	
	local cameraProfileMissing = derefOpt( docValue, "_localOnly", "cameraProfileInfo", "isMissing" )
	local hasStandardProfile = derefOpt( docValue, "_localOnly", "cameraProfileInfo", "isStandard" )
	
	return cameraProfileDigest, cameraProfileMissing, hasStandardProfile
end

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

local NmbDocValueDevelopSettingsUtils_getSanitizedDocValueDevelopStanza =
		assert( NmbDocValueDevelopSettingsUtils.getSanitizedDocValueDevelopStanza,
				"missing NmbDocValueDevelopSettingsUtils.getSanitizedDocValueDevelopStanza" )

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

local function shouldDisallowLocalEdits( docValue )

	local canEditAsset = require 'WFCatSharedWithYouUtils'.canEditAsset( docValue )
	return not canEditAsset

end

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

local function maybeGetLocalExternalXmpPathInfo( docValue )
	
	local xmpDevelopInfo = 
			WFCatFileSpec.getXmpDevelopInfoFromDocValue( docValue ) 

	if type( xmpDevelopInfo ) ~= "table" then

		return

	end

	return xmpDevelopInfo.path, xmpDevelopInfo.sha256

end

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

local function maybeAddAuxToExternalDevelopFiles(
				externalDevelopFiles, auxId, digest, auxDigestToHrefMap,
				localAssetId, ozAssetId )

	if not auxDigestToHrefMap then

		return externalDevelopFiles

	end


	if type( digest ) ~= "string" then

		logger:warnf( "AssetId %s (oz assetId %s) auxId %s is missing the digest",
						localAssetId, ozAssetId, auxId )

		return externalDevelopFiles

	end

	externalDevelopFiles[ #externalDevelopFiles + 1 ] =
		{ 'aux', digest, auxDigestToHrefMap[ digest ] }

	return externalDevelopFiles

end

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

local function extractRenderInfoFromDocValue( docValue )
	
	local renditions, invalid_renditions, proxies,
			createFullRenditionURITemplate, downloadFullRenditionURI = 
				getDownloadableBinaryInfo( docValue )
				
	local disallowLocalEdits = nil

	if shouldDisallowLocalEdits( docValue ) then

		-- don't allow editing shared album assets unless marked editable
		disallowLocalEdits = true

	end
	
	local ozProxyLink, ozProxySize = nil, nil
	
	if not disallowLocalEdits then
		-- If we disallow local editing, we no longer need the proxy link.
		-- this ensures we do not accidentally download a proxy when we don't need to.
		ozProxyLink, ozProxySize = WFCatFileSpec.getProxyLinkFromDocValue( docValue )
	end
	
	local cameraProfileDigest, cameraProfileMissing, hasStandardProfile = maybeGetCameraProfileInfo( docValue )
	
	local originalCannotBeRendered = WFCatFileSpec.getOriginalCannotBeRendered( docValue )

	local maybePathToXmpDevelop = maybeGetLocalExternalXmpPathInfo( docValue )

	-- cannot import WFCatAssetUtils at file scope due to circular dependency
	local WFCatAssetUtils = require 'WFCatAssetUtils'

	local hasExternalXmpDevelop = WFCatAssetUtils.hasExternalXmpDevelop( docValue )

	local maybeExternalDevelopFiles

	if hasExternalXmpDevelop then

		maybeExternalDevelopFiles = maybeExternalDevelopFiles or {}

		local xmpDevelopLink = WFCatFileSpec.getXmpDevelopLinkAndSha256FromDocValue( docValue )
			-- discard the sha256 result and not add it to the table

		maybeExternalDevelopFiles[ #maybeExternalDevelopFiles + 1 ] =
			{ 
				'develop', 	
				WFCatAssetUtils.maybeGetExternalXmpDevelopSha256( docValue ),
				xmpDevelopLink,
			}

	end

	-- cannot import WFCatAssetAux at file scope due to circular dependency
	local WFCatAssetAux = require 'WFCatAssetAux'

	local activeAuxObjects = WFCatAssetAux.extractLongestDevelopAuxDigestsFromDocValue( docValue )
	activeAuxObjects = WFTableMerge.applyOverlay( activeAuxObjects, WFCatAssetAux.extractC2paAuxDigestFromDocValue( docValue ) )

	local hasDevelopAuxFiles = next( activeAuxObjects ) ~= nil

	local haveAllLocalAuxPathsInDocstore =
			hasDevelopAuxFiles and
				not WFCatAssetAux.needsAuxDownloadForDevelopAndC2pa( docValue ) or
				nil

	if hasDevelopAuxFiles then

		local auxDigestToHrefMap =
				WFCatFileSpec.getTableOfAuxLinksFromDocValue( docValue ) or {}

		maybeExternalDevelopFiles = 
			reduceT( maybeExternalDevelopFiles or {},
				maybeAddAuxToExternalDevelopFiles,
					activeAuxObjects, auxDigestToHrefMap, docValue._localId,
					docValue._id ) 

	end

	local maybeOriginalLink, isOzOriginalLinkInvalid, maybeInvalidOriginalLink =
			WFCatFileSpec.getOriginalLinkFromDocValue( docValue )

	local proxyPath, proxyPathOzBinarySubtype = WFCatFileSpec.buildProxyPathFromDocValue( docValue )
	
	local isVideo = WFCatIndexUtils_isVideoDocument( docValue )
	
	local videoDevelopVersion 
	
	if isVideo then
		videoDevelopVersion =
			{
				version = derefOpt( docValue, 'develop', 'version' ),
				processVersion = derefOpt( docValue, 'develop', 'processVersion' )
			}
	end

	return {

		_id = docValue._id,

		_localId = docValue._localId,

		_revId = docValue._revId or docValue._newRevId,
			-- In the case where the docValue has no revId, which can happen if the document
			-- hasn't yet been written in to the doc store, attempt to use the _newRevId field
			-- which will eventually populate the revId field.
			
		_spaceId = docValue._spaceId,
		
		_changeCounter = docValue._changeCounter,
		
		subtype = docValue.subtype,

		isVideo = isVideo,
		
		videoDevelopVersion = videoDevelopVersion,

		originalPath = WFCatFileSpec.buildOriginalPathFromDocValue( docValue ),

		proxyPath = proxyPath,

		proxyPathOzBinarySubtype = proxyPathOzBinarySubtype,

		pathToXmpSidecar = WFCatFileSpec.getXmpSidecarPathFromDocValue( docValue ),

		xmpSidecarExists = WFCatFileSpec.getXmpSidecarExistsFromDocValue( docValue ),

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

		localOriginalSha256 = WFCatFileSpec.getLocalOriginalSha256WithFallback( docValue ),

		ozValidOriginalLink = not isOzOriginalLinkInvalid and maybeOriginalLink or nil,	

		ozInvalidOriginalLink = maybeInvalidOriginalLink,
		
		localProxySha256 = WFCatFileSpec.getLocalProxySha256( docValue ),
		
		ozProxyLink = ozProxyLink,
		
		ozProxySize = ozProxySize,
		
		ozXmpLink = WFCatFileSpec.getXmpLinkFromDocValue( docValue ),

		-- develop settings related
		develop = NmbDocValueDevelopSettingsUtils_getSanitizedDocValueDevelopStanza(
					docValue ),
		
		localXmpDevelopPathFromDocstore = maybePathToXmpDevelop,
		
		ozXmpDevelopLink = WFCatFileSpec.getXmpDevelopLinkAndSha256FromDocValue( docValue ),
			-- discarding the sha256 result returned

		hasExternalXmpDevelop = WFCatAssetUtils.hasExternalXmpDevelop( docValue ),

		hasEmbeddedXmpDevelop = WFCatAssetUtils.hasEmbeddedXmpDevelop( docValue ),

		hasDevelopAuxFiles = hasDevelopAuxFiles,
			-- Aux files appear in the develop.masks, develop.filters or importSource.c2paIngredients section

		haveAllLocalAuxPathsInDocstore = haveAllLocalAuxPathsInDocstore,

		externalDevelopFiles = maybeExternalDevelopFiles,
		
		renditions = renditions,

		invalid_renditions = invalid_renditions,
		
		proxies = proxies,

		createFullRenditionURITemplate = createFullRenditionURITemplate,

		downloadFullRenditionURI = downloadFullRenditionURI,
		
		cameraProfileDigest = cameraProfileDigest,
		
		cameraProfileMissing = cameraProfileMissing,
		
		hasStandardProfile = hasStandardProfile,
		
		willNotUploadMaster = derefOpt( docValue, 'importSource', 'willNotUploadMaster' ),
		
		originalCannotBeRendered = originalCannotBeRendered,

		disallowLocalEdits = disallowLocalEdits,
		
}

end

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

local _didNotRenderCounts = {}

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

local function concatReasonRow( rows, reason, count )

	rows[ #rows + 1 ] =
		wrapInTag( "tr", 
			wrapInTag( "td", count ) ..
			wrapInTag( "td", reason ) )

	return rows
end

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

local function maybeReportDidNotRenderCounts()

	if not next( _didNotRenderCounts ) then

		return

	end

	local reasonRows = reduceT( {}, concatReasonRow, _didNotRenderCounts )

	local skippedRenderReasonsTable =
			wrapInTag( "table",
				wrapInTag( "tr",
							wrapInTag( "th", "count" ) ..
							wrapInTag( "th", "reason" ) ) ..
				table.concat( reasonRows ) )
	
	WFDiagnosticLog.addSectionToLog( {

		id = 'CannotRenderReasons',

		name = "Skipped Render Reasons",

		weight = 420,

		body = skippedRenderReasonsTable,

	} )

end

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

WFDiagnosticLog.addLogSectionFunc( maybeReportDidNotRenderCounts )

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

local function maybeExtractRenderInfoFromDocValue( docValue )

	local canRender, maybeCannotRenderReason =
			WFCatRenderCapabilities.isRenderableAsset( docValue ) 

	if not canRender then

		if logger.will_info then

			logger:infof( "not extracting render info for docValue._localId = %s because: %s", 
							docValue._localId or 
								( docValue._deleted and "deleted docValue" ) or
								"missing _localId",
							maybeCannotRenderReason )

		end

		_didNotRenderCounts[ maybeCannotRenderReason ] =
			( _didNotRenderCounts[ maybeCannotRenderReason ] or 0 ) + 1

		return

	end

	return extractRenderInfoFromDocValue( docValue )

end

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

local function renderInfoHasLocalOrDownloadableOriginal( renderInfo )

	if renderInfo.ozValidOriginalLink or renderInfo.originalPath then

		return true

	end

	return false

end

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

local function renderInfoHasLocalOrDownloadableProxy( renderInfo )

	if renderInfo.proxyPath or #renderInfo.proxies > 0 then
	
		local maybeFirstProxyInfo = renderInfo.proxies[ 1 ]
		local hasProxy = renderInfo.proxyPath or maybeFirstProxyInfo

		if hasProxy and not renderInfo.disallowLocalEdits then

		  -- no permissions issue
		  return true

		end

		if hasProxy and renderInfo.disallowLocalEdits and
			  maybeFirstProxyInfo and 
			  maybeFirstProxyInfo.size ~= "720p" then

			-- For legacy reasons, 720p proxies are actually renditions. For video 1.0 assets,
			-- we set both the proxies and renditions links to the same 720p rendition
			-- but store the files in the previews or proxie catalog folders depending on the context
			-- of the album role. If the user owns the album/asset then the 720p rendition will be stored
			-- in the proxy folder. However, if they are not owner (contributor or view role), the 720p
			-- rendition will be in the previews folder alongside other renditions of the file.
			
			-- This condition checks if the proxy is a legacy proxy and how we should treat it based
			-- on the if editing is allowed (set from album permissions).
			-- owner - Use this and treat it as a proxy
			-- contributor/viewer - Skip this and treat it as a rendition

			  return true

		end

	end

	return false

end

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

local function renderInfoHasLocalOrDownloadableNegative( renderInfo )

	if renderInfoHasLocalOrDownloadableOriginal( renderInfo ) then

		return true

	end

	if renderInfoHasLocalOrDownloadableProxy( renderInfo ) then

		return true

	end

	return false

end

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

local function renderInfoHasDownloadableFullSizeRendition( renderInfo )

	if renderInfo.createFullRenditionURITemplate or
			renderInfo.downloadFullRenditionURI then

		return true

	end

	return false

end

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

local function renderInfoHasDownloadable2048Rendition( renderInfo )
	
	if renderInfo.willNotUploadMaster and renderInfo.renditions then
		for _, renditionInfo in ipairs( renderInfo.renditions ) do
			if renditionInfo.size == '2048' then
				return true
			end
		end
	
	end

	return false

end

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

local function hasNoBinariesForDocValue( docValue )

	local renderInfo = extractRenderInfoFromDocValue( docValue )

	return not renderInfoHasLocalOrDownloadableNegative( renderInfo ) and
			not renderInfoHasDownloadableFullSizeRendition( renderInfo ) and
			not renderInfoHasDownloadable2048Rendition( renderInfo )

end

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

local function hasFullSizeRenditionOnlyForDocValue( docValue )

	local renderInfo = extractRenderInfoFromDocValue( docValue )

	return not renderInfoHasLocalOrDownloadableNegative( renderInfo ) and
				renderInfoHasDownloadableFullSizeRendition( renderInfo ) 

end

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

local function has2048RenditionOnlyForDocValue( docValue )

	local renderInfo = extractRenderInfoFromDocValue( docValue )

	return not renderInfoHasLocalOrDownloadableNegative( renderInfo ) and
				renderInfoHasDownloadable2048Rendition( renderInfo ) 

end

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

local function hasProxyForDocValue( docValue )

	local renderInfo = extractRenderInfoFromDocValue( docValue )

	return renderInfoHasLocalOrDownloadableProxy( renderInfo )

end

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

local function disallowLocalEditsForDocValue( docValue )

	local renderInfo = extractRenderInfoFromDocValue( docValue )

	return renderInfo.disallowLocalEdits == true or
			not renderInfoHasLocalOrDownloadableNegative( renderInfo )

end

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

local function addExternalFileKeyToSet( externalFileKeySet, externalFileInfo )

	local fileType, uuid = unpack( externalFileInfo )

	externalFileKeySet[ string.format( "%s%s", fileType, uuid ) ] = true

	return externalFileKeySet

end

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

local function areExternalDevelopFilesEqual( 
				externalDevelopFiles1, externalDevelopFiles2 )

	if not externalDevelopFiles1 and
			externalDevelopFiles1 == externalDevelopFiles2 then

		return true

	end

	if ( not externalDevelopFiles1 and externalDevelopFiles2 ) or
			( externalDevelopFiles1 and not externalDevelopFiles2 ) then

		return false

	end

	local externalDevelopFilesArray1 = 
			type( externalDevelopFiles1 ) == "table" and
				externalDevelopFiles1 or {}

	local externalDevelopFilesArray2 = 
			type( externalDevelopFiles2 ) == "table" and
				externalDevelopFiles2 or {}

	local externalDevelopFilesSet1 =
			WFFunctionUtils.reduce( {},
				addExternalFileKeyToSet,
					externalDevelopFilesArray1 )

	local externalDevelopFilesSet2 =
			WFFunctionUtils.reduce( {},
				addExternalFileKeyToSet,
					externalDevelopFilesArray2 )

	return KSTableUtils.deepEquals( 
			externalDevelopFilesSet1, externalDevelopFilesSet2 )

end

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

return {

	extractRenderInfoFromDocValue = extractRenderInfoFromDocValue,

	maybeExtractRenderInfoFromDocValue = maybeExtractRenderInfoFromDocValue,

	hasNoBinariesForDocValue = hasNoBinariesForDocValue,

	hasFullSizeRenditionOnlyForDocValue = hasFullSizeRenditionOnlyForDocValue,

	has2048RenditionOnlyForDocValue = has2048RenditionOnlyForDocValue,

	hasProxyForDocValue = hasProxyForDocValue,

	disallowLocalEditsForDocValue = disallowLocalEditsForDocValue,

	areExternalDevelopFilesEqual = areExternalDevelopFilesEqual,

}
