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

ADOBE CONFIDENTIAL
------------------
Copyright 2016 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.

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

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.diagnostic_log
local WFDiagnosticLog = require 'WFDiagnosticLog'

	-- WF.core.utils
local KSPromises = require 'KSPromises'
local KSReactive = require 'KSReactive'
local WFCloseSupport = require 'WFCloseSupport'
local WFArguments = require 'WFArguments'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.oz_client.index_store
local WFQueryDrivenWorkQueueScheduler = require 'WFQueryDrivenWorkQueueScheduler'

	-- WF.lrmobile
local LrmConfig = require 'LrmConfig'

	-- WF.lrmobile.models
local LrmCatXmpDevelop = require "LrmCatXmpDevelop"
local LrmCatPreset = require "LrmCatPreset"

	-- WF.web.http_client
local WFRxHttpClient = require 'WFRxHttpClient'

	-- WF.oz_client.lrmobile
local LrmCatSyncStatus = require 'LrmCatSyncStatus'

	-- WF.core.rxlua
local asKSPromise = require 'WFRxPromiseBridge'
local KSStringUtils = require 'KSStringUtils'

local WFCatAssetAux = require 'WFCatAssetAux'

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

local check = assert( WFArguments.check )

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

LrmConfig.registerBinaryStrategyCheck()

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

local ozBinaryDownloaderMethods = {

	_catalog = false,
		-- (OzCatalogService )
		
	_indexStore = false,
		-- (WFIndexStore)
		
	_renditionServer = false,
		-- (WFOzRenditionServer)
		
	_LrmOzProfileModule = false,
		-- (LrmWFOzProfileUtils)
	
	_workScheduler = false,
		-- (WFQueryDrivenWorkQueueScheduler)
		
	_downloadOneBinaryFn = false,
		-- (function)
	
	-- These are all Android Specific, as we still dont have Blob Provider in place
	_presetWorkScheduler = false,
		-- (WFQueryDrivenWorkQueueScheduler)

	_dcpWorkScheduler = false,
		-- (WFQueryDrivenWorkQueueScheduler)

	_downloadPresetBinaryFn = false,
		-- (function)

	_downloadDcpBinaryFn = false,
		-- (function)

	_updateAcrPresetProfileDatabase = false,
		-- <boolean> send true trough the subject when we need ACR to refresh its
		-- internal preset V2 database

	_activeDcpDownloadCountB = false,
		-- <number> how many active downloads we have

	_updateAcrProfileDatabase = false,
		-- <boolean> send true trough the subject when we need ACR to refresh its 
		-- internal profile database
	
	_activeDownloadCountB = false,
		-- <number> how many active downloads we have
		
	_activeAlbumB = false,
		-- <string> actively downloading
		
	_binaryStrategy = false,

	_enableDirectDownload = false,
		-- <boolean> feature flag to enable direct download

	WFCloseSupport.addCloseSupportMethods,
	
	KSReactive.addReactiveSupportMethods,
	
}

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

local function isAuxRendition( renditionName )

	return renditionName == 'aux-original' or renditionName == 'aux-proxy-2560'

end

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

function ozBinaryDownloaderMethods:downloadSingleRenditionP( renditionServer, binaryInfo )

	check( 'LrmOzBinaryDownloader', 'WFOzRenditionServer', 'table' )
	
	if binaryInfo.renditionName == 'master' then
	
		return self._catalog:originalDataPathP( binaryInfo.assetId, false, WFRxHttpClient.requestPriorityLow )
		
	elseif binaryInfo.renditionName == 'proxy' or binaryInfo.renditionName == 'proxyWithoutMaster' then
	
		return self._catalog:proxyDataPathP( binaryInfo.assetId, false, 2560, WFRxHttpClient.requestPriorityLow )
	
	elseif binaryInfo.renditionName == 'profiles' then
	
		return self._LrmOzProfileModule.ensureProfilesExistLocallyP( self._catalog, binaryInfo.assetId )
			:observeP( function( status, info )
			
				if status == 'data' and type( info ) == 'table' then
				
					if info.updateAcrProfileCache then
						self._updateAcrProfileDatabase:onNext( true )
					end
					
				end
				
			end )
			
	elseif binaryInfo.renditionName == 'thumbnail2x' or binaryInfo.renditionName == 'preview' then
	
		return renditionServer:downloadRenditionP( binaryInfo.assetId, binaryInfo.renditionName )
		
	elseif binaryInfo.renditionName == 'xmp_develop' then
	
		return LrmCatXmpDevelop.getExternalXmpDevelopP( self._catalog, binaryInfo.assetId )
		
	elseif isAuxRendition( binaryInfo.renditionName ) then

		local imageType = string.match( binaryInfo.renditionName, "aux%-(.+)" )
	
		return WFCatAssetAux.ozCatalogService_ensureAuxFilesExistLocally_Mobile( self._catalog, binaryInfo.assetId, imageType )

			:call( asKSPromise ) 

	elseif WFPlatformInfo.isAndroid and binaryInfo.renditionName == 'presetProfile' then

		return LrmCatPreset.getExternalPresetP( self._catalog, binaryInfo.assetId )

	elseif WFPlatformInfo.isAndroid and binaryInfo.renditionName == 'dcpProfile' then

		local LrmWFOzProfileUtils = require 'LrmWFOzProfileUtils'
		return LrmWFOzProfileUtils.ensureCustomCameraProfilesExistLocallyP( self._catalog, { binaryInfo } )

	end
	
	return KSPromises.resolvedPromiseP( 'error', 
		string.format( 'No support for BinaryType: %s', binaryInfo.renditionName ) )
		
end

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

local function makeDownloadDcpBinariesFn( self )

	local function downloadRenditionP( binaryInfo )
	
		self._activeDcpDownloadCountB:set( self._activeDcpDownloadCountB:valueNow() + 1 )

		return self._renditionServer
			:call( asKSPromise )
			:mapDataP_new( function( renditionServer )
			
				return self:downloadSingleRenditionP( renditionServer, binaryInfo )
				
			end )
		
			:mapP( function( status, info )

                self._activeDcpDownloadCountB:set( self._activeDcpDownloadCountB:valueNow() - 1 )

                if ( status == 'data' and (self._activeDcpDownloadCountB:valueNow() == 0 ) ) then

                    self._updateAcrProfileDatabase:onNext( true )

                end

				return status, info

			end )

            :mapP(function ( status, info )

                if ( status == 'data' ) then

                    local innerResultP = self._catalog:docStore():applyOverlayToDocumentP {
                        docType = 'asset',
                        docId = binaryInfo.assetId,
                        overlay = {
                            _localOnly = {
                                -- fileName = profile.filename,
                                files = {
                                    dcpProfile = {
                                        sha256 = binaryInfo.sha256
                                    }
                                }
                            }
                        },
                    }
                    return innerResultP

                end

                return status, info

            end)
	end

	return function( row )

		local missingBinaries = {}

		local assetId = row[ 1 ]
        local url = row [ 2 ]
        local filename = row [ 3 ]
        local sha256 = row [ 4 ]

        table.insert( missingBinaries, {
            assetId = assetId,
            renditionName = 'dcpProfile',
            masterUrl = url,
            type = 'camera',
            filename = filename,
            sha256 = sha256,
            userProfile = true,
        })

		return KSPromises.allP( downloadRenditionP, missingBinaries )

	end

end

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

local function makeDownloadPresetBinariesFn( self )

	local function downloadRenditionP( binaryInfo )

		return self._renditionServer
			:call( asKSPromise )
			:mapDataP_new( function( renditionServer )

				return self:downloadSingleRenditionP( renditionServer, binaryInfo )

			end )

			:mapP( function( status, info )

				if status == 'data' then

					self._updateAcrPresetProfileDatabase:onNext( true )

				end

				return status, info

			end)
	end

	return function( row )

		local missingBinaries = {}
		
		local assetId = row[ 1 ]

		table.insert( missingBinaries, {
					assetId = assetId,
					renditionName = 'presetProfile',
				} )

		return KSPromises.allP( downloadRenditionP, missingBinaries )
		
	end
	
end

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

local function makeDownloadAssetBinariesFn( self )

	local function downloadRenditionP( binaryInfo )

		self._activeDownloadCountB:set( self._activeDownloadCountB:valueNow() + 1 )

		return self._renditionServer
			:call( asKSPromise )
			:mapDataP_new( function( renditionServer )

				return self:downloadSingleRenditionP( renditionServer, binaryInfo )

			end )

			:mapP( function( ... )
			
				self._activeDownloadCountB:set( self._activeDownloadCountB:valueNow() - 1 )
				
				return ...
				
			end )
		
	end
	
	return function( row )
	
		local missingBinaries = {}
		
		local assetId = row[ 1 ]
		local renditionName = row[ 2 ]
		local albumId = row[ 3 ]
		
		self._activeAlbumB:set( albumId )
		
		table.insert( missingBinaries, {
					assetId = assetId,
					renditionName = renditionName,
				} )
		
		if renditionName == 'proxy' then
		
			table.insert( missingBinaries, {
				assetId = assetId,
				renditionName = 'profiles'
			} )
			
		end
		
		return KSPromises.allP( downloadRenditionP, missingBinaries )
		
	end
	
end

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

function ozBinaryDownloaderMethods:__init( args )

	self._catalog = assert( args.catalogService )
--	assert( OzCatalogService:isClassOf( self._catalog ) )
	
	self._renditionServer = assert( args.renditionServer )
--	assert( WFOzRenditionServer:isClassOf( self._renditionServer ) )
	
	self._LrmOzProfileModule = assert( args.LrmOzProfileModule )
	
	self._indexStore = self._catalog:indexStore()
	
	self._updateAcrProfileDatabase = assert( args.updateAcrProfileDatabase )
	
	self._downloadOneBinaryFn = makeDownloadAssetBinariesFn( self )
	
	if WFPlatformInfo.isAndroid then

		self._updateAcrPresetProfileDatabase = assert( args.updateAcrPresetProfileDatabase )

		self._downloadDcpBinaryFn = makeDownloadDcpBinariesFn(self)
		self._downloadPresetBinaryFn = makeDownloadPresetBinariesFn(self)

		self:createReactiveVariable( '_activeDcpDownloadCountB', 0 )

	end

	self:createReactiveVariable( '_activeDownloadCountB', 0 )
	
	self:createReactiveVariable( '_activeAlbumB', nil )
	
	self._binaryStrategy = args.binaryStrategy
	
	self._enableDirectDownload = args.enableDirectDownload
	
	WFDiagnosticLog.addLogProvider( self )
	
end

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

function ozBinaryDownloaderMethods:isDownloading()

	return self._activeDownloadCountB:valueNow() > 0
	
end

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

function ozBinaryDownloaderMethods:isDownloadingDcpsB()

    return self._activeDcpDownloadCountB
    :closingMapB( function(activeDcpDownloadCount)

        return activeDcpDownloadCount > 0

    end ):relayB()

end

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

function ozBinaryDownloaderMethods:isDownloadingB()

	return self._activeDownloadCountB
		:closingMapB( function( activeDownloadCount )
		
			return activeDownloadCount > 0 
			
		end ):relayB()
	
end

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

function ozBinaryDownloaderMethods:activeAlbumB()

	return self._activeAlbumB:relayB()
	
end

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

function ozBinaryDownloaderMethods:stopDownloading()

	if self._workScheduler then
	
		self._workScheduler:close()

		if WFPlatformInfo.isAndroid then

			self._activeDownloadCountB:set ( 0 )

		end

		self._workScheduler = nil
		
	end
	
end

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

function ozBinaryDownloaderMethods:stopDownloadingPresets()

    if self._presetWorkScheduler then

        self._presetWorkScheduler:close()
        self._presetWorkScheduler = nil

    end

end

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

function ozBinaryDownloaderMethods:stopDownloadingDcps()

    if self._dcpWorkScheduler then

        self._dcpWorkScheduler:close()
        self._dcpWorkScheduler = nil
        self._activeDcpDownloadCountB:set( 0 )

    end

end

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

function ozBinaryDownloaderMethods:startDownloading( strategy )

	WFArguments.check( 'LrmOzBinaryDownloader', 'binaryStrategy' )
	
	if not self._workScheduler or strategy ~= self._binaryStrategy then

		local masterRenditionNames, proxyRenditionNames = LrmCatSyncStatus.getDownloadRenditionNames()
	
		local masterSql =  [[SELECT r.docId, renditionName, album_asset.albumId FROM
					renditionRevisions as r
				INNER JOIN album_asset ON album_asset.assetId = r.assetId
				INNER JOIN albums ON albums.docId = album_asset.albumId AND albums.useOffline = 1
				LEFT JOIN space s ON s.docId = albums.spaceId
				LEFT JOIN space_user su ON s.spaceUserId = su.fullDocId
				WHERE
					( albums.spaceId IS NULL OR su.role == 'editor' ) 
					AND renditionName IN CARRAY(?,?,'char*')
					OR ( albums.spaceId IS NOT NULL AND ( su.role IS NULL OR su.role != 'editor' ) 
					AND renditionName IN CARRAY(?,?,'char*') )
				]]

		local proxySql = [[SELECT r.docId, renditionName, album_asset.albumId FROM
					renditionRevisions as r
				INNER JOIN album_asset ON album_asset.assetId = r.assetId
				INNER JOIN albums ON albums.docId = album_asset.albumId and albums.useOffline = 1
				WHERE
					renditionName IN CARRAY(?,?,'char*')
				]]

		local queryArgs

		if strategy == LrmConfig.StrategyPreferMaster then
			queryArgs = { 
				sql = masterSql, 
				masterRenditionNames,
				#masterRenditionNames,
				proxyRenditionNames,
				#proxyRenditionNames
			}
		else
			queryArgs = { 
				sql = proxySql, 
				proxyRenditionNames,
				#proxyRenditionNames
			}
		end

		local queryB = self._indexStore:queryB( queryArgs )
		self._binaryStrategy = strategy
		
		self:setOwned {
			_workScheduler = WFQueryDrivenWorkQueueScheduler( queryB,
															   5, --self._uploadQueueOptions.concurrentTasks or 2,
															   40, --self._uploadQueueOptions.blackout or 60,
															   self._downloadOneBinaryFn )
		}
		
	end
	
end

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

function ozBinaryDownloaderMethods:startDownloadingPresets( strategy )

	if not self._presetWorkScheduler or strategy ~= self._binaryStrategy then

		local sql = [[SELECT
						docId from presetsProfiles WHERE needsSave = 1
					LIMIT 20]]

		local queryB = self._indexStore:queryB( sql )
		self._binaryStrategy = strategy

		self:setOwned {
			_presetWorkScheduler = WFQueryDrivenWorkQueueScheduler( queryB,
				5, --self._uploadQueueOptions.concurrentTasks or 2,
				40, --self._uploadQueueOptions.blackout or 60,
				self._downloadPresetBinaryFn )
		}

	end

end

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

function ozBinaryDownloaderMethods:startDownloadingDcps( modelName)

    -- First convert modelName to upper case characters
    modelName = modelName and KSStringUtils.upper( modelName ) or modelName

	if not self._dcpWorkScheduler then

		local sql = [[ SELECT docId, url, filename, sha256, digest from CustomCameraProfiles WHERE needsSave = 1 AND
		                modelName = ? LIMIT 20 ]]

		local queryB = self._indexStore:queryB( sql, modelName)

		self:setOwned {
			_dcpWorkScheduler = WFQueryDrivenWorkQueueScheduler( queryB,
				5, --self._uploadQueueOptions.concurrentTasks or 2,
				40, --self._uploadQueueOptions.blackout or 60,
				self._downloadDcpBinaryFn )
		}

	end

end

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

function ozBinaryDownloaderMethods:sendDiagnosticLogData()

	return {

		name = "Lrm Oz Binary Downloader",
		id = 'LrmOzBinaryDownloader',
		weight = 510,

		body = {
		}
	}
	
end

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

local BinaryDownloaderClass = Rx.Class 'LrmOzBinaryDownloader'( ozBinaryDownloaderMethods )

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

return BinaryDownloaderClass
