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

WFImsAppIdentity.lua

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

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

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

local WFCapabilities = require 'WFCapabilities'
local WFArguments = require 'WFArguments'

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

local WFImsAppIdentity = {}

local kClientIdAndSecretFilterKey = "WFImsAppIdentity.getClientIdAndSecret"

local kClientKey = "WFImsAppIdentity.clientId"
local kFilterKey = "WFImsAppIdentity.getClientId"

local kSecretKey = "WFImsAppIdentity.secret"
local kSecretFilterKey = "WFImsAppIdentity.DEBUG_getClientSecret"

local kDeviceKey = "WFImsAppIdentity.deviceId"
local kDeviceKeyWithNewAlgo = "WFImsAppIdentity.deviceIdForWithNewAlgo"
local kDeviceFilterKey = "WFImsAppIdentity.getImsDeviceId"

local kAnalyticsAppIdKey = "WFImsAppIdentity.analyticsAppIdKey"
local kAnalyticsAppIdFilterKey = "WFImsAppIdentity.analyticsAppIdFilterKey"
local WFPlatformInfo = require 'WFPlatformInfo'
--------------------------------------------------------------------------------

--[[!
	\brief Set the client id and secret for talking with IMS.  Any WF application that
	needs to use IMS must call this function prior to a call to any API that relies
	on making authenticated connections to Oz or IMS. Many WF APIs in WFOzClient do this,
	so it is best to make the call as early as possible after launch.
	
	\param clientId (string) the App client id, as registered with IMS
	\param secret (string) the app secret, as provided by IMS for your client id
--]]
function WFImsAppIdentity.setClientIdAndSecret( clientId, secret )
	
	WFArguments.check( '+string', '+string' )

	WFCapabilities.setCapability( kClientKey, clientId )
	WFCapabilities.setCapability( kSecretKey, secret )

end

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

--[[
	\brief Get the stored client id and secret.  This will be available in all
	message processors, no matter which one was the one to set it. If you are in
	a performance sensitive context, it is a good idea to cache the result, as
	each fetch requires a mutex lock.
	
	You must have previously called WFImsAppIdentity.setClientIdAndSecret to set
	these values.
	
	WFImsAppIdentity.getClientIdAndSecret processes the results through
	WFCapabilities.callFilters with the key "WFImsAppIdentity.getClientIdAndSecret".
	Thus you may temporarily, globally change the clientId and secret (for example during
	a unit test) by adding a filter for that key.
	
	\return clientId (string) the IMS client id
	\return secret (string) the secret
--]]
function WFImsAppIdentity.getClientIdAndSecret( ... )

	-- Ensure no parameters sent.
	assert( select( "#", ... ) == 0, "getClientIdAndSecret takes no arguments" )
	
	local clientId = WFCapabilities.getCapability( kClientKey )
	local secret = WFCapabilities.getCapability( kSecretKey )
	
	return WFCapabilities.callFilters( kClientIdAndSecretFilterKey, clientId, secret )
	
end

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

--[[!
	\brief Set the client id from clients IMS.  Any WF application that
	needs to use IMS must call this function prior to a call to any API that relies
	on making authenticated connections to Oz. Many WF APIs in WFOzClient do this,
	so it is best to make the call as early as possible after launch.
	
	\param clientId (string) the App client id, as registered with IMS
--]]
function WFImsAppIdentity.setClientId( clientId )
	
	WFArguments.check( '+string' )

	WFCapabilities.setCapability( kClientKey, clientId )

end

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

--[[
	\brief Get the stored client id.  This will be available in all
	message processors, no matter which one was the one to set it. If you are in
	a performance sensitive context, it is a good idea to cache the result, as
	each fetch requires a mutex lock.
	
	You must have previously called WFImsAppIdentity.setClientId to set clientId value.
	
	WFImsAppIdentity.getClientId processes the results through WFCapabilities.callFilters
	with the key "WFImsAppIdentity.getClientId".
	Thus you may temporarily, globally change the clientId (for example during
	a unit test) by adding a filter for that key.
	
	\return clientId (string) the IMS client id
--]]
function WFImsAppIdentity.getClientId( ... )

	-- Ensure no parameters sent.
	assert( select( "#", ... ) == 0, "getClientId takes no arguments" )
	
	local clientId = WFCapabilities.getCapability( kClientKey )

	return WFCapabilities.callFilters( kFilterKey, clientId )
	
end

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

--[[!
	\brief Set the client secret. This API is for unit test only.
	
	\param clientSecret (string) the App client secret, as provided by IMS 
		for your client id
--]]
function WFImsAppIdentity.DEBUG_setClientSecret( clientSecret )
	
	WFArguments.check( '+string' )

	WFCapabilities.setCapability( kSecretKey, clientSecret )

end

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

--[[
	\brief Get the stored client secret. The value would only be setup for unit tests.
	
	Unit test setup must call WFCapabilities.setCapability or 
	KSIdentityManagementUtils_setImsClientIdAndSecret to set client secret value.
	
	WFImsAppIdentity.DEBUG_getClientSecret processes the results through WFCapabilities.callFilters
	with the key "WFImsAppIdentity.DEBUG_getClientSecret".
	Thus you may temporarily, globally change the clientId (for example during
	a unit test) by adding a filter for that key.
	
	\return clientSecret (string) the IMS client secret
--]]
function WFImsAppIdentity.DEBUG_getClientSecret( ... )

	-- Ensure no parameters sent.
	assert( select( "#", ... ) == 0, "DEBUG_getClientSecret takes no arguments" )
	
	local clientSecret = WFCapabilities.getCapability( kSecretKey )

	return WFCapabilities.callFilters( kSecretFilterKey, clientSecret )
	
end

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

--[[!
	\brief Set the device id for using the device token grant type with IMS.
	Any WF application that wants to use the device token grant type must call
	this function prior to a call to any API that relies on making authenticated
	connections to Oz or IMS. Many WF APIs in WFOzClient do this,
	so it is best to make the call as early as possible after launch.
	
	\param deviceId (string or nil) a sha256 hashed unique identifier built according
		to IMS's guidelines
--]]
function WFImsAppIdentity.setImsDeviceId( deviceId )
	
--	WFArguments.check( '+string' )

	WFCapabilities.setCapability( kDeviceKey, deviceId )

end

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

--[[
	\brief Get the stored device id.  This will be available in all
	message processors, no matter which one was the one to set it. If you are in
	a performance sensitive context, it is a good idea to cache the result, as
	each fetch requires a mutex lock.
	
	You must have previously called WFImsAppIdentity.setImsDeviceId to set
	this value.
	
	WFImsAppIdentity.getImsDeviceId processes the results through
	WFCapabilities.callFilters with the key "WFImsAppIdentity.getImsDeviceId".
	Thus you may temporarily, globally change the clientId and secret (for example during
	a unit test) by adding a filter for that key.
	
	\return deviceId (string) the device ID (a sha256 hash)
--]]
function WFImsAppIdentity.getImsDeviceId( ... )

	-- Ensure no parameters sent.
	assert( select( "#", ... ) == 0, "getImsDeviceId takes no arguments" )

	if WFPlatformInfo.isAndroid then
        local KSPrefs = require 'KSPrefs'
        local flag = KSPrefs[ 'IsAndroidIDGeneratedFromNewAlgo' ]
		if flag then
			return WFImsAppIdentity.getImsDeviceIdWithNewAlgo( ... )
		end
	end

	local deviceId = WFCapabilities.getCapability( kDeviceKey )
	return WFCapabilities.callFilters( kDeviceFilterKey, deviceId )
	
end

--[[!
	\brief Set the device id for using the device token grant type with IMS.
	Any WF application that wants to use the device token grant type must call
	this function prior to a call to any API that relies on making authenticated
	connections to Oz or IMS. Many WF APIs in WFOzClient do this,
	so it is best to make the call as early as possible after launch.

	\param deviceId (string or nil) a sha256 hashed unique identifier built according
		to IMS's guidelines
--]]
function WFImsAppIdentity.setImsDeviceIdWithNewAlgo( deviceId )

    --	WFArguments.check( '+string' )

    WFCapabilities.setCapability( kDeviceKeyWithNewAlgo, deviceId )

end

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

--[[
--	This method was added to align the logic of deviceid with CSDK.
	\brief Get the device id.  This will be available in all
	message processors, no matter which one was the one to set it. If you are in
	a performance sensitive context, it is a good idea to cache the result, as
	each fetch requires a mutex lock.

	You must have previously called WFImsAppIdentity.setImsDeviceIdWithNewAlgo to set
	this value.

	WFImsAppIdentity.getImsDeviceIdWithNewAlgo processes the results through
	WFCapabilities.callFilters with the key "WFImsAppIdentity.getImsDeviceId".
	Thus you may temporarily, globally change the clientId and secret (for example during
	a unit test) by adding a filter for that key.

	\return deviceId (string) the device ID (a sha256 hash)
--]]
function WFImsAppIdentity.getImsDeviceIdWithNewAlgo( ... )

    -- Ensure no parameters sent.
    assert( select( "#", ... ) == 0, "getImsDeviceId takes no arguments" )

    local deviceId = WFCapabilities.getCapability( kDeviceKeyWithNewAlgo )
    return WFCapabilities.callFilters( kDeviceFilterKey, deviceId )

end

--------------------------------------------------------------------------------
--[[
--	This method was added to algin the logic of deviceid generation with CSDK.
	\brief Construct a unique identifier for this device, following the IMS
	 requirements for the Device Token grant type
	 (https://zerowing.corp.adobe.com/display/ims/Device+Token+Grant+Type).

	By using the same algorithm, WF clients on the same device can share a device
	token (additionally requires shared device token storage, such as a group keychain.)

	\param optSystemInfo (table) an optional table with the keys "systemUserName"
		and "ANDROID ID". If not supplied, this function will use the corresponding
		APIs in WFDeviceInfo.

	\return deviceId (string) the device ID (a AES hash)
--]]

function WFImsAppIdentity.generateDeviceIdWithNewAlgo( optSystemInfo )

    local systemUserName
    local uniqueId

    if optSystemInfo then

        assert( type( optSystemInfo ) == 'table', "generateDeviceId's systemInfo parameter must be a table" )
        systemUserName = optSystemInfo.systemUserName
        uniqueId = optSystemInfo.macAddress

        assert( uniqueId, "generateDeviceId's systemInfo parameter must include a macAddress field" )

    else

        local WFDeviceInfo = require 'WFDeviceInfo'

        systemUserName = WFDeviceInfo.userName()
        uniqueId = WFDeviceInfo.deviceId()
    end

    -- Use a placeholder username if none exists on this platform.
    -- (This is the same string that WFDeviceInfo.userName() returns on iOS)

    if not systemUserName then
        systemUserName = 'Unknown'
    end

    return uniqueId

end

--[[
	\brief Construct a unique identifier for this device, following the IMS
	 requirements for the Device Token grant type
	 (https://zerowing.corp.adobe.com/display/ims/Device+Token+Grant+Type).
	
	By using the same algorithm, WF clients on the same device can share a device
	token (additionally requires shared device token storage, such as a group keychain.)
	
	\param optSystemInfo (table) an optional table with the keys "systemUserName"
		and "macAddress". If not supplied, this function will use the corresponding
		APIs in WFDeviceInfo.
			
	\return deviceId (string) the device ID (a sha256 hash)
--]]

function WFImsAppIdentity.generateDeviceId( optSystemInfo )

	local systemUserName
	local uniqueId
	
	if optSystemInfo then
	
		assert( type( optSystemInfo ) == 'table', "generateDeviceId's systemInfo parameter must be a table" )
		systemUserName = optSystemInfo.systemUserName
		uniqueId = optSystemInfo.macAddress
		
		assert( uniqueId, "generateDeviceId's systemInfo parameter must include a macAddress field" )
	
	else
	
		local WFDeviceInfo = require 'WFDeviceInfo'
		
		systemUserName = WFDeviceInfo.userName()
		uniqueId = WFDeviceInfo.uniqueVendorIdentifier()
			
	end
	
	-- Use a placeholder username if none exists on this platform.
	-- (This is the same string that WFDeviceInfo.userName() returns on iOS)
	
	if not systemUserName then
		systemUserName = 'Unknown'
	end

	local WFCrypto = require 'WFCrypto'
	
	local platform
	
	if WFPlatformInfo.isiOS then
		platform = 'iOS'
	elseif WFPlatformInfo.isMac then
		platform = 'Mac'
	elseif WFPlatformInfo.isAndroid then
		platform = 'Android'
	elseif WFPlatformInfo.isWindows then
		platform = 'Windows'
	else
		error( "Unsupported/unknown platform in WFImsAppIdentity.generateDeviceId()" )
	end
	
	
	return WFCrypto.SHA256.digest(
		table.concat(
			{
				platform,
				systemUserName,
				uniqueId,
			}, ', ' ) )

end

function WFImsAppIdentity.setAnalyticsAppId( appId )

--	WFArguments.check( '+string' )

	WFCapabilities.setCapability( kAnalyticsAppIdKey, appId)
	
end


function WFImsAppIdentity.getAnalyticsAppId( ... )

-- Ensure no parameters sent.
	assert( select( "#", ... ) == 0, "getAnalyticsAppId takes no arguments" )
	
	
	local appId = WFCapabilities.getCapability( kAnalyticsAppIdKey )

	return WFCapabilities.callFilters( kAnalyticsAppIdFilterKey, appId )
	
end


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

return WFImsAppIdentity
