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

NmbUserModel_LocalStorage.lua

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

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

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

	-- Rx
local Rx = require 'Rx'
local Observable = Rx.Observable
local BehaviorSubject = Rx.BehaviorSubject

	-- WF.core
local WFFileUtils = require 'WFFileUtils'
local WFCloseSupport = require 'WFCloseSupport'

	-- WF.oz_client.managed_catalog
local WFCatSyncStatus = require 'WFCatSyncStatus'
local WFCatVolumeAgent = require 'WFCatVolumeAgent'

	-- lightroomcc.file_management
local NmbFileManagementSettings = require 'NmbFileManagementSettings'
local NmbStorage = require 'NmbStorage'

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

local function extractStatus( self, statusTable )
	
	local _allVolumes = self._allVolumes
	
	if statusTable then
	
		local volumeGuid = statusTable.volumeGuid
		
		if not statusTable.isOnline then
			_allVolumes.offlineVolumes[ volumeGuid ] = true
		else
			_allVolumes.offlineVolumes[ volumeGuid ] = nil
		end

		self._offlineVolumesStream:onNext( _allVolumes.offlineVolumes )
		
		if statusTable.isOutOfSpace then
			_allVolumes.volumesOutOfSpace[ volumeGuid ] = true
		else
			_allVolumes.volumesOutOfSpace[ volumeGuid ] = nil
		end
		
		self._volumesOutOfSpaceStream:onNext( _allVolumes.volumesOutOfSpace )
		
		if statusTable.isOverTargetDiskSpaceUsage then
			_allVolumes.volumesOverDiskSpaceTarget[ volumeGuid ] = true
		else
			_allVolumes.volumesOverDiskSpaceTarget[ volumeGuid ] = nil
		end
		
		if statusTable.isOverIdleSpaceThreshold then
			_allVolumes.volumesOverIdleSpaceThreshold[ volumeGuid ] = true
		else
			_allVolumes.volumesOverIdleSpaceThreshold[ volumeGuid ] = nil
		end
		
		_allVolumes.remainingSpace[ volumeGuid ] = statusTable.remainingSpace
		
		if statusTable.isReadOnly then
			_allVolumes.readOnlyVolumes[ volumeGuid ] = statusTable.isReadOnly
		else
			_allVolumes.readOnlyVolumes[ volumeGuid ] = nil
		end
		
		self._readOnlyVolumesStream:onNext( _allVolumes.readOnlyVolumes )
		
	end

end

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

local function monitorVolumes( self, wfCatalog, mainVolumeAgent, 
		maybeOriginalVolumeAgent, maybeProxyVolumeAgent, idleDownloader, 
		fileMover, binaryUploader, styleDownloader )

	local volumeStatusStreamsSubscription = Observable.combineLatest( 
			mainVolumeAgent:getVolumeStatusStream(),
			maybeOriginalVolumeAgent and maybeOriginalVolumeAgent:getVolumeStatusStream() 
					or Observable.just( nil ),
			maybeProxyVolumeAgent and maybeProxyVolumeAgent:getVolumeStatusStream() 
					or Observable.just( nil ) )
			
		:subscribe( function( mainVolumeStatusTable, originalVolumeStatusTable, proxyVolumeStatusTable )
			
			extractStatus( self, mainVolumeStatusTable )
			extractStatus( self, originalVolumeStatusTable )
			extractStatus( self, proxyVolumeStatusTable )
			
			idleDownloader:reportStatusOfVolumes( self._allVolumes )
			fileMover:reportStatusOfVolumes( self._allVolumes )		
			binaryUploader:reportStatusOfVolumes( self._allVolumes )
			styleDownloader:reportStatusOfVolumes( self._allVolumes )
			
		end )

	local volumesRequiringSyncingSubscription = Observable.combineLatest( 
			WFCatSyncStatus.offlineVolumesRequiringSyncing( wfCatalog ) )
			
		:subscribe( function( volumesRequiringSyncTable )
			self._volumesRequiringSyncingStream:onNext( volumesRequiringSyncTable )	
		end )

	mainVolumeAgent:addCloseAction( function()
	
		volumeStatusStreamsSubscription:dispose()
		volumeStatusStreamsSubscription = nil
		
		volumesRequiringSyncingSubscription:dispose()
		volumesRequiringSyncingSubscription = nil
		
	end )

end

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

local function gbToBytes( gb )

	return gb * 1024 * 1024 * 1024

end

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

local function bytesToGb( bytes )

	return bytes / 1024 / 1024 / 1024

end

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

local function createVolumeAgent( wfCatalog, idleDownloader, nimbusPathInfo, 
		targetDiskSpaceUsage, keepAllOriginals, keepAllProxies, isOnlineStream, 
		isSyncPausedStream, isMainVolume )

	local volumeGuid = nimbusPathInfo.volumeGuid
	local volumeRootPath = nimbusPathInfo.relativePath

	local keepLocalInfo = {
		keepAllOriginals = keepAllOriginals,
		keepAllProxies = keepAllProxies
	}
	
	local overrides = {}
	
	if not isMainVolume then
		overrides.disableProactivePurge = true
	end

	local targetCacheSize = NmbFileManagementSettings.targetCacheSize()
	local reservedSpace = NmbFileManagementSettings.minimumFreeSpace()

	local catalogPath = wfCatalog:fileSpec():catalogPath()
	local freeSpace, totalSpace = WFFileUtils.freeSpaceAtPath( catalogPath )
	
	if targetCacheSize == -1 and isMainVolume then
	
		-- If this is the first time the user is launching the new purge agent
		-- then we need to convert their existing user-specified cache size from
		-- a percentage into a number.
		
		local percentage = NmbFileManagementSettings.targetNimbusDiskSpacePercentage()

		freeSpace = math.max( 0, freeSpace - reservedSpace )

		targetCacheSize = percentage * freeSpace

		local targetCacheSizeInGbs = bytesToGb( targetCacheSize )

		local roundedSizeInGbs = math.ceil( targetCacheSizeInGbs )

		local roundedSizeInBytes = gbToBytes( roundedSizeInGbs )

		NmbFileManagementSettings.setTargetCacheSize( roundedSizeInBytes )

	end

	if not isMainVolume then

		-- If not on the main volume, use as much space as we can.
		
		overrides.targetCacheSize = totalSpace - reservedSpace
	
	end
	
	local volumeAgent = WFCatVolumeAgent(
			wfCatalog, volumeRootPath, volumeGuid, idleDownloader,
			nil, keepLocalInfo, overrides )

	local onlineSubscription = isOnlineStream:subscribe( function( onlineStatus )
		volumeAgent:setInternetConnectionStatus( onlineStatus )
	end )

	local pausedSubscription = isSyncPausedStream:subscribe( function( isPaused )
		volumeAgent:setSyncPausedStatus( not not isPaused )
	end )
			
	volumeAgent:addCloseAction( function()
	
		onlineSubscription:dispose()
		onlineSubscription = nil
		
		pausedSubscription:dispose()
		pausedSubscription = nil
		
	end )
			
	volumeAgent:start()

	wfCatalog:addVolumeAgent( volumeAgent )

	return volumeAgent

end


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

local NmbUserModel_LocalStorage = {

	_allVolumes = false,

	_volumesByType = false,

	_volumesOutOfSpaceStream = false,
	
	_volumesRequiringSyncingStream = false,
	
	_offlineVolumesStream = false,
	
	_readOnlyVolumesStream = false,

	WFCloseSupport.addCloseSupportMethods,

}

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

function NmbUserModel_LocalStorage:__init()
	
	self._allVolumes = {
		volumesOutOfSpace = {},
		volumesOverIdleSpaceThreshold = {},
		volumesOverDiskSpaceTarget = {},
		offlineVolumes = {},
		readOnlyVolumes = {},
		remainingSpace = {}
	}

	self._volumesByType = {}

	self._volumesOutOfSpaceStream = BehaviorSubject( {} )
	self._volumesRequiringSyncingStream = BehaviorSubject( {} )
	self._offlineVolumesStream = BehaviorSubject( {} )
	self._readOnlyVolumesStream = BehaviorSubject( {} )
	
end

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

function NmbUserModel_LocalStorage:setupVolumeAgents( wfCatalog, idleDownloader, 
		fileMover, binaryUploader, styleDownloader, isOnlineStream, isSyncPausedStream )

	-- Create one volume agent per unique Nimbus Volume.

	local fileSpec = wfCatalog:fileSpec()

	local catalogPath = fileSpec:catalogPath()
	
	local catalogPathInfo = {
		volumeGuid = WFFileUtils.getVolumeGuidForPath( catalogPath ),
		relativePath = WFFileUtils.removeVolumeFromPath( catalogPath )
	}
	
	self._volumesByType[ 'main' ] = catalogPathInfo.volumeGuid
	
	local originalsPathInfo = fileSpec:originalsPathInfo()
	self._volumesByType[ 'original' ] = originalsPathInfo.volumeGuid
	
	local proxiesPathInfo = fileSpec:proxiesPathInfo()
	self._volumesByType[ 'proxy' ] = proxiesPathInfo.volumeGuid
	
	-- Create a volume agent on the Main Volume
	
	local targetDiskSpaceUsageForVolume = NmbFileManagementSettings.targetNimbusDiskSpacePercentage()
	
	local keepAllOriginals = NmbFileManagementSettings.keepAllOriginals()
	local keepAllProxies = NmbFileManagementSettings.keepAllProxies()
	
	if catalogPathInfo.volumeGuid == originalsPathInfo.volumeGuid and
		catalogPathInfo.volumeGuid == proxiesPathInfo.volumeGuid and
		keepAllOriginals and
		keepAllProxies then
	
		-- If the main volume is being used to store all of the originals
		-- and all of the proxies, then we allow all the space to be used.
		
		targetDiskSpaceUsageForVolume = 1

	end
	
	local mainVolumeAgent = createVolumeAgent( wfCatalog, idleDownloader, catalogPathInfo, 
				targetDiskSpaceUsageForVolume, keepAllOriginals, keepAllProxies, isOnlineStream, 
				isSyncPausedStream, true )
	
	-- Create a volume agent on the Originals Volume, if needed.
	
	local originalVolumeAgent = wfCatalog:getVolumeAgentByVolumeGuid( originalsPathInfo.volumeGuid )
	
	if not originalVolumeAgent then
	
		originalVolumeAgent = createVolumeAgent( wfCatalog, idleDownloader, originalsPathInfo, 1, 
				keepAllOriginals, keepAllProxies, isOnlineStream, isSyncPausedStream, false )
				
	else
		originalVolumeAgent = nil
	end
	
	-- Create a volume agent on the Proxies Volume, if needed.
	
	local proxyVolumeAgent = wfCatalog:getVolumeAgentByVolumeGuid( proxiesPathInfo.volumeGuid )
	
	if not proxyVolumeAgent then
	
		proxyVolumeAgent = createVolumeAgent( wfCatalog, idleDownloader, proxiesPathInfo, 1,
				keepAllOriginals, keepAllProxies, isOnlineStream, isSyncPausedStream, false )
				
	else
		proxyVolumeAgent = nil
	end

	-- Monitor the volumes' statuses

	monitorVolumes( self, wfCatalog, mainVolumeAgent, originalVolumeAgent, 
			proxyVolumeAgent, idleDownloader, fileMover, binaryUploader, styleDownloader )
		
	-- Track volume info in the diagnostic log
	
	NmbStorage.addDiagnosticLogSectionFunc( wfCatalog )
		
	-- Return the volume guid of the main volume.
	
	return catalogPathInfo.volumeGuid
		
end

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

function NmbUserModel_LocalStorage:whichVolumes( volumesToCheck )
	
	local hasOriginalVolume = volumesToCheck[ self._volumesByType[ 'original' ] ]
	local hasProxyVolume = volumesToCheck[ self._volumesByType[ 'proxy' ] ]
	local hasMainVolume = volumesToCheck[ self._volumesByType[ 'main' ] ]

	return hasOriginalVolume, hasProxyVolume, hasMainVolume

end

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

function NmbUserModel_LocalStorage:offlineVolumeRequiresSyncing( offlineVolumes, volumesRequiringSyncing )

	for volume, _ in pairs( offlineVolumes ) do 
	
		if volumesRequiringSyncing[ volume ] then
			return true
		end
	
	end

	return false

end

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

function NmbUserModel_LocalStorage:getVolumesOutOfSpaceStream()
	return self._volumesOutOfSpaceStream
end

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

function NmbUserModel_LocalStorage:getOfflineVolumesStream()
	return self._offlineVolumesStream
end

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

function NmbUserModel_LocalStorage:getVolumesRequiringSyncingStream()
	return self._volumesRequiringSyncingStream
end

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

function NmbUserModel_LocalStorage:getReadOnlyVolumesStream()
	return self._readOnlyVolumesStream
end
	
--------------------------------------------------------------------------------

return Rx.Class 'NmbUserModel_LocalStorage'( NmbUserModel_LocalStorage )
