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

WFCatCollaborator.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
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.

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

local Rx = require 'Rx'
local Observable = Rx.Observable

local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFLogger = require 'WFLogger'
local WFTableMerge = require 'WFTableMerge'

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

local logger = WFLogger( 'WFCatCollaborator' )
local check = WFArguments.check
local derefOpt = KSTableUtils.derefOpt
local avatarDownloadsInProgress = {}
local kAvatarResolutionFallbackOrder = { 'large2x', 'large', 'medium2x',
										 'medium', 'small2x', 'small' }
local kAvatarResolutionToPriority = {}
for i, resolution in ipairs( kAvatarResolutionFallbackOrder ) do
	kAvatarResolutionToPriority[ resolution ] = i
end

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

local function generateIndexSchema( _, indexStoreSchema )

	check( '_', 'WFRxIndexStoreSchema' )

	-- table feedbackuser

	indexStoreSchema:addTable {

		sql = [[CREATE TABLE feedback_user( userId, fullName, firstName, lastName, email, spaceId )]],
		
		relevantTypeSubtypeTuples = {
			{ 'feedbackuser', 'NULL' },
		},

		map = function( docValue, emit )

			if docValue and not docValue._deleted then

				local userId = derefOpt( docValue, '_id' )
				local fullName = derefOpt( docValue, '_serverProvidedMetadata', 'full_name' )
				local firstName = derefOpt( docValue, '_serverProvidedMetadata', 'first_name' )
				local lastName = derefOpt( docValue, '_serverProvidedMetadata', 'last_name' )
				local email = derefOpt( docValue, '_serverProvidedMetadata', 'email' )
				local spaceId = derefOpt( docValue, '_spaceId' )
								
				emit( userId, fullName, firstName, lastName, email, spaceId )
					
			end
								
		end,

		useLocalId = true,
		hasOneRowPerDocument = true,
	}
	
	indexStoreSchema:addIndex [[CREATE INDEX feedback_user_index ON feedback_user( userId )]]
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Get the list of all collaborators to a catalog (feedabckusers)

	\param wfCatalog (WFCatalog) The catalog

	\return <code>Observable< ></code> An Observable object that fires the list of
		collaborators, including names and userIds.
]]--

local function getAllCollaborators( _, wfCatalog )
	check( '_', 'WFCatalog' )
	
	return wfCatalog:indexStore():query {
		sql = [[ SELECT userId, fullName, email, spaceId FROM feedback_user ]],
		calm = 2,
	}
	:map( function( rows )
		local feedbackUsers = {}
		
		for i, row in ipairs( rows ) do
			local userId = row[ 1 ]
			local fullName = row[ 2 ]
			local email = row[ 3 ]
			local spaceId = row[ 4 ]
			
			feedbackUsers[ userId ] = {
				fullName = fullName,
				email = email,
				spaceId = spaceId,
			}
		end
		
		return feedbackUsers
	end )
end

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

-- @mark WFCatCollaborator namespace

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

local WFCatCollaborator = {
	class = {
		generateIndexSchema = generateIndexSchema,
		getAllCollaborators = getAllCollaborators,
	},
}

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

function WFCatCollaborator:_downloadAvatarBytes( catalogSpec, requestUrl )
	
	return catalogSpec:get( requestUrl )
		:flatMap( function( response, headers )
			if headers.status_code and headers.status_code == "302" then
			
				local redirectUrl = headers.Location
				
				if redirectUrl then
					return self:_downloadAvatarBytes( catalogSpec, redirectUrl )
				end
			end

			return Observable.just( response, headers )
		end )
end

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

function WFCatCollaborator:_downloadAvatarImp( catalog, feedbackuserDocValue, resolution )
	
	local resolutionIndex = kAvatarResolutionToPriority[ resolution ]
	local downloadLink = derefOpt( feedbackuserDocValue, '_serverProvidedMetadata', 'links', '/rels/avatars/' .. resolution, 'href' )
	
	local catalogSpec = catalog:ozCatalogSpec()
	local requestUrl = string.format( '%s%s', catalogSpec:serverBaseUrl(), downloadLink )
	
	return self:_downloadAvatarBytes( catalogSpec, requestUrl )
		
		:flatMap( function( response, headers )
			if response
				and headers.status_code
				and headers.status_code == "200" then

				return self:_saveAvatar( catalog, response, resolution, downloadLink )
			else
				logger:infof( 'Failed to fetch avatar:' )
				logger:infof( '  url: %s', requestUrl )
				logger:infof( '  status_code: %s', tostring( headers.status_code ) )
				return Observable.throw()
			end
		end )

		:catch( function()
			if resolutionIndex < #kAvatarResolutionFallbackOrder then
				local nextResolution = kAvatarResolutionFallbackOrder[ resolutionIndex + 1 ]
				return self:_downloadAvatarImp( catalog, feedbackuserDocValue, nextResolution )
			end
			
			return Observable.empty()
		end )
end

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

local function createProgressKey( userId, resolution )

	return string.format( "%s::%s", userId, resolution )

end

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

function WFCatCollaborator:_downloadAvatar( catalog, feedbackuserDocValue, preferredResolution )
	
	local userId = feedbackuserDocValue._id
	
	local progressKey = createProgressKey( userId, preferredResolution )
	
	if avatarDownloadsInProgress[ progressKey ] then
		return
	end
	
	avatarDownloadsInProgress[ progressKey ] = self:_downloadAvatarImp( catalog, feedbackuserDocValue, preferredResolution )
		:flatMapLatest( function( newAvatarOverlay, newAvatarResolution )
			
			-- If we already have an avatar downloaded, remove it
			return self:_deleteLocalAvatar( feedbackuserDocValue, newAvatarResolution )
			
				:map( function( deleteOverlay )

					local jointOverlay = WFTableMerge.applyOverlay( deleteOverlay, newAvatarOverlay )
					
					KSTableUtils.setNested( jointOverlay, nil, '_localOnly', 'universal', 'files', 'avatar', preferredResolution, '_delete' )
					
					jointOverlay._id = userId
					
					return catalog:docStore():applyOverlay( jointOverlay )
					
				end )
			
		end )
		:subscribe(
			-- onNext
			function()
			end,
			
			-- onError
			function()
				avatarDownloadsInProgress[ progressKey ] = nil
			end,
			
			-- onCompleted
			function()
				avatarDownloadsInProgress[ progressKey ] = nil
			end
		)
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Gets the avatar of a collaborator (feedbackuser), downloads if necessary

	\param userId (string) The unique wcd_guid of the user.
	\param optPreferredResolution (optional string) the preferred resolution for the avatar
		See the list above.

	\return <code>Observable< ></code> An Observable object that fires the avatar information
		If the avatar isn't available locally, it will be downloaded and an update fired.
]]--
function WFCatCollaborator:getAvatar( catalog, wfCatSynchronizer, userId, optPreferredResolution )
	
	check( '_', 'WFCatalog', 'WFCatSynchronizer', 'string', '?string' )
	
	logger:infof( 'Get user info %s', userId )
	
	return catalog:docStore():ongoingDocValue( userId )
		:flatMapLatest( function( docValue )
			
			local localAvatarPath, localAvatarResolution, localAvatarLink = self:_getLocalAvatarInfo( docValue )
			
			local currentLinkForDownloadedResolution = localAvatarResolution
				and derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/avatars/' .. localAvatarResolution, 'href' )
				
			local preferredResolution = optPreferredResolution or 'large2x'
			local preferredResolutionIndex = kAvatarResolutionToPriority[ preferredResolution ]
			local localResolutionIndex = localAvatarResolution and kAvatarResolutionToPriority[ localAvatarResolution ] or 0
			
			local needsDownload = self:_canDownloadAvatar( wfCatSynchronizer )
				and ( not localAvatarPath
					or localAvatarLink ~= currentLinkForDownloadedResolution
					or preferredResolutionIndex < localResolutionIndex )
			
			if needsDownload then
				self:_downloadAvatar( catalog, docValue, preferredResolution )
			end
			
			if not localAvatarPath then
				return Observable.empty()
			end
			
			return Observable.just( localAvatarPath )
			
		end )
end

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

return Rx.Class 'WFCatCollaborator'( WFCatCollaborator )
