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

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.

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

	-- WF.core.rxlua
local _ = require 'WFRxPromiseBridge'
local Rx = require 'Rx'
local Observable = Rx.Observable

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

	-- WF.core.utils
local WFCloseSupport = require 'WFCloseSupport'
local KSLuaUtils = require 'KSLuaUtils'
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFUnknown = require "WFUnknown"
local WFLogger = require "WFLogger"
local WFRxRunUntilDone = require 'WFRxRunUntilDone'

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

	-- WF.lrmobile_models
local LrmOzDevelopSession = require 'LrmOzDevelopSession'
local LrmAssetUtils = require 'LrmAssetUtils'
local LrmWFOzProfileUtils = require 'LrmWFOzProfileUtils'
local LrmConfig = require 'LrmConfig'
local LrmCatXmpDevelop = require "LrmCatXmpDevelop"

	-- WF.oz_client.utils
local OzCatalogUploadStatus = require 'OzCatalogUploadStatus'

	-- WF.oz_client.manged_catalog
local WFCatAssetAux = require 'WFCatAssetAux'
local WFCatVersions = require 'WFCatVersions'
local WFCatSharedWithYouUtils = require 'WFCatSharedWithYouUtils'

	-- WF.web.http_client
local WFRxHttpClient = require 'WFRxHttpClient'

local OzFilePaths = require "OzFilePaths"

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

local logger = WFLogger( 'LrmDevelopModel' )
--logger:enable( 'print' )

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

local derefOpt = assert( KSTableUtils.derefOpt )

local runUntilDone = WFRxRunUntilDone

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

local LrmDevelopModel = {}

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

local Model = {}

--------------------------------------------------------------------------------
WFArguments.unregisterChecker( 'binaryPreference' )

WFArguments.registerChecker( 'binaryPreference', function( strategy )
	return type( strategy ) == 'string' and
		strategy == LrmConfig.StrategyPreferMaster or
		strategy == LrmConfig.StrategyPreferProxy or
		strategy == LrmConfig.StrategyProxy or
		strategy == LrmConfig.StrategyMaster or
		strategy == LrmConfig.StrategyProxyAndMaster
end )

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

function Model:developChanges( optImageType )
	
	local devChangesB = self.catalog:photoInfoB( self.assetId )
	
		:closingMapSwitchB( function( docValue )
		
			if WFUnknown.isUnknown( docValue ) then
				return KSReactive.constantB( docValue )
			end
		
			local develop = docValue.develop or {}
			
			local hasExternalXmpDevelop = LrmAssetUtils.hasExternalXmpDevelop( docValue )
			local hasExternalXmpDevelopLocally = LrmAssetUtils.hasExternalXmpDevelopLocally( docValue )

			-- Android: xmpCameraRawFiles might be in the docValue but is missing (user accessible storage)
			local externalXmpDevelopMissing
			if WFPlatformInfo.isAndroid then
				externalXmpDevelopMissing = hasExternalXmpDevelop and not ( hasExternalXmpDevelopLocally
					and WFFileUtils.exists( OzFilePaths.getAbsoluteXmpDevelopPath( docValue ) ) )
			else
				externalXmpDevelopMissing = hasExternalXmpDevelop and not hasExternalXmpDevelopLocally
			end

			if externalXmpDevelopMissing then

				local sha256 = nil

				local getExternalXmpP = LrmCatXmpDevelop.getExternalXmpDevelopAndAuxP( self.catalog, docValue, sha256, optImageType )
					:mapDataP_new( function( _, xmpRelPath )
						
						develop.localXmpDevelopPath = xmpRelPath
						
						return develop
					
					end )
					
					return getExternalXmpP:untilResolvedB( 'data', WFUnknown.value() )
			
			end
			
			develop.localXmpDevelopPath = derefOpt( docValue, '_localOnly', 'files', 'xmp_develop', 'path' )
			
			return KSReactive.constantB( develop )
			
		end )
	
	return {
		valueB = devChangesB
	}
	
end

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

function Model:reset()

	return {
		valueB = self.resetB,
	}
	
end

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

function Model:master()

	return {
		valueB = self.masterB,
	}

end

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

local function cancelSession( self )

	if self.developSession then
		self.developSession = nil
	end
	 
end

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

local function createChangesStructure( dict )

	local changes = {}
	changes.xmpCameraRaw = dict.xmpCameraRaw
	changes.userOrientation = dict.userOrientation
	changes.thumbnail = dict.thumbnail
	changes.preview = dict.preview
	changes.croppedWidth = dict.croppedWidth
	changes.croppedHeight = dict.croppedHeight
	changes.localXmpDevelopPath = dict.localXmpDevelopPath
	changes.crsVersion = dict.crsVersion
	changes.cameraProfileFileName = dict.cameraProfileFileName
	changes.cameraProfileLink = dict.cameraProfileLink
	changes.appliedCameraProfileDigest = dict.appliedCameraProfileDigest or nil
	changes.cameraModelName = dict.cameraModelName or nil
	changes.fromDefaults = dict.fromDefaults
	changes.crsHDREditMode = dict.crsHDREditMode
	changes.needsUpdateAISettings = dict.needsUpdateAISettings
	changes.auxInfo = dict.auxInfo or {}

	return changes

end

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

local function applyDevelopChanges( self, resultB, changes, versionCreatedManually, maybeVersionName, maybeLinkedLocation )
	
	KSLuaUtils.dpcall( 'applyDevelopChanges', function()
		
		if changes then
		
			-- SPAM [ashj 2020-08-25]: DevelopSession instance will be closed
			-- along with the model. So copy the session object to a temp variable
			-- so that session will be alive till the changes are saved.
			 
			local developSessionForSaving = self.developSession
		
			LrmDevelopModel.createBootVersionIfNeeded( self.catalog, self.assetId )
				:tapOnCompleted( function()
						
					logger:debug( "applyDevelopChanges: about to save. Session= %s", developSessionForSaving )
					
					developSessionForSaving:saveAndCloseP( changes )
						:whenResolvedDo( function( status, message )
						
							logger:debugf( "developSession:saveAndCloseP resolved with status %s  - message %s",
											status, message )
							
							if status == 'data' then
							
								local newRevId = message
							
								-- This is probably the most appropriate point to tear down
								-- the develop UI. At this point, the local Oz database has
								-- confirmed that it has stored the new develop settings.
								
								if maybeVersionName then
								
									self.catalog:getWfCatalog()
										:take( 1 )
										:flatMap( function( wfCatalog )
											return WFCatVersions.createVersionForAsset( wfCatalog, self.assetId, versionCreatedManually, maybeVersionName, maybeLinkedLocation )
										end )
										:map( function()
											resultB:set( newRevId )
										end )
										:call( WFRxRunUntilDone )

								else
								
									resultB:set( newRevId )
									
								end
								
							else
							
								resultB:set( false )
							
								-- FIXME [aksherry < scouten 2011-07-23]: We failed to record
								-- develop settings (unlikely, but we should cover the case).
								-- We should at least warn user. Not sure if there's a viable
								-- recovery path sat this point.
							
							end
							
							if not WFCloseSupport.hasBeenOrWillBeClosed( self.resetB ) then
								self.resetB:set( true )
							end
							
						end ):runUntilDone()
					
				end )
				:call( WFRxRunUntilDone )
			

		else
		
			logger:debug( "WARNING: Got called with settings = nil. Treating this as a cancel." )
			
			resultB:set( false )
			cancelSession( self )
		
		end
	
	end )
	
end

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

function Model:applyChanges( dict ) --, thumbnail, preview )
	
	local resultB = KSReactive.variableB( nil )
	
	local changes = createChangesStructure( dict )
	
	applyDevelopChanges( self, resultB, changes, false )
	
	return {
		valueB = resultB,
	}

	
end

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

function Model:applyChangesAndCreateVersion( dict, versionCreatedManually, versionName, maybeLinkedLocation )
			
	local resultB = KSReactive.variableB( nil )
	
	if not versionName then
	
		logger:warnf( "versionName was not supplied. Defaulting to 'Unknown'" )
		versionName = 'Unknown'
		
	end

	local changes = createChangesStructure( dict )
	
	applyDevelopChanges( self, resultB, changes, versionCreatedManually, versionName, maybeLinkedLocation )

	return {
		valueB = resultB,
	}

end

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

function LrmDevelopModel.requestBinaryP( catalog, photoId, binaryStrategy, developInfo, priority )

	WFArguments.check( 'OzCatalogService', 'string', 'binaryStrategy', 'table', '?number' )
	
	priority = priority or WFRxHttpClient.requestPriorityHigh

	if developInfo.useBinaryFromCameraRoll then
		return nil
	end
	
	if binaryStrategy == LrmConfig.StrategyProxyAndMaster then
		return KSPromises.resolvedPromiseP( 'error', "Wrong usage of requestBinaryP" )
	end
	
	if ( not developInfo.hasProxy and not developInfo.hasMaster ) then
		-- always a serious error
		return KSPromises.resolvedPromiseP( 'error', "No proxy and master available on Oz" )
	end

	if ( not developInfo.hasProxy and binaryStrategy == LrmConfig.StrategyProxy ) then
		-- might be an ignorable error - if both proxy and master got requested - or not
		return KSPromises.resolvedPromiseP( 'error', "No proxy available on Oz" )
	end

	if ( not developInfo.hasMaster and binaryStrategy == LrmConfig.StrategyMaster ) then
		-- might be an ignorable error - if both proxy and master got requested - or not
		return KSPromises.resolvedPromiseP( 'error', "No master available on Oz" )
	end

	if ( binaryStrategy == LrmConfig.StrategyPreferProxy and developInfo.hasProxy ) or
		( binaryStrategy == LrmConfig.StrategyPreferMaster and not developInfo.hasMaster ) or
		( binaryStrategy == LrmConfig.StrategyProxy ) then

        if not developInfo.proxyPath then
            return catalog:proxyDataPathP( photoId, false, 2560, priority )
					:mapP( function( status, data )
                            return status, data, false
                    end )
            else
                return KSPromises.resolvedPromiseP( 'data', developInfo.proxyPath, false )
            end

    else

        if not developInfo.originalPath then
            return catalog:originalDataPathP( photoId, false, priority )
                        :mapP( function( status, data )
                                return status, data, true
                        end )
        else
            return KSPromises.resolvedPromiseP( 'data', developInfo.originalPath, true )
        end

	end

end

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

function LrmDevelopModel.createBootVersionIfNeeded( catalog, photoId )
	return Observable.zip( catalog:getWfCatalog():take( 1 ), catalog:docStore():docValue( photoId ) )
		:flatMapLatest( function( wfCatalog, docValue )
		
			if not WFCatSharedWithYouUtils.isAssetOwner( docValue ) then
				-- Not the owner. Do not create bootversion.
				-- Oz will create boot version for any edits
				-- made by editors.
				return Observable.just( nil )
			end
			
			return WFCatVersions.getAutoVersionCountForAsset( wfCatalog, photoId )
				
				:flatMapLatest( function( numOfAutoVersions )
			
					if numOfAutoVersions > 0 then
						-- Already has autoversions. 
						-- Do not create bootversion.
						return Observable.just( nil )
					end
					
					logger:debug( "applyDevelopChanges: Creating boot version." )
					
					return WFCatVersions.createVersionForAsset( wfCatalog, photoId, false )
				end )
				
		end )
end

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

function LrmDevelopModel.makeModel( catalog, photoId, binaryStrategy, priority, byPassXmpError, optImageType )

	assert( catalog )
	assert( photoId )
	
	binaryStrategy = binaryStrategy or LrmConfig.StrategyPreferProxy
	priority = priority or WFRxHttpClient.requestPriorityHigh
	
	local developSession = LrmOzDevelopSession( catalog, photoId )
	local developInfoB = KSReactive.variableB( nil )
	local masterB = KSReactive.variableB( nil )
	local resetB = KSReactive.variableB( nil )

	local developInfo = {
		proxyPath = nil,
		originalPath = nil,
		settings = nil,
		hasProxy = false,
		hasMaster = false,
		hasOriginalInCameraRoll = false
	}
	
	local updateAcrProfileCache = false
	local selectedDigests = nil
	
	local developInfoP = developSession
	
		:docValueP()
		
		:mapP( function( status, docValue )
		
			local settings = docValue.develop or {}
			
			if status ~= 'data' or not settings then
				-- FIXME [aksherry < scouten 2011-07-23]: We failed to get develop settings
				-- or to acquire the lock on this photo. Signal an error to user and bounce
				-- out of develop.
				return KSPromises.resolvedPromiseP( 'error', 'No develop settings locally available' )
			end
			
			developInfo.settings = settings
			
			developInfo.hasProxy = LrmAssetUtils.hasProxy( docValue )
			developInfo.hasMaster = LrmAssetUtils.hasMaster( docValue )
			developInfo.docValue = docValue
			developInfo.width = derefOpt( docValue, 'importSource', 'originalWidth' )
			developInfo.height = derefOpt( docValue, 'importSource', 'originalHeight' )

			if type( settings.xmpCameraRaw )  == 'string' then

				-- xmpCameraRaw is inline
				return KSPromises.resolvedPromiseP( 'data', settings.xmpCameraRaw )

			end

			local sha256 = derefOpt( docValue, 'develop', 'xmpCameraRaw', 'sha256' )
			
			-- assets imported from device will not have xmpCameraRaw attached
			if not sha256 then
				return KSPromises.resolvedPromiseP( 'data', "" )
			end

			local getExternalXmpDevelopP = LrmCatXmpDevelop.getExternalXmpDevelopAndAuxP( catalog, docValue, sha256, optImageType )

			if byPassXmpError then
				return getExternalXmpDevelopP
				:mapP(function (status, xmpCameraRawOrErrorMsg, receivedSelectedDigests)
					
					selectedDigests = receivedSelectedDigests
					return 'data', xmpCameraRawOrErrorMsg

				end)
			end

			return getExternalXmpDevelopP
				:mapDataP_new( function( xmpCameraRaw, xmpRelPath, receivedSelectedDigests )

					-- store the relative filename in our in-memory docValue for later
					-- in the reactive chain

					if not docValue._localOnly then
						docValue._localOnly = {}
					end

					if not docValue._localOnly.files then
						docValue._localOnly.files = {}
					end

					docValue._localOnly.files.xmp_develop = {
						path =  xmpRelPath,
						sha256 = sha256
					}
					
					selectedDigests = receivedSelectedDigests

					return xmpCameraRaw

				end )

		end )
		
		:mapDataP_new( function( xmpCameraRaw )

			return LrmWFOzProfileUtils.ensureProfilesExistLocallyP( catalog, photoId )
		
		end)
		
		:mapDataP_new( function( info )

			if type( info ) == 'table' then
				
				if info.updateAcrProfileCache then
					updateAcrProfileCache = true
				end
				
			end

			return catalog:proxyDataPathP( photoId, true, 2560, priority )
			
		end )
		
		:mapDataP_new( function( localProxyDataPath )
		
			if localProxyDataPath then
				developInfo.proxyPath = localProxyDataPath
			end
			
			return catalog:originalDataPathP( photoId, true, priority )
			
		end )
		
		:mapDataP_new( function( localOriginalDataPath )
		
			if localOriginalDataPath then
				developInfo.originalPath = localOriginalDataPath
			end
			
			return developInfo
			
		end )
		
	developInfoP
		
		:mapDataP_new( function( developInfo )
			 
			local strategy = binaryStrategy
			if binaryStrategy == LrmConfig.StrategyProxyAndMaster then
				strategy = LrmConfig.StrategyProxy
			end

			return LrmDevelopModel.requestBinaryP( catalog, photoId, strategy, developInfo, priority)
			
		end )
		
		:whenResolvedDo( function( status, binaryPath, isOriginal )

			if status == 'data' then
			
				local localOnly = derefOpt( developInfo, 'docValue', '_localOnly' )
				
				local localProxyPath = OzCatalogUploadStatus.checkAndCorrectLocalPath( derefOpt( localOnly, 'proxy', 'filePath' ) )
				local localOriginalPath = OzCatalogUploadStatus.checkAndCorrectLocalPath( derefOpt( localOnly, 'originalFilePath' ) )
				
				if isOriginal then
					localOriginalPath = binaryPath
				else
					localProxyPath = binaryPath
				end
				
				-- this path is relative, so we don't need to correct it
				local localXmpDevelopPath = derefOpt( localOnly, 'files', 'xmp_develop', 'path' )

				if WFPlatformInfo.isAndroid then
					localProxyPath = binaryPath
				end
				developInfoB:set( {
					state = true,
					settings = developInfo.settings,
					selectedDigests = selectedDigests,
					proxyPath = localProxyPath,
					updateAcrProfileCache = updateAcrProfileCache,
					localUrl = derefOpt( developInfo, 'docValue', '_localOnly', 'localUrl' ),
					localProxyPath = localProxyPath,
					localOriginalPath = localOriginalPath,
					localXmpDevelopPath = localXmpDevelopPath
				} )
				
			else
			
				developInfoB:set( {
					state = false,
					error = binaryPath
				} )
				
			end
			
		end )
		
		:attachTo( developInfoB )

	if binaryStrategy == LrmConfig.StrategyProxyAndMaster then
		-- request a second binary, this will always be the master. Fire subModel 'master' when ready

		developInfoP

			:mapDataP_new( function( developInfo )

				return LrmDevelopModel.requestBinaryP( catalog, photoId, LrmConfig.StrategyMaster, developInfo, priority)

			end )

			:whenResolvedDo( function( status, binaryPath, isOriginal )

				if status == 'data' then

					local localOnly = derefOpt( developInfo, 'docValue', '_localOnly' )

					local localProxyPath = OzCatalogUploadStatus.checkAndCorrectLocalPath( derefOpt( localOnly, 'proxy', 'filePath' ) )
					local localOriginalPath = OzCatalogUploadStatus.checkAndCorrectLocalPath( derefOpt( localOnly, 'originalFilePath' ) )

					if isOriginal then
						localOriginalPath = binaryPath
					else
						localProxyPath = binaryPath
					end

					-- this path is relative, so we don't need to correct it
					local localXmpDevelopPath = derefOpt( localOnly, 'files', 'xmp_develop', 'path' )

					masterB:set( {
						state = true,
						settings = developInfo.settings,
						selectedDigests = selectedDigests,
						proxyPath = localProxyPath,
						updateAcrProfileCache = updateAcrProfileCache,
						useBinaryFromCameraRoll = developInfo.useBinaryFromCameraRoll,
						localUrl = derefOpt( developInfo, 'docValue', '_localOnly', 'localUrl' ),
						localProxyPath = localProxyPath,
						localOriginalPath = localOriginalPath,
						localXmpDevelopPath = localXmpDevelopPath
					} )

				else

					masterB:set( {
						state = false,
						error = binaryPath
					} )

				end

			end )

			:attachTo( masterB )

		end
		
	catalog:getSyncClient():lockAssets( { photoId } ):call( runUntilDone )

	return {
		assetId = photoId,
		catalog = catalog,
		resetB = resetB,
		masterB = masterB,
		developSession = developSession,
		
		valueB = developInfoB,
		models = Model,
		
		events = {
		
			cancel = cancelSession,
			
			updateRenditionsForDevSettings = function( self, dict )
				
				self.developSession:updateRenditionsForDevSettings( dict )
					
					:call( runUntilDone )
				
			end,
			
			lockSettings = function( self )
				developSession:loadSettings()
			end,
			
			resetSession = function( self )
				self.resetB:set( false )
			end,
		},
		
		close = function( self )
			catalog:getSyncClient():unlockAssets( { photoId } ):call( runUntilDone )
			cancelSession( self )
		end,
	}

end

return LrmDevelopModel
