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

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.web.http_client
local WFReachability = require "WFReachability"

	-- WF.oz_client.catalog
local WFOzRenditionServer = require "WFOzRenditionServer"
local OzCatalogServiceViaWFCatalog = require 'OzCatalogServiceViaWFCatalog'

	-- WF.oz_client.managed_catalog
local WFCatalog = require 'WFCatalog'
local WFOzSyncConstants = require "WFOzSyncConstants"

	-- WF.core.utils
local KSPathUtils = require 'KSPathUtils'
local WFArguments = require 'WFArguments'

	-- WF.core.os
local WFFileUtils = require "WFFileUtils"

	-- WF.lrmobile
local LrmOzBinaryDownloader = require 'LrmOzBinaryDownloader'
local LrmOzBinaryDownloaderUtils = require "LrmOzBinaryDownloaderUtils"
local LrmSyncManager = require "LrmSyncManager"
local LrmLibraryModelFactory = require 'LrmLibraryModelFactory'
local LrmWFOzProfileUtils = require 'LrmWFOzProfileUtils'
local LrmSyncClient = require 'LrmSyncClient'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFCapabilities = require 'WFCapabilities'

--------------------------------------------------------------------------------
local LrmTHLibraryLoaded

if not WFPlatformInfo.isAndroid then
    LrmTHLibraryLoaded = pcall( require, 'LrmTHLibrary' )
end
local runUntilDone = require 'WFRxRunUntilDone'

local logger = require 'WFLogger' 'LrmCatLibraryModel'
--logger:enable( 'print' )

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

local LrmCatLibraryModel = {}

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

local currentWFCatalog
local currentRenditionServer

function LrmCatLibraryModel.makeLibraryModel( catSpec, isSyncEnabled, binaryPreference, locale )

	WFArguments.check( 'table', 'boolean', 'binaryStrategy', 'string' )

	local indexSpecScript = 'LrmCatalogIndexDefinitions.lua'
	
	local catalogId
	
	local wfCatalogStream = catSpec
		:take( 1 )
		:map( function( wfCatSpec )

		catalogId = wfCatSpec:catalogId()

		local databasePath = ''
		if WFPlatformInfo.isAndroid then
			-- Reason for having this block is android has different paths for database and preview database
			local rootDir = KSPathUtils.getStandardFilePath 'carouselDocumentsInternal'
			local path = KSPathUtils.child( rootDir, wfCatSpec:catalogId() )
			databasePath = KSPathUtils.child( path, "Managed Catalog.mcat" )
		end
		
		local enablePDL = WFPlatformInfo.isAndroid 
			and WFCapabilities.getCapability( "LrmCatLibraryModel.enablePlatformIndexStore" ) == "true"

		local catalogOptions = {
				indexSpecScript = indexSpecScript,
				docStorePath = databasePath,
				noCatalogSearch = true,
				indexStoreReadThreads = 2,
				enablePlatformIndexStore = enablePDL,
				docStoreInitProgressRequested = true
			}
			
			local rootCatalogPath = KSPathUtils.getStandardFilePath 'carouselDocuments'
	
			local cat = WFCatalog( wfCatSpec, rootCatalogPath, catalogOptions )
			
			if currentWFCatalog then
				currentWFCatalog:close()
			end

			cat:getSimpleDocStoreUpdater():start()

			currentWFCatalog = cat
			
			return cat
		
		end )
		:shareReplay( 1 )
	
	
	local catalog = OzCatalogServiceViaWFCatalog( wfCatalogStream )
	
	local binaryDownloadStrategy = Rx.ReplaySubject( 1 )
	binaryDownloadStrategy:onNext( binaryPreference )

	local syncEnabled = Rx.ReplaySubject( 1 )
	syncEnabled:onNext( isSyncEnabled )

	local setIsUploading = Rx.ReplaySubject( 1 )
	setIsUploading:onNext( true )

	local isAppInBackground = Rx.ReplaySubject( 1 )
	isAppInBackground:onNext( false )

	local renditionServer = Rx.Observable.combineLatest( function( wfCatalog, dataCache )
	
			local ozRenditionServer = WFOzRenditionServer( catalog,
										wfCatalog:ozUser(),
										wfCatalog:ozCatalogId(),
										dataCache, wfCatalog )
					
			currentRenditionServer = ozRenditionServer
			
			return ozRenditionServer	
			
		end, wfCatalogStream, catalog:getDataCache() )
	
		:shareReplay( 1 )

	local updateAcrProfileDatabase = Rx.ReplaySubject( 1 )
	local updateAcrPresetProfileDatabase = Rx.ReplaySubject( 1 )

	local isWiFiOnlySync = Rx.ReplaySubject( 1 )
	isWiFiOnlySync:onNext( true )
	
	local isInLoupe = Rx.ReplaySubject( 1 )
	isInLoupe:onNext( false )

	local forceUpgrade = Rx.ReplaySubject( 1 )
	forceUpgrade:onNext( false )
	
	local enableDirectDownload = WFPlatformInfo.isAndroid 
			and WFCapabilities.getCapability( "LrmCatLibraryModel.enableDirectDownload" ) ~= "false"
			
	local binaryDownloader = LrmOzBinaryDownloader {
						catalogService = catalog,
						renditionServer = renditionServer,
						updateAcrProfileDatabase = updateAcrProfileDatabase,
						updateAcrPresetProfileDatabase = updateAcrPresetProfileDatabase,
						LrmOzProfileModule = LrmWFOzProfileUtils,
						binaryStrategy = binaryPreference,
						enableDirectDownload = enableDirectDownload,
					}

	catalog:setBinaryDownloader(binaryDownloader)

	local user = wfCatalogStream
		:map( function( wfCatalog )
		
			return wfCatalog:ozUser()
	
		end )
		:shareReplay( 1 )
	
	local entitlement = user
	
		:flatMap(function( wfCatUser )
	
			return wfCatUser:entitlement()
			
		end )
		
		
		:map(function( entitlement )
			
			-- update created accounts to enable space sync
			if entitlement and entitlement.status == 'created' then
					
					syncEnabled:onNext( true )
					isWiFiOnlySync:onNext( false )
			end
			
			return entitlement
			
		end )
							
	local isReachable = user
		:flatMap( function( wfCatUser )
	
			return WFReachability.hostIsReachable( wfCatUser:serverVersionUrl() or wfCatUser:serverBaseUrl() )
			
		end )
		:shareReplay( 1 )
	
	local hasValidConnectionToOz = user
		:flatMap( function ( wfCatUser )
		
			if wfCatUser:userId() == WFOzSyncConstants.kGlobalDocumentId then
				return Rx.Observable.just( false )
			end
			
			return wfCatUser:hasValidConnectionToOz()
		
		end )
	
	local enableBinaryDownloader = LrmOzBinaryDownloaderUtils.makeBinaryDownloaderManager {
		catalog = catalog,
		binaryDownloader = binaryDownloader,
		isReachable = isReachable,
		isWiFiOnlySync = isWiFiOnlySync,
		isSyncEnabled = syncEnabled,
		isInLoupe = isInLoupe,
		isAuthenticatedUser = hasValidConnectionToOz,
		forceUpgrade = forceUpgrade,
		binaryDownloadStrategy = binaryDownloadStrategy,
		isAppInBackground = isAppInBackground,
		entitlement = entitlement,
	}

	local binaryDownloaderSubscription = enableBinaryDownloader
		:subscribe( function( downloadBinaries, strategy, downloadPresetBinaries)

			if downloadBinaries then
				binaryDownloader:startDownloading( strategy )
            else
				binaryDownloader:stopDownloading()
			end

			if WFPlatformInfo.isAndroid then
				if downloadPresetBinaries then
					binaryDownloader:startDownloadingPresets(strategy)
				else
					binaryDownloader:stopDownloadingPresets()
					binaryDownloader:stopDownloadingDcps()
				end
			end

	end )

	-- INFO [pkamin 2014-09-22]: do not backup the previewCache as it can also become very large
	-- INFO [jrechtie 2017-06-27]: Freemium case: we want to backup the previewCache, because renditions can't be recreated.

	Rx.Observable.combineLatest( function ( path, wfCatUser )

		if wfCatUser:userId() == WFOzSyncConstants.kGlobalDocumentId then
			-- old installations up to release 2.7.4 might have the backup flag disabled
			-- explicitly re-enable it here for the Freemium case.
				WFFileUtils.removeDoNotBackUpFlag( path )
				WFFileUtils.removeDoNotBackUpFlag( path .. '-shm' )
				WFFileUtils.removeDoNotBackUpFlag( path .. '-wal' )
			else
				WFFileUtils.setDoNotBackUpFlag( path )
				WFFileUtils.setDoNotBackUpFlag( path .. '-shm' )
				WFFileUtils.setDoNotBackUpFlag( path .. '-wal' )
			end
			return path
		end,
		catalog:getDataCachePath(), user )
		:take(1)
		:call( runUntilDone )

	local syncManager = LrmSyncManager {
		catalog = catalog,
		catalogId = catalogId,
		isWiFiOnlySync = isWiFiOnlySync,
		isReachable = isReachable,
		forceUpgrade = forceUpgrade,
		isAuthenticatedUser = hasValidConnectionToOz,
		entitlement = entitlement,
		syncEnabled = syncEnabled,
		isAppInBackground = isAppInBackground,
		setIsUploading = setIsUploading
	}
	
	local dependentDisposible = Rx.CompositeDisposable()

	local syncSubscription = wfCatalogStream
	
		:flatMap( function( wfCatalogObj )
		
			local syncClient = catalog:getSyncClient()
			
			if not syncClient then
				
				syncClient = LrmSyncClient( wfCatalogObj, indexSpecScript, locale, nil )
				
				syncClient:setBinaryStrategy( binaryPreference ):call( runUntilDone )
				
				dependentDisposible:add( syncClient:getDeleteFeedCallback()

					:tap( function( status )

						updateAcrPresetProfileDatabase:onNext( status )

					end )

					:subscribe() )

				catalog:setSyncClient( syncClient )

			end
		
			local accountIsUnderMaintenanceStream = syncClient:getOzMaintenanceNotificationStream()
			
				:filter( function( ozMaintenanceInfo )
				
					return ozMaintenanceInfo and ozMaintenanceInfo.id == 'catalog'
				
				end )
			
				:map( function( ozMaintenanceInfo )
		
					return ozMaintenanceInfo.isUnderMaintenance
				
				end )
                        
			return Rx.Observable.combineLatest(
					binaryDownloadStrategy, setIsUploading,
					accountIsUnderMaintenanceStream:startWith( false ),
					syncManager:getSyncActivityControl() )
				:distinctUntilChanged()

				:flatMap( function( strategy, setIsUploading, accountIsUnderMaintenance,
							shouldSync, hasOzCatalog, isResidualMode, isBinaryUploading, isAppInBackground )

					logger:infof('syncSubscription %s shouldSync: %s, hasOzCatalog: %s, isResidualMode: %s, isAppInBackground: %s, isBinaryUploading: %s, accountIsUnderMaintenance: %s',
						wfCatalogObj:ozCatalogId(), tostring(shouldSync), tostring(hasOzCatalog), tostring(isResidualMode),
						tostring(isAppInBackground), tostring(isBinaryUploading), tostring(accountIsUnderMaintenance) )

					if hasOzCatalog and not isResidualMode then
						dependentDisposible:add( syncClient:startContinuousPurge():subscribe() )
					end

					shouldSync = shouldSync and not accountIsUnderMaintenance

					local modifySyncObservable = LrmSyncManager.syncClientManager( syncClient, shouldSync, hasOzCatalog, 
						isResidualMode, setIsUploading, isBinaryUploading, isAppInBackground )

					local accountSyncAction

					if not isAppInBackground then
						accountSyncAction = syncClient:startAccountsSync()
					else
						accountSyncAction = syncClient:stopAccountsSync()
					end

					return modifySyncObservable

						:merge( syncClient:setBinaryStrategy( strategy ) )

						:merge( accountSyncAction )

						:map( function()
							return wfCatalogObj, strategy
						end )

				end )
				
		end ):subscribe()
		
	local documentObserver

	if LrmTHLibraryLoaded then

		local docStoreFromCatalog = wfCatalogStream
			:map( function( catalog )
				return catalog:docStore()
			end )
			:shareReplay( 1 )

		documentObserver = docStoreFromCatalog
			:flatMap( function( docStore )
				return docStore:scannedFilteredMappedChangesFeed( 'LrmTHLibrary', 'scanFilterMap', 'THLibrary-scanToken' )
			end )
			:subscribe()

	end

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

	return {
		_syncSubscription = syncSubscription,
		_binaryDownloaderSubscription = binaryDownloaderSubscription,
		catalog = catalog,
		_wfCatalog = currentWFCatalog,
		_wfCatalogStream = wfCatalogStream,
		_renditionServer = renditionServer,
		_avatarServer = nil, -- lazily initialized
		_binaryDownloader = binaryDownloader,
		_updateAcrProfileDatabase = updateAcrProfileDatabase,
		_updateAcrPresetProfileDatabase = updateAcrPresetProfileDatabase,
		_isWiFiOnlySync = isWiFiOnlySync,
		_isReachable = isReachable,
		_isInLoupe = isInLoupe,
		_documentObserver = documentObserver,
		_dependentDisposible = dependentDisposible,
		
		_binaryDownloadStrategy = binaryDownloadStrategy,
		_syncEnabled = syncEnabled,
		_isAppInBackground = isAppInBackground,
		_setIsUploading = setIsUploading,

		_syncManager = syncManager,
		
		value = Rx.ReplaySubject( 1 ),
		
		models = LrmLibraryModelFactory.makeLibrarySubModels(),
		
		events = {
			init = function()
				
			end,
			
			shutdownDatabases = function( self )
			
				catalog:docStore():halt()
				catalog:indexStore():halt()
			
			end,
			
			purgeAllEligible = function( self, binaryStrategy )
			
				catalog:getSyncClient():purgeImmediately():call( runUntilDone )

			end,

			applyOverlayToDocument = function( parent, docType, docId, overlay )
			
				overlay.type = docType
				overlay._id = docId
			
				parent.catalog:docStore():applyOverlay( overlay )

				:call( runUntilDone )
				
			end,
			
			applyOverlay = function( parent, overlay )
			
				parent.catalog:docStore():applyOverlay( overlay )
					:call( runUntilDone )
				
			end,
			
			bulkApplyOverlay = function( parent, overlay )
			
				parent.catalog:docStore():bulkApplyOverlay( overlay )
					:call( runUntilDone )
					
			end,
			
			setSyncEnabled = function( self, isEnabled )
			
				self._syncEnabled:onNext( isEnabled )
				
			end,
			
			setSyncIsPaused = function( self, isPaused)

				local syncEnable = not isPaused

				self._syncEnabled:onNext(syncEnable)

			end,
		},
		
		close = function( self )
			
			if self._syncSubscription then
				self._syncSubscription:dispose()
			end
			
			if self._binaryDownloaderSubscription then
				self._binaryDownloaderSubscription:dispose()
			end
			
			if self._binaryDownloader then
				self._binaryDownloader:close()
			end
			if self._dependentDisposible then
				self._dependentDisposible:dispose()
			end
			
			self._updateAcrProfileDatabase:dispose()
			self._updateAcrPresetProfileDatabase:dispose()
			
			self._isWiFiOnlySync:dispose()
			self._isInLoupe:dispose()
			
			self._binaryDownloadStrategy:dispose()
			self._syncEnabled:dispose()
			self._isAppInBackground:dispose()
			self._setIsUploading:dispose()

			self._binaryDownloader = nil
			self._updateAcrProfileDatabase = nil
			self._updateAcrPresetProfileDatabase = nil
			self._enableBinaryDownloader = nil
			self._isWiFiOnlySync = nil
			self._isReachable = nil
			self._isInLoupe = nil
			self._binaryDownloadStrategy = nil
			self._syncEnabled = nil
			self._isAppInBackground = nil

			if self._syncManager then
				self._syncManager:close()
			end
			
			if currentWFCatalog then
				currentWFCatalog:close()
				currentWFCatalog = nil
				self._wfCatalog = nil
			end
			
			if currentRenditionServer then
				currentRenditionServer:close()
				currentRenditionServer = nil
			end

			if self._documentObserver then
				self._documentObserver:dispose()
				self._documentObserver = nil
			end

			self._syncManager = nil
			
			if self._avatarServer then
				self._avatarServer:close()
				self._avatarServer = nil
			end
						
			catalog:close()
			
			collectgarbage( "collect" )
			collectgarbage( "collect" )
			collectgarbage( "collect" )
			
		end
	}
end

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

return LrmCatLibraryModel
