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

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 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 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 )
	
		if xmpFiles.sha256 == 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 catalogId = wfCatalog:ozCatalogSpec():catalogId()

			local xmpRelPath, xmpAbsPath = OzFilePaths.getXmpDevelopPath( catalogId, docValue, sha256, 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 )
				
			end
			
			return catalog:getCatUser()
				:flatMap( function( ozUser )
					
					return ozUser:getBinary( hrefXmpDevelop )
						:flatMap( function( xmpCameraRaw )
						
							return WFAsyncFileUtils.saveToFile( xmpCameraRaw, xmpAbsPath )
								:map( function()
									
									return xmpCameraRaw, xmpRelPath, KSData.length( xmpCameraRaw )
								end )
						end )
				end )
		end )

		:flatMap( function( xmpCameraRaw, xmpRelPath, fileSize )

			-- 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 ] = sha256,
								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
	
	\param catalog the catalog service
	\param docIdOrValue the document id or the docValue
	\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, docIdOrValue, sha256 )

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

	if type(docIdOrValue) == 'table' then
		return getExternalXmpDevelop( catalog, docIdOrValue, sha256 )
	end
	
	return catalog:docStore():docValue( docIdOrValue )
		
		:flatMap( function( docValue )
			
			return getExternalXmpDevelop( catalog, docValue, sha256 )
		
		end )

end

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

function LrmCatXmpDevelop.getExternalXmpDevelopP( catalog, docIdOrValue, sha256 )

	return LrmCatXmpDevelop.getExternalXmpDevelop( catalog, docIdOrValue, 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
