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

WFCatPersonBinaryDownloader.lua

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

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

NOTICE: All information contained herein is, and remain 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 may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

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

--[[!
	\brief Save thumbnail to Path and return the Path via response stream. 
		   If the file is already preset, no downlaod happens and returns the path.  
		   thumbnail path stored in _localOnly.
--]]

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

local Rx = require 'Rx'
local KSData = require 'KSData'
local WFCloseSupport = require 'WFCloseSupport'
local WFBinaryRequester = require 'WFBinaryRequester'
local WFCatBinaryDownloader = require 'WFCatBinaryDownloader'
local WFRxHttpClient = require 'WFRxHttpClient'
local WFFileUtils = require 'WFFileUtils'
local WFAsyncFileUtils = require 'WFAsyncFileUtils'
local KSTableUtils = require 'KSTableUtils'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFLogger = require 'WFLogger'

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

local derefOpt = KSTableUtils.derefOpt

local logger = WFLogger 'WFCatPersonBinaryDownloader'
	--:setLevel 'debug'

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

local function getPathToSave( self, catalog, ozPersonId )
	return catalog:fileSpec():pathForPerson( ozPersonId )
end

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

local function saveBinaryToFile( self, path, blob )
	
	return WFAsyncFileUtils.saveToFile( blob, path )
		:map( function()
			return path
		end )
end

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

local function updateDocStorePersonThumbnailInfo( docStore, ozPersonId, filePath, binaryType, size )

	return docStore:docValue( ozPersonId )
		:flatMapLatest( function( docValue )
			if docValue._empty then
				local errStr = string.format( "AssetId %s has been deleted", ozPersonId )
				return Rx.Observable.throw( errStr )
			end
			
			local href = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/' .. size, 'href' )
			if href then
				local localOnly = {
					files = {
						[ binaryType ] = {
							[ size ] = {
								href = href,
								filePath = filePath,
							}
						}
					}
				}

				local overlay = { 
					_id = ozPersonId,
					_localOnly = localOnly,
				}
							
				return docStore:applyOverlay( overlay ) 
					:take( 1 )
			else
				return Rx.Observable.throw( "Unable to create doc store annotation for file " .. filePath )
			end
		end )

end

--------------------------------------------------------------------------------
--[[!
	\brief Save File to Path and return the Path via download response stream. 
--]]
local function getBinaryResponseHandler( self, catalog )

	return function ( okay, maybeKSDataOrError, revId, ozPersonId, binaryType, size, additionalInfo )
	
		local binaryBlob = KSData.isData( maybeKSDataOrError ) and maybeKSDataOrError or nil
		
		if okay and binaryBlob then
		
			local path = getPathToSave( self, catalog, ozPersonId )
			
			return saveBinaryToFile( self, path, binaryBlob )
				:flatMapLatest( function( path )
					return updateDocStorePersonThumbnailInfo( catalog:docStore(), ozPersonId, path, binaryType, size )
						:map( function()
							return path
						end )
				end )
				:tap( function( path )
					self._inFlightRequests[ ozPersonId ] = nil
					self._binaryDownloadResponseStream:onNext( ozPersonId, true, path, true )
				end )
				:tapOnError( function( err )
					self._inFlightRequests[ ozPersonId ] = nil
					local e = tostring( err )
					logger:errorf( "Error while fetching master for %s: %s", ozPersonId, e )
					self._binaryDownloadResponseStream:onNext( ozPersonId, false, e, false )
				end )
				:finally( function()
					if binaryBlob then
						binaryBlob:close()
					end
				end )
				:call( WFRxRunUntilDone )
		else
			self._inFlightRequests[ ozPersonId ] = nil
			local err = tostring( maybeKSDataOrError )
			logger:errorf( "Error while downloading master for %s: %s", ozPersonId, err )
			self._binaryDownloadResponseStream:onNext( ozPersonId, false, err, false )
		end
	end
end 


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

local WFCatPersonBinaryDownloader = {
	_catalog = false,
	_binaryRequester = false,
	_binaryRequesterSubscription = false,
	_binaryDownloadResponseStream = false,
	_inFlightRequests = false,
	WFCloseSupport.addCloseSupportMethods,
}

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

function WFCatPersonBinaryDownloader:__init( catalog )
	self._catalog = catalog
	self._inFlightRequests = {}
	self._binaryRequester = WFBinaryRequester( WFCatBinaryDownloader( catalog ) )
	self._binaryRequesterSubscription = self._binaryRequester:getBinaryRequestStream()
										:subscribe( getBinaryResponseHandler( self, catalog ) )
	self._binaryDownloadResponseStream = Rx.Subject()
end

--------------------------------------------------------------------------------
--[[!
	\brief Save thumbnail to Path and return the Path via response stream. 
		   If the file is already preset, no downlaod happens and returns the path.  
		   thumbnail path stored in _localOnly.

	\param ozPersonId (string) oz-person-id
--]]
function WFCatPersonBinaryDownloader:requestPersonBinary( personId )
	if self._inFlightRequests[ personId ] then
		-- New request received for the ozPersonId, which is
		-- already in progress. This should not happen.
		logger:warnf( "Multiple requests for the same style ozPersonId: %s", personId )
		return
	end
				
	self._catalog:docStore():docValue( personId )
		:map( function( docValue )
			local httpPriority = WFRxHttpClient.requestPriorityHigh
			local ozBinaryType = "rendition"
			local ozBinarySubtype = "thumbnail2x"

			local href = derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/rendition_type/'..ozBinarySubtype, 'href' )
			local storedhref = derefOpt( docValue, '_localOnly', 'files', ozBinaryType, ozBinarySubtype, 'href' )
			local filePath = derefOpt( docValue, '_localOnly', 'files', ozBinaryType, ozBinarySubtype, 'filePath' )
			
			local download = true
			if filePath and WFFileUtils.exists( filePath ) then
				download = false
				self._inFlightRequests[ personId ] = nil
				self._binaryDownloadResponseStream:onNext( personId, true, filePath, false )
			end
			
			if href then
				if href ~= storedhref or download then 
					local cancelDownloadFunc = self._binaryRequester:requestBinary( personId, ozBinaryType, ozBinarySubtype, httpPriority )

					assert( type( cancelDownloadFunc ) == "function", "bad cancelDownloadFunc WFCatPersonBinaryDownloader" )

					self._inFlightRequests[ personId ] = cancelDownloadFunc
				end
			else
				self._inFlightRequests[ personId ] = nil
				self._binaryDownloadResponseStream:onNext( personId, false, "nohref", false )
			end
		end)
		:call( WFRxRunUntilDone )
end

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

function WFCatPersonBinaryDownloader:cancelPersonBinaryRequest( ozPersonId )
	local cancelDownloadFunc = self._inFlightRequests[ ozPersonId ]
	if cancelDownloadFunc then
		cancelDownloadFunc()
		self._inFlightRequests[ ozPersonId ] = nil
	end
end

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

function WFCatPersonBinaryDownloader:getResponseStream()
	return self._binaryDownloadResponseStream
end

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

function WFCatPersonBinaryDownloader:close()
	for _, cancelFn in pairs( self._inFlightRequests ) do
		if cancelFn then
			cancelFn()
		end
	end
	
	self._binaryRequesterSubscription:dispose()
	self._binaryRequester:close()
	self._binaryDownloadResponseStream:dispose()
end

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

return Rx.Class 'WFCatPersonBinaryDownloader'( WFCatPersonBinaryDownloader )
