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

WFReachability.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.

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

	-- wichitafoundation.web.http_client
local WFReachabilityImp = require 'WFReachabilityImp'
	-- (private to this service)
	
	-- Rx
local Rx = require "Rx"
local Observable = Rx.Observable
local WFRxScheduler = require 'WFRxScheduler'

	-- WF.core.rxLua
local WFRxEventBridge = require 'WFRxEventBridge'
local WFRxWeakObservableTable = require 'WFRxWeakObservableTable'

	-- WF.web.http_client
local WFRxHttpClient = require 'WFRxHttpClient'

	-- WF.core.logging
local WFLogger = require 'WFLogger'

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

local asKSEvent = WFRxEventBridge
local logger = WFLogger 'WFReachability'

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

local kINTERVAL_WHEN_NOT_REACHABLE = 5 * 60 -- 5 mins
local kHTTP_TIMEOUT = 10 -- secs

local kUnreachable = 'unreachable'

--============================================================================--
--[[!
	\class  WFReachability
	\brief This namespace helps you determine whether
	network access is available, and if so, what class of service may be available.

 	This information should be considered advisory and is obtained at the least
 	possible cost in spinning up network services.
		
	Use this as a hint as to when you should retry network connections
	if they have failed in the past, and also a hint about the type
	of network connection that is available (3G vs Wi-Fi).
	
	THIS NAMESPACE DOES NOT WORK WELL and should be considered DEPRECATED
	until a better implementation can be found.
]]--

-- @mark WFReachability namespace

local WFReachability = {}

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

local reachabilityObservableTable = WFRxWeakObservableTable(
	function ( url )
		local result = Rx.ReplaySubject( 1 )
		local request = WFReachabilityImp.hostIsReachable( url )
		rawset( result, '_request', request )
		
		return result
	end ) 

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

local function confirmHostReachableObservable( url )
	local options = {
		requestMethod = "HEAD",
		returnHttpErrorsAsData = true,
		timeout = kHTTP_TIMEOUT,
		bypassRateLimiter = true,
	}
	return WFRxHttpClient.get( url, options )
		:map( function( data, headers )
			
			-- There might be http errors. Server might
			-- be busy temporarily, or page might not
			-- be found. But atleast we are able to reach
			-- the server, unless we have auth errors.
			
			if headers and headers.status_code == "407" then
				logger:debugf( "Host:%s- Needs proxy authentication", url )
				return false
			end
			
			logger:debugf( "Host:%s is reachable", url )
			return true
			
		end )
		:catch( function( err, _, errorData )
		
			-- Not reachable
			logger:debugf( "Host:%s is NOT reachable. Error:%s", url, err )
			return Observable.just( false )
		end )
end

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

local function updateHostReachableStatus( result, url, status )
	
	logger:debugf( "Checking status for host:%s. OS status:%s", url, status )
	
	return confirmHostReachableObservable( url )
		:flatMap( function( isReachable )
		
			if isReachable then
				
				result:onNext( status )
				-- Confirmed to be reachable. No need to ping again
				-- to confirm until the underlying reachability status
				-- changes
				return Observable.just()
				
			else
			
				-- Not reachable, but we think we should be. 
				-- Will have to ping again to check if the status
				-- is still unsuccesful.
				result:onNext( kUnreachable )
				
				return Observable.timer( kINTERVAL_WHEN_NOT_REACHABLE, WFRxScheduler.sharedScheduler() )
					:flatMap( function()
					
						-- 'status' should be the initial string
						return updateHostReachableStatus( result, url, status )
					end )
				
			end
			
		end )
	
end

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

local function reachabilityStatusHandler( url, status )

	if url and reachabilityObservableTable:hasObservableForKey( url ) then

		local result = reachabilityObservableTable:observableForKey( url )
		rawset( result, "_networkStatus", status )
		
		logger:infof( "Network status changed or refresh requested. Status:%s Url:%s", status, url )
		
		local oldSubscription = rawget( result, "_timerSubscription" )
			
		if oldSubscription then
			oldSubscription:dispose()
			rawset( result, "_timerSubscription", nil )
		end
		
		if status ~= kUnreachable then
			-- Check for the actual status
			
			local subn = updateHostReachableStatus( result, url, status )
				:subscribe( function()
					-- Nothing to do here since the function
					-- takes care of updating the status as
					-- well. We just need to keep this alive
				end )
			
			subn:disposeWith( result )
				-- In the event the reachability observable is disposed,
				-- make sure we dispose of this timer-based observable.
			
			-- Keep the subscription alive
			rawset( result, "_timerSubscription", subn )
			
			return
		end
		
		-- Unreachable. No need to ping the server.
		-- We will get a notification once its online.
		result:onNext( status )
	end
	
end

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

WFReachabilityImp.registerReachabilityHandler( reachabilityStatusHandler )

--------------------------------------------------------------------------------
--[[!
	\brief Creates a behavior that indicates whether a specific named host on
	the Internet is thought to be reachable at this time.
 
	\param url (string) The URL to query.
	
	\return B< string > network status. The behavior returns one of these network status codes:
	
		- \c "unreachable": No connection is thought to be available. This is the initial state.
		- \c "interventionRequired": A connection might be available, but the user
			will have to enter a password or perform some other manual intervention
			to use it.
		- \c "wifi": A Wi-Fi (or similarly fast) connection is thought to be available.
		- \c "wwan": A mobile data connection (3G, etc.) connection is thought to be
			available.
		- \c "other": Some other connection is available.
	
	Some of these status codes may never be reported on some platforms.
	(For instance, Mac OS has no notion of \c "wwan" or \c "interventionRequired".)
	
	The behavior starts in the \c "unreachable" state and later
	updates to another state as more information becomes available.

--]]

function WFReachability.hostIsReachable( url )

	local result = url 
			and reachabilityObservableTable:observableForKey( url ) 
			or Observable.just( kUnreachable )
	
	return result:distinctUntilChanged()
	
end

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

function WFReachability.hostIsReachableB( url )
	return WFReachability.hostIsReachable( url )
		:call( asKSEvent )
		:holdB( kUnreachable )
		:autoCloseRelayB()
end

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

function WFReachability.hasCellularDataCapability()

	if WFReachabilityImp.hasCellularDataCapability then
		return WFReachabilityImp.hasCellularDataCapability()
	else
		-- Not implemented on this platform, assume true
		return true
	end

end

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

function WFReachability.refreshReachabilityStatus( url )
	
	if url and reachabilityObservableTable:hasObservableForKey( url ) then

		local result = reachabilityObservableTable:observableForKey( url )
		-- Using the last known network status
		local status = rawget( result, "_networkStatus" )
		reachabilityStatusHandler( url, status )
	
	end
end

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

return WFReachability
