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

WFCatIndexUtils.lua

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

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

NOTICE: All information contained herein is, and remains 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 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 Systems Incorporated.

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

local KSTableUtils = require 'KSTableUtils'
local WFCatFileSpec = require 'WFCatFileSpec'
local WFImageOrientation = require 'WFImageOrientation'

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

local derefOpt = KSTableUtils.derefOpt

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

local WFCatIndexUtils = {}

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

local function maybeGetDisplayableDoc( docValue )

	if docValue.subtype == 'stack' then
		return derefOpt( docValue, '_localOnly', 'cover' )
	else
		return docValue
	end
	
end

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

local function maybeGetSearchableDoc( docValue )
	-- SPAM: Yes this is the same as maybeGetDisplayableDoc for now, but these
	-- may diverge in the future a bit as stacks and other asset types may be 
	-- displayable but not searchable.
	return maybeGetDisplayableDoc( docValue )
end

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

local function hasLocalOriginal( docValue )

	local localOriginalVolumeGuid = WFCatFileSpec.getOriginalVolumeGuidFromDocValue( docValue )
	local localOriginalRelativePath = WFCatFileSpec.getOriginalRelativePathFromDocValue( docValue )
	return localOriginalVolumeGuid and localOriginalRelativePath

end

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

local function hasLocalProxy( docValue, optProxyType )

	local localProxyVolumeGuid = WFCatFileSpec.getProxyVolumeGuidFromDocValue( docValue, optProxyType )
	local localProxyRelativePath = WFCatFileSpec.getProxyRelativePathFromDocValue( docValue, optProxyType )
	return localProxyVolumeGuid and localProxyRelativePath

end

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

function WFCatIndexUtils.hasLocalProxy( docValue, optProxyType )
	return hasLocalProxy( docValue, optProxyType )
end


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

function WFCatIndexUtils.hasLocalOriginal( docValue )
	return hasLocalOriginal( docValue )
end

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

function WFCatIndexUtils.hasLocalProxyOrOriginal( docValue )
	return hasLocalOriginal( docValue ) or 
		hasLocalProxy( docValue )
end

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

local function haveLocalThumbnail( docValue )

	-- Locally copied assets will have a _localOnly.sourceAssetId set,
	-- and any thumbnails which it has (which it must as it had to be displayble
	-- in order to be copied) will be copied to the copied asset.  This is
	-- needed in order for copy to work when no proxy or original exists and
	-- sync is paused.  It also improves percieved performance of the copy operation.

	-- The same as the above is true for Trash and Restored assets as well, in
	-- order to have issued a delete or restore a thumbnail must have existed.
	-- moveAssetsToTrash and restoreTrashAssets will move the thumbnail as part
	-- of the operation.

	return derefOpt( docValue, '_localOnly', 'sourceAssetId' ) or
		derefOpt( docValue, '_localOnly', 'universal', 'trash' )

end

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

function WFCatIndexUtils.assetHasThumbnail( docValue )
	
	local assetDoc = maybeGetDisplayableDoc( docValue )
	
	-- This filter should be applied to all indexes that drive user-visible content so that
	-- we don't show assets for which we don't have, or can't produce, a thumbnail
	
	if ( not assetDoc ) or ( assetDoc.type ~= 'asset' ) then return false end
	
	-- Include if we could generate a thumbnail locally
	
	if hasLocalOriginal( assetDoc ) or hasLocalProxy( assetDoc ) then
		return true
	end

	if haveLocalThumbnail( assetDoc ) then
		return true
	end

	-- Include if we can get a thumbnail from Oz.
	local links = derefOpt( assetDoc, '_serverProvidedMetadata', 'links' )
	
	if derefOpt( links, '/rels/rendition_type/144h', 'href' ) or
		derefOpt( links, '/rels/rendition_type/thumbnail2x', 'href' )
	then
		return true
	end
	
	-- Include if we can get an original/proxy from Oz in order to render locally
	if derefOpt( links, '/rels/proxy_type/2560', 'href' ) or
		derefOpt( links, '/rels/master', 'href' ) then
		return true
	end
	
	-- No thumbnail? Pretend it doesn't exist yet.
	
	return false

end

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

-- TODO: This function should probably return true for subtypes 'preset' and
-- 'profile' too, but that would lead to the need for touching many places in
-- the code that can otherwise be left alone at this point, to avoid problems
-- with file mover code etc. Punting on that, at least for now.
-- 
-- Another option is to rename this function to represent what it is actually
-- used for, e.g. 'isVideoImageOrStackDocument'. That would need to be coupled
-- with renaming of things like 'isValidAssetDocument', though, which turns
-- it into quite the snowball.

function WFCatIndexUtils.isAssetDocument( docValue )

	local subtypeOfAsset = docValue and docValue.type == 'asset' and docValue.subtype

	return subtypeOfAsset == 'image' or subtypeOfAsset == 'video' or subtypeOfAsset == 'stack'
		or subtypeOfAsset == 'deleted_image' or subtypeOfAsset == 'deleted_video'

end

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

function WFCatIndexUtils.isValidAssetDocument( docValue )
	
	return docValue
		and WFCatIndexUtils.isAssetDocument( docValue )
		and not docValue._deleted

end

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

function WFCatIndexUtils.isVersionDocument( docValue )

	return docValue and docValue.type == 'version' and docValue.subtype == 'edit'

end

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

function WFCatIndexUtils.isValidVersionDocument( docValue )
	
	return docValue
		and WFCatIndexUtils.isVersionDocument( docValue )
		and not docValue._deleted

end

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

function WFCatIndexUtils.isWatermarkDocument( docValue )

	return docValue and docValue.type == 'watermark'

end

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

function WFCatIndexUtils.isValidWatermarkDocument( docValue )
	
	return docValue
		and WFCatIndexUtils.isWatermarkDocument( docValue )
		and not docValue._deleted

end

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

function WFCatIndexUtils.isFileBasedAssetDocument( docValue )

	local subtype = docValue and docValue.type == 'asset' and docValue.subtype
	
	return subtype == 'image' or subtype == 'video'
		or subtype == 'deleted_image' or subtype == 'deleted_video'

end

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

function WFCatIndexUtils.isTrashAssetDocument( docValue )

	local subtype = docValue and docValue.subtype

	return docValue
		and WFCatIndexUtils.isAssetDocument( docValue )
		and not docValue._deleted
		and subtype == 'deleted_image' or subtype == 'deleted_video'

end

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

function WFCatIndexUtils.isImageDocument( docValue )

	local type = docValue and docValue.type
	local subtype = docValue and docValue.subtype

	return type == 'asset' and ( subtype == 'image' or subtype == 'deleted_image' )

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

function WFCatIndexUtils.isVideoDocument( docValue )

	local type = docValue and docValue.type
	local subtype = docValue and docValue.subtype

	return type == 'asset' and ( subtype == 'video' or subtype == 'deleted_video' )

end

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

function WFCatIndexUtils.isRevisionOnOz( revisionId, revisionIdsOnOz )

	local count = revisionIdsOnOz and #revisionIdsOnOz or 0

	if count == 0 then
		return false
	end

	for i = count, 1, -1 do

		local ozRevId = revisionIdsOnOz[ i ]
		if revisionId == ozRevId then
			return true
		end

	end

	return false

end

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

function WFCatIndexUtils.isAssetRevisionOnOz( docValue, revisionId )

	local revisionIdsOnOz = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )

	return WFCatIndexUtils.isRevisionOnOz( revisionId, revisionIdsOnOz )

end

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

function WFCatIndexUtils.isAssetOnOz( docValue )
	return derefOpt( docValue, '_serverProvidedMetadata' ) ~= nil
end

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

function WFCatIndexUtils.isStyleDocument( docValue )
	
	return docValue
		and docValue.type == 'asset'
		and ( docValue.subtype == 'preset' or docValue.subtype == 'profile' )

end

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

function WFCatIndexUtils.isNonDeletedStyleDocument( docValue )

	return WFCatIndexUtils.isStyleDocument( docValue )
		and not docValue._deleted

end

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

function WFCatIndexUtils.isDCPDocument( docValue )
	
	return docValue
		and docValue.type == 'asset'
		and ( docValue.subtype == 'camera_profile' )

end

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

function WFCatIndexUtils.isNonDeletedDCPDocument( docValue )

	return WFCatIndexUtils.isDCPDocument( docValue )
		and not docValue._deleted

end

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

function WFCatIndexUtils.doesAssetDocumentHaveLocalBinary( docValue )

	return
			( WFCatIndexUtils.isValidAssetDocument( docValue ) and
			WFCatIndexUtils.hasLocalProxyOrOriginal( docValue ) )
		or
			( WFCatIndexUtils.isNonDeletedStyleDocument( docValue ) and
			hasLocalOriginal( docValue ) )
		or
			( WFCatIndexUtils.isNonDeletedDCPDocument( docValue ) and
			hasLocalOriginal( docValue ) )

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

function WFCatIndexUtils.isValidSearchableAssetDocument( docValue )
	local isSearchable = WFCatIndexUtils.isValidAssetDocument( docValue )
		and WFCatIndexUtils.assetHasThumbnail( docValue )
		and not WFCatIndexUtils.isTrashAssetDocument( docValue )
	
	return isSearchable,
		isSearchable and maybeGetSearchableDoc( docValue ) or nil
end

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

function WFCatIndexUtils.isValidDisplayableAssetDocument( docValue )
	
	local isDisplayable = WFCatIndexUtils.isValidAssetDocument( docValue )
		and WFCatIndexUtils.assetHasThumbnail( docValue )
		
	return isDisplayable,
		isDisplayable and maybeGetDisplayableDoc( docValue ) or nil

end

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

function WFCatIndexUtils.getDuration( docValue )

	if not WFCatIndexUtils.isVideoDocument( docValue ) then
		return
	end

	local videoDuration

	local developDuration = derefOpt( docValue, 'develop', 'temporal', 'duration' )
	
	if developDuration then
		local developTemporalFraction = derefOpt( docValue, 'develop', 'temporal', 'temporalFraction' ) or 100000000
		videoDuration = developDuration / developTemporalFraction
	end

	local durationRatio = derefOpt( docValue, 'video', 'duration' )
	if not videoDuration and type( durationRatio ) == 'table' and #durationRatio == 2 then
		videoDuration = durationRatio[ 2 ] == 0 and 0
						or ( durationRatio[ 1 ] or 0 ) / ( durationRatio[ 2 ] or 1 )
	end

	return videoDuration

end

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

function WFCatIndexUtils.getOrientationWidthAndHeight( docValue )

	local subtype = docValue.subtype

	-- If the develop stanza is present it's required contain croppedWidth and croppedHeight and they
	-- should be used. If the develop stanza is not present, the payload width/height are used instead.
	-- Both of these will have the orientation already baked into them: width/height will be
	-- flipped at import time based on the TIFF camera orientation, and croppedWidth/croppedHeight
	-- will be populated and flipped as needed each time the userOrientation changes.
	
	local width = derefOpt( docValue, 'develop', 'croppedWidth' )
					or derefOpt( docValue, 'importSource', 'originalWidth' )

	local height = derefOpt( docValue, 'develop', 'croppedHeight' )
					or derefOpt( docValue, 'importSource', 'originalHeight' )

	if not width or not height then
		-- NMB-7361: No width/height? then set width/height values that will work (I hope)
		-- to define the most common aspect ratio
		if subtype == 'image' or subtype == 'deleted_image' then
			width = 3
			height = 2
		elseif subtype == 'video' or subtype == 'deleted_video' then
			width = 16
			height = 9
		else
			width = 1
			height = 1
		end
	end

	-- This describes the transformation that should be applied to the original file dimensions,
	-- which are not currently recorded anywhere in Oz. The payload width/height might be swapped,
	-- depending on the camera orientation at import time.  You have to open the negative and get its
	-- dimensions to know how to apply this orientation.
	
	local tiffOrientation = derefOpt( docValue, 'develop', 'userOrientation' )
	local vfOrientation = derefOpt( docValue, 'develop', 'adjustments', 'geometric', 'orientation' )
	local orientation = 1
	
	if WFCatIndexUtils.isVideoDocument( docValue ) then
		if vfOrientation then
			orientation = WFImageOrientation.vfOrientationToTiffOrientation( vfOrientation )
		end
	else
		if tiffOrientation then
			orientation = tiffOrientation
		end
	end

	return orientation, width, height

end

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

function WFCatIndexUtils.getSupportedSmartAlbumDocumentVersion()
	return 1
end

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

function WFCatIndexUtils.isValidSmartAlbumDocument( docValue )
	
	local maxSupportedVersion = WFCatIndexUtils.getSupportedSmartAlbumDocumentVersion()
	
	return docValue 
			and not docValue._deleted
			and docValue.type == "album"
			and docValue.subtype == "smart"
			and docValue.version and docValue.version <= maxSupportedVersion
			
end
--------------------------------------------------------------------------------

return WFCatIndexUtils
