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

WFCatSpec.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2015 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.utils
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFURI = require 'WFURI'

local WFLogger = require 'WFLogger'

local logger = WFLogger( 'WFCatSpec' )
--------------------------------------------------------------------------------

local check = assert( WFArguments.check )

local kDefaultBaseHref = "/v2/"

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

-- @mark WFCatSpec class

--------------------------------------------------------------------------------
--[[!
	\class WFCatSpec

	\brief A \c WFCatSpec gathers together very basic information about an Oz
		catalog so that other classes that do the work of communicating with that
		catalog can share a common vocabulary for accessing the catalog.

	Please help keep this class simple and STATELESS!
]]--

local WFCatSpec = {

	_catalogId = false,
		-- (string) UUID for the catalog
	
	_catalogDoc = false,
		-- (table) optional - the full catalog document from Oz, including links
		
	_baseHref = false,
		-- (string) relative HREF for the Oz API

	_catalogHref = false,
		-- (string) relative HREF for the catalog
		
	_catalogUpdatesHref = false,
		-- (string) relative HREF for the catalog bulk updates feed

	_spaceUploadHref = false,
		-- (string) relative HREF for the space sync up API 

	_wfCatUser = false,
		-- (WFCatUser) user account for the catalog

}

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

local function initializeSpecWithCatalogIdOrHref( self, catalogIdOrHref )
	-- Needs to set _catalogId, _baseHref, _catalogHref, and optionally
	-- _catalogUpdatesHref
	
	-- most validation should happen in __init, so don't worry too much about it here
	logger:warnf( "Creating a WFCatSpec from the id or href will cause some URLs to be manually constructed" )
	if catalogIdOrHref:match( 'v1%.0' ) then
		error( "WFCatSpec: Can not be used with v1 APIs" )
	end

	if catalogIdOrHref:match( 'v2%.0' ) then
		error( "WFCatSpec: Incorrect v2 URL base" )
	end

	if catalogIdOrHref:match( '^/' ) then

		local _, _, baseHref = string.find( catalogIdOrHref, "^(/[^/]+)/?" )

		if not baseHref then
			error( "WFCatSpec: catalog HREF malformed (no baseHref): " .. catalogIdOrHref, 4 )
		end

		self._baseHref = baseHref ..  '/'
		self._catalogHref = catalogIdOrHref
		self._catalogId = catalogIdOrHref:sub( -32 )

	else

		self._baseHref = kDefaultBaseHref
		self._catalogHref = self._baseHref .. "catalogs/" .. catalogIdOrHref
		self._catalogId = catalogIdOrHref

	end
		
end

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

local function initializeSpecWithCatalogTable( self, catalogDoc )
	-- Needs to set _catalogId, _baseHref, _catalogHref, and optionally
	-- _catalogUpdatesHref
	
	-- most validation should happen in __init, so don't worry too much about it here
	
	self._catalogId = catalogDoc.id
	self._catalogDoc = catalogDoc
	local selfHref = KSTableUtils.derefOpt( catalogDoc, 'links', 'self', 'href' )
							or ( 'catalogs/' .. self._catalogId )
	
	if catalogDoc.base then
		
		-- calculate base href (usually '/v2/')
		local parsedBaseUri = WFURI.parse( catalogDoc.base )
		
		self._baseHref = parsedBaseUri.rawPath or kDefaultBaseHref

		-- calculate catalog href (usually '/v2/catalogs/<id>')
		local catalogUri = WFURI.resolve( catalogDoc.base, selfHref )
		local parsedCatalogUri = WFURI.parse( catalogUri )
		
		self._catalogHref = parsedCatalogUri.rawPath
		
		-- calculate the catalog updates href
		local catalogUpdatesHref = KSTableUtils.derefOpt( catalogDoc, 
									'links', '/rels/updates', 'href' )
									
		local updatesUri = WFURI.resolve( catalogDoc.base, catalogUpdatesHref )
		local parsedUpdatesUri = WFURI.parse( updatesUri )
		
		self._catalogUpdatesHref = parsedUpdatesUri.rawPath
	
	else
		
		self._baseHref = kDefaultBaseHref
		
		self._catalogHref = self._baseHref .. selfHref
		
	end

	
end

--------------------------------------------------------------------------------
--[[!
	\brief Constructs the catalog.

	\param wfCatUser (WFCatUser) user account for the catalog
	\param catalogIdOrHrefOrDoc (string|table) catalog document, relative HREF or UUID for the catalog. The 
		catalog document is preferred here as we can use the links section provided by Oz. If a relative HREF or
		catalog UUID are used here some of the urls we use will need to be manually constructed and are a bit 
		more fragile.
]]--

function WFCatSpec:__init( wfCatUser, catalogIdOrHrefOrDoc )

	check( 'WFCatSpec', 'WFCatUser', '+string|table' )
	
	if type( catalogIdOrHrefOrDoc ) == 'string' then
		initializeSpecWithCatalogIdOrHref( self, catalogIdOrHrefOrDoc )
	else
		initializeSpecWithCatalogTable( self, catalogIdOrHrefOrDoc )
	end
	
	if not self._catalogHref:match( "^/v[0-9.]+/catalogs/[0-9a-f]+$" ) then
		error( "WFCatSpec: catalog HREF malformed: " .. self._catalogHref, 4 )
	end
	
	self._catalogUpdatesHref = self._catalogUpdatesHref or 
									( self._catalogHref .. '/updates' )
	
	self._spaceUploadHref = '/v2/spaces/updates'
	
	if #self._catalogId ~= 32 or
		not self._catalogId:match( "^[0-9a-f]+$" )
	then
		error( "WFCatSpec: catalog ID malformed: " .. self._catalogId, 4 )
	end

	self._wfCatUser = wfCatUser

end

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

function WFCatSpec:__tostring()

	return string.format( "WFCatSpec( id = %s, user = %s )",
						  self._catalogId,
						  tostring( self._wfCatUser ) )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the base URL for the server.

	\return (string) URL for the catalog
]]--

function WFCatSpec:serverBaseUrl()

	check( 'WFCatSpec' )

	return self._wfCatUser:serverBaseUrl()

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the UUID for the catalog.

	\return (string) UUID for the catalog
]]--

function WFCatSpec:catalogId()

	check( 'WFCatSpec' )

	return self._catalogId

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the base URL for OZ api.

	\return (string) base URL for the OZ api (i.e. "/v2/")
]]--

function WFCatSpec:baseHref()

	check( 'WFCatSpec' )

	return self._baseHref

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the base URL for the catalog.

	\return (string) base URL for the catalog (i.e. "/v2/catalogs/(uuid)")
]]--

function WFCatSpec:catalogHref()

	check( 'WFCatSpec' )

	return self._catalogHref

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the base catalog doc (if present) used to create this WFCatSpec. 
	This can be used to create an equivalent WFCatSpec in another thread.
	
	\return (string) catalog doc used to create this WFCatSpec (or nil if it was 
		created by catalogId only )
]]--
function WFCatSpec:catalogDoc()
	
	check( 'WFCatSpec' )
	
	return self._catalogDoc and self._catalogDoc or nil

end

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

--[[!
	\brief Returns the updates URL (both upload and download) for the catalog sync feed.
	
	\return (string) URL for the catalog sync feed (both up and down)
]]--
function WFCatSpec:catalogUpdatesUrl()
	
	check( 'WFCatSpec' )
		
	return WFURI.stringByAppendingPathComponents( self:serverBaseUrl(),
														 self._catalogUpdatesHref )
end

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

--[[!
	\brief Returns the updates URL (upload) for the space sync feed.
	
	\return (string) URL for the space sync up feed
]]--

function WFCatSpec:spaceUploadUrl()
	
	check( 'WFCatSpec' )
		
	return WFURI.stringByAppendingPathComponents( self:serverBaseUrl(),
														 self._spaceUploadHref )
														 
end

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

--[[!
	\brief Returns the user's Oz ID.
]]--

function WFCatSpec:ozUserId()

	check( 'WFCatSpec' )

	return self._wfCatUser:userId()

end


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

--[[!
	\brief Returns the observable of user's IMS ID.
]]--

function WFCatSpec:userIdIMS()

	check( 'WFCatSpec' )

	return self._wfCatUser:userIdIMS()

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the WFCatUser object for this user.
]]--

function WFCatSpec:wfOzUser()

	check( 'WFCatSpec' )

	return self._wfCatUser

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns true if running against stage, false if not.
]]--

function WFCatSpec:isStageEnv()

	check( 'WFCatSpec' )
	
	-- This should be made fragile, e.g. have the client tell the daemon whether
	-- running against stage or not when launching it, when someone has time.

	return self._wfCatUser and
		self._wfCatUser:serverBaseUrl():find( "photos%-stage%.adobe%.io" ) ~= nil

end

--------------------------------------------------------------------------------
--[[!
	\brief Starts an HTTP GET request, signing the request with the current
		access token of the current user and handles refreshing the access
		token if needed. Returns the response via an observable.

	\param url (string) The URL to fetch. If it is a server-relative URL
		(e.g. "/v2/..."), the URL will be assumed to be relative to the core
		server URL for this user.
	\param optArgs (optional, table) Options to modify the request.

	\return <code>Observable< data, headers ></code> An Observable that resolves to the response
		from HTTP server

	\sa WFHttpClient.getP
]]--

function WFCatSpec:get( url, optArgs )

	check( 'WFCatSpec', '+string', '?table' )

	return self._wfCatUser:get( url, optArgs )

end

--------------------------------------------------------------------------------
--[[!
	\brief Starts an HTTP GET request for a large binary download, signing the request 
		with the current access token of the current user and handles refreshing 
		the access token if needed. Returns the response via an observable.

	\param url (string) The URL to fetch. If it is a server-relative URL
		(e.g. "/v2/..."), the URL will be assumed to be relative to the core
		server URL for this user.
	\param optArgs (optional, table) Options to modify the request.

	\return <code>Observable< data, headers ></code> An Observable that resolves to the response
		from HTTP server

	\sa WFHttpClient.getP
]]--

function WFCatSpec:getBinary( url, optArgs )

	check( 'WFCatSpec', '+string', '?table' )

	return self._wfCatUser:getBinary( url, optArgs )

end

--------------------------------------------------------------------------------
--[[!
	\brief Starts an HTTP PUT request, signing the request with the current
		access token of the current user and handles refreshing the access
		token if needed. Returns the response via an observable.

	\param url (string) The URL to fetch. If it is a server-relative URL
		(e.g. "/v2/..."), the URL will be assumed to be relative to the core
		server URL for this user.
	\param data (\c KSData or table) data to send with the request
		(use a Lua table for form data)
	\param optArgs (optional, table) options to modify the request

	\return <code>Observable< data, headers ></code> An Observable that resolves to the response
		from HTTP server

	\sa HttpClient.putP
]]--

function WFCatSpec:put( url, data, optArgs )

	check( 'WFCatSpec', '+string', '_', '?table' )

	return self._wfCatUser:put( url, data, optArgs )

end

--------------------------------------------------------------------------------
--[[!
	\brief Starts an HTTP PUT request of a large binary payload, signing the 
		request with the current access token of the current user and handles 
		refreshing the access token if needed. Returns the response via an observable.

	\param url (string) The URL to fetch. If it is a server-relative URL
		(e.g. "/v2/..."), the URL will be assumed to be relative to the core
		server URL for this user.
	\param data (\c KSData or table) data to send with the request
		(use a Lua table for form data)
	\param optArgs (optional, table) options to modify the request

	\return <code>Observable< data, headers ></code> An Observable that resolves to the response
		from HTTP server

	\sa HttpClient.putP
]]--

function WFCatSpec:putBinary( url, data, optArgs )

	check( 'WFCatSpec', '+string', '_', '?table' )

	return self._wfCatUser:putBinary( url, data, optArgs )

end

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

return Rx.Class 'WFCatSpec'( WFCatSpec )
