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

PixelProviderDownloadNegativeModel.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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 KSTableUtils = require "KSTableUtils"
local KSFunctionUtils = require "KSFunctionUtils"
local WFFunctionUtils = require "WFFunctionUtils"
local Rx = require "Rx"

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

local logger = require "WFLogger"( "PixelProviderDownloadNegativeModel" )

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

local function logInfo( infoStr )

	logger:info( infoStr )

end

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

local function logError( errStr )

	logger:error( errStr )

end

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

local function pack( ... )

	return { ... }

end

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

local function describeResult( pixStreamId, assetId, success, pixelPathOrError,
								maybeSize )

	return string.format( "%s getting %s path for %s (%s): %s",
							success and "SUCCESS" or "ERROR", 
							success and maybeSize or "N/A",
							assetId, pixStreamId, pixelPathOrError )

end

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

local function describeResultStream( pixelProviderProcessor, pixStreamId )

	if not pixelProviderProcessor then

		return Rx.Observable.just( "missing pixel provider" )

	end

	return pixelProviderProcessor
				:containsPixStreamIdSubstrPixelPathStream( pixStreamId )
				:map( describeResult )

end

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

local _validPathQualityArray = { "any", "best" }

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

local _validPathQuality = KSTableUtils.mapArrayToSet( _validPathQualityArray )

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

local function makeGetNegativePathPixStreamId( pathQuality, pixStreamIdPrefix )

	return string.format( "%s%s", pixStreamIdPrefix, pathQuality )

end

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

local function negativePathPixStreams( pixStreamIdPrefix )

	return WFFunctionUtils.map( makeGetNegativePathPixStreamId, 
									_validPathQualityArray, pixStreamIdPrefix )

end

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

negativePathPixStreams = KSFunctionUtils.memoizeFn( negativePathPixStreams )

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

local function removePreviousRequestStream( pixStreamId,
											pixelProviderProcessor, 
											prevAssetId )

	return pixelProviderProcessor
			:dispatch( "removeRequest",
						pixStreamId, prevAssetId )
			:map(
				function()

					return string.format( 
							"Removed previous request %s ( %s )", 
								prevAssetId, pixStreamId )

				end )

end

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

local function removePreviousRequestsStream( pixelProviderProcessor,
												pixStreamIds, prevAssetId )

	local removePreviousRequestStreamArray =
			WFFunctionUtils.map( 
				removePreviousRequestStream,
					pixStreamIds, pixelProviderProcessor, prevAssetId )

	return Rx.Observable.concat( unpack( removePreviousRequestStreamArray ) )

end

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

local function makeUpdatePixelProcessorGetNegativePathRequestStream( 
				pixelProviderProcessor, pixStreamIdPrefix, prevAssetId,
				pathQuality, activeAssetId )

	if not pixelProviderProcessor then

		return Rx.Observable.just( 
				string.format( 
					"Cannot request %s: no pixel provider",
					activeAssetId ) )

	end

	local removeRequestResult

	if prevAssetId and prevAssetId ~= activeAssetId then

		removeRequestResult =
			removePreviousRequestsStream( 
				pixelProviderProcessor,
				negativePathPixStreams( pixStreamIdPrefix ), prevAssetId )

	end

	removeRequestResult = 
		removeRequestResult or 
		Rx.Observable.just( "No previous request when updating active asset" )

	local addOrUpdateRequestResult

	if activeAssetId then

		local pixelProviderAPIName = 
				pathQuality == "any" and
				"getLocalNegativePathASAP" or 
				"getBestPossibleLocalNegativePathASAP"

		local pixStreamIdToAdd = 
				makeGetNegativePathPixStreamId( 
					pathQuality, pixStreamIdPrefix )

		local addOrUpdateRequestStream =
			pixelProviderProcessor
				:dispatch( pixelProviderAPIName, 
								pixStreamIdToAdd, activeAssetId )
				:map(
					function()

							return string.format( 
									"Added or updated request %s (%s) via %s", 
										activeAssetId, pixStreamIdToAdd,
										pixelProviderAPIName )

					end )

		-- now remove existing request of old type

		local pathQualityToRemove = pathQuality == "any" and "best" or "any"

		local pixStreamIdToRemove = 
				makeGetNegativePathPixStreamId( 
					pathQualityToRemove, pixStreamIdPrefix )

		local removeExistingRequestStream =
				removePreviousRequestStream(
					pixStreamIdToRemove, pixelProviderProcessor, 
					activeAssetId )

		addOrUpdateRequestResult =
			Rx.Observable.concat( 
				addOrUpdateRequestStream, removeExistingRequestStream )

	end

	addOrUpdateRequestResult =
		addOrUpdateRequestResult or Rx.Observable.just( "No active asset" )

	return Rx.Observable.concat( 
			removeRequestResult,
			addOrUpdateRequestResult )

end

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

local function getNegativePathASAP( pixelProviderProcessorStream, 
										pixStreamIdPrefix )

	local currAssetId

	local activeAssetIdStream = Rx.Subject()

	local pixelProviderRequestStatusStream =
			Rx.Observable.combineLatest( 
					pixelProviderProcessorStream, 
					activeAssetIdStream )
				:flatMapLatest( 
					function( pixelProviderProcessor, pathQuality, activeAssetId )

						local prevAssetId = currAssetId

						currAssetId = activeAssetId

						return makeUpdatePixelProcessorGetNegativePathRequestStream( 
									pixelProviderProcessor, pixStreamIdPrefix,
									prevAssetId, pathQuality, activeAssetId )

					end )

	local pixelProviderResultStream =
			pixelProviderProcessorStream
				:flatMapLatest( 
					function( pixelProviderProcessor )

						return describeResultStream(
								pixelProviderProcessor, 
								pixStreamIdPrefix )

					end )

	local valueStream =
				Rx.Observable.merge(
						pixelProviderRequestStatusStream,
						pixelProviderResultStream )
					:share()

	local valueStreamSubscription =
			valueStream
				:subscribe( logInfo, logError )

	return {

		value = valueStream,

		events = {

			getNegativePath = function( _, pathQuality, maybeAssetId )

				if type( pathQuality ) ~= "string" or
						not _validPathQuality[ pathQuality ] then

					logger:warnf( "Will not getNegativePath for asset %s: pathQuality '%s' is invalid",
									maybeAssetId, pathQuality )

					return

				end

				activeAssetIdStream:onNext( pathQuality, maybeAssetId )

			end,

			
		},

		close = function()

			valueStreamSubscription:dispose()

		end,

	}

end

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

local function makePrefetchNegativePathsStream( pixelProviderProcessorStream, 
												pixStreamId, 
												assetIdsStream )

	local prevAssetIds

	return Rx.Observable.combineLatest( 
				pixelProviderProcessorStream, 
				assetIdsStream )
			:flatMapLatest( 
				function( pixelProviderProcessor, assetIds )

					if not pixelProviderProcessor then

						return Rx.Observable.just( {} )

					end

					pixelProviderProcessor
						:runUntilDone( "prefetchNegativePaths", 
										'soon', pixStreamId,
										prevAssetIds, assetIds )

					prevAssetIds = assetIds 

					return pixelProviderProcessor
								:containsPixStreamIdSubstrPixelPathStream( 
									pixStreamId )
								:map( pack )

				end )

end

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

local function ensureLocalNegativePathForPrefetchImageIds( 
				pixelProviderProcessorStream, pixStreamId )

	pixStreamId = string.format( 
					"%s_ensureLocalNegativePathForPrefetchImageIds", 
					pixStreamId )

	local prefetchAssetIdsStream = Rx.Subject()

	return {

		value = makePrefetchNegativePathsStream( 
					pixelProviderProcessorStream, pixStreamId,
					prefetchAssetIdsStream ),

		events = {

			updateAssetIds = function( _, assetIds )

				assetIds = type( assetIds ) == "table" and assetIds or {}

				prefetchAssetIdsStream:onNext( assetIds )

			end,

			
		},

	}

end

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

return {

	getNegativePathASAP = getNegativePathASAP,

	ensureLocalNegativePathForPrefetchImageIds =
		ensureLocalNegativePathForPrefetchImageIds,

}
