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

WFOzVideoAuth.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 WFURI = require 'WFURI'
local KSJsonDecoder = require 'KSJsonDecoder'
local OzCatalogUrls = require 'OzCatalogUrls'
local OzCatHelperFns = require 'OzCatHelperFns'
local KSTableUtils = require 'KSTableUtils'
local KSPromises = require 'KSPromises'
local WFHttpClient = require 'WFHttpClient'
local WFLogger = require 'WFLogger'
local KSReactivePromiseBridge = require 'KSReactivePromiseBridge'
local KSReactive = require 'KSReactive'
local WFUnknown = require 'WFUnknown'
local WFOzContentTypes = require 'WFOzContentTypes'

	-- WF.oz_client.video
local WFVideoTranscodingOptions = require 'WFVideoTranscodingOptions'


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

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


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

local WFOzVideoAuth = {}

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

local couldBeJson = function( maybe )
	-- TODO [arahn 2013-08-22]: As soon as Oz is reliably setting Content-Type we
	-- won't need this.
	
	return string.sub( tostring( maybe ), 1,5 ) == 'while'
end

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

local handleVideoRelatedOzErrorsMapT =  {
	error = function( _, maybeJsonBlob, headers )
		if WFHttpClient.responseContentTypeIsJson( headers ) or couldBeJson( maybeJsonBlob ) then
			local errorInfo = KSJsonDecoder.decodeWebSafeJson_stripNull_ignoreEmpty( maybeJsonBlob )
			local masterError = KSTableUtils.derefOpt( errorInfo, "errors", "master", 1 )
			local code = KSTableUtils.derefOpt( errorInfo, "code" )
			local description = KSTableUtils.derefOpt( errorInfo, "description" )
			if masterError == 'does not exist' then
				return 'error', 'missing_original'
			elseif code == 4302 then
				-- "Access forbidden due to copyright"
				return 'error', 'not_authorized'
			elseif code then
				if description then
					logger:error( "video playback error code (" .. tostring( code ) .. "):" .. tostring( description ) )
				end
				-- WHy not use the description field -- it's in plain English!
				-- Because, it is in English.  Showing English to people who speak other languages is
				-- not polite.
				return 'error', 'unknown_code', code
			end
		end
	end
}

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

local function getRendition( type )
	return function( docValue )
		return KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', "/rels/rendition_type/" .. type, 'href' )
	end
end

local getHlsHref = getRendition 'hls'
local get720pHref = getRendition '720p'
local get360pHref = getRendition '360p'

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

-- Ask Oz to generate the HLS rendition.  When done, resolve the promise
-- to an error and errorCode "no_hls".

local function generateNecessaryRenditionP( catalog, assetId, docValue, needHls )
	--  If no url is found in the document, we can request one by
	--	HTTP POST to /v1.0/catalogs/catalog_id/assets/asset_id/revisions/revision_id/renditions
	--	with X-Generate-Renditions header to specify the renditions you want
	
	-- We really ought to message to the user that this is happening because it will take several minutes, potentially.
	
	local ozUrl = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/latest_revision', 'href' )
	ozUrl = ozUrl and ( ozUrl .. "/renditions" )
	
	ozUrl = OzCatHelperFns.checkForRelativeUrlAndAppendBase( ozUrl, OzCatalogUrls.baseServerUrl )
	
	if not ozUrl then
		return 'error', 'cannot stream because no url'
	end
	
	return catalog:ozUser():getP( ozUrl, {
		requestMethod = "POST",
	    requestHeaders = {                  -- optional
			WFVideoTranscodingOptions.ozTranscodingHeaders( needHls )
		},
		followRedirects = false,
	} )
	:mapP( handleVideoRelatedOzErrorsMapT )
	:mapDataP( function( status, docValue )
		return 'error', "no_hls"
	end )
end

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

local function getOrRequestHlsP( catalog, assetId, docValue )

	local ozUrl = getHlsHref( docValue )
	
	if ozUrl then
		return KSPromises.resolvedPromiseP( 'data', ozUrl )
	else
		return generateNecessaryRenditionP( catalog, assetId, docValue, true )
	end
	
end

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

local function simpleAuthHlsUrlP( catalog, assetId, docValue )
	
	local hlsUrl
	
	return getOrRequestHlsP( catalog, assetId, docValue )
	:mapDataP( function( status, ozUrl )
		hlsUrl = OzCatHelperFns.checkForRelativeUrlAndAppendBase( ozUrl, OzCatalogUrls.baseServerUrl )
	
		return catalog:ozUser():getP( hlsUrl, {
		    requestHeaders = {                  -- optional
		        { header = 'Accept', value = 'application/json' },
			},
			followRedirects = false,
		} )
	end )
	:mapP( handleVideoRelatedOzErrorsMapT )
	:mapDataP( KSJsonDecoder.decodeWebSafeJson_stripNullP )
	:mapDataP( function( status, info, headers )
		
		if status == 'data' and headers and headers.status_code == '307' and headers.Location then
			return 'data', {
				url = headers.Location,
				auth_type = 'none',
			}
		else
			return 'error', 'Unexpected server response; cannot authorize'
		end
	end )
end

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

local function simpleAuth360UrlP( catalog, assetId, docValue )

	local url = get720pHref( docValue ) or get360pHref( docValue )
	
	if not url then
		return generateNecessaryRenditionP( catalog, assetId, docValue, false )
	end
	
	url = OzCatHelperFns.checkForRelativeUrlAndAppendBase( url, OzCatalogUrls.baseServerUrl )

	return catalog:ozUser():getP( url, {
		requestMethod = "HEAD",
	    requestHeaders = {
	        { header = 'X-Authentication-Method', value = 'presigned' },
			{ header = 'Accept', value = 'application/json' },
		},
		followRedirects = false,
	} )
	:mapP( handleVideoRelatedOzErrorsMapT )
	:mapDataP( function( status, maybeJsonBlob, headers )
		local signedUrl = headers.Location
		if not signedUrl and WFHttpClient.responseContentTypeIsJson( headers ) then
			local linkInfo = KSJsonDecoder.decodeWebSafeJson_stripNull_ignoreEmpty( maybeJsonBlob )
			signedUrl = KSTableUtils.derefOpt( linkInfo, "url" )
			
			if not signedUrl then
				local err = KSTableUtils.derefOpt( linkInfo, "error" )
				if err then
					return 'error', 'no_hls', err
				end
			end
		end
		
		if signedUrl then
			return 'data', {
				url = signedUrl,
				auth_type = 'none',
			}
		else
			-- TODO [arahn 2013-08-21]: Probably should have a separate error code for auth error
			return 'error', 'no_hls'
		end
	end )
	
end

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

--[[!
	\brief Returns whether an asset is ready to play.
	
	\param catalog the catalog service
	\param assetId asset id
	
	\return B< bool, reason_or_versions > A behavior whos first result is whether
	the video can be played.  If it can be played, then reason_or_versions is a table with
	keys 'local', 'hls', '720p', and '360p' -- values true or false depending on the quality
	of playback.  If it cannot be played, then reason_or_versions is a string with value
	'loading', 'not_video', 'transcoding' or 'uploading'
--]]
function WFOzVideoAuth.isReadyToPlayB( catalog, assetId )
	
	return KSReactivePromiseBridge.promiseToBehavior(
		catalog:originalDataPathP( assetId, true ), WFUnknown.value()
	)
	:mapSwitchB( function( dataPath )
		
		if WFUnknown.isUnknown( dataPath ) then
			return KSReactive.constantB( false, 'loading' )
		end
		
		local haveLocal = not not dataPath
		
		return catalog._docStore:docValueB( 'asset', assetId )
		:mapB( function( docValue )
			
			if not docValue then
				return false, 'loading'
			end
			
			local contentType = KSTableUtils.derefOpt( docValue, 'importSource', 'contentType' )
			local subtype = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'subtype' )
			
			if subtype ~= 'video' and ( subtype == nil and not WFOzContentTypes.isVideo( contentType ) ) then
					
				-- For locally uploaded videos, we don't have a subtype in the document until
				-- after we sync down Oz changes.  To enable video player sooner we can
				-- test the importSource contentType
				return false, 'not_video'
			end
			
			local href_hls = getHlsHref( docValue )
			local href_720 = get720pHref( docValue )
			local href_360 = get360pHref( docValue )
			 
			if haveLocal or href_hls or href_360 or href_720 then
				return true, {
					[ 'local' ] = haveLocal,
					hls = not not href_hls,
					[ '720p' ] = not not href_720,
					[ '360p' ] = not not href_360,
				}
			end
			
			local original = KSTableUtils.derefOpt( docValue, '_serverProvidedMetadata', 'links', "/rels/master", 'href' )
			
			if original then
				return false, "transcoding"
			end
			
			return false, "uploading"

		end )
	end )
	
end

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

function WFOzVideoAuth.getStreamingPlaybackAuthInfoP( catalog, assetId )
	
	return catalog:originalDataPathP( assetId, true )
	:mapDataP( function( status, filePath )
	
		if filePath then
			return 'data', {
				auth_type = "none",
				url = "file:" .. WFURI.filePathToURIPath( filePath )
			}
		else
	
			return catalog:photoInfoP( assetId )
			
			:mapDataP( function( status, docValue )
			
				local duration = KSTableUtils.derefOpt( docValue, 'video', 'duration' )
				
				if duration and duration[ 1 ] / ( duration[ 2 ] or 1 ) < 600 then
					-- for videos less than 10 minutes, don't bother with HLS
					return simpleAuth360UrlP( catalog, assetId, docValue )
				else
					return simpleAuthHlsUrlP( catalog, assetId, docValue )
				end
				
			end )
		end
		
	end )
		
	
end

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

return WFOzVideoAuth
