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

LrmCatXmpDevelop.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.reactive
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.rxlua
local WFRxPromiseBridge = require 'WFRxPromiseBridge'

local WFArguments = require "WFArguments"
local KSTableUtils = require "KSTableUtils"
local KSData = require "KSData"
local KSPathUtils = require "KSPathUtils"
local OzFilePaths = require "OzFilePaths"
local WFCatAssetUtils = require "WFCatAssetUtils"
local WFFileUtils = require "WFFileUtils"
local WFAsyncFileUtils = require "WFAsyncFileUtils"
local WFLogger = require "WFLogger"
local WFCrypto = require "WFCrypto"
local WFPlatformInfo = require "WFPlatformInfo"

local WFCatAssetAux = require 'WFCatAssetAux'

local check = WFArguments.check

local asKSPromise = WFRxPromiseBridge

local derefOpt = KSTableUtils.derefOpt

local logger = WFLogger( 'LrmCatXmpDevelop' )
	 --:setLevel 'DEBUG'

local INLINE_XMP_CAMERA_RAW_MAX_LENGTH = 10 * 1024 --10KB

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

local LrmCatXmpDevelop = {}

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

local function getExternalXmpDevelop( catalog, docValue, sha256 )

	-- resolve external xmpCameraRaw
	local hrefXmpDevelop = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
	local isXmpDevelopLinkInvalid = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'invalid' )
	local xmpFiles = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop' )
	local documents = KSPathUtils.getStandardFilePath( 'carouselDocuments' )

	sha256 = sha256 or derefOpt( docValue, 'develop', 'xmpCameraRaw', 'sha256' )

	if xmpFiles and xmpFiles.path then
					
		local xmpAbsPath = KSPathUtils.child( documents, xmpFiles.path )

		local key = 'sha256'

		if WFPlatformInfo.isiOS then
			key = 'contentId'
		end

		if derefOpt( xmpFiles, key ) == sha256 and WFFileUtils.exists( xmpAbsPath ) then

			-- xmpCameraRaw is already present					
			return Observable.just( KSData.memoryMappedContentsOfFile( xmpAbsPath ), xmpFiles.path )

		end

	end
		
	if not sha256 or not hrefXmpDevelop then
		
		return Observable.throw( 'No xmpCameraRaw available, not even external' )

	end

	return catalog._wfCatalogStream

		:flatMap( function( wfCatalog )

			local wfCatSpec = wfCatalog:ozCatalogSpec()

			return wfCatSpec:getBinary( hrefXmpDevelop )

				:map( function( xmpCameraRaw )

					local developSettingsSha256 = sha256

					if isXmpDevelopLinkInvalid then

						developSettingsSha256 = WFCrypto.SHA256.digest( tostring( xmpCameraRaw ) )

					end

					return xmpCameraRaw, developSettingsSha256

				end )

				:flatMap( function( xmpCameraRaw, developSettingsSha256 )

					local catalogId = wfCatalog:ozCatalogSpec():catalogId()

					local xmpRelPath, xmpAbsPath = OzFilePaths.getXmpDevelopPath( catalogId, docValue, developSettingsSha256, true )

					-- If the file already exists( maybe it was downloaded as part of
					-- another document ), return the same data.

					if WFFileUtils.exists( xmpAbsPath ) then
						local fileSize = ( xmpFiles and xmpFiles.fileSize ) or WFFileUtils.fileAttributes( xmpAbsPath ).fileSize
						return Observable.just( KSData.memoryMappedContentsOfFile( xmpAbsPath ), xmpRelPath, fileSize, developSettingsSha256 )
					end

					if not KSData.isData( xmpCameraRaw ) then
						xmpCameraRaw = KSData.make( xmpCameraRaw )
					end

					return WFAsyncFileUtils.saveToFile( xmpCameraRaw, xmpAbsPath )
						:map( function()
							return xmpCameraRaw, xmpRelPath, KSData.length( xmpCameraRaw ), developSettingsSha256
						end )

			end )

		end )

		:flatMap( function( xmpCameraRaw, xmpRelPath, fileSize, developSettingsSha256 )

			-- MEH [immisch 2016-09-28]: WFRxDocStore has _id, but the compatibility layer gives us _docId
			local docId = docValue._id or docValue._docId

			local key = 'sha256'

			if WFPlatformInfo.isiOS then
				key = 'contentId'
			end

			-- update the document
			return catalog:docStore():applyOverlay {
					_id = docId,
					_localOnly = {
						files = {
							xmp_develop = {
								[ key ] = developSettingsSha256,
								path = xmpRelPath,
								fileSize = fileSize,
							}
						}
					}
				}
				:flatMap( function( )

					local maybeOldXmpDevelopFilename = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )
					if maybeOldXmpDevelopFilename and maybeOldXmpDevelopFilename ~= xmpRelPath then
						return catalog:getSyncClient():maybePurgeExternalDevelopSettingsFile( maybeOldXmpDevelopFilename )
					else
						return Observable.just()
					end
				
				end )
				:map( function( )
				
					return xmpCameraRaw, xmpRelPath
				
				end )
				
		end )

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

-- SPAM [ashj 2020-06-24]: This is almost same as WFCatDevelopSettings.xmpCameraRawOverlay
-- At some point in future, we should be unifying these.

local function xmpCameraRawOverlay( wfCatalog, cameraRawXmp, baseOverlay, forceExternalization, maybeCameraRawLimitOverride )
	
	local settingsLength = cameraRawXmp and string.len( cameraRawXmp ) or 0
	
	if maybeCameraRawLimitOverride and maybeCameraRawLimitOverride < INLINE_XMP_CAMERA_RAW_MAX_LENGTH then
		logger:infof( "Overriding max ACR settings length. Was %s, now %s", INLINE_XMP_CAMERA_RAW_MAX_LENGTH, maybeCameraRawLimitOverride )
		INLINE_XMP_CAMERA_RAW_MAX_LENGTH = maybeCameraRawLimitOverride
	end
	
	-- ensure baseOverlay meets the minimum requirements
	local overlay = baseOverlay or {}
	overlay.develop = overlay.develop or {}
	
	if settingsLength < INLINE_XMP_CAMERA_RAW_MAX_LENGTH and ( not forceExternalization ) then
		
		overlay.develop.xmpCameraRaw = cameraRawXmp
		
		return Observable.just( overlay )

	end

	-- too big to send to Oz inline, save to local file, 
	-- send sha256 of develop settings and let the upload 
	-- process attach the file to the asset record on Oz

	local settingsAsKSData = KSData.make( cameraRawXmp )
	
	local catalogId = wfCatalog:ozCatalogId()

	return WFCrypto.asyncDigestData( settingsAsKSData, 'SHA256' )
		:map( function( sha256 )
							
			local relPath, absPath = OzFilePaths.getXmpDevelopPath( catalogId, baseOverlay, sha256, true )
			
			overlay.develop.xmpCameraRaw = { sha256 = sha256 }

			local key = 'sha256'

			if WFPlatformInfo.isiOS then
				key = 'contentId'
			end

			overlay._localOnly = { 
				files = {
					xmp_develop = {
						path = relPath,
						fileSize = settingsLength,
						[ key ] = sha256,
					}
				},
				sourceXmpDevelopLink = { _delete = true } -- WF-1323: settings changes should remove sourceXmpDevelopLink as its no longer valid
			}
			
			return overlay, relPath, absPath
			
		end )
		:flatMap( function( overlayWithSha256, xmpRelPath, xmpAbsPath )
	
			
			return WFAsyncFileUtils.calculatePathSize( xmpAbsPath )
				:flatMap( function( pathSize )
					local settingsLen = KSData.length( settingsAsKSData )
					
					if pathSize == settingsLen then -- settings already exist at this location, don't resave
						return Observable.just( overlayWithSha256 )
					end
					
					return WFAsyncFileUtils.saveToFile( settingsAsKSData, xmpAbsPath )
						:tapOnError( function( stdError, platformError )
							logger:errorf( "failed to write external develop settings to %s: %s (%s)", xmpAbsPath, stdError, platformError )
						end )
						:catch( function()
							return Observable.just( nil )
						end )
						:map( function()
							return overlayWithSha256
						end )
				end )
		:tap( function()
			settingsAsKSData:close()
		end )
			
	end )

end

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

--[[!
	\brief retrieve and store the external xmpCameraRaw develop settings and develop aux binaries
	
	\param catalog the catalog service
	\param docIdOrValue the document id or the docValue
	\param sha256 the sha256 of the external develop settings (optional)
	\param optImageType ( optional string ) Variable that indicates what size of aux is being requested. Available types:
		'original' - Fetch full-size if possible, otherwise proxy size
		'proxy-2560' - Fetch proxy size. If not available WF will make a resample request to Oz.
	\return (KSData, string) xmpCameraRaw and the relative path to the file
--]]
function LrmCatXmpDevelop.getExternalXmpDevelopAndAux( catalog, docIdOrValue, sha256, optImageType )

	check( 'OzCatalogService', 'table|string', '?string', '?string' )

	return catalog._wfCatalogStream

		:take( 1 )

		:flatMap( function( wfCatalog )

			-- Always fetch the docValue from the WFRxDocStore directly, because
			-- that's the docValue format that WFCatAssetAux expects.

			local docId = docIdOrValue

			if type( docIdOrValue ) == 'table' then

				docId = docIdOrValue._id or docIdOrValue._docId

			end

			return wfCatalog:docStore():docValue( docId )

				:flatMap( function( docValue )

					return WFCatAssetAux.ensureAuxFilesExistLocally( wfCatalog, docValue, optImageType )

						:map( function( results )

							return docValue, results.selectedDigests

						end )

				end )

		end )

		:flatMap( function( docValue, selectedDigests )

			return getExternalXmpDevelop( catalog, docValue, sha256 )

				:map( function( xmpCameraRaw, xmpRelPath )

					return xmpCameraRaw, xmpRelPath, selectedDigests

				end )

		end )

end

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

function LrmCatXmpDevelop.getExternalXmpDevelopAndAuxP( catalog, docIdOrValue, sha256, optImageType )

	return LrmCatXmpDevelop.getExternalXmpDevelopAndAux( catalog, docIdOrValue, sha256, optImageType )
		:call( asKSPromise )

end

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

--[[!
	\brief retrieve and store the external xmpCameraRaw develop settings
	
	\param catalog the catalog service
	\param docId the document id
	\param sha256 the sha256 of the external develop settings (optional)
	\return (KSData, string) xmpCameraRaw and the relative path to the file
--]]
function LrmCatXmpDevelop.getExternalXmpDevelop( catalog, docId, sha256 )

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

	return catalog._wfCatalogStream

		:take( 1 )

		:flatMap( function( wfCatalog )

			return wfCatalog:docStore():docValue( docId )

		end )

		:flatMap( function( docValue )

			return getExternalXmpDevelop( catalog, docValue, sha256 )

				:map( function( xmpCameraRaw, xmpRelPath )

					return xmpCameraRaw, xmpRelPath

				end )

		end )

end

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

function LrmCatXmpDevelop.getExternalXmpDevelopP( catalog, docId, sha256 )

	return LrmCatXmpDevelop.getExternalXmpDevelop( catalog, docId, sha256 )
		:call( asKSPromise )

end

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

--[[!
	\brief Externalizes the xmp settings. 
	
	\param catalog the wfcatalog
	\param assetId the document id or the docValue
	\param maybeMaxLength the sha256 of the external develop settings (optional)
	\return (Observable) which fires true or false depending on whether the 
			task was successful
--]]
function LrmCatXmpDevelop.externalizeDevelopSettings( wfCatalog, assetId, maybeMaxLength )

	check( 'WFCatalog', 'string', '?number')
	
	return wfCatalog:docStore():docValue( assetId )
		:flatMap( function( docValue )
			if docValue._empty then
				-- doc has been deleted, ignore externalizing develop settings
				return Observable.just( true )
			end
			
			local currentSettingsXmp = derefOpt( docValue, 'develop', 'xmpCameraRaw' )
			
			if type( currentSettingsXmp ) ~= 'string' then
				-- doc already has settings externalized, no need to redo
				return Observable.just( true )
			end
			
			local baseOverlay = {
				_id = docValue._id,
				_spaceId = docValue._spaceId,
					-- By including the spaceId in the baseOverlay, we ensure that
					-- the call to xmpCameraRawOverlay will create the proper, space-aware
					-- path for the externalized develop settings.
				develop = {
				}
			}
			
			return xmpCameraRawOverlay( wfCatalog, currentSettingsXmp, baseOverlay, true, maybeMaxLength )
				:flatMap( function( overlay )
					if not overlay then
						return Observable.just( false )
					end
					
					return wfCatalog:docStore():applyOverlay( overlay )
						:map( true )
				end )
		end )
		:catch( function( err )
			logger:errorf( "LrmCatXmpDevelop.externalizeDevelopSettings failed: %s", err )
			return Observable.just( false )
		end )
end

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

return LrmCatXmpDevelop
