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

KSOAuth2Session.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2011 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.core.logging
local WFLogger = require 'WFLogger'

	-- WF.core.threading
local WFWorkerThread = require 'WFWorkerThread'

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFCloseSupport = require 'WFCloseSupport'
local WFObjectProxy = require 'WFObjectProxy'


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

local WFArguments_check = assert( WFArguments.check )
local KSOAuth2Session = 'KSOAuth2Session'

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

local logger = WFLogger 'KSOAuth2Session'
--	:setLevel 'debug'


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

-- @mark KSOAuth2Session class

--[[!
      \class KSOAuth2Session
      \brief Represents an OAuth2 authorization session.

	An OAuth2 session assumes responsibility for the access and refresh tokens
	of an OAuth2 authenticated service and manages the logic for all HTTP requests
	on that service. It provides persistent storage of the tokens.
	
	A session is identified by a unique ID which must be provided by the caller
	who first receives the login tokens and must be then provided by all subsequent
	callers who wish to interact with that service.
	
	Other code within the app need only remember the session's unique ID, which will
	not change regardless of changes in refresh or access token values.
]]--

local oAuth2SessionMethods = {

	_oAuth2SessionImp = false,
		-- (WFWorkerThread) event processor that handles the OAuth2
		-- for this session ID
	
	_sessionId = false,
		-- (string) compact UUID representing the handle for this service
		-- (URL + refresh token + access token tuple)

	_hasValidLoginB = false,
		-- (B< boolean >) true if we have a valid refresh token

	_isWaitingForTokensB = false,

	WFCloseSupport.addCloseSupportMethods,

}

--------------------------------------------------------------------------------
--[[!
	\brief Creates or restores an OAuth2 session for a client.
	
	If the client ID has been used previously within the system,
	the existing OAuth2 state is restored. If not, this call starts
	an initially invalid session. You must call \c :storeTokens to provide
	the initial credentials.
	
	\param sessionId (string) The client ID.
	
]]--

function oAuth2SessionMethods:__init( sessionId )

	WFArguments_check( KSOAuth2Session, 'string' )
	
	WFCloseSupport.mustClose( self )

	logger:infof( "Created session ID %s via %s", sessionId, ':traceback:' )
	
	self._sessionId = sessionId
	
	self:setOwned {
		_oAuth2SessionImp = WFWorkerThread( 'KSOAuth2SessionImp.lua', sessionId )
	}

end

--------------------------------------------------------------------------------
--[[!
	\brief Stores new tokens for this client.
	
	Typically, you call this only once, immediately after this session is
	created for the first time.

	\warning The parameter sequence was recently reversed!

	\param accessToken (string) The client's access token
	\param refreshToken (string) The client's refresh token
	
	\return A promise, which you can safely ignore
]]--

function oAuth2SessionMethods:storeTokensP( accessToken, refreshToken, deviceToken )

	WFArguments_check( KSOAuth2Session, '+string', '+string', '?+string' )

	logger:infof( "Store tokens via %s", ':traceback:' )

	local resultP = self._oAuth2SessionImp:requestP( 'storeTokensP',
													 accessToken, refreshToken, deviceToken )
	
	resultP:runUntilDone()
	
	return resultP

end

--------------------------------------------------------------------------------
--[[!
	Creates a behavior that reports the login status for this session.

	\return <code>B < boolean ></code> A Behavior whose value is \c true whenever
		we have a valid refresh token. When it changes to \c false, the user has
		effectively been forcibly logged out.
]]--

function oAuth2SessionMethods:hasValidLoginB()

	WFArguments_check( KSOAuth2Session )

	if not self._hasValidLoginB then

		self:setOwned {
			_hasValidLoginB = self._oAuth2SessionImp:requestB {
						message = 'hasValidLoginB',
						startingValue = true
					}
		}

	end
	
	return self._hasValidLoginB:relayB()

end

function oAuth2SessionMethods:isWaitingForTokensB()

	WFArguments_check( KSOAuth2Session )

	if not self._isWaitingForTokensB then

		self:setOwned {
			_isWaitingForTokensB = self._oAuth2SessionImp:requestB {
						message = 'isWaitingForTokensB',
						startingValue = false
					}
		}

	end
	
	return self._isWaitingForTokensB:relayB()

end

--------------------------------------------------------------------------------
--[[!
	\brief Performs a simple asynchronous HTTP GET request and returns the
	response via promise.

	\param url (string) The URL for the request.
	\param args (table) The request.
	
	Basically the same as WFHttpClient.getP.

	Usage:
	<pre>
		local args = {

			requestHeaders = {
				{ header = 'Content-Type', value = 'application/json' },
				{ header = 'Accept', value = 'application/json' },
			}
			requestMethod = 'DELETE',			-- optional
						-- (must be a valid HTTP request method)
			returnHttpErrorsAsData = true,		-- optional

			storeNewUserInfo = function( userData )	-- optional
			
				-- If present, this function is called when we receive
				-- a new user data packet from IMS.
			
			end,

		}
		
		local urlDataP = oAuth2Session:getP( url, optArgs )
			:whenResolvedDo( function( status, data, headers )

				if status == 'data' then

					-- The promise will resolve as "data" in the "normal" case of a
					-- valid HTTP response with a non-error (<400) HTTP status code.
					-- The promise will also resolve to "data" if the server returns
					-- an HTTP error code, but only if returnHttpErrorsAsData = true
					-- was specified.

					-- data will be a KSData with the complete response body.

					-- 'header' will be a Lua table of key/value pairs.
					-- A special key 'status_code' is inserted into the table,
					-- which contains a string with the HTTP response code (e.g. "200").

					-- By default, the HTTP request method is GET but this can be
					-- overridden via optArgs.requestMethod.
	
				elseif status == 'error' then
				
					-- The promise will resolve with "error" status if the system does
					-- not receive a valid HTTP response (i.e. connection refused,
					-- timeout, etc.). In this case, the data argument will be a string
					-- describing the error and headers will be nil.
					
					-- The promise will also resolve with "error" if a valid HTTP response
					-- is received but the HTTP status code indicates an error (>= 400).
					-- You can override this behavior with returnHttpErrorsAsData = true.
					-- See discussion above.
					
					-- If an HTTP error response is signaled via "error" status, then
					-- data and headers will be KSData and table as in the "data" case
					-- above.
		
				end )
	
	</pre>

	\return A standard promise. You are responsible for managing the lifetime
		of the promise via the action(s) you attach to it. The HTTP request is canceled
		if all of its promise actions are closed.

	\warning This function does no caching. If you call this function, there *will* be
		network traffic.
]]--

function oAuth2SessionMethods:getP( url, args, sequencing )

	WFArguments_check( KSOAuth2Session, 'string', '?table', '?string' )

	logger:infof( "GET URL %s\n\nargs %s\n\nvia %s",
						   url, args, ':traceback:' )

	return self._oAuth2SessionImp:requestP( 'getP', url, args, sequencing )

end

--------------------------------------------------------------------------------
--[[!
	\brief Performs a simple asynchronous HTTP PUT request and returns the response
	via promise.
	
	\param url (string) The URL for the request.
	\param data (KSData or table) Form data.
	\param args (table) The request.
 	
	Basically the same as WFHttpClient.putP, except that optArgs is
	replaced with a required args section with some required values (accessToken,
	refreshToken, and so on).
	
	Usage:
	<pre>
		local args = {

			accessToken = '(uuid)', -- required
			refreshToken = '(uuid)', -- required

			requestHeaders = {
				{ header = 'Content-Type', value = 'application/json' },
				{ header = 'Accept', value = 'application/json' },
			}
			requestMethod = 'DELETE',			-- optional
						-- (must be a valid HTTP request method)
			returnHttpErrorsAsData = true,		-- optional

		}

		local urlDataP = oAuth2Session:putP( url, args )
			:whenResolvedDo( function( status, data, headers )

				if status == 'data' then

					-- The promise will resolve as "data" in the "normal" case of a
					-- valid HTTP response with a non-error (<400) HTTP status code.
					-- The promise will also resolve to "data" if the server returns
					-- an HTTP error code, but only if returnHttpErrorsAsData = true
					-- was specified.

					-- data will be a KSData with the complete response body.

					-- 'header' will be a Lua table of key/value pairs.
					-- A special key 'status_code' is inserted into the table,
					-- which contains a string with the HTTP response code (e.g. "200").

					-- By default, the HTTP request method is GET but this can be
					-- overridden via optArgs.requestMethod.
	
				elseif status == 'error' then
				
					-- The promise will resolve with "error" status if the system does
					-- not receive a valid HTTP response (i.e. connection refused,
					-- timeout, etc.). In this case, the data argument will be a string
					-- describing the error and headers will be nil.
					
					-- The promise will also resolve with "error" if a valid HTTP response
					-- is received but the HTTP status code indicates an error (>= 400).
					-- You can override this behavior with returnHttpErrorsAsData = true.
					-- See discussion above.
					
					-- If an HTTP error response is signaled via "error" status, then
					-- data and headers will be KSData and table as in the "data" case
					-- above.
		
				end )
		
	</pre>

	\return A standard promise. You are responsible for managing the lifetime
	of the promise via the action(s) you attach to it. The HTTP request is canceled
	if all of its promise actions are closed.

	\warning This function does no caching. If you call this function, there *will* be
	network traffic.
]]--

function oAuth2SessionMethods:putP( url, data, args )

	WFArguments_check( KSOAuth2Session, 'string', '_', '?table' )

	logger:infof( "PUT URL %s\n\nargs %s\n\nvia %s",
				  url, args, ':traceback:' )

	return self._oAuth2SessionImp:requestP( 'putP', url, data, args )

end

--------------------------------------------------------------------------------
--[[!
	\brief Simulates forced logout. Should only be used in debug builds.
	
	\return A promise that you can safely ignore.
]]--

function oAuth2SessionMethods:simulateForcedLogoutP()

	WFArguments_check( KSOAuth2Session )

	logger:infof( "Simulate forced logout via %s", ':traceback:' )

	local resultP = self._oAuth2SessionImp:requestP( 'simulateForcedLogoutP' )
	
	resultP:runUntilDone()
	
	return resultP

end

--------------------------------------------------------------------------------
--[[!
	\brief Logs the user out. Deletes all store tokens and pretend this session
	never existed.

	\return <code>P< true ></code> A promise that resolves when the logout
		has been completed. You may ignore this promise.
]]--

function oAuth2SessionMethods:logoutP()

	WFArguments_check( KSOAuth2Session )

	logger:infof( "Logout via %s", ':traceback:' )

	local resultP = self._oAuth2SessionImp:requestP( 'logoutP' )
	
	resultP:runUntilDone()
	
	return resultP

end

--------------------------------------------------------------------------------
--[[!
	\brief To be used by test cases only.

	\param path (string) A full path to a log file.

	\return  A promise that you can safely ignore.
]]--

function oAuth2SessionMethods:DEBUG_dumpCoverageP( path )

	WFArguments_check( KSOAuth2Session, 'string' )

	local resultP = self._oAuth2SessionImp:requestP( 'DEBUG_dumpCoverageP', path )
	
	resultP:runUntilDone()
	
	return resultP

end

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

local KSOAuth2Sesion_class = Rx.Class 'KSOAuth2Session'( oAuth2SessionMethods )

return WFObjectProxy( 'KSOAuth2Session', KSOAuth2Sesion_class, nil, true )
