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

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

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

local Rx = require 'Rx'
local Observable = Rx.Observable

local WFLogger = require 'WFLogger'
local KSTableUtils = require 'KSTableUtils'

local WFCatUndo = require 'WFCatUndo'
local WFRxBatchedBulkApplyOverlay = require 'WFRxBatchedBulkApplyOverlay'
local WFCatTask = require 'WFCatTask'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFFunctionUtils = require 'WFFunctionUtils'
local WFArguments = require 'WFArguments'
local WFCatUtils = require 'WFCatUtils'

local derefOpt = assert( KSTableUtils.derefOpt )
local logger = WFLogger( "WFCatKeywordTask" )

local WFFunctionUtils_reduce = assert( WFFunctionUtils.reduce,
			"WFFunctionUtils.reduce does not exist" )

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

local kDeleteToken = { _delete = true }

local kDefaultBatchSize = 25

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

local function addToBatch( batches, assetId, batchSize )
	local currBatchSize = #( batches[ #batches ] )

	if currBatchSize == batchSize then
		currBatchSize = 0
		batches[ #batches + 1 ] = {}
	end

	batches[ #batches ][ currBatchSize + 1 ] = assetId

	return batches
end

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

local function getCount( arrayOfArray )
    if not arrayOfArray or #arrayOfArray == 0 then
        return 0
    end

    local count = 0
    for _, array in ipairs( arrayOfArray ) do
        count = #array + count
    end

    return count
end

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

local function undoSetKeywords( docStore, optUndoStateToken, progressReporter, cancelObservable )

    local undoState = WFCatUndo.getUndoState( optUndoStateToken )

    assert ( undoState and type(undoState) == "table" )
    assert ( undoState.arrayOfOverlaysArray and type (undoState.arrayOfOverlaysArray) == "table" )
    assert ( undoState.verb and type (undoState.verb) == "string" )

    progressReporter:setTotal( getCount( undoState.arrayOfOverlaysArray ) )

    return Observable.from( undoState.arrayOfOverlaysArray )

        :map( function( arrayOfOverlays )

            return Observable.just( arrayOfOverlays )

                :flatMap( function( arrayOfOverlays )
                    for _, overlay in ipairs( arrayOfOverlays ) do
                        local subject = derefOpt( overlay, 'xmp', 'dc', 'subject' ) or {}

                        -- flip polarity of individual overlay entries (add is now delete, delete is now add)
                        for key, value in pairs( subject ) do
                            if value == true then
                                subject[ key ] = kDeleteToken
                            else
                                subject[ key ] = true
                            end
                        end

                    end

                    return WFRxBatchedBulkApplyOverlay( "undo set keywords", docStore, arrayOfOverlays )
                        :map( function( ret )
                            progressReporter:reportBulkSuccess( arrayOfOverlays and #arrayOfOverlays or 0 )
                            return arrayOfOverlays
                        end )
                end )

        end )

        :flatMerge( 1 )

        :finally( function()
            progressReporter:done()
        end )

        :call( WFRxRunUntilDone )

end

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

local function setKeywords( docStore, assetIds, keywords, delete, progressReporter, cancelObservable )
	
	assert( assetIds )
	assert( keywords )
		
	local batchSize = kDefaultBatchSize
		
	progressReporter:setTotal( #assetIds )
	
	local batchedDocIds = WFFunctionUtils_reduce( { {} }, addToBatch, assetIds, batchSize )
		
	return Observable.from( batchedDocIds )
		
		:map( function( docIdsBatch )
		
			return docStore:bulkDocValue( docIdsBatch )
			
				:flatMapLatest( function( docValueArray )
					local arrayOfOverlays = {}
		
					for _, docValue in ipairs( docValueArray or {} ) do
						if not docValue._empty and not docValue._deleted then
							local assetId = docValue._id
							
							local oldSubject = derefOpt( docValue, 'xmp', 'dc', 'subject' ) or {}
							local newSubject = {}
							
							for _, keyword in ipairs( keywords ) do
								if delete then
									if oldSubject[ keyword ] == true then
										newSubject [ keyword ] = kDeleteToken
									end
								else
									if oldSubject[ keyword ] == nil then
										newSubject[ keyword] = true
									end
								end
							end
							
							if next( newSubject ) then
							
								local baseOverlay = WFCatUtils.createBaseOverlayWithUserUpdatedField( assetId )
							
								baseOverlay.xmp = {
									dc = {
										subject = newSubject
									}
								}
								
								table.insert( arrayOfOverlays, baseOverlay )
								
							end
						end
					end 
					
					if #arrayOfOverlays == 0 then
						progressReporter:reportBulkSuccess( docValueArray and #docValueArray or 0 )
						return Observable.just( nil )
					else
						return WFRxBatchedBulkApplyOverlay( "setKeywords", docStore, arrayOfOverlays )
							:map( function( ret )
								progressReporter:reportBulkSuccess( docValueArray and #docValueArray or 0 )
								return arrayOfOverlays
							end )
					end	
				end )
		end )
		
		:flatMerge( 1 )
		
		:takeUntil( cancelObservable )
		
        :filter( function( arrayOfOverlays )
            return arrayOfOverlays
        end )

        :toArray()

        :map( function( processedDocValueArrays )
            local undoStateToken

            undoStateToken = WFCatUndo.saveUndoState {
                arrayOfOverlaysArray = processedDocValueArrays,
                verb = delete and "delete" or "add",
            }

            progressReporter:reportBulkSuccess( 0, {
                undoToken = undoStateToken,
                totalCount = getCount( processedDocValueArrays )
            }, true )
        end )
		
		:tapOnError( function( err )
			progressReporter:reportBulkError( 0, { errorMessage = err }, true )
	
			logger:errorf( "WFCatKeywordTask.setKeywords error : %s", err )
		end )
		
		:finally( function()
			progressReporter:done()
		end )
		
		:call( WFRxRunUntilDone )
		
end

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

local function createKeywordTask( wfCatalog, assetIds, keywords, delete, optUndoStateToken )
	
	WFArguments.check( 'WFCatalog', '?table', '?table', '?boolean', '?number' )
	
	local docStore = wfCatalog:docStore()
	assert ( docStore )
		
	local function setKeywordFunc( args, progressReporter, cancelObservable )
		if optUndoStateToken then
			undoSetKeywords( docStore, optUndoStateToken, progressReporter )
		else
			setKeywords( docStore, assetIds, keywords, delete, progressReporter, cancelObservable )
		end
	end
	
	local request = {
		args = {
			wfCatalog = wfCatalog, 
			assetIds = assetIds, 
			keywords = keywords, 
			delete = delete, 
			optUndoStateToken = optUndoStateToken
		},
		
		action = setKeywordFunc, 
	}
	
	local task = WFCatTask( request )
	
	return task
end

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

return {
	
	createKeywordTask = createKeywordTask,
	
}

