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

KrConfiguration.lua


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

ADOBE CONFIDENTIAL
------------------
Copyright 2009 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 params = { ... }
params = params[ 1 ]

local withAutoReleasePool = assert( params.withAutoReleasePool )
local areNativeSwitchesInited = assert( params.areNativeSwitchesInited )
local setNativeSwitch = assert( params.setNativeSwitch )
local getAppDir = assert( params.getAppDir )
local getAppDataDir = assert( params.getAppDataDir )
local child = assert( params.child )
local serializeConfiguration = assert( params.serializeConfiguration )

-- constants
local kConfigurationMissing = "missing"

local logWarn = function(...)
	-- KrConfiguration is not allowed to depend on LrLogger and config.lua
	-- is used in release builds, too, so use AgAlwaysPrint, but only for
	-- things that should really never happen in practice
	print( "KrConfiguration", "WARN", ... )
end

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

--[[
--- KrConfiguration provides a shared mechanism for retrieving application-wide configuration
--	details. Configurations are represented as a tree of configuration properties.

--  When KrConfiguration is first loaded, it searches both the Lightroom application directory
--  and the Lightroom support files directory for a config.lua file. It merges the results of
--  these files if found and exports them as the KrConfiguration namespace.

--  Example Configuration File:

--  loggers.AgImageMetadata = {
--    logLevel = "trace",
--    action = "print"
--  }

--  This will result in an entry in KrConfiguration called 'logger', that contains the tree
--  described in the configuration file.
--]]

--=============================================================================
-- @mark Configuration Environment
--=============================================================================

local function configurationEnvironment( removeMt )
	local result = {}
	
	local environmentMt = {
		__index = function( env, key )
			-- local result = setmetatable( { _={} }, { __index = function( t, k ) local v = t._[k] print( "accessing ", k, v ) return v end, __newindex = function( t, k, v ) t._[k] = v print( "setting ", k, v ) end } )
			local result = {}
			env[ key ] = result
			
			return result
		end,
		
		__newindex = function( env, key, value )
			if type( value ) ~= "table" then
				-- ignore non-table top level keys because their use is discouraged
				-- in order to encourage and simplify the test for "namespaced"
				-- config switches, top level indexes into the config table return
				-- a table even if absent, so the use of a non-table will result
				-- in values of different types than expected or for booleans a
				-- true value in all cases except where it precisely set to false
				--
				-- note that you'll see this message once per universe if it is hit
				logWarn( "ignoring non-table top level entry", tostring( key ))
			else
				rawset( env, key, value )
			end
		end,
			
		__call = function()
			return removeMt and setmetatable( result, nil ) or result
		end
	}
	
	return setmetatable( result, environmentMt )
end

--=============================================================================
-- @mark Reading a Configuration
--=============================================================================

local function readConfiguration( configurationFunction, env )
	env = env or configurationEnvironment( true )
	
	setfenv( configurationFunction, env )
	local result, msg = pcall( configurationFunction )

	return result and env(), msg
end

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

local function readConfigurationFromFile( filePath, env )
	assert( filePath )
	
	if ( not io ) or ( not io.open ) then
		return nil, "io not installed"
	end
	
	local file = io.open( filePath )
	local msg
	
	if file then
		local fileString = file:read( "*a" )
		file:close()
		
		local func
		func, msg = loadstring( fileString, 'config.lua' )
		
		if func then
			return readConfiguration( func, env )
		end
	else
		msg = kConfigurationMissing
	end
		
	return nil, msg
end

--=============================================================================
-- @mark Configuration Loading
--=============================================================================

local function getConfigurationBuilder()
	local result = {}
	local environment = configurationEnvironment()
	
	result.readConfigurationFromFile = function( configFile )
		local success, result, msg = pcall( readConfigurationFromFile, configFile, environment )
		
		if not ( success and result ) and msg ~= kConfigurationMissing then
			logWarn( "Could not load file: ", configFile, msg )
		end
	end
	
	result.finalizeConfiguration = function()
		return setmetatable( environment, { __index = function() return {} end } )
			-- after the config files have been read, we change the environment
			-- that's actually returned (the KrConfiguration table) so that any
			-- index into it will return an empty table so that the presence of
			-- a namespace doesn't need to be tested for.
	end
	
	return result
end

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

local function kernelConfigFile()
	return child( getAppDataDir() or getAppDir(), "config-kernel.lua" )
end

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

local configDirs = {
	getAppDir, getAppDataDir,
}
local configFilenames = {
	'config.lua',
	'config.lua.txt',
		-- If someone creates a file called 'config.lua' on windows using notepad, by
		-- default the file on disk will be config.lua.txt, but in explorer
		-- it will appear as 'config.lua'.  This is because by default explorer
		-- hides the last file extension, and notpad always adds a '.txt' regardless
		-- of what filename you choose in the save dialog.  So we'll look for both variants and
		-- load them.
}

local function listConfigFiles()
	local files = { }
	for _, dirFetcher in ipairs( configDirs ) do
		local dirPath = dirFetcher()
		if dirPath then
			for _, filename in ipairs( configFilenames ) do
				files[ #files + 1 ] = child( dirPath, filename )
			end
		end
	end
	
	files[ #files + 1 ] = kernelConfigFile()
	
	return files
end

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

local function warnf( ... )
	return logWarn( string.format( ... ) )
end

local function updateKernelConfiguration( updateFn )
	local outfile = kernelConfigFile()
	local env = configurationEnvironment()
	local t = readConfigurationFromFile( outfile, env )
	
	local cfg = t or env
	updateFn( cfg )
	
	local newConfig = serializeConfiguration( cfg, warnf )
	local f, message = io.open( outfile, "w" )
	if f then
		f:write( newConfig )
		f:close()
	
		return true
	else
		return false, message
	end
	
end

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

local function loadConfiguration()
	local builder = getConfigurationBuilder()

	for _, configPath in ipairs( listConfigFiles() ) do
		builder.readConfigurationFromFile( configPath )
	end
	
	return builder.finalizeConfiguration()
end

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

local KrConfiguration = loadConfiguration()

if areNativeSwitchesInited and not areNativeSwitchesInited() then
	
	for k, v in pairs( KrConfiguration.nativeSwitches ) do
		local success, msg = setNativeSwitch( k, v )
		if not success then
			print( msg )
		end
	end
	
end

--=============================================================================
-- @mark KrConfiguration
--=============================================================================

return {
	config = KrConfiguration,
	update = function( ... )
		return withAutoReleasePool( updateKernelConfiguration, ... )
	end
}
