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

NmbFileManagementSettings.lua

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

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.

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

local KSPrefs = require 'KSPrefs'

local WFArguments = require 'WFArguments'

local WFConfiguration = require 'WFConfiguration'

local WFFileUtils = require 'WFFileUtils'

local WFTableArchiving = require "WFTableArchiving"

	-- libs.json
local KSJsonDecoder = require 'KSJsonDecoder'
local KSJsonEncoder = require 'KSJsonEncoder'

--============================================================================--
--[[!
	\class NmbFileManagementSettings
	\brief This namespace provides access to the settings that control disk space management.
--]]

-- @mark NmbFileManagementSettings namespace

local DEFAULT_TARGET_DISK_SPACE_USAGE = 0.25

local DEFAULT_TARGET_CACHE_SIZE = -1

local DEFAULT_KEEP_ALL_ORIGINALS = false
local DEFAULT_KEEP_ALL_PROXIES = false

local NmbFileManagementSettings = {}

local _NmbFileManagementSettings = {

	_targetNimbusDiskSpacePercentage = WFConfiguration.Nimbus.targetNimbusDiskSpacePercentage or DEFAULT_TARGET_DISK_SPACE_USAGE,
		--[[ 
		
			The percentage of disk space that Nimbus should try not to exceed. 
			
			Please note that Nimbus can exceed the disk space usage target if the user has a 
			lot of smart previews, or if they explicitly choose to mark a lot of images for
			offline use; the purge system will not remove smart previews or offline originals
			to try to stay within the target disk space usage window.

			For Example:
			
			If the hard drive is aleady 50% full, we'll use up to N% of the remaining space. If N
			is equal to 40%, and the HD is 100GB is size, Nimbus will try to use less than 20GB.

		]]
		
	_targetCacheSize = WFConfiguration.VolumeAgent.targetCacheSize or DEFAULT_TARGET_CACHE_SIZE,
		--[[ 
		
			The maximum size of the Lr Photo Cache, in bytes.

		]]
		
	_minimumFreeSpace = ( WFConfiguration.Nimbus.minimumFreeSpace or 2048 ) * ( 1024 * 1024 ),
		--[[ 
		
			Never let Nimbus use disk space when there is less than `N` amount left.
			If Nimbus exceeds this amount, we're in a no-disk-space state.

		]]
		
	_maxPollingInterval = 30,
		--[[
		
			Recalculate disk space needs, at most, every n seconds.
		
		]]

	_keepAllOriginals = DEFAULT_KEEP_ALL_ORIGINALS,
		-- Whether or not Nimbus should store all originals locally.
		
	_keepAllProxies = DEFAULT_KEEP_ALL_PROXIES,
		-- Whether or not Nimbus should store all proxies locally.
		
	_relativeOriginalStorageLocation = false,
		-- Custom location where originals are stored, relative to the volume identfier.
		-- If false, there's no custom location defined.
		
	_originalStorageVolumeGuid = false,
		-- The GUID of the volume where the originals are stored.
		-- If false, there's no custom location defined.
		
	_relativeProxyStorageLocation = false,
		-- Custom location where proxies are stored, relative to the volume identfier.
		-- If false, there's no custom location defined.
		
	_proxyStorageVolumeGuid = false,
		-- The GUID of the volume where the originals are stored.
		-- If false, there's no custom location defined.
		
	_volumeReadableIdentifiers = {},
		-- (table) Key: Volume GUID Value: Readable Identifier
		-- The UI volume identifier strings, such as the user-defined volumes' names.
		-- This is used in the UI to identify the drive to the user.
		
	_lastUpdateTime = os.time(),
		-- time (in UNIX epoch) of the last update
}


-- getter/setter metatable pattern to monitor changes the the table
-- only elements whose names begin with an "_" are monitored
local function get(t, key)

	if key:sub(1,1) == "_" then
		-- print("NmbFileManagementSettings.get: ", key, _NmbFileManagementSettings[key])
		return _NmbFileManagementSettings[key]
	else
		return rawget(t, key)
	end

end

local function set(t, key, value)

	if key:sub(1,1) == "_" then
		-- print("NmbFileManagementSettings.set: ", key, value)
		_NmbFileManagementSettings[key] = value

		if type(NmbFileManagementSettings.saveSecondaryPrefs) == "function" then
			_NmbFileManagementSettings["_lastUpdateTime"] = os.time()
			KSPrefs.NmbFileManagementSettings_lastUpdateTime = NmbFileManagementSettings._lastUpdateTime
			pcall( NmbFileManagementSettings.saveSecondaryPrefs )
		end
	else
		rawset(t, key, value)
	end
end

setmetatable(NmbFileManagementSettings, {__index = get, __newindex = set} )

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

do
	
	if not WFConfiguration.Nimbus.targetNimbusDiskSpacePercentage then
	
		local mpref = KSPrefs.NmbFileManagementSettings_targetNimbusDiskSpacePercentage
		
		if mpref and type( mpref ) == 'number' and mpref >= 0 then
		
			NmbFileManagementSettings._targetNimbusDiskSpacePercentage = mpref
			
		end

	end

	if not WFConfiguration.Nimbus.targetCacheSize then
	
		local mpref = KSPrefs.NmbFileManagementSettings_targetCacheSize
		
		if mpref and type( mpref ) == 'number' and mpref >= 0 then
		
			NmbFileManagementSettings._targetCacheSize = mpref
			
		end

	end

	if not WFConfiguration.Nimbus.keepAllOriginals then

		local mpref = KSPrefs.NmbFileManagementSettings_KeepAllOriginals

		if mpref then
			
			NmbFileManagementSettings._keepAllOriginals = mpref
			
		end

	end

	if not WFConfiguration.Nimbus.keepAllProxies then

		local mpref = KSPrefs.NmbFileManagementSettings_KeepAllProxies

		if mpref then
			
			NmbFileManagementSettings._keepAllProxies = mpref
			
		end

	end

	local mpref = KSPrefs.NmbFileManagementSettings_relativeOriginalStorageLocation

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._relativeOriginalStorageLocation = mpref
		
	end

	mpref = KSPrefs.NmbFileManagementSettings_relativeProxyStorageLocation

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._relativeProxyStorageLocation = mpref
		
	end
	
	mpref = KSPrefs.NmbFileManagementSettings_originalStorageVolumeGuid

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._originalStorageVolumeGuid = mpref
		
	end
	
	mpref = KSPrefs.NmbFileManagementSettings_proxyStorageVolumeGuid

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._proxyStorageVolumeGuid = mpref
		
	end

	mpref = KSPrefs.NmbFileManagementSettings_volumeReadableIdentifiers

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._volumeReadableIdentifiers = KSJsonDecoder.decodeWebSafeJson( mpref )

		if type( NmbFileManagementSettings._volumeReadableIdentifiers ) ~= 'table' then
			NmbFileManagementSettings._volumeReadableIdentifiers = {}
		end

	else
	
		NmbFileManagementSettings._volumeReadableIdentifiers = {}
	
	end

	mpref = KSPrefs.NmbFileManagementSettings_lastUpdateTime

	if mpref and type( mpref ) == 'string' then
		
		NmbFileManagementSettings._lastUpdateTime = mpref
		
	else
	
		NmbFileManagementSettings._lastUpdateTime = 0
	
	end

end

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

function NmbFileManagementSettings.setSecondaryPrefsLocation( secondaryPrefsLocation )

	NmbFileManagementSettings.secondaryPrefsLocation = secondaryPrefsLocation

	if not WFFileUtils.exists( secondaryPrefsLocation ) then
		-- This causes us to create the file even if the prefs are just read
		NmbFileManagementSettings._lastUpdateTime = os.time()
	end

end

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

--[[!
	\brief Save all file management settings to the catalog location which allows
	regular LrCC and MAS LrCC to interoperate
]]--

function NmbFileManagementSettings.saveSecondaryPrefs()

	-- Any caller who changes a property must provide the location to write the prefs file to
	assert( NmbFileManagementSettings.secondaryPrefsLocation )

	if NmbFileManagementSettings.secondaryPrefsLocation then

		_NmbFileManagementSettings._lastUpdateTime = os.time()

		local prefsString = WFTableArchiving.tableToString( _NmbFileManagementSettings or {} )

		-- print("SAVE: ", NmbFileManagementSettings.secondaryPrefsLocation, prefsString)

		WFFileUtils.saveToFile( NmbFileManagementSettings.secondaryPrefsLocation, prefsString )

	end

end

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

--[[!
    \brief Load all file management settings from the catalog location which:
		1. Allows regular LrCC and MAS LrCC to interoperate.
		2. Prevents the settings from getting cleared by reset prefs so that
		users don't accidentally have their originals moved onto their main
		drive.
		3. If a catalog is restored without the preferences it will still refer
		to other directories or external volumes. And it will remember to keep
		all originals or proxies local.
		4. It retains the _volumeReadableIdentifiers so we can properly show
		volume names for the GUIDs that are stored in the catalog.
]]--

function NmbFileManagementSettings.maybeLoadSecondaryPrefs()

	-- To load caller must provide the location to read the prefs file from
	assert( NmbFileManagementSettings.secondaryPrefsLocation )

	if NmbFileManagementSettings.secondaryPrefsLocation then

		local prefsString = WFFileUtils.getFileContents( NmbFileManagementSettings.secondaryPrefsLocation )

		if prefsString then
			-- there is no WFTableArchiving.tableFromString( )

			local tempSettings
			local status

			local f = loadstring( "return " .. prefsString )
			if f then
				local nullEnv = {}
				setfenv( f, nullEnv ) -- simplistic sandbox
				status, tempSettings = pcall( f )
			end

			if not status or not tempSettings then
				-- secondary prefs file is corrupt, save current settings so we have a valid set
				NmbFileManagementSettings.saveSecondaryPrefs()

				return
			end

			-- If the secondary settings are from a prior version they may be missing
			-- fields so just copy them on top of the existing settings.
			-- For example 5.2 doesn't have _targetCacheSize but 5.4 does.

			-- Note we are copying into the backing version of _NmbFileManagementSettings
			-- to avoid calling the metatable functions here.
			for k, v in pairs( tempSettings ) do
				_NmbFileManagementSettings[ k ] = v
			end

			-- Make sure the settings in KSPrefs are aligned with the settings in the secondary
			-- prefs location.

			KSPrefs.NmbFileManagementSettings_targetNimbusDiskSpacePercentage
					= _NmbFileManagementSettings._targetNimbusDiskSpacePercentage
				
			KSPrefs.NmbFileManagementSettings_targetCacheSize
					= _NmbFileManagementSettings._targetCacheSize
				
			KSPrefs.NmbFileManagementSettings_KeepAllOriginals
					= _NmbFileManagementSettings._keepAllOriginals
				
			KSPrefs.NmbFileManagementSettings_KeepAllProxies
					= _NmbFileManagementSettings._keepAllProxies

			KSPrefs.NmbFileManagementSettings_relativeOriginalStorageLocation
					= _NmbFileManagementSettings._relativeOriginalStorageLocation

			KSPrefs.NmbFileManagementSettings_relativeProxyStorageLocation
					= _NmbFileManagementSettings._relativeProxyStorageLocation

			KSPrefs.NmbFileManagementSettings_originalStorageVolumeGuid
					= _NmbFileManagementSettings._originalStorageVolumeGuid

			KSPrefs.NmbFileManagementSettings_proxyStorageVolumeGuid
					= _NmbFileManagementSettings._proxyStorageVolumeGuid

			KSPrefs.NmbFileManagementSettings_volumeReadableIdentifiers
					= _NmbFileManagementSettings._volumeReadableIdentifiers
							and KSJsonEncoder.encodeWebSafeJson( _NmbFileManagementSettings._volumeReadableIdentifiers )

		end

	end

end

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

local function getReadableIdentifierForVolumeGuid( volumeGuid )

	-- Intentionally don't check type of volumeGuid as it may be false.

	if volumeGuid and type( NmbFileManagementSettings._volumeReadableIdentifiers ) == 'table' then

		return NmbFileManagementSettings._volumeReadableIdentifiers[ volumeGuid ]

	end

	return nil

end

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

function NmbFileManagementSettings.originalStorageLocationInfo()

	return {
		relativePath = NmbFileManagementSettings._relativeOriginalStorageLocation,
		volumeGuid = NmbFileManagementSettings._originalStorageVolumeGuid,
		readableIdentifier = getReadableIdentifierForVolumeGuid( NmbFileManagementSettings._originalStorageVolumeGuid )
	}
	
end

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

function NmbFileManagementSettings.proxyStorageLocationInfo()

	return {
		relativePath = NmbFileManagementSettings._relativeProxyStorageLocation,
		volumeGuid = NmbFileManagementSettings._proxyStorageVolumeGuid,
		readableIdentifier = getReadableIdentifierForVolumeGuid( NmbFileManagementSettings._proxyStorageVolumeGuid )
	}
	
end

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

function NmbFileManagementSettings.keepAllOriginals()

	return NmbFileManagementSettings._keepAllOriginals
	
end

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

function NmbFileManagementSettings.keepAllProxies()

	return NmbFileManagementSettings._keepAllProxies
	
end

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

function NmbFileManagementSettings.targetNimbusDiskSpacePercentage()

	return NmbFileManagementSettings._targetNimbusDiskSpacePercentage
	
end

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

function NmbFileManagementSettings.targetCacheSize()

	return NmbFileManagementSettings._targetCacheSize
	
end

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

function NmbFileManagementSettings.minimumFreeSpace()

	return NmbFileManagementSettings._minimumFreeSpace
	
end

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

function NmbFileManagementSettings.maxPollingInterval()

	return NmbFileManagementSettings._maxPollingInterval
	
end

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

function NmbFileManagementSettings.lastUpdateTime()

	return NmbFileManagementSettings._lastUpdateTime
	
end

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

--[[!
    \brief Sets the target amount of free space that Nimbus should use on the given system.
	This is persistent across sessions and is stored in a preference.
	
	\param targetDiskSpacePercentage (number) The target percent of free disk space.
]]--

function NmbFileManagementSettings.setTargetDiskSpacePercentage( targetDiskSpacePercentage )

	WFArguments.check( 'number' )
	
	if targetDiskSpacePercentage < 0 or targetDiskSpacePercentage > 1 then
		error( "NmbFileManagementSettings.setTargetFreeDiskSpace: targetDiskSpacePercentage must be a positive number, but less than 1", 2 )
	end
	
	NmbFileManagementSettings._targetNimbusDiskSpacePercentage = targetDiskSpacePercentage
	KSPrefs.NmbFileManagementSettings_targetNimbusDiskSpacePercentage = targetDiskSpacePercentage

end

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

--[[!
    \brief Sets the maximum size of the photo cache.
	\param targetCacheSize (number) The maximum size of the cache, in bytes.
]]--

function NmbFileManagementSettings.setTargetCacheSize( targetCacheSize )

	WFArguments.check( 'number' )

	NmbFileManagementSettings._targetCacheSize = targetCacheSize
	KSPrefs.NmbFileManagementSettings_targetCacheSize = targetCacheSize

end

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

--[[!
    \brief Sets whether or not we should store all originals locally.
	
	\param value (boolean)
]]--

function NmbFileManagementSettings.setKeepAllOriginals( value )

	WFArguments.check( 'boolean' )
	
	NmbFileManagementSettings._keepAllOriginals = value
	KSPrefs.NmbFileManagementSettings_KeepAllOriginals = value
	
end

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

--[[!
    \brief Sets whether or not we should store all proxies locally.
	
	\param value (boolean)
]]--

function NmbFileManagementSettings.setKeepAllProxies( value )

	WFArguments.check( 'boolean' )
	
	NmbFileManagementSettings._keepAllProxies = value
	KSPrefs.NmbFileManagementSettings_KeepAllProxies = value
	
end

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

local function getStorageInfo( maybeLocation )

	local storageInfo = {}
	
	if maybeLocation then
	
		local volumeGuid = WFFileUtils.getVolumeGuidForPath( maybeLocation )
		
		if volumeGuid then
		
			local relativePath = WFFileUtils.removeVolumeFromPath( maybeLocation )
			local volumeName = WFFileUtils.getVolumeNameFromGuid( volumeGuid )
			
			storageInfo.volumeGuid = volumeGuid
			storageInfo.relativePath = relativePath
			storageInfo.readableIdentifier = volumeName
			
		end
		
	end

	return storageInfo

end


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

--[[!
     \brief Sets the user-facing volume identifier for a specified volume.
	
	\param readableIdentifier (string) A user readable string containing the name of the
	volume or other identifible characteristics.
]]--

function NmbFileManagementSettings.setVolumeReadableIdentifier( volumeGuid, readableIdentifier )

	WFArguments.check( '?string', '?string' )
	
	if volumeGuid then
	
		local volumeReadableIdentifiers = NmbFileManagementSettings._volumeReadableIdentifiers
		if type(volumeReadableIdentifiers) ~= 'table' then
			volumeReadableIdentifiers = {}
		end

		volumeReadableIdentifiers[ volumeGuid ] = readableIdentifier
		
		KSPrefs.NmbFileManagementSettings_volumeReadableIdentifiers = KSJsonEncoder.encodeWebSafeJson( volumeReadableIdentifiers )
			-- Encode the table as a JSON string, since KSPrefs only supports scalar values.
			
		NmbFileManagementSettings._volumeReadableIdentifiers = volumeReadableIdentifiers

	end

end

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

--[[!
    \brief Get the user-facing volume identifier for the given volume GUID.
	
	\param volumeGuid (string) The volume GUID to retrieve the readable identifier for.
	
	\return (?string) If we've cached a readable identifier for the volume, return it.
]]--

function NmbFileManagementSettings.getReadableIdentifierForVolumeGuid( volumeGuid )

	WFArguments.check( '?string' )
	
	return getReadableIdentifierForVolumeGuid( volumeGuid )
	
end

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

--[[!
    \brief Sets a custom storage location for originals.
	
	\param maybeLocation (string) The custom storage location. If nil, the custom
	location is removed and we go back to using the default storage location.
]]--

function NmbFileManagementSettings.setOriginalStorageLocation( maybeLocation )

	WFArguments.check( '?string' )
	
	local storageInfo = getStorageInfo( maybeLocation )
	
	KSPrefs.NmbFileManagementSettings_relativeOriginalStorageLocation = storageInfo.relativePath
	NmbFileManagementSettings._relativeOriginalStorageLocation = storageInfo.relativePath

	KSPrefs.NmbFileManagementSettings_originalStorageVolumeGuid = storageInfo.volumeGuid
	NmbFileManagementSettings._originalStorageVolumeGuid = storageInfo.volumeGuid
	
	NmbFileManagementSettings.setVolumeReadableIdentifier( storageInfo.volumeGuid, storageInfo.readableIdentifier )

end

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

--[[!
    \brief Sets a custom storage location for proxies.
	
	\param maybeLocation (string) The custom storage location. If nil, the custom
	location is removed and we go back to using the default storage location.
]]--

function NmbFileManagementSettings.setProxyStorageLocation( maybeLocation )

	WFArguments.check( '?string' )
		
	local storageInfo = getStorageInfo( maybeLocation )
	
	KSPrefs.NmbFileManagementSettings_relativeProxyStorageLocation = storageInfo.relativePath
	NmbFileManagementSettings._relativeProxyStorageLocation = storageInfo.relativePath
	
	KSPrefs.NmbFileManagementSettings_proxyStorageVolumeGuid = storageInfo.volumeGuid
	NmbFileManagementSettings._proxyStorageVolumeGuid = storageInfo.volumeGuid
	
	NmbFileManagementSettings.setVolumeReadableIdentifier( storageInfo.volumeGuid, storageInfo.readableIdentifier )
	
end

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

--[[!
    \brief Reset all file management settings to the default settings.
]]--

function NmbFileManagementSettings.reset()

	NmbFileManagementSettings.setTargetDiskSpacePercentage( DEFAULT_TARGET_DISK_SPACE_USAGE )
	NmbFileManagementSettings.setTargetCacheSize( DEFAULT_TARGET_CACHE_SIZE )
	NmbFileManagementSettings.setKeepAllOriginals( DEFAULT_KEEP_ALL_ORIGINALS )
	NmbFileManagementSettings.setKeepAllProxies( DEFAULT_KEEP_ALL_PROXIES )
	NmbFileManagementSettings.setOriginalStorageLocation( nil )
	NmbFileManagementSettings.setProxyStorageLocation( nil )
	
end

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

return NmbFileManagementSettings
