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

WFCatReorderAssetsTask.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 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 WFCatSortByCustomOrder = require 'WFCatSortByCustomOrder'

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

local WFFunctionUtils_reduce = assert( WFFunctionUtils.reduce,
			"WFFunctionUtils.reduce does not exist" )
local check = assert( WFArguments.check )
local WFCatIdUtils = require 'WFCatIdUtils'
local WFSortOrderStrings = require 'WFSortOrderStrings'

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

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( batches )
    if not batches or #batches == 0 then
        return 0
    end

    local count = 0
    for _, batch in ipairs( batches ) do
        count = #batch.ids + count
    end

    return count
end

--------------------------------------------------------------------------------
 -- This method will be of use to decide if we need to apply sort order on all
 -- the album assets or only the assets being dragged/dropped.
 
local function getNumberOfAssetsWithSortOrderNotNull( wfCatalog, albumUuid )
	check( "WFCatalog", 'string' )

	local sortOrderCountObservable = wfCatalog:indexStore():queryOnce {
		sql = [[SELECT COUNT(*) FROM album_asset_v2 as aa 
				JOIN assets as a
				WHERE aa.assetId = a.assetId AND aa.albumId = ? AND aa.sortOrder notnull]],
		albumUuid,
	}
	
	return sortOrderCountObservable:map( function( sortOrderCount )
		return derefOpt( sortOrderCount, 1, 1 ) or 0
	end )
end 

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

local function haveAssetsWithDuplicateOrders( wfCatalog, albumUuid )
	check( "WFCatalog", 'string' )

	local sortOrderCountObservable = wfCatalog:indexStore():queryOnce {
		sql = [[
			SELECT COUNT(*) as sortOrderCounts FROM album_asset_v2 as aa 
			JOIN assets as a
			WHERE aa.assetId = a.assetId AND aa.albumId = ?
			AND aa.sortOrder notnull
			GROUP BY aa.sortOrder
			HAVING sortOrderCounts > 1 LIMIT 1
		]],
		albumUuid,
	}
	
	return sortOrderCountObservable:map( function( sortOrderCount )
		local duplicates = derefOpt( sortOrderCount, 1, 1 ) or 0
		return duplicates > 0
	end )
end 

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

local function getNumberOfAssets( wfCatalog, albumUuid )
	
	check( "WFCatalog", 'string' )
	
	local totalAssetCountObservable = wfCatalog:indexStore():queryOnce {
		sql = [[SELECT COUNT(*) FROM album_asset_v2 as aa 
				JOIN assets as a
				WHERE aa.assetId = a.assetId AND aa.albumId = ?]],
		albumUuid,
	}
	
	return totalAssetCountObservable:map( function( totalCount )
		return derefOpt( totalCount, 1, 1 ) or 0
	end )
	
end

-------------------------------------------------------------------------
local function getAssetWithHighestOrderInAlbum( wfCatalog, albumUuid, assetIds )
	
	local sortedAssetInfoObservable = WFCatSortByCustomOrder.getAssetWithHighestOrderInAlbum( wfCatalog, albumUuid, assetIds )
	return sortedAssetInfoObservable
	:take( 1 )
	:map( function( sortedAssetInfo )
		return derefOpt( sortedAssetInfo, 1, 1 ) or nil
	end )
	
end

-------------------------------------------------------------------------
-- Borrowed from _getOrders() of catalog_album_assets.coffee (pony/LrW)
local function getOrders(count, lowOrder, highOrder)
	
	check( 'number', '?string', '?string' )
	
	local order = false
	local lastOrder = false

	local orders = {}

	-- If we're just ordering 1 item, use a middle bias
	if count == 1 then
		if lowOrder and highOrder then
			order = WFSortOrderStrings.insertBetween_lex64(lowOrder, highOrder, 'middle')
		elseif lowOrder then
			order = WFSortOrderStrings.insertAfter_lex64(lowOrder, 'middle')
		elseif highOrder then
			order = WFSortOrderStrings.insertBefore_lex64(highOrder, 'middle')
		else
			order = WFSortOrderStrings.MIDPOINT
		end
		
		if not order then
			order = nil
		end
		
		orders[1] = order
		logger:tracef("getOrders(): returning %s", order )
		return orders
	end
	
	if lowOrder then
		lastOrder = lowOrder
	elseif highOrder then
		lastOrder = WFSortOrderStrings.insertBefore_lex64(highOrder, 'middle')
	else
		lastOrder = WFSortOrderStrings.MIDPOINT
	end
	
	local ordersStr = ''
	
	for n=1, count do
		if highOrder then
			order = WFSortOrderStrings.insertBetween_lex64(lastOrder, highOrder, 'low')
		else
			order = WFSortOrderStrings.insertAfter_lex64(lastOrder, 'low')
		end
		
		if not order then -- might trigger a full reindex, or error
			return nil
		end
		
		orders[ #orders + 1] = order
		
		if logger:will_trace() then
			ordersStr = ordersStr .. order
			if n < count then
				ordersStr = ordersStr .. ', '
			end
		end
		
		lastOrder = order
	end
	
	logger:tracef("getOrders(): returning %s", ordersStr )
	
	return orders
end

-----------------------------------------------------------------------
local function getSortOrderFromAlbumAsset(wfCatalog, albumId, assetId)
	check( "WFCatalog", 'string|number', 'number' )

	if not assetId then
		return Observable.just( {} )
	end
	
	local assets = {}
	assets[1] = assetId
	local albumUuid = WFCatIdUtils.albumUuidForMaybeLocalId( wfCatalog, albumId )
	local assetUuidsAndTypes = WFCatIdUtils.getOzAssetIdsAndTypeForIds( wfCatalog, assets )
	return Observable.combineLatest( albumUuid, assetUuidsAndTypes )
	:map( function( albumId, assetUuidsAndTypes )
		
		local assetIds = {}
		for _, assetUuidsAndType in pairs( assetUuidsAndTypes ) do
			local assetId = assetUuidsAndType[1]
			assetIds[#assetIds + 1] = assetId
		end
		
		local albumAssets = wfCatalog:indexStore():queryOnce {
			-- get all the album_assets
			sql = [[SELECT aa.docId, aa.assetId, aa.sortOrder FROM album_asset_v2 AS aa JOIN CARRAY( ?, ?, 'char*' ) AS t ON t.value = aa.assetId WHERE aa.albumId = ?]],
			assetIds,
			#assetIds,
			albumId,
		}
		
		return albumAssets:map( function (albumAssetsIds )
			local sortOrders = {}
			for _, albumAsset in pairs( albumAssetsIds ) do
				local sortOrder = albumAsset[3]					
				table.insert(sortOrders, sortOrder)
			end
			
			return sortOrders
		end)

	end)
	:flatMapLatest(function (orders)
		return orders
	end)

end

-----------------------------------------------------------------------
-- Borrowed from _getOrdersForInsert() of catalog_album_assets.coffee (pony/LrW)
local function getOrdersForInsert(wfCatalog, albumId, sortedAssetIds, count, insertBeforeIndex)
	
	local lowOrderObservable = Observable.just( {} )
	local highOrderObservable = Observable.just( {} )
	
	if insertBeforeIndex - 1 >= 1 and insertBeforeIndex - 1 <= #sortedAssetIds then
		local lowOrderAssetId = sortedAssetIds[insertBeforeIndex - 1]
		lowOrderObservable = getSortOrderFromAlbumAsset(wfCatalog, albumId, lowOrderAssetId)
	end
	if insertBeforeIndex <= #sortedAssetIds then
		local highOrderAssetId = sortedAssetIds[insertBeforeIndex]
		highOrderObservable = getSortOrderFromAlbumAsset(wfCatalog, albumId, highOrderAssetId)
	end
	
	return Observable.combineLatest(lowOrderObservable, highOrderObservable)
	:map( function(lowOrder, highOrder)
		local lo = lowOrder[1]
		local hi = highOrder[1]
				
		if (not lo or lo == "") and
			(not hi or hi == "") then
			
			if logger:will_trace() then
				local loStr
				local hiStr
				
				if not lo then
					loStr = "nil"
				end
				if not hi then
					hiStr = "nil"
				end
				
				logger:tracef("getOrdersForInsert(): returning nil - lo = %s, hi = %s", loStr, hiStr )
			end
			
			return nil
		end
		
		logger:tracef("getOrdersForInsert(): low = %s, hi = %s", lo, hi )
		
		return getOrders(count, lo, hi)
	end)

end

-------------------------------------------------------------------------
local function getAlbumAssetDocs(wfCatalog, albumId, assets)
	
	check( "WFCatalog", 'string|number', 'table' )
	
	-- Retrieval of album asset documents from doc store 
	-- by using the information available so far.
	
	-- The order of album asset documents returned will be the same as the
	-- order of assets (which has local asset doc IDs) for the input albumId
	logger:tracef("getAlbumAssetDocs(): #assets = %d", #assets )
	
	return WFCatIdUtils.albumUuidForMaybeLocalId( wfCatalog, albumId )
	:map( function (albumId)
		
		local albumAssetsObservable = wfCatalog:indexStore():queryOnce {
			-- get all the album_assets

			sql = [[
					SELECT t.value as assetDocId, aa.docId as albumAssetDocId 
					FROM CARRAY( ?, ?, 'int32' ) as t 
					JOIN assets as a 
					JOIN album_asset_v2 as aa
					WHERE t.value = a.docId
					AND a.assetId = aa.assetId
					AND aa.albumId = ?]],
			assets,
			#assets,
			albumId,
		}
					
		return albumAssetsObservable: map( function ( assetToAlbumAssets )
			
			local albumAssetMap = {}
			for _, assetToAlbumAsset in ipairs( assetToAlbumAssets ) do
				local assetDocId = assetToAlbumAsset[1]
				local albumAssetDocId = assetToAlbumAsset[2]
				albumAssetMap[ assetDocId ] = albumAssetDocId
			end
			
			local albumAssetDocs = {}

			for _, assetDocId in ipairs(assets) do
				albumAssetDocs[#albumAssetDocs + 1] = albumAssetMap[assetDocId]
			end
			
			return albumAssetDocs
			
		end)
	end)
	:flatMapLatest(function (albumAssetDocs)
		return albumAssetDocs
	end)

end

-------------------------------------------------------------------------
local function applyReorderData(wfCatalog, albumId, assets, sortOrders, progressReporter, cancelObservable)
	logger:tracef("applyReorderData(): #assets = %d, #sortOrders = %d", 
					#assets,
					#sortOrders )
	
	local albumAssetDocsObservable = getAlbumAssetDocs(wfCatalog, albumId, assets)
	
	progressReporter:setTotal( #assets )
	
	-------------------------------------------
	return albumAssetDocsObservable:map(function( assetToLocalDocIds )
		
		return wfCatalog:docStore():bulkDocValue( assetToLocalDocIds )
		:flatMap( function( docs )
			logger:tracef("applyReorderData(): #album_assets = %d", #docs )
			
			local batchSize = kDefaultBatchSize
			
			local batchedDocs = WFFunctionUtils_reduce( { {} }, addToBatch, docs, batchSize )
				
			return Observable.from( batchedDocs )
			:map( function( batchedDocs )
				local existingDocs = {}
				
				local docsForUndoState = {}
				local albumAssetIds = {}
				local existingOrders = {}
				logger:tracef("applyReorderData(): #batchedDocs = %d", #batchedDocs )
				for i, doc in ipairs( batchedDocs ) do
					
					local overlay = WFCatUtils.createBaseOverlayWithUserUpdatedField( doc._id )
					overlay.order = sortOrders[ i ]

					existingDocs[ #existingDocs + 1 ] = overlay
					
					albumAssetIds[#albumAssetIds + 1] = doc._localId
					existingOrders[doc._localId] = doc.order
					
				end
				
				-- Erase the first "n" (batchedDocs size) elements from sortOrders
				-- If we were to get to the for loop the next time, we need to
				-- assign sortOrders starting from the "n+1" onwards...
				for i=1,#batchedDocs do
					table.remove(sortOrders, 1)
				end
				
				logger:tracef("applyReorderData(): #existingDocs = %d, remaining #sortOrders = %d", 
								#batchedDocs, #sortOrders )
				if #existingDocs > 0 then
				
					return WFRxBatchedBulkApplyOverlay( "reorderAssetsInAlbumGivenDocIds", wfCatalog:docStore(), existingDocs )
						:map( function(newDocs)
							logger:tracef("applyReorderData(): WFRxBatchedBulkApplyOverlay succeeded, add %d assets to undo", 
								#albumAssetIds )
              				progressReporter:reportBulkSuccess( existingDocs and #existingDocs or 0, nil, true )

							docsForUndoState.ids = albumAssetIds
							docsForUndoState.orders = existingOrders
							
							return docsForUndoState
						end )
						
				else
					progressReporter:reportBulkSuccess( docs and #docs or 0, nil, true )
					return Observable.just( {} )
				end
			end)
		end )
	end)
	:flatMapLatest(function (albumAssetDocs)
		return albumAssetDocs
	end)
end

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

local function getAssetsToOrder(sortedAssetIds, targetAssets, insertBeforeIndex, startAtIndex)

	local assetsToOrder = {}
	-- The map below is used as a lookup inside the for look that follows
	local targetAssetsMap = KSTableUtils.invert(targetAssets)
	-- We need to loop past the last index to properly insert if we're
	-- ordering at the end
	for index = startAtIndex, #sortedAssetIds + 1 do
		if index == insertBeforeIndex then
			KSTableUtils.appendArray(assetsToOrder, targetAssets)
		end
		
		if index <= #sortedAssetIds then
			local existingAsset = sortedAssetIds[index]
			if existingAsset and not targetAssetsMap[existingAsset] then					
				table.insert(assetsToOrder, existingAsset)
			end
		end
	end
	
	return assetsToOrder
	
end

-------------------------------------------------------------------------
local function doPartialOrdering(wfCatalog, sortedAssetIds, highestOrderAssetPos, albumId, targetAssets, insertBeforeIndex, progressReporter, cancelObservable)
	
	local lowOrderAssetId = sortedAssetIds[highestOrderAssetPos]
	local lowOrderObservable = getSortOrderFromAlbumAsset(wfCatalog, albumId, lowOrderAssetId)
	return lowOrderObservable:flatMap(function(lowOrder)
		local lo = lowOrder[1]
		local assetsToOrder = getAssetsToOrder(sortedAssetIds, targetAssets, insertBeforeIndex, highestOrderAssetPos + 1)
		local orders = getOrders(#assetsToOrder, lo)
		return applyReorderData(wfCatalog, albumId, assetsToOrder, orders, progressReporter, cancelObservable)
	end)
	
end

-------------------------------------------------------------------------
local function doOrdering(wfCatalog, sortedAssetIds, isOrdered, albumId, targetAssets, insertBeforeIndex, forceReindex, progressReporter, cancelObservable)

	logger:tracef("doOrdering(): #sortedAssetIds = %d, isOrdered = %s, albumId = %s, #targetAssets = %d, insertBeforeIndex = %s, forceReindex = %s", 
					#sortedAssetIds,
					isOrdered,
					albumId,
					#targetAssets,
					insertBeforeIndex,
					forceReindex )

	if isOrdered and not forceReindex then
		local ordersObservable = getOrdersForInsert(wfCatalog, albumId, sortedAssetIds, #targetAssets, insertBeforeIndex)
		return ordersObservable:flatMap( function(sortOrders)
				if not sortOrders then
					return doOrdering(wfCatalog, sortedAssetIds, isOrdered, albumId, targetAssets, insertBeforeIndex, true, progressReporter, cancelObservable) -- force full reindex
				end
				return applyReorderData(wfCatalog, albumId, targetAssets, sortOrders, progressReporter, cancelObservable)
		end)
		
		
	else
		-- Create the full list of all assets in the right order
		local orders = getOrders(#sortedAssetIds)
		local assetsToOrder = getAssetsToOrder(sortedAssetIds, targetAssets, insertBeforeIndex, 1)
		
		return applyReorderData(wfCatalog, albumId, assetsToOrder, orders, progressReporter, cancelObservable)
	end
	
	return Observable.just( {} )
end

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

-- The following custom iterator is borrowed from
-- https://www.lua.org/pil/19.3.html
-- This function returns an iterator that will iterate over the table 't'
-- based on the sorted order of its keys 

local function pairsByKeys (t, f)
	local a = {}
	for n in pairs(t) do table.insert(a, n) end
	table.sort(a, f)
	local i = 0      -- iterator variable
	local iter = function ()   -- iterator function
		i = i + 1
		if a[i] == nil then return nil
		else return a[i], t[a[i]]
		end
	end
	return iter
end
-----------------------------------------------------------------------

local function reorderAssetsInAlbumImpl( wfCatalog, albumId, anchorAssetId, assetIdsToReorder, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable, maybeForce )
	return WFCatIdUtils.albumUuidForMaybeLocalId( wfCatalog, albumId )
	:flatMap( function( albumUuid )
			
		-- If anchorAssetId is nil, it indicates that the assets being dragged/dropped
		-- should be moved to the end of the list
		
		---------------------------------------------------------------------------
		-- 
		-- 1. Counting stuff!!
		-- Determine if there are any assets in album_asset_v2 table with sortOrder
		-- field set.
		local highestOrderAssetIdObservable = getAssetWithHighestOrderInAlbum( wfCatalog, albumUuid, maybeAllAssetIds )
		local numSortOrderAssetsObservable = getNumberOfAssetsWithSortOrderNotNull( wfCatalog, albumUuid )
		local numAssetsObservable = getNumberOfAssets( wfCatalog, albumUuid )
		local haveDuplicatesObservable = haveAssetsWithDuplicateOrders( wfCatalog, albumUuid )
		
		return Observable.combineLatest( highestOrderAssetIdObservable, numSortOrderAssetsObservable, haveDuplicatesObservable, numAssetsObservable )
		:flatMap( function( highestOrderAssetId, numSortOrderAssets, haveDuplicates, totalAssetsInAlbum )
		  	logger:tracef("reorderAssetsInAlbumImpl(): numSortOrderAssets = %d", numSortOrderAssets )

			---------------------------------------------------------------------------
			-- 2. Extract position of the anchor within the sorted asset album list
			
			-- Option 1 (UNIMPLEMENTED): Retrieve the sorted asset list constructed
			-- on successful completion of executeSearchAndSort()
			
			-- Buggy!! Might need more work to get to this!
			-- For now, let's use what's passed in. (i.e., Option 2)
			-- Removed references to sortedAssetIds in WFCatSearchAndSort
			-- and the getter method getSortedAssetIds()
			
			-- local sortedAssetIds = WFCatSearchAndSort.getSortedAssetIds()
		
			-- Option 2 (IMPLEMENTED): Retrieve the sorted asset list from the client
			-- via dataSource:getAssetIds() and then being passed along
			-- submodel.
			
			-- This is already being passed as arg maybeAllAssetIds
			local sortedAssetIds = maybeAllAssetIds
			
			---------------------------------------------------------------------------
			
			-- 3. Ordering stuff!!
			
			-- TODO:
			-- Think of adding a new method in WFCatSearchAndSort which
			-- returns this map instead of having to invert everytime we are reordering
			local assetIdsToPos = KSTableUtils.invert( sortedAssetIds or {} )
			local anchorPos = false
			
			if insertWhere == "last" then
				anchorPos = #sortedAssetIds + 1
			else
				anchorPos = assetIdsToPos[anchorAssetId]
			end
			
			local forceReindex = false
			if numSortOrderAssets == 0 or haveDuplicates or maybeForce then
				forceReindex = true
			end
			
			local partialOrdering = false
			local posOfAssetWithHighestOrder = nil
			if not forceReindex and highestOrderAssetId then
				posOfAssetWithHighestOrder = assetIdsToPos[highestOrderAssetId]
				if posOfAssetWithHighestOrder < totalAssetsInAlbum and 
					anchorPos > posOfAssetWithHighestOrder then
					partialOrdering = true
				end
			end
			
			local isOrdered = (numSortOrderAssets > 0)
			
			-------------------------------------------------------------------
			-- Prepare a new list of target assets given the input asset list 
			-- which ensures that the ordering within the target assets is also
			-- preserved according to their appearance in the sorted asset ids
			-- after they have been placed in right location relative to the 
			-- anchor asset.
			
			local posOfTargetAssets = {}
			
			for i, v in ipairs(assetIdsToReorder) do
				posOfTargetAssets[assetIdsToPos[v]] = v
			end
			
			local newTargetAssets = {}
			for i, v in pairsByKeys(posOfTargetAssets) do
				newTargetAssets[#newTargetAssets + 1] = v
			end
			
			assetIdsToReorder = newTargetAssets
			
			-------------------------------------------------------------------
			
			if partialOrdering then
				return doPartialOrdering(wfCatalog, sortedAssetIds, posOfAssetWithHighestOrder, albumId, assetIdsToReorder, anchorPos, progressReporter, cancelObservable)
			else
				return doOrdering(wfCatalog, sortedAssetIds, isOrdered, albumId, assetIdsToReorder, anchorPos, forceReindex, progressReporter, cancelObservable)
			end
	
		end)
		
	end )

	:flatMerge( 1 )
	
	:takeUntil( cancelObservable )
	
	:filter( function( arrayOfOverlays )
		return arrayOfOverlays
	end )

	:toArray()

	:map( function( undoState )
		local undoStateToken
		logger:tracef("reorderAssetsInAlbumImpl(): saving undo state" )
		undoStateToken = WFCatUndo.saveUndoState(undoState)

		progressReporter:reportBulkSuccess( 0, {
			undoToken = undoStateToken,
			totalCount = getCount( undoState )
		}, true )
	end )
	
	:tapOnError( function( err )
		progressReporter:reportBulkError( 0, { errorMessage = err }, true )

		logger:errorf( "WFCatReorderAssetsInAlbumTask.reorderAssetsInAlbum error : %s", err )
	end )
	
	:finally( function()
		progressReporter:done()
	end )
	
	:call( WFRxRunUntilDone )
end

-----------------------------------------------------------------------
local function reorderAssetsInAlbum( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable )
	return reorderAssetsInAlbumImpl( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable, false )
end

-----------------------------------------------------------------------
local function forceReorderAssetsInAlbum( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable )
	return reorderAssetsInAlbumImpl( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable, true )
end
-----------------------------------------------------------------------

local function undoReorderAssetsInAlbum( wfCatalog, optUndoStateToken, progressReporter, cancelObservable )

    local undoState = WFCatUndo.getUndoState( optUndoStateToken )

    assert ( undoState and type(undoState) == "table" )
	progressReporter:setTotal( getCount( undoState ) )

    return Observable.from( undoState )

        :map( function( batches )

            return Observable.just( batches )

                :flatMap( function( batch )
				
					local docsToUndo = {}
				
                    for _, albumAssetDocId in ipairs( batch.ids ) do
							
						local overlay = WFCatUtils.createBaseOverlayWithUserUpdatedField( albumAssetDocId )
						local order = batch.orders[albumAssetDocId]
						
						if not order then
							overlay.order = { _delete = true }
						else
							overlay.order = order
						end
						
						docsToUndo[ #docsToUndo + 1 ] = overlay
                    end

                    return WFRxBatchedBulkApplyOverlay( "undo reorder assets", wfCatalog:docStore(), docsToUndo )
                        :map( function( ret )
                            progressReporter:reportBulkSuccess( docsToUndo and #docsToUndo or 0, nil, true )
                            return docsToUndo
                        end )
                end )

        end )

        :flatMerge( 1 )

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

        :call( WFRxRunUntilDone )

end

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

local function createReorderingTask( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, optUndoStateToken )
	if not optUndoStateToken then
		-- actual ordering action
		WFArguments.check( 'WFCatalog', 'string|number', 'number', 'table', 'string', '?table', '?number' )
	else
		-- undo action
		WFArguments.check( 'WFCatalog', '?string|number', '?number', '?table', '?string', '?table', 'number' )
	end
	
	local function reorderAssetsFunc( args, progressReporter, cancelObservable )
		if optUndoStateToken then
			undoReorderAssetsInAlbum( wfCatalog, optUndoStateToken, progressReporter )
		else	
			reorderAssetsInAlbum( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable )
		end
	end
	
	local request = {
		args = {
			wfCatalog = wfCatalog,
			albumId = albumId,
			anchorAssetId = anchorAssetId,
			assetIds = assetIds,
			insertWhere = insertWhere,
			maybeAllAssetIds = maybeAllAssetIds,
			optUndoStateToken = optUndoStateToken
		},
		
		action = reorderAssetsFunc, 
	}
	
	local task = WFCatTask( request )
	
	return task
end


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

local function createForceReorderingTask( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, optUndoStateToken )
	if not optUndoStateToken then
		-- actual ordering action
		WFArguments.check( 'WFCatalog', 'string|number', 'number', 'table', 'string', '?table', '?number' )
	else
		-- undo action
		WFArguments.check( 'WFCatalog', '?string|number', '?number', '?table', '?string', '?table', 'number' )
	end
	
	local function forceReorderAssetsFunc( args, progressReporter, cancelObservable )
		if optUndoStateToken then
			undoReorderAssetsInAlbum( wfCatalog, optUndoStateToken, progressReporter )
		else
			forceReorderAssetsInAlbum( wfCatalog, albumId, anchorAssetId, assetIds, insertWhere, maybeAllAssetIds, progressReporter, cancelObservable )
		end
	end
	
	local request = {
		args = {
			wfCatalog = wfCatalog,
			albumId = albumId,
			anchorAssetId = anchorAssetId,
			assetIds = assetIds,
			insertWhere = insertWhere,
			maybeAllAssetIds = maybeAllAssetIds,
			optUndoStateToken = optUndoStateToken
		},
		
		action = forceReorderAssetsFunc, 
	}
	
	local task = WFCatTask( request )
	
	return task
end

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

return {
	
	createReorderingTask = createReorderingTask,
	createForceReorderingTask = createForceReorderingTask,
	
}
