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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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.oz_client.managed_catalog
local WFOzSyncConstants = require "WFOzSyncConstants"

	-- WF.core.utils
local WFCloseSupport = require 'WFCloseSupport'
local KSPrefs = require "KSPrefs"
local WFRxScheduler = require "WFRxScheduler"

	-- WF.core.os
local WFFileUtils = require 'WFFileUtils'
local WFDeviceInfo = require 'WFDeviceInfo'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFArguments = require 'WFArguments'

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

local WFLogger = require 'WFLogger'

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

local check = WFArguments.check

local logger = WFLogger( 'LrmSyncManager' )
	-- :setLevel( 'debug' )

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

local syncStates = {
	stopped = 1,
	residual = 2,
	normal = 3,
	noBinaryUploader = 4,
	noOzCatalog = 5
}

local currentSyncState
	-- Used to keep track of the current state of the sync manager.

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

local function controlSyncActivity( catalogId, entitlement, reachability, isWiFiOnlySync, forceUpgrade, isAuthenticatedUser,
									syncEnabled, isAppInBackground, setIsUploading, freeSpace, isGeoRestrictedAccount )

	local minimumFreeSpace = 300 * 1024 * 1024
	local shouldSync = true
	local hasOzCatalog = true
	local isResidualMode = false

	if not syncEnabled then
		shouldSync = false
	end

	if WFPlatformInfo.isAndroid and isAppInBackground then
		shouldSync = false
	end

	if reachability == 'unreachable' or ( reachability == 'wwan' and isWiFiOnlySync ) then
		shouldSync = false
	end

	if not WFPlatformInfo.isAndroid and isGeoRestrictedAccount then
		shouldSync = false
	end

	if entitlement and ( entitlement.status == 'trial_expired' or entitlement.status == 'subscription_expired' ) then
		isResidualMode = true
	end

	if entitlement and entitlement.status == 'created' then
		hasOzCatalog = false
	end

	if catalogId == WFOzSyncConstants.kGlobalDocumentId then
		hasOzCatalog = false
	end

	if WFPlatformInfo.isAndroid and freeSpace and freeSpace < minimumFreeSpace then
		shouldSync = false
	end

	if forceUpgrade then
		shouldSync = false
	end

	if not isAuthenticatedUser then
		shouldSync = false
		hasOzCatalog = false
	end

	local isBinaryUploading = shouldSync and hasOzCatalog and not isResidualMode and setIsUploading

	return shouldSync, hasOzCatalog, isResidualMode, isBinaryUploading, isAppInBackground

end

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

local function syncClientManager( _, syncClient, shouldSync, hasOzCatalog, isResidualMode, setIsUploading, isAppInBackground )

	check( '_', 'LrmSyncClient', 'boolean', '?boolean', '?boolean', '?boolean', '?boolean' )

	logger:debugf( 'shouldSync: %s hasOzCatalog: %s isResidualMode: %s, setIsUploading: %s, isAppInBackground: %s',
		tostring( shouldSync ), tostring( hasOzCatalog ), tostring( isResidualMode ), tostring( setIsUploading ), tostring( isAppInBackground ) )

	local obs = Rx.Observable.just( true )

	local newSyncState

	if shouldSync then

		if hasOzCatalog then

			if isResidualMode then

				if currentSyncState ~= syncStates.residual then

					newSyncState = syncStates.residual

					obs = syncClient:stopSync()
							:concat( syncClient:startSyncInResidualMode() )

				end

			elseif setIsUploading then

				if currentSyncState ~= syncStates.normal then

					newSyncState = syncStates.normal

					logger:infof( 'starting normal sync' )

					obs = syncClient:stopSync()
							:concat( syncClient:startSync() )

				end

			elseif currentSyncState ~= syncStates.noUploader then

				newSyncState = syncStates.noUploader

				logger:infof( 'starting sync without uploader' )

				obs = syncClient:stopSync()
						:concat( syncClient:startSyncWithoutUploader() )

			end

		elseif currentSyncState ~= syncStates.noOzCatalog then

			newSyncState = syncStates.noOzCatalog

			logger:infof( 'starting space sync without Oz catalog' )

			obs = syncClient:stopSync()
					:concat( syncClient:startSpaceSyncWithoutOzCatalog() )

		end

	elseif currentSyncState ~= syncStates.stopped then

		newSyncState = syncStates.stopped

		logger:infof( 'stopping sync' )

		obs = syncClient:stopSync()

	end

	local maybeEnableBackgroundUpload = function()
		if WFPlatformInfo.isiOS then
			-- ONLY possible on iOS platform. Source of CSO_202212190022
			return  syncClient:enableBackgroundUpload( isAppInBackground )
		else
			return Rx.Observable.just( false )
		end

	end

	obs = obs

		:concat( Rx.Observable.just( true ) )

		:takeLast( 1 ) -- Make sure the following `map` only runs once, after the work to modify sync is done.

		:map( function( ... ) -- Map instead of tap, because tap is evil.

			-- Track the current sync status, but only once it reflects the truth
			-- of what the current system is doing.

			if newSyncState then
				currentSyncState = newSyncState
			end

			return ...

		end )

		:concat( maybeEnableBackgroundUpload() )

	return obs

end

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

local lrmSyncManagerMethods = {

	class = {
		syncClientManager = syncClientManager
	},

	_syncActivityControl = false,

	_isWifiOnlySync = false,

	_isReachable = false,

	_catalog = false,

	WFCloseSupport.addCloseSupportMethods,

}

--------------------------------------------------------------------------------
local function makeDiskSpaceWatchdog( catalog )

	local freeSpace = Rx.Observable.just (0)

	if WFPlatformInfo.isAndroid then

		freeSpace = Rx.Observable.interval( 60, WFRxScheduler.sharedScheduler() )

		:startWith(0)
		:flatMapLatest(function()

			return catalog

			:getDataCachePath()

			:map( function( path )

				if WFPlatformInfo.isAndroid then
					local KSPathUtils = require 'KSPathUtils'
					path = KSPathUtils.parent (path)
				end

				local fileSystemFreeSpace, _ -- fileSystemSize
				= WFFileUtils.freeSpaceAtPath( path )

				return fileSystemFreeSpace

			end )

		end)

	end

	return freeSpace

end

function lrmSyncManagerMethods:__init( args )

	local catalog = assert( args.catalog )

	local catalogId = Rx.Observable.just( assert( args.catalogId ) )
	local entitlement = assert( args.entitlement )
	local isReachable = assert( args.isReachable )
	local isWiFiOnlySync = assert( args.isWiFiOnlySync )
	local forceUpgrade = assert( args.forceUpgrade )
	local isAuthenticatedUser = assert( args.isAuthenticatedUser )
	local syncEnabled = assert( args.syncEnabled )
	local isAppInBackground = assert( args.isAppInBackground )
	local setIsUploading = Rx.Observable.just( true )
	local freeSpace = makeDiskSpaceWatchdog( catalog )
	local isGeoRestrictedAccountStream = Rx.Observable.just( false )

	if WFPlatformInfo.isAndroid then
		setIsUploading = assert( args.setIsUploading )
	else
		isGeoRestrictedAccountStream = assert( args.isGeoRestrictedAccountStream )
	end

	local syncActivityControl = Rx.Observable.combineLatest( controlSyncActivity,
		catalogId, entitlement, isReachable, isWiFiOnlySync, forceUpgrade, isAuthenticatedUser,
		syncEnabled, isAppInBackground, setIsUploading, freeSpace, isGeoRestrictedAccountStream )
		:distinctUntilChanged()

	self._syncActivityControl = syncActivityControl
	self._isWifiOnlySync = isWiFiOnlySync
	self._isReachable = isReachable
	self._catalog = catalog
	currentSyncState = nil

	WFDiagnosticLog.addLogProvider( self )

end

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

function lrmSyncManagerMethods:close()

end

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

function lrmSyncManagerMethods:getSyncActivityControl()

	return self._syncActivityControl

end

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

function lrmSyncManagerMethods:sendDiagnosticLogData()

    local hasUserPausedSync = KSPrefs.syncPausedByUser or false

	return {

		name = "Lrm Sync Manager",
		id = 'LrmSyncManager',
		weight = 520,

		body = {
			isWifiOnlySync = self._isWifiOnlySync,
			hasUserPausedSync = hasUserPausedSync,
			uniqueDeviceId = WFDeviceInfo.uniqueDeviceId(),
			reachability = self._isReachable,
			userInfo = self._catalog:getCatUser()
						:flatMapLatest( function( user )
							return user:userInfoCache():docValue( user:userId() )
						end ),
			systemStats = WFPlatformInfo.getSystemStats(),
			migrationStats = self._catalog:docStore():localDocValue( 'migratedFromV1' )
				:map( function( docValue )

					if docValue._empty then
						return {
							docstoreMigratedFromV1 = false,
						}
					end

					docValue._id = nil

					return docValue

				end ),
			catalogAssetCount = self._catalog:indexStore()
				:queryP( 'SELECT count(docId) from coreInfo' )
				:mapDataP_new( function( row )

					return row and row[ 1 ] and row[ 1 ][ 1 ] or 0

				end )
				:runUntilDoneP(),
			catalogAlbumCount = self._catalog:indexStore()
				:queryP( 'SELECT count(docId) from albums' )
				:mapDataP_new( function( row )

					return row and row[ 1 ] and row[ 1 ][ 1 ] or 0

				end )
				:runUntilDoneP(),
			catalogPersonCount = self._catalog:indexStore()
			:queryP( 'SELECT count(docId) from persons' )
			:mapDataP_new( function( row )

				return row and row[ 1 ] and row[ 1 ][ 1 ] or 0

			end )
			:runUntilDoneP(),
			catalogFacesCount = self._catalog:indexStore()
			:queryP( 'SELECT count(docId) from faces' )
			:mapDataP_new( function( row )

				return row and row[ 1 ] and row[ 1 ][ 1 ] or 0

			end )
			:runUntilDoneP(),
		}
	}

end

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

return Rx.Class( 'LrmSyncManager', lrmSyncManagerMethods )
