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

LrmOzDevelopSession.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2013 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.

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

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.diagnostic_log
local WFDiagnosticLog = require 'WFDiagnosticLog'

	-- WF.core.logging
local WFLogger = require 'WFLogger'

	-- WF.core.lua
local KSData = require 'KSData'

	-- WF.core.os
local KSDate = require 'KSDate'
local KSPathUtils = require 'KSPathUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFFileUtils = require 'WFFileUtils'

	-- WF.core.reactive
local KSPromises = require 'KSPromises'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFAnalytics = require 'WFAnalytics'
local WFArguments = require 'WFArguments'
local WFCapabilities = require 'WFCapabilities'
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFCloseSupport = require 'WFCloseSupport'
local WFTableArchiving = require 'WFTableArchiving'

	-- WF.oz_client.catalog
local OzReferenceDimension = require 'OzReferenceDimension'


local KSSystemNotifications = require 'KSSystemNotifications'
local WFOzRenditionServerUtils = require 'WFOzRenditionServerUtils'
local WFOzRenditionServerUtils_getFullMD5HashedKey = WFOzRenditionServerUtils.getFullMD5HashedKey

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

local logger = WFLogger 'LrmOzDevelopSession'
--	:setLevel 'debug'

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

local OzReferenceDimension_getReferenceDimensions = assert( OzReferenceDimension.getReferenceDimensions )

local check = WFArguments.check

local LrmOzDevelopSession = 'LrmOzDevelopSession'
local OzCatalogService = 'OzCatalogService'
local PhotoId = 'string'

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

local recentDevelopSessions = {}

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

WFDiagnosticLog.addLogSectionFunc( function()

	if #recentDevelopSessions > 0 then

		return {
		
			name = "Recent Develop Sessions",
			id = 'LrmOzDevelopSession',
			weight = 1200,
			
			body = {
				recentSessions = recentDevelopSessions,
			},
		
		}
	
	end

end )

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

KSSystemNotifications.onNotificationDo(
		'didCatalogClose',
		function() 
			recentDevelopSessions = {}
		end )


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

-- @mark LrmOzDevelopSession

local developSessionMethods = {

	_catalogService = false,
		-- (OzCatalogService)

	_docStore = false,
		-- (OzDocStore)

	_indexStore = false,
		-- (OzIndexStore)
	
	_photoId = false,
		-- (string) photo's asset ID
	
	_clearTempXMP = false,
		-- (boolean) if set to true, then we will forcibly overwrite the
		-- TEMP_XMP field on exit.
		
	_originalDocValueP = false,
		-- (P< table >) private: complete document value when we started this session
	
	_originalDevelopSettingsP = false,
		-- (P< table >) develop settings value when we started this session
	
	_cacheEpochB = false,
		-- (B< number >) epoch number behavior swiped from OzCatalogService
	
	_tsLogEntry = false,
		-- (table) info in recentDevelopSessions log about this entry
	
	WFCloseSupport.addCloseSupportMethods,

}

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

function developSessionMethods:__init( ozCatalogService, photoId )

	check( LrmOzDevelopSession, OzCatalogService, PhotoId )

	self._catalogService = ozCatalogService
	self._docStore = ozCatalogService:docStore()
	self._indexStore = ozCatalogService:indexStore()
	self._cacheEpochB = ozCatalogService:getCacheEpochB()
		-- SPAM [arahn 2012-10-05]: Groping internals of ozCatalogService
	
	self._photoId = photoId
	self:addCloseTarget( self._cacheEpochB )
	self._tsLogEntry = {}
	
	recentDevelopSessions[ #recentDevelopSessions + 1 ] = self._tsLogEntry
	if #recentDevelopSessions > 10 then
		table.remove( recentDevelopSessions, 1 )
	end
	
	logger:infof( "acquiring original develop settings for photo %s", photoId )
	
	self:loadSettings()
	
	WFCloseSupport.mustClose( self )
	
end


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

function developSessionMethods:xmpAbsoluteFilePath( relativePath )

	local documentsPath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
	
	return KSPathUtils.child( documentsPath, relativePath)
	
end

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

function developSessionMethods:loadSettings()

	self._originalDocValueP = self._docStore:docValueOnceP( 'asset', self._photoId )
	
	self._originalDevelopSettingsP = self._originalDocValueP:mapP( function( status, docValue )
	
		logger:debug( "mapping originalDocValue through to develop settings" )

		self._tsLogEntry.docValueStatus = status

		if status == 'data' then
		
			logger:infof( "acquired initial develop settings for photo %s\n\ndocValue = %s",
						  self._photoId, docValue )
	
			self._tsLogEntry.docValueAtEntry = docValue
	
			if WFPlatformInfo.isAndroid and docValue._localOnly then
				-- This piece of code is written to embed existance of proxy/original on disk at the time of loading the development version.
				local OzCatalogUploadStatus = require 'OzCatalogUploadStatus'
				local WFFileUtils = require 'WFFileUtils'
				local OzCatHelperFns = require 'OzCatHelperFns'
				local OzCatHelperFns_findAndCorrectOriginalPath = OzCatHelperFns.findAndCorrectOriginalPath

				local firstTimeString = 'First Time Edit'

				local function checkIfProxyExistsLocally()

					local proxyPath = docValue
							and docValue._localOnly
							and docValue._localOnly.proxy
							and docValue._localOnly.proxy.filePath

					proxyPath = OzCatalogUploadStatus.checkAndCorrectLocalPath( proxyPath )

					if proxyPath then
						if WFFileUtils.exists( proxyPath ) then
							return true
						else
							return firstTimeString
						end
					end

					return false
				end

				local function checkIfOriginalExitsLocally()

					local originalFilePath = docValue
							and docValue._localOnly
							and docValue._localOnly.latestOriginal
							and docValue._localOnly.latestOriginal.filePath

					local localPath = OzCatHelperFns_findAndCorrectOriginalPath( docValue )

					originalFilePath = OzCatalogUploadStatus.checkAndCorrectLocalPath( originalFilePath )

					if OzCatalogUploadStatus.assetOriginalIsValid( docValue ) then

						if localPath and WFFileUtils.exists( localPath ) then
							return true
						end

					else

						if OzCatalogUploadStatus.assetLatestOriginalIsValid( docValue ) then

							if originalFilePath and WFFileUtils.exists( originalFilePath ) then
								return true
							end

						end

					end

					if not localPath and not originalFilePath then
						return firstTimeString
					end

					return false

				end

				local proxyAvailable = checkIfProxyExistsLocally()
				local originalAvailable = checkIfOriginalExitsLocally()

				local isFirstTime = false

				if proxyAvailable == firstTimeString or originalAvailable == firstTimeString then
					isFirstTime = true
				end

				if proxyAvailable == true then
					self._tsLogEntry.docValueAtEntry.FileExists = 'Proxy File Locally Exists'
				elseif originalAvailable == true then
					self._tsLogEntry.docValueAtEntry.FileExists = 'Original File Locally Exists'
				else
					if isFirstTime then
						self._tsLogEntry.docValueAtEntry.FileExists = 'First time Edit request, Editable Version Does Not Exists'
					else
						self._tsLogEntry.docValueAtEntry.FileExists = 'Editable Version Does Not Exists'
					end
				end

			end

			local develop = docValue.develop or {}
		
			self._clearTempXMP = develop.TEMP_XMP ~= nil
			
			return 'data', {
				xmpSingleBlock = develop.TEMP_XMP or "", -- Legacy field
				xmpCameraRaw = develop.xmpCameraRaw or "",
--				xmpLook = develop.xmpLook or "",
				userOrientation = develop.userOrientation,
				croppedWidth = develop.croppedWidth,
				croppedHeight = develop.croppedHeight,
				docValue = docValue
			}
				-- Replace "" with whatever we should use as default develop settings.
				-- When this becomes a table, we need to construct it each time through
				-- because the caller is free to modify it.
				
		else

			self._tsLogEntry.docValueError = docValue

			logger:errorf( "FAILED to acquire develop settings for photo %s %s",
						   self._photoId, docValue )
	
			return status, docValue

		end
	
	end )
	self._originalDevelopSettingsP:runUntilDone()
end

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

function developSessionMethods:docValueB()

	return self._originalDocValueB
	
end
--------------------------------------------------------------------------------

function developSessionMethods:developSettingsP()

	return self._originalDevelopSettingsP

end

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

function developSessionMethods:docValueP()

	return self._originalDocValueP

end

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

-- function developSessionMethods:hasPendingChangeFromOzB()
	-- is this of interest?

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

local resolvedPromiseP = KSPromises.resolvedPromiseP( 'data', true )

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

function developSessionMethods:cancelP()

	self._tsLogEntry.exitStatus = 'cancel'
	self._tsLogEntry.timeAtExit = KSDate.timeInIso8601Format()

	return resolvedPromiseP
		-- FIXME [scouten 2011-09-04]: Perhaps we no longer need cancel API
		-- at all?

end

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

function developSessionMethods:applyOverlayToDocumentP( overlay )

	return self._docStore:applyOverlayToDocumentP {
		
			_replace = true,

			docType = 'asset',
			docId = self._photoId,
			overlay = overlay,
		}

end

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

function developSessionMethods:saveAndCloseP( changes )

	self._tsLogEntry.exitStatus = 'saveAndClose'
	self._tsLogEntry.timeAtExit = KSDate.timeInIso8601Format()
	
	self._tsLogEntry.changes = {
		newBaseSettings = changes.xmpCameraRaw or '(nil)',
		newLookSettings = changes.xmpLook or '(nil)',
		newUserOrientation = changes.userOrientation or '(nil)',
		newThumbnail = KSData.length( changes.thumbnail ) .. ' bytes',
		newHighDpiThumbnail = KSData.length( changes.highDpiThumbnail ) .. ' bytes',
		croppedWidth = changes.croppedWidth or '(nil)',
		croppedHeight = changes.croppedHeight or '(nil)',
		negativeIsInadequate = changes.negativeIsInadequate or '(nil)',
		newCameraProfileFileName = changes.cameraProfileFileName or '(nil)',
		newCameraProfileLink = changes.cameraProfileLink or '(nil)',
	}

	if changes.preview then
		self._tsLogEntry.changes.newPreview = KSData.length( changes.preview ) .. ' bytes'
	end

	local newBaseSettings = changes.xmpCameraRaw
	local newUserOrientation = changes.userOrientation
	local newPreview = changes.preview
	local newHighDpiThumbnail = changes.thumbnail
	local croppedWidth = changes.croppedWidth
	local croppedHeight = changes.croppedHeight
	local crsVersion = changes.crsVersion
	local newCameraProfileFileName = changes.cameraProfileFileName
	local newCameraProfileLink = changes.cameraProfileLink
	
	if type( newBaseSettings ) ~= 'string' then

		if type( newBaseSettings ) ~= 'table' then
			local debugValue = {
				value = newBaseSettings
			}
			local errorEvent = {}
		 	errorEvent['event.type'] = 'error'
			errorEvent['event.subtype'] = 'save_and_close'
		 	errorEvent['event.workflow'] = 'WF'
		 	errorEvent['event.subcategory'] = 'LrmOzDevelopSession'
		 	errorEvent['content.id'] = self._photoId
			errorEvent['event.value'] = WFTableArchiving.tableToString( debugValue )
			
			WFAnalytics.trackEvent( errorEvent )
		end
		
		assert( type( newBaseSettings ) == 'table',
			"OzDevelopSession:saveAndCloseP: develop base settings should be a string or a table" )

		assert( newBaseSettings.sha256,
			"OzDevelopSession:saveAndCloseP: external develop base settings must have a sha256 key" )

		assert( changes.localXmpDevelopPath,
			"OzDevelopSession:saveAndCloseP: external develop changes must have localXmpDevelopPath key" )
		
	end
		
	assert( type( newUserOrientation ) == 'number',
		"New orientation should be a number" )
			
	assert( KSData.isData( newHighDpiThumbnail ),
		"OzDevelopSession:saveAndCloseP: newHighDpiThumbnail missing" )

	local previewMD5
	
	if newPreview then
	
		previewMD5 = newPreview:digest( 'MD5' )
		previewMD5 = WFOzRenditionServerUtils_getFullMD5HashedKey (previewMD5, self._photoId)
		logger:debugf( "writing preview (MD5 = %s)", previewMD5 )
	
	end
	
	local thumbnail2xMD5 = newHighDpiThumbnail:digest( 'MD5' )
	thumbnail2xMD5 = WFOzRenditionServerUtils_getFullMD5HashedKey (thumbnail2xMD5, self._photoId)

	self._tsLogEntry.saveStatus = {}

	local dataCacheP = self._catalogService:getDataCacheP()
	
	local dataCache
	
	local writePreviewP = dataCacheP
		:mapDataP_new( function( localDataCache )
			
			dataCache = localDataCache
			
			assert( dataCache )
			
			if newPreview then
				return dataCache:putP( previewMD5, newPreview )
			else
			
				return self._originalDocValueP
					:mapDataP_new( function( docValue )
					
						local oldPreviewMD5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'preview', 'md5' )

						if oldPreviewMD5 then
							return dataCache:removeP( oldPreviewMD5 )
						else
							KSPromises.resolvedPromiseP( 'data' )
						end

					end )
						
			end
			
		end )

	local writeThumbnail2xP = writePreviewP:mapP {
		data = function( status, data )
		
			assert( dataCache )
			
			return dataCache:putP( thumbnail2xMD5, newHighDpiThumbnail )
		end,
	}
	
	local cameraProfileLinkSql = "SELECT uniqueId from profileRegistration where fileName = ? COLLATE NOCASE"

	local getCameraProfileLinkP = writeThumbnail2xP
		:mapDataP( function()

			if ( newCameraProfileLink and newCameraProfileLink ~= '') or
				not newCameraProfileFileName or newCameraProfileFileName == '' then

				return KSPromises.resolvedPromiseP( 'data' )

			else

				return self._indexStore:queryP(cameraProfileLinkSql, newCameraProfileFileName)
					:mapDataP( function( _, idRows )

						local uniqueId = idRows and idRows[ 1 ] and idRows[ 1 ][ 1 ]

						if uniqueId then
							newCameraProfileLink = uniqueId
						end

						return 'data', ""
					end )

			end

		end )

	-- Once those are done, then write new info to document.

	local epochNow = self._cacheEpochB:valueNow()
	
	local thumbnail2xSize = KSData.length( newHighDpiThumbnail )

	if not newHighDpiThumbnail or thumbnail2xSize < 1 then
		logger:warn( "Develop handed us a thumbnail2x that is empty" )
	end

	-- The reference width and height tells us how big the actual image is.
		
	local referenceWidth, referenceHeight = croppedWidth, croppedHeight
	
	if not referenceWidth or not referenceHeight then
		referenceWidth, referenceHeight = OzReferenceDimension_getReferenceDimensions( newPreview )
	end
	
	local newRevId = KSIdGenerator.uuidInLowercaseHex()
	
	local previewDoc = { _delete = true }
	
	if newPreview then
	
		previewDoc = {
			md5 = previewMD5,
			revId = newRevId,
			epoch = epochNow,
			size = KSData.length( newPreview ),
			referenceWidth = referenceWidth,
			referenceHeight = referenceHeight,
		}
		
	end
		
	local thumbnail2xDoc = {
		md5 = thumbnail2xMD5,
		revId = newRevId,
		epoch = epochNow,
		size = KSData.length( newHighDpiThumbnail ),
		referenceWidth = referenceWidth,
		referenceHeight = referenceHeight,
	}
	
	local localOnly = {
		sourceXmpDevelopLink = { _delete = true }, -- WF-1323: settings changes should remove sourceXmpDevelopLink as its no longer valid
		devSettingsLastSetAtRev = 'pending',
	}
	
	localOnly[ 'thumbnail2x' ] = thumbnail2xDoc	
	localOnly[ 'preview' ] = previewDoc
	
	if type( newBaseSettings ) == 'table' then
	
		local documents = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
		local xmpAbsPath = KSPathUtils.child( documents, changes.localXmpDevelopPath )
		local xmpSize = WFFileUtils.fileAttributes( xmpAbsPath ).fileSize
	
		localOnly.files = {
			xmp_develop = {
				path = changes.localXmpDevelopPath,
				sha256 = newBaseSettings.sha256,
				fileSize = xmpSize,
				uploadStatus = { _delete = true },
			}
		}
	else
		localOnly.files = {
			xmp_develop = { _delete = true },
		}
	end
	
	local overlay = {
			
		develop = {
		
			processingModel = "lightroom",
			xmpCameraRaw = newBaseSettings,
			
			userUpdated = KSDate.timeInIso8601Format( nil, false ),
			userOrientation = newUserOrientation,
			
				-- cropped width and height are largely functions of develop
			croppedWidth = croppedWidth,
			croppedHeight = croppedHeight,
			
			crsVersion = crsVersion,

		},
		
		userUpdated = KSDate.timeInIso8601Format( nil, false ),

		_localOnly = localOnly,
		
		_newRevId = newRevId,
		
	}
	
	self._tsLogEntry.overlay = overlay

	local saveSettingsP
	if WFPlatformInfo.isAndroid then

		saveSettingsP = writeThumbnail2xP
		:mapDataP( function()

			return self._originalDevelopSettingsP

		end )
		:mapDataP( function( originalStatus, developSettings )
			
			if originalStatus == 'data' then

				-- INFO [pkamin 2015-05-12]: if this asset uses lens/camera profiles
				-- we need to retain this information as we replace the full develop hash

				local oldCameraProfileFilename = KSTableUtils.derefOpt( developSettings, 'docValue', 'develop', 'profiles', 'camera', 'filename' )

				if oldCameraProfileFilename ~= changes.cameraProfileFileName and ( not changes.cameraProfileLink or changes.cameraProfileLink == '' )
						and changes.appliedCameraProfileDigest and changes.appliedCameraProfileDigest ~= '' and changes.cameraModelName and changes.cameraModelName ~= '' then

					local LrmWFOzProfileUtils = require 'LrmWFOzProfileUtils'
					return LrmWFOzProfileUtils.getCustomCameraProfilesP(self._catalogService, changes.appliedCameraProfileDigest, changes.cameraModelName)
					:mapP(function(_, profileFilename, profileLink)

						logger:debugf('_dcp, query response for custom dcp sync:%s', profileLink)

						changes.cameraProfileFileName = profileFilename
						changes.cameraProfileLink = profileLink

						return originalStatus, developSettings

					end)

				end

			end

		return KSPromises.resolvedPromiseP(originalStatus, developSettings)

		end )

		:mapP( function( originalStatus, developSettings )

			-- "develop.fromDefaults" should only be set to false in the case that the key already existed before
			-- this particular develop settings change is made. Because the calling side has a hard time to
			-- inspect the old doc value for the key in question, we do it here.
			local oldFromDefaults = KSTableUtils.derefOpt( developSettings, 'docValue', 'develop', 'fromDefaults' )

			if changes.fromDefaults == false then
				if oldFromDefaults ~= nil then
					overlay.develop.fromDefaults = changes.fromDefaults
				end
			elseif changes.fromDefaults == true then
				overlay.develop.fromDefaults = changes.fromDefaults
			end

			return originalStatus, developSettings


		end )

		:mapP(function(status, developSettings)

			local oldCameraProfiles = KSTableUtils.derefOpt( developSettings, 'docValue', 'develop', 'profiles', 'camera' )

			local appliedCameraProfileFileName = changes.cameraProfileFileName
			local appliedCameraProfileLink = changes.cameraProfileLink

			local cameraProfileNeedsToBeUpdated = (appliedCameraProfileLink and appliedCameraProfileLink ~= '')

			if oldCameraProfiles then
				cameraProfileNeedsToBeUpdated = cameraProfileNeedsToBeUpdated and oldCameraProfiles.filename ~= appliedCameraProfileFileName
			end

			logger:debugf('_dcp, cameraProfileNeedsToBeUpdated:%s', cameraProfileNeedsToBeUpdated)
			logger:debugf('_dcp, appliedCameraProfileFileName:%s', appliedCameraProfileFileName)
			logger:debugf('_dcp, already appliedCameraProfileFile:%s', oldCameraProfiles and oldCameraProfiles.filename)

			local newProfiles = {}
			if cameraProfileNeedsToBeUpdated then

				newProfiles['camera'] = {
					filename = appliedCameraProfileFileName,
					href = appliedCameraProfileLink,
				}

			end

			local profiles = {}
			local oldProfiles = KSTableUtils.derefOpt( developSettings, 'docValue', 'develop', 'profiles' )

			if oldProfiles then
				profiles = KSTableUtils.deepCopy( oldProfiles )
			end

            local WFTableMerge = require 'WFTableMerge'

			WFTableMerge.applyOverlay( profiles, newProfiles)

			if next(profiles) then
				overlay.develop.profiles = profiles
			end

			return status, overlay, developSettings
		end)

		:mapDataP(function(status, overlay, developSettings)

			logger:debugf('_dcp, status:%s, overlay.develop.profiles:%s', status, overlay.develop.profiles)
			local maybeNewXmpDevelopFilename = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( overlay )
			
			return self:applyOverlayToDocumentP( overlay )
				:mapDataP( function( status, overlayResult )

					local maybeOldXmpDevelopFilename = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( developSettings.docValue )
					
					if maybeOldXmpDevelopFilename and maybeOldXmpDevelopFilename ~= maybeNewXmpDevelopFilename then
						return self._catalogService:getSyncClient():maybePurgeExternalDevelopSettingsFile( maybeOldXmpDevelopFilename ):call( require 'WFRxPromiseBridge' )
							:mapP( function()					
								return status, overlayResult
							end )
					else
						return KSPromises.resolvedPromiseP( status, overlayResult )
					end
				end )

		end )

	else

		saveSettingsP = getCameraProfileLinkP
		:mapDataP( function()

			return self._originalDevelopSettingsP

		end )
		:mapDataP( function( originalStatus, developSettings )

			if originalStatus == 'data' then

				-- INFO [pkamin 2015-05-12]: if this asset uses lens/camera profiles
				-- we need to retain this information as we replace the full develop hash

				local oldProfiles = KSTableUtils.derefOpt( developSettings, 'docValue', 'develop', 'profiles' )

				if oldProfiles then

					overlay.develop[ 'profiles' ] = KSTableUtils.deepCopy( oldProfiles )

				end

				-- Finally, if we got a new cameraProfile update fileName and link
				if newCameraProfileLink and newCameraProfileLink ~= '' then

					KSTableUtils.setNested( overlay, newCameraProfileFileName, 'develop', 'profiles', 'camera', 'filename')
					KSTableUtils.setNested( overlay, newCameraProfileLink, 'develop', 'profiles', 'camera', 'href')

				end


			end

			return self:applyOverlayToDocumentP( overlay )

		end )
	
	end

	return saveSettingsP:runUntilDoneP()

end

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

local function canUpdateRenditionForDocValue( docValue, xmpCameraRaw, externalXmpSHA,
	userOrientation, croppedWidth, croppedHeight )
	
	local docXmpCameraRaw = KSTableUtils.derefOpt( docValue, 'develop', 'xmpCameraRaw' )

	if type( xmpCameraRaw ) == 'string' then
		
		if  docXmpCameraRaw ~= xmpCameraRaw then
			return false
		end
	else 
		local docExternalXmpSHA = KSTableUtils.derefOpt( docXmpCameraRaw, 'sha256' )
		
		if docExternalXmpSHA ~= externalXmpSHA then
			return false
		end
	end
	
	local docUserOrientation = KSTableUtils.derefOpt( docValue, 'develop', 'userOrientation' )
	
	if docUserOrientation ~= userOrientation then
		return false
	end
	
	local docCroppedWidth = KSTableUtils.derefOpt( docValue, 'develop', 'croppedWidth' )
	
	if docCroppedWidth ~= croppedWidth then
		return false
	end
	
	local docCroppedHeight = KSTableUtils.derefOpt( docValue, 'develop', 'croppedHeight' )
	
	if docCroppedHeight ~= croppedHeight then
		return false
	end
	
	return true
	
end

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

local function updateDataCacheForRendition( photoId, dataCache, docValue, renditionKey, renditionData )

	local returnObservable = Rx.Observable.just()
	
	if not renditionData then
		return returnObservable
	end
	
	local newMD5 = renditionData:digest( 'MD5' )
	newMD5 = WFOzRenditionServerUtils_getFullMD5HashedKey( newMD5, photoId )
	
	local oldMD5 = KSTableUtils.derefOpt( docValue, '_localOnly', renditionKey, 'md5' )
	if oldMD5 then
		
		returnObservable = returnObservable
			:flatMap( function()
				return dataCache:remove( oldMD5 )
			end )
			:defaultIfEmpty()
	end
	
	return returnObservable
			:flatMap( function()
				return dataCache:put( newMD5, renditionData )
					:defaultIfEmpty()
			end )
			:map( function()
				return newMD5
			end )
end

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

local function isValidRenditionUpdate( thumbnail, preview, xmpCameraRaw, 
	externalXmpSHA, userOrientation, croppedWidth, croppedHeight )
	
	if ( (thumbnail == nil or not KSData.isData( thumbnail )) and
		( preview == nil or KSData.isData( preview ))) then
		
		return false, "thumbnail or preview is not KSData"
	end
	
	if not xmpCameraRaw and not externalXmpSHA then
		return false, "Either xmpCameraRaw or externalXmpSHA should be present"
	end
	
	if not userOrientation then
		return false, "userOrientation should be present"
	end
	
	if not croppedWidth then
		return false, "croppedWidth should be present"
	end
	
	if not croppedHeight then
		return false, "croppedHeight should be present"
	end
	
	return true
	
end

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

function developSessionMethods:updateRenditionsForDevSettings( dict )

	local thumbnail = dict.thumbnail
	local preview = dict.preview
	local externalXmpSHA = dict.externalXmpSHA
	local xmpCameraRaw = dict.xmpCameraRaw
	local userOrientation = dict.userOrientation
	local croppedWidth = dict.croppedWidth
	local croppedHeight = dict.croppedHeight
	
	logger:debugf( "Received updateRenditionsForDevSettings request. photoId:%s", self._photoId )
	
	-- Validate the arguments
	local isValid, message = isValidRenditionUpdate( thumbnail, preview, 
		xmpCameraRaw, externalXmpSHA, userOrientation, croppedWidth, croppedHeight )
		
	if not isValid then
		return Observable.throw( message )
	end
	
	local epochNow = self._cacheEpochB:valueNow()
	local revisionId
	
	local result = Observable.combineLatest( 
				self._docStore:docValue( self._photoId ),
				self._catalogService:getDataCache() )
	
		:flatMap( function( docValue, dataCache )
			
			-- Check if renditions can be updated to the document
			-- in current state. Match develop settings to determine.
		
			if not canUpdateRenditionForDocValue( docValue, xmpCameraRaw, externalXmpSHA,
				userOrientation, croppedWidth, croppedHeight ) then
				
				logger:infof( "Cannot update rendition since the settings have changed" )
				return Rx.Observable.just( false )
			end
			
			revisionId = docValue._revId
			
			-- Update rendition cache and return the MD5 of the new rendition
			return Rx.Observable.combineLatest(
				updateDataCacheForRendition( self._photoId, dataCache, docValue, 'thumbnail', thumbnail ),
				updateDataCacheForRendition( self._photoId, dataCache, docValue, 'preview', preview ))
			
		end )
	
		
		:flatMap( function( thumbnailMD5, previewMD5 )
		
			local localOnly = {}
			
			if thumbnailMD5 then
				localOnly[ 'thumbnail2x' ] = {
					md5 = thumbnailMD5,
					revId = revisionId,
					epoch = epochNow,
					size = KSData.length( thumbnail ),
					referenceWidth = dict.croppedWidth,
					referenceHeight = dict.croppedHeight,
				}
			end
			
			if previewMD5 then
				localOnly[ 'preview' ] = {
					md5 = previewMD5,
					revId = revisionId,
					epoch = epochNow,
					size = KSData.length( preview ),
					referenceWidth = dict.croppedWidth,
					referenceHeight = dict.croppedHeight,
				}
			end
			
			local overlay = {
				
				_id = self._photoId,

				_localOnly = localOnly,
				
			}
					
			if next( localOnly ) then
				return self._docStore:applyOverlay( overlay )
			end
			
			return Observable.just( false )
			
		end )
		
		:tapOnCompleted( function()

			logger:debugf( "Closing updateRenditionsForDevSettings" )
		
		end )
	
	
	return result
	
end

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

function developSessionMethods:wantsProxyP( photoId )

	if not photoId then
		return KSPromises.resolvedPromiseP( 'data', nil )
	end
	
	local docValueOnceP = self._docStore:docValueOnceP( 'asset', photoId )
	
	local resultP = docValueOnceP:mapDataP( function( status, docValue )
	
		local width = ( docValue and docValue.develop ) and docValue.develop.croppedWidth
						 or ( docValue and docValue.width or 0 )
						
		local height = ( docValue and docValue.develop ) and docValue.develop.croppedHeight
						 or ( docValue and docValue.height or 0 )
		
		local pSize = WFCapabilities.callFilters( "OzCatalogService.defaultProxyType", 2048 )
		
		if pSize == nil then
			pSize = 2560
		end
		local proxySize = tonumber( pSize )
		local wantsProxy = width >= proxySize or height >= proxySize

		return 'data', wantsProxy
	
	end )
	
	return resultP
	
end

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

function developSessionMethods:logDevelopSessionInfo( info )

	assert( type( info ) == 'table' )

	for k, v in pairs(info) do
		self._tsLogEntry[ k ] = v
	end

end

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

return Rx.Class 'LrmOzDevelopSession'( developSessionMethods )
