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

NmbStdProfileInfoCache.lua

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

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

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

local Rx = require 'Rx'
local ReplaySubject = Rx.ReplaySubject
local NmbAcr = require 'NmbAcr'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFLogger = require 'WFLogger'
local WFCloseSupport = require 'WFCloseSupport'
local WFFunctionUtils = require 'WFFunctionUtils'
local KSTableUtils = require 'KSTableUtils'

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

local logger = WFLogger( "NmbStdProfileInfoCache" )
local WFFunctionUtils_reduce = WFFunctionUtils.reduce
local derefOpt = KSTableUtils.derefOpt
local _cameraModelProfilesMapStream
local WFAcrOperations = assert( NmbAcr.WFAcrOperations )

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

local function addFilenameInfoToMap( filenameInfoMap, info )

	filenameInfoMap[ info.fileName ] = info

	return filenameInfoMap

end

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

local function getModelProfileInfoStream()

	-- SPAM [ashj 2018-07-20]: The model name that we have
	-- is in upper case. But the one which is with ACR is
	-- not. Hence we are caching the map here with the key
	-- in upper case.
	
	if not _cameraModelProfilesMapStream then
		
		_cameraModelProfilesMapStream = ReplaySubject( 1 )

		WFAcrOperations.getCoreCameraProfilesMap()
			:map( function( modelToProfileMap )
				local infoMap = {}
				local filenameInfoMap = {}
				
				for modelName, infos in pairs( modelToProfileMap ) do
					infoMap[ string.upper( modelName ) ] = infos
					WFFunctionUtils_reduce( filenameInfoMap, 
						addFilenameInfoToMap, infos )
				end
				
				return infoMap, filenameInfoMap
			end )
		:tap( function( infoMap, filenameInfoMap )
			_cameraModelProfilesMapStream:onNext( infoMap, filenameInfoMap )
		end )
		:call( WFRxRunUntilDone )
		
	end
	
	return _cameraModelProfilesMapStream
end

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

local function getProfilesForModel( cameraModel )

	return getModelProfileInfoStream()
		:take( 1 )
		:map( function( cameraModelProfilesMap )
			return cameraModelProfilesMap[ string.upper( cameraModel ) ]
		end )
end

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

local function standardCameraProfileStream( self )
	
	-- TOFIX [ashj 2018-07-26]: Oz global catalog contains
	-- multiple entries for the same profile. Needs to
	-- handle that once we get clarification on why that
	-- is so.
	
	local sql = [[ SELECT name, filename, digest as resolvedDigest, href,
			model, cameraProfileDigest
		FROM 
			profiles
		WHERE 
			profile_type == 'camera_profile' ]]

	return self._profileCatalog:indexStore():query {
			sql = sql,
			quiesceUntilUpToDate = true,
		}
		:combineLatest( getModelProfileInfoStream() )
		:tap( function( results, infoMap, filenameInfoMap )
			
			-- SPAM [ashj 2018-05-04]: Global catalog
			-- profiles doesn't contain the model and
			-- digest info. 
			local nameInfoMap = {}
			local digestInfoMap = {}
			local hrefInfoMap = {}
			local modelDigestInfoMap = {}
			
			for i, row in ipairs( results ) do
				local info = {
					name = row[ 1 ],
					fileName = row[ 2 ],
					resolvedDigest = row[ 3 ],
					href = row[ 4 ],
					model = row[ 5 ],
					cameraProfileDigest = row[ 6 ],
				}
				nameInfoMap[ string.lower( info.name ) ] = info
				digestInfoMap[ info.resolvedDigest ] = info
				hrefInfoMap[ info.href ] = info
				
				if info.model and info.cameraProfileDigest then
					modelDigestInfoMap[ string.format( "%s_%s", 
						string.lower( info.model ), string.lower( info.cameraProfileDigest )) ] = info
				end
				
				-- Add relative path information. Only the
				-- ACR provided map has this information
				local relativePath = derefOpt( filenameInfoMap, info.fileName, "relativePath" )
				info.relativePath = relativePath
				
			end
			
			self._standardCameraProfileSubject:onNext( nameInfoMap, digestInfoMap, hrefInfoMap, modelDigestInfoMap )
		end )
	
end

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

local function getGenericLogFunctionsForSubscribe( streamName )

	return {
		function()
			-- noop onNext fn
		end,
		
		function( err )
			logger:errorf( "Stream: %s ended with error: %s",
									streamName, err )
		end
	}
	
end

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

local NmbStdProfileInfoCache = {

	_profileCatalog = false,
	
	_standardCameraProfileSubject = false,
	
	_standardCameraProfileSubscription = false,
	
	class = {},
	
	WFCloseSupport.addCloseSupportMethods,
}

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

function NmbStdProfileInfoCache:__init( profileCatalog )

	self._profileCatalog = profileCatalog
	
	self._standardCameraProfileSubject = Rx.ReplaySubject( 1 )
	self._standardCameraProfileSubscription = 
		standardCameraProfileStream( self )
			:subscribe( unpack( getGenericLogFunctionsForSubscribe( "standardCameraProfileStream" ) ) )
	
end

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

function NmbStdProfileInfoCache:close()

	if self._standardCameraProfileSubject then
		self._standardCameraProfileSubject:onCompleted()
		self._standardCameraProfileSubject = nil
	end
	
	if self._standardCameraProfileSubscription then
		self._standardCameraProfileSubscription:dispose()
		self._standardCameraProfileSubscription = nil
	end
	
end

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

function NmbStdProfileInfoCache:getProfilesForModel( cameraModel )

	return getProfilesForModel( cameraModel )
end

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

function NmbStdProfileInfoCache:getStandardProfileInfo( cameraProfileName, cameraModel )

	-- Currently, the global profile catalog has info
	-- about the name. It doesnt have the digest and
	-- the camera model info. Till we figure out how
	-- to find standard vs custom profile, we will have
	-- to stick with the below hack.
	
	-- TODO [ashj 2018-05-04]: Using only the name to
	-- compare. if a custom profile is having the same
	-- name as a standard profile, then assets referring
	-- to the profile will not get re-rendered through
	-- this mechanism.
	
	return self._standardCameraProfileSubject
		:take( 1 )
		:map( function( nameInfoMap )
		
			local fullName = string.format( "%s %s", cameraModel, cameraProfileName )
			return nameInfoMap[ string.lower( fullName ) ]
		end )
	
end

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

function NmbStdProfileInfoCache:getStandardProfileInfoForHref( href )

	return self._standardCameraProfileSubject
		:take( 1 )
		:map( function( _, _, hrefInfoMap )
		
			return hrefInfoMap[ href ]
		end )
	
end
--------------------------------------------------------------------------------

function NmbStdProfileInfoCache:isStandardProfile( cameraProfileName, cameraModel )

	return self:getStandardProfileInfo( cameraProfileName, cameraModel )
		:map( function( standardProfileInfo )
			return not not standardProfileInfo
		end )
	
end

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

function NmbStdProfileInfoCache:getFilenameAndHrefForResolvedDigest( resolvedDigest )

	return self._standardCameraProfileSubject
		:take( 1 )
		:map( function( _, digestInfoMap )
			local info = digestInfoMap[ string.lower( resolvedDigest ) ]
			if info then
				return info.fileName, info.href
			end
		end )
end

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

function NmbStdProfileInfoCache:getFilenameAndHrefForModelAndDigest( cameraModel, cameraProfileDigest )

	return self._standardCameraProfileSubject
		:take( 1 )
		:map( function( _, _, _, modelDigestInfoMap )
			local info = modelDigestInfoMap[ string.format( "%s_%s", 
					string.lower( cameraModel ), string.lower( cameraProfileDigest )) ]
			if info then
				return info.fileName, info.href
			end
		end )
end
--------------------------------------------------------------------------------

return Rx.Class( 'NmbStdProfileInfoCache', NmbStdProfileInfoCache )