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

WFCatPerson.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

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local WFArguments = require 'WFArguments'
local KSTableUtils = require 'KSTableUtils'
local WFRxBatchedBulkApplyOverlay = require "WFRxBatchedBulkApplyOverlay"

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

local check = WFArguments.check
local derefOpt = KSTableUtils.derefOpt

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

local WFCatPerson = {}

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

local function createPersonDocument( person )
	
	check( 'table' )
	
	local docId = person.uuid or KSIdGenerator.uuidInLowercaseHex( )
	
	if person.cover then
		return {
			_id = docId,
			type = 'asset',
			subtype = 'person',
			name = person.name,
			hidden = person.hidden,
			primary = person.primary,
			cover = {
				id = person.cover
			}
		}
	else
		return {
			_id = docId,
			type = 'asset',
			subtype = 'person',
			name = person.name,
			hidden = person.hidden,
			primary = person.primary,
		}
	end
end

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

--[[!
	\brief 

	\param 
	\param 

	\return 
--]]

function WFCatPerson.addPersons( catalog, persons )
	
	check( 'WFCatalog', 'table' )
	
	local docs = {}
	for i, person in ipairs( persons ) do
		
		docs[ #docs + 1 ] = createPersonDocument( person )
		
	end
	
	if #docs > 0 then
		return WFRxBatchedBulkApplyOverlay( "add people", catalog:docStore(), docs )
	else
		return Observable.just( {} )
	end
end

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

--[[!
	\brief 

	\param 
	\param 

	\return 
--]]

function WFCatPerson.addPerson( catalog, person )
	
	check( 'WFCatalog', 'table' )
	
	local overlay = createPersonDocument( person )

	return catalog:docStore():applyOverlay( overlay )
		:take( 1 )
		:map( function ()
			return overlay._id
		end )
end

--------------------------------------------------------------------------------
--[[!
	\brief Updates Person Name or delete if name is empty.

	\param wfCatalog (WFCatalog) catalog
	\param ozPersonId (number) oz-id of a person.
	\param personName (string) person name.

	\return Observable which fires only once.
--]]

function WFCatPerson.updatePersonName( wfCatalog, personId, personName )
	
	check( 'WFCatalog', 'uuid', '?string' )
	
	return wfCatalog:indexStore():queryOnce {
		sql = [[ SELECT person.docId 
				 FROM person 
				 WHERE parentId == ? ]],
		quiesceUntilUpToDate = true,
		personId
	}
	:map( function( rows )
		return rows
	end )
	:flatMap( function( rows )
		local overlays = {}
		
		if personName then
			overlays[ #overlays + 1 ] = {
				_id = personId,
				name = personName,
			}
		else
			overlays[ #overlays + 1 ] = {
				_id = personId,
				name = { _delete = true },
			}
		end
		
		for _, row in ipairs( rows ) do
			local overlay
			local id = row[1]
			
			if personName then
				overlay = {
					_id = id,
					name = personName,
				}
			else
				overlay = {
					_id = id,
					name = { _delete = true },
				}
			end

			overlays[ #overlays + 1 ] = overlay
		end
		
		if #overlays > 0 then
			return WFRxBatchedBulkApplyOverlay( "update person name", wfCatalog:docStore(), overlays )
		else
			return Observable.just( {} )
		end
	end )
	:take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Updates People Name or delete if name is empty.

	\param wfCatalog (WFCatalog) catalog
	\param localPeopleIdNameList (table) local-ids and Name of a people.

	\return Observable which fires only once.
--]]

function WFCatPerson.updatePeopleName( wfCatalog, localPeopleIdNameList )
	
	check( 'WFCatalog', 'table' )
	
	local overlays = {}
	for i, v in ipairs( localPeopleIdNameList ) do
		local overlay = {
			_id = v.id,
		}
		
		if v.name then
			overlay["name"] = v.name
		else
			overlay["name"] = { _delete = true }
		end
		
		overlays[ #overlays + 1 ] = overlay
	end
	
	if #overlays > 0 then
		return WFRxBatchedBulkApplyOverlay( "update people name", wfCatalog:docStore(), overlays ):take( 1 )
	else
		return Observable.just( {} ):take( 1 )
	end
end

--------------------------------------------------------------------------------
--[[!
	\brief Delete Person.

	\param wfCatalog (WFCatalog) catalog
	\param personIds (table) local-id of a person.

	\return Observable which fires only once.
--]]
function WFCatPerson.deletePerson( wfCatalog, personIds )
	
	check( 'WFCatalog', 'table' )
	
	local overlays = {}
	for _, v in ipairs( personIds ) do
		local overlay = {
			_id = v,
			_deleted = true,
		}
		
		overlays[ #overlays + 1 ] = overlay
	end
	
	if #overlays > 0 then
		return WFRxBatchedBulkApplyOverlay( "delete person", wfCatalog:docStore(), overlays ):take( 1 )
	else
		return Observable.just( {} ):take( 1 )
	end
end

--------------------------------------------------------------------------------
--[[!
	\brief Hide/Show Person.

	\param wfCatalog (WFCatalog) catalog
	\param personId (uuid) id of a person.
	\param hide (boolean) hide or show.

	\return Observable which fires only once.
--]]
function WFCatPerson.hidePerson( wfCatalog, personId, hide )
	
	check( 'WFCatalog', 'uuid', 'boolean' )
	
	return wfCatalog:indexStore():queryOnce {
		sql = [[ SELECT person.docId 
				 FROM person 
				 WHERE parentId == ? ]],
		quiesceUntilUpToDate = true,
		personId
	}
	:map( function( rows )
		return rows
	end )
	:flatMap( function( rows )
		local overlays = {}
		
		overlays[ #overlays + 1 ] = {
			_id = personId,
			hidden = hide,
		}
		
		for _, row in ipairs( rows ) do
			local overlay
			local id = row[1]
			
			overlay = {
				_id = id,
				hidden = hide,
			}
			
			overlays[ #overlays + 1 ] = overlay
		end
		
		if #overlays > 0 then
			return WFRxBatchedBulkApplyOverlay( "hide person", wfCatalog:docStore(), overlays )
		else
			return Observable.just( {} )
		end
	end )
	:take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Hide/Show People.

	\param wfCatalog (WFCatalog) catalog
	\param peopleHideMap (table) map of people and their hide/show value.

	\return Observable which fires only once.
--]]
function WFCatPerson.hidePeople( wfCatalog, peopleHideSet )
	
	check( 'WFCatalog', 'table' )
	
	local overlays = {}
	for key,value in pairs( peopleHideSet ) do
		
		local overlay = {
			_id = key,
			hidden = value,
		}

		overlays[ #overlays + 1 ] = overlay
	end
	
	local returnObservable
	
	if #overlays > 0 then
		returnObservable = WFRxBatchedBulkApplyOverlay( "hide people", wfCatalog:docStore(), overlays )
	else
		returnObservable = Observable.just( {} )
	end
	
	return returnObservable:take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Set Cover.

	\param wfCatalog (WFCatalog) catalog
	\param personIds (uuid) oz-id of people.
	\param assetId (number) local asset id.

	\return Observable which fires only once.
--]]
function WFCatPerson.setPersonCover( wfCatalog, personId, assetId )
	
	check( 'WFCatalog', 'uuid', 'number' )
	
	return wfCatalog:indexStore():queryOnce {
		sql = [[ SELECT assetId FROM faces where docId = ? LIMIT 1]],
		singleColumnQuery = true,
		assetId
	}
	:map( function( assetIds )
		return derefOpt( assetIds, 1 )
	end )
	:flatMap( function( ozAssetId )

		local overlay = {
			_id = personId,
			cover = {
				id = ozAssetId
			}
		}

		return wfCatalog:docStore():applyOverlay( overlay )
	end )
	:take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Set Cover.

	\param wfCatalog (WFCatalog) catalog
	\param personIds (table) local-id of people.
	\param assetId (number) local asset id.

	\return Observable which fires only once.
--]]
function WFCatPerson.setPersonCoverReturnFaceInfo( wfCatalog, personIds, assetId )
	
	check( 'WFCatalog', 'table', 'number' )

	return wfCatalog:indexStore():queryOnce {
		sql = [[ SELECT faces.x_axis, faces.y_axis, faces.height, faces.width, faces.assetId
					FROM faces 
					INNER JOIN CARRAY( ?, ?, 'char*' ) as pi 
					ON pi.value == faces.personId
					WHERE faces.docId = ?
					LIMIT 1 ]],
		personIds,
		#personIds,
		assetId,
	}
	:flatMap( function( rows )
	
		for _,row in ipairs( rows ) do
			
			local faceInfo = {
				x_axis = row[1],
				y_axis = row[2],
				height = row[3],
				width = row[4],
			}
			
			local overlay = {
				_id = personIds[1],
				cover = {
					id = row[5]
				}
			}
			
			return wfCatalog:docStore():applyOverlay( overlay )
			
				:map( function()
				
					return faceInfo
				
				end )
				
				--just one is required
				
		end
		
		return Observable.just( false )
		
	end)
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Set Cover.

	\param wfCatalog (WFCatalog) catalog
	\param personIds (table) local-id of people.
	\param assetId (number) local asset id to exclude

	\return Observable which fires only once.
--]]
function WFCatPerson.setPersonCoverReturnFaceInfoExcept( wfCatalog, personIds, assetIds )
	
	check( 'WFCatalog', 'table', '?table' )

	return wfCatalog:indexStore():queryOnce {
		sql = [[ SELECT faces.x_axis, faces.y_axis, faces.height, faces.width, faces.docId, faces.assetId
					FROM faces 
					INNER JOIN CARRAY( ?, ?, 'char*' ) as pi 
					ON pi.value == faces.personId ]],
		personIds,
		#personIds,
	}
	:flatMap( function( rows )
		local faceInfo
		local cover
		for _,row in ipairs( rows ) do
			local okay = true
			for _,u in ipairs( assetIds or {} ) do
				if u == row[5] then
					okay = false
					break
				end
			end
			
			if okay then
				cover = row[6]
				faceInfo = {
					x_axis = row[1],
					y_axis = row[2],
					height = row[3],
					width = row[4],
				}
				break
			end
		end	
		
		local overlay
		
		if cover then
		
			overlay = {
				_id = personIds[1],
				cover = {
					id = cover
				}
			}
			
		end
		
		local t = {
			cover = cover,
			faceInfo = faceInfo
		}
		
		if overlay then
		
			return wfCatalog:docStore():applyOverlay( overlay )
			
				:map( function()
				
					return t
				
				end )
				
		end
		
		
		return Observable.just( t )
		
	end)
	
end

--------------------------------------------------------------------------------
--[[!
	\brief shallow merges people buckets. 
		   shallow merge means primary item in the person data model is set to 
		   oz-parentId 

	\param wfCatalog (WFCatalog) catalog
	\param parentId (uuid) id of a person to set to.
	\param personIds (table) list of people ids (uuid) to merge.
	\param personName (string) person name.

	\return Observable which fires only once.
--]]
function WFCatPerson.shallowMergePeople( wfCatalog, parentId, personIds, personName )

	check( 'WFCatalog', 'uuid', 'table', '?string' )

	local overlays = {}
	
	local overlay = {
		_id = parentId,
		primary = { _delete = true }
	}
	
	if personName then
		overlay["name"] = personName
	else
		overlay["name"] = { _delete = true }
	end
	
	overlays[ #overlays + 1 ] = overlay
	
	for _,id in ipairs( personIds ) do
		if id ~= parentId then
			overlay = {
				_id = id,
				primary = parentId,
			}
			
			if personName then
				overlay["name"] = personName
			else
				overlay["name"] = { _delete = true }
			end
			
			overlays[ #overlays + 1 ] = overlay
		end
	end
	
	if #overlays > 0 then
		return WFRxBatchedBulkApplyOverlay( "shallow merge people", wfCatalog:docStore(), overlays ):take( 1 )
	else
		return Observable.just( {} ):take( 1 )
	end
end

--------------------------------------------------------------------------------
--[[!
	\brief split people buckets. Also used during undo ( could have a parent ).

	\param wfCatalog (WFCatalog) catalog
	\param peopleIdNameList (table) list of people to split.

	\return Observable which fires only once.
--]]
function WFCatPerson.splitPeople( wfCatalog, peopleInfoList )
	
	check( 'WFCatalog', 'table' )
	
	local overlays = {}
	for i, v in ipairs( peopleInfoList ) do
		local overlay = {
			_id = v.id,
		}
		
		if v.parentId then
			overlay["primary"] = v.parentId
		else
			overlay["primary"] = { _delete = true }
		end
		
		if v.name then
			overlay["name"] = v.name
		else
			overlay["name"] = { _delete = true }
		end
		
		overlays[ #overlays + 1 ] = overlay
	end
	
	if #overlays > 0 then
		return WFRxBatchedBulkApplyOverlay( "split people", wfCatalog:docStore(), overlays ):take( 1 )
	else
		return Observable.just( {} ):take( 1 )
	end
end

--------------------------------------------------------------------------------
--[[!
	\brief reject Similar Person 

	\param wfCatalog (WFCatalog) catalog
	\param personId (uuid) id of a person.
	\param personId (uuid) id of the rejected person.
	\param undo (boolean)
	
	\return Observable which fires only once.
--]]
function WFCatPerson.rejectSimilarPerson( wfCatalog, personId, rejectedPersonId, undo )
	
	check( 'WFCatalog', 'uuid', 'uuid', 'boolean' )
	
	local overlay = {
		_id = personId,
	}
	
	if undo then
		overlay["rejected"] = {
				[rejectedPersonId] = false 
			}
	else
		overlay["rejected"] = {
				[rejectedPersonId] = true
			}
	end

	return wfCatalog:docStore():applyOverlay( overlay ):take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief reject Similar People 

	\param wfCatalog (WFCatalog) catalog
	\param personId (uuid) id of a person.
	\param personIds (table) ids of the rejected people.

	\return Observable which fires only once.
--]]
function WFCatPerson.rejectSimilarPeople( wfCatalog, personId, rejectedPersonIds, undo )
	
	check( 'WFCatalog', 'uuid', 'table', 'boolean' )
	
	local overlays = {}
	for _, uuid in ipairs( rejectedPersonIds ) do
		local overlay = {
			_id = personId,
		}
		
		if undo then
			overlay["rejected"] = {
					[uuid] = false 
				}
		else
			overlay["rejected"] = {
					[uuid] = true
				}
		end
		
		overlays[#overlays + 1] = overlay
	end
	
	if #overlays > 0 then
		return WFRxBatchedBulkApplyOverlay( "reject Similar People", wfCatalog:docStore(), overlays ):take( 1 )
	else
		return Observable.just( {} ):take( 1 )
	end	
end

--------------------------------------------------------------------------------
--[[!
	\brief get Similar People 

	\param wfCatalog (WFCatalog) catalog
	\param personId (uuid) oz-id of a person.

	\return Observable which fires only once.
--]]
function WFCatPerson.getSimilarPeopleForPersonId( wfCatalog, personId )
	
	check( 'WFCatalog', 'uuid' )
			
	return wfCatalog:docStore():docValue( personId )
		:take( 1 )
		:map( function( docValue )
			local result = {}
			
			local rejectedMap = derefOpt( docValue, 'rejected' ) or  {}
			local start = "/rels/people/similar"
			local links = derefOpt( docValue, '_serverProvidedMetadata', 'links' )	
			local len = string.len( start ) 
			
			for k,v in pairs( links or {} ) do
			
				if string.sub( k, 1, len ) == start  then
					
					local similarPersonId = v.id
					
					if not rejectedMap[similarPersonId] then
						
						local confidence = v.confidence
						
						if similarPersonId and confidence and personId ~= similarPersonId then
							result[similarPersonId] = confidence
						end
					end
				end
			end
			
			return result
		end )
end

--------------------------------------------------------------------------------
--[[!
	\brief Download person's thumbnail using binary downloader.
			if the href inside _localonly matches the docvalue then don't download
			and just return the path.

	\param wfCatalog (WFCatalog) catalog
	\param personId (uuid) id of a person.

	\return An Observable object completes when operation
		is completed; or resolves to an error if an error occurs.
--]]
function WFCatPerson.downloadPersonThumbnail( wfCatalog, wfCatSynchronizer, personId )
	
	check( 'WFCatalog', 'WFCatSynchronizer', 'uuid' )

	local downloader = wfCatSynchronizer:getPersonThumbnailDownloader()
	downloader:requestPersonBinary( personId )
	
	return Observable.just( {} ):take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Download person's thumbnail using binary downloader.
			if the href inside _localonly matches the docvalue then don't download
			and just return the path.

	\param wfCatalog (WFCatalog) catalog
	\param personIds (table) id of a person.

	\return An Observable object completes when operation
		is completed; or resolves to an error if an error occurs.
--]]
function WFCatPerson.downloadPeopleThumbnails( wfCatalog, wfCatSynchronizer, personIds )
	
	check( 'WFCatalog', 'WFCatSynchronizer', 'table' )
	
	local downloader = wfCatSynchronizer:getPersonThumbnailDownloader()
		
	for _, id in ipairs( personIds ) do
		downloader:requestPersonBinary( id )
	end
	
	return Observable.just( {} ):take( 1 )
end

--------------------------------------------------------------------------------
--[[!
	\brief Response stream used to receive the file path for display.

	\param wfCatSynchronizer (WFCatSynchronizer) catalog

	\return An Observable object whose value is a table that contains
			the filepath, success/failure, if the thumbnail was updated, 
			personId and ozpersonId.  
--]]
function WFCatPerson.downloadPersonThumbnailResponseStream( wfCatSynchronizer )
	
	check( 'WFCatSynchronizer' )
	
	local downloader = wfCatSynchronizer:getPersonThumbnailDownloader()
	local responseStream = downloader:getResponseStream()
	
	return responseStream
			:map( function( personId, okay, path, update )
					local result = {}
					result.personId = personId
					result.okay = okay
					result.path = path
					result.update = update
					return result
				end
			)
end

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

function WFCatPerson.generateIndexSchema( indexStoreSchema )

	check( 'WFRxIndexStoreSchema' )
	
	indexStoreSchema:addTable {

		sql = [[CREATE TABLE person( personId, name, hidden, parentId, cover, href )]],

		useLocalId = true,
		
		hasOneRowPerDocument = true,
		
		relevantTypeSubtypeTuples = {
			{ 'asset', 'person' },
		},
		
		map = function( docValue, emit )

			if docValue.type == 'asset' and docValue.subtype == 'person' and not docValue._deleted then
				
				local personId = derefOpt( docValue, '_id' )
				
				local name = derefOpt( docValue, 'name' )
				
				local hidden = not not derefOpt( docValue, 'hidden' )
				
				local parentId = derefOpt( docValue, 'primary' )
				
				local cover = derefOpt( docValue, 'cover', 'id' )
				
				-- this is used to check if the latest thumbnail is used on the UI side.. PeopleThumbnailUtility.lua
				-- also Oz sends cover update before href update.. so can't use cover. 
				-- the thumbnails is garunteed to be updated after href change.. not cover change -> on Oz that is..
				local href = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/thumbnail2x', 'href' )
				
				if personId then
					emit( personId, name, hidden, parentId, cover, href )
				end
			end

		end,

	}
	
	indexStoreSchema:addIndex [[CREATE INDEX person_parentId ON person( parentId )]]
	indexStoreSchema:addIndex [[CREATE INDEX person_personId_parentId_name_hidden ON person( personId, parentId, name, hidden )]]
end

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

return WFCatPerson
