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

WFCatImportManifestFacesProcessor.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2017 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remains 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 are protected by all applicable
intellectual property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

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

local WFConfiguration = require 'WFConfiguration'

	-- WF.oz_client.managed_catalog
local WFCatFaces = require 'WFCatFaces'

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local KSIdGenerator = require 'KSIdGenerator'

	-- WF.oz_client.managed_catalog.import
local WFCatImportManifestConstants = require 'WFCatImportManifestConstants'
local WFCatImportManifestProcessorBase = require 'WFCatImportManifestProcessorBase'

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

local derefOpt = assert( KSTableUtils.derefOpt )

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

Stategy for handling pending entries
	Since the docstore update and status table update 
	are not atomic, there are chances where app was
	closed/killed after a face is added to  the
	docstore, but before the status is recorded.
	
	Below is the strategy followed to avoid duplicates:
	
	1. We update the status table for the faces
		with pending status before each docstore update.
		We will be using a generated id here, which will
		be passed to the docstore update API to create
		the face overlay.
	2. After docstore update, we update the same entry
		with the actual status.
	3. On resume, if we have records with pending
		status, then we reuse the id.
	4. Since we are reusing the id, and since face doc
		is just an asset overlay, the update wont
		result in any duplicate entry.
		
	-- TODO [ashj 2017-05-30]: 
		Currently, the second overlay will differ
		from the first wrt to the Timestamp field
		and a new rev will be created. This can be
		prevented if we can read the associated
		asset doc and prevent the update if it has
		the face already.

]]
local function addStatus( manifestStatusDatabase, faces, status )
	
	local statusData = {}
	
	for _, face in ipairs( faces ) do
		table.insert( statusData, {
			local_id = face.local_id,
			oz_id = face.faceInfo and face.faceInfo.uuid,
				-- face.faceInfo will not be present for skipped faces.
			status = status,
		} )
	end
	
	return manifestStatusDatabase:writeData( 'addOrReplaceFacesStatus', statusData )
		:ignoreElements()
	
end

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

local function updateStatus( manifestStatusDatabase, faces, status )
	
	local statusData = {}
	
	for _, face in ipairs( faces ) do
		table.insert( statusData, {
			local_id = face.local_id,
			status = status,
		} )
	end
	
	return manifestStatusDatabase:writeData( 'updateFacesStatus', statusData )
		:ignoreElements()
	
end

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

local WFCatImportManifestFacesProcessor = {
	WFCatImportManifestProcessorBase,
	
}

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

function WFCatImportManifestFacesProcessor:__init()
	
	self._stageName = WFCatImportManifestConstants.stageNames.faces
	
	self._messageCodeCompleted = WFCatImportManifestConstants.messages.facesProcessingComplete.code
	self._messageCodeErrorCount = WFCatImportManifestConstants.messages.facesProcessingErrorCount.code
	self._messageCodeGenericError = WFCatImportManifestConstants.messages.facesProcessingError.code
end

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

function WFCatImportManifestFacesProcessor:setup( catalog, manifestDatabase, manifestStatusDatabase, idCache, messageHandler )
	
	return self:WFCatImportManifestProcessorBase_setup( catalog, 
		manifestStatusDatabase, idCache, messageHandler, 'selectFacesStatus' )

end

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

function WFCatImportManifestFacesProcessor:process( ... )

	if WFConfiguration.Manifest.disableFaces then
		local retSubject = ReplaySubject( 1 )
		retSubject:onNext( true )
		retSubject:onCompleted()
		return retSubject
	end
	
	return self:WFCatImportManifestProcessorBase_process( ... )
end

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

function WFCatImportManifestFacesProcessor:processImpObservable( catalog, profileCatalog, wfCatSynchronizer, 
		manifestDatabase, manifestStatusDatabase, idCache, messageHandler )

	return self:_maybeSetCount( manifestDatabase, 'countFaces' )
	
		:flatMap( function()
		
			return manifestDatabase:readData( 'selectFaces' )
			
		end )
		
		:flatMap( function( rows )
			
			-- This will get executed once per read batch
			local assetFaces = {}
			local skippedFaces = {}
			
			for i, row in ipairs( rows ) do
			
				-- Check if the faces has been processed before
				if self:_isAlreadyProcessed( row.local_id ) then
					
					self:_updateAlreadyProcessed( messageHandler, 1 )
				
				else
			
					local assetOzId = idCache:getOzIdForLocalId( WFCatImportManifestConstants.idCacheSets.assets, row.asset )
					if not assetOzId then
					
						table.insert( skippedFaces, rows[ i ] )
						self:_updateError( messageHandler, 1 )
						self:_sendMessage( 
							messageHandler,
							WFCatImportManifestConstants.messages.facesProcessingMissingAssetError.code,
							{ { row.asset } }
						)
					else

						local faceArea = {
							x = row.x,
							y = row.y,
							w = row.w,
							h = row.h,
							d = row.d,
						}
						
						-- Use the pending id, if it exists already.
						local pendingUUID = self._processedRows[ row.local_id ] and self._processedRows[ row.local_id ].oz_id
						local faceInfo = {
							uuid = pendingUUID or KSIdGenerator.uuidInLowercaseHex( )
						}
						
						local people
						if row.personId then
							local personUUID = idCache:getOzIdForLocalId( 
												WFCatImportManifestConstants.idCacheSets.persons, row.personId )
							if personUUID then
								people = {
									{
										id = personUUID,
										confirmed = true,
									}
								}
							else
								-- Send an error, but continue wth face addition.
								self:_sendMessage( 
									messageHandler,
									WFCatImportManifestConstants.messages.facesProcessingMissingPersonWarning.code,
									{ { row.personId, row.asset } }
								)
							end
						end
						
						local faceDetails = {
							local_id = rows[ i ].local_id,
							faceInfo = faceInfo,
							faceArea = faceArea,
							people = people,
						}
						
						local facesForAsset = assetFaces[ assetOzId ] or {}
						facesForAsset[ #facesForAsset + 1 ] = faceDetails
						
						
						assetFaces[ assetOzId ] = facesForAsset
					end
				end
			end
			
			local updateStatusObservable
			if #skippedFaces ~= 0 then
				updateStatusObservable = addStatus( manifestStatusDatabase, skippedFaces, WFCatImportManifestConstants.status.failure )
			else
				updateStatusObservable = Observable.empty()
			end
			
			return updateStatusObservable
				:defaultIfEmpty( nil )
				:map( function()
					
					return assetFaces
					
				end )
			

		end )
		
		:flatMap( function( assetFaces )
		
			return Observable.create( function( observer )
				for assetId, faces in pairs( assetFaces ) do
					
					observer:onNext( assetId, faces )
				end
				
				observer:onCompleted()
			end )
		end )
		
		:flatMap( function( assetId, faces )
		
			return catalog:docStore():docValue( assetId )
				:map( function( docValue )
					local w = derefOpt( docValue, 'importSource', 'originalWidth' )
					local h = derefOpt( docValue, 'importSource', 'originalHeight' )
					local appliedToDimensions = {
						w = w,
						h = h,
					}
					return assetId, appliedToDimensions, faces
				end )
		end)
		
		:flatMap( function( assetId, appliedToDimensions, faces )

			return addStatus( manifestStatusDatabase, faces, WFCatImportManifestConstants.status.pending )
				:defaultIfEmpty()
				:flatMap( function()
					
					return WFCatFaces.addFacesToAsset( catalog, assetId, appliedToDimensions, faces )
				end )
				:flatMap( function()
				
					self:_updateSuccess( messageHandler, #faces )
					return updateStatus( manifestStatusDatabase, faces, WFCatImportManifestConstants.status.success )
					
				end )
				:catch( function( err )
				
					self:_updateError( messageHandler, #faces )
					self:_sendMessage(
						messageHandler,
						WFCatImportManifestConstants.messages.facesProcessingUpdateError.code,
						{ { assetId, #faces } }
					)
					return updateStatus( manifestStatusDatabase, faces, WFCatImportManifestConstants.status.failure )
					
				end )
				
		end )
		:ignoreElements()
		:defaultIfEmpty( nil )
		:flatMap( function()
		
			local queryId = "countFacesStatus"
			local dataType = WFCatImportManifestConstants.dataTypes.faces
			return self:_sendSummary( messageHandler, manifestStatusDatabase, queryId, dataType )
			
		end )
		:ignoreElements()
			-- Adding summary step to the chain and then explicitely
			-- ignoring elements to denote that we are not interested
			-- in the return values.

end

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

return Rx.Class 'WFCatImportManifestFacesProcessor'( WFCatImportManifestFacesProcessor )
