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

WFCatCollaborationActivity.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2020 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.

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

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

local KSStringUtils = require 'KSStringUtils'
local KSTableUtils = require 'KSTableUtils'
local KSIdGenerator = require 'KSIdGenerator'
local WFArguments = require 'WFArguments'
local WFCatIdUtils = require 'WFCatIdUtils'
local WFCatUtils = require 'WFCatUtils'

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

local check = assert( WFArguments.check )

local OZ_MAX_COMMENT_BODY_CHARS = 1024

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

-- @mark WFCatCollaborationActivity namespace

local WFCatCollaborationActivity = {}

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

local function maybeGetFullDocId( docStore, maybeLocalId )

	if not maybeLocalId or type( maybeLocalId ) == 'string' then
		return Observable.just( maybeLocalId )
	end
	
	return docStore:localIdToDocId( maybeLocalId )
	
end

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

local function getSpaceDocForAlbum( docStore, albumId )

	return docStore:docValue( albumId )
	
		:flatMap( function( albumDoc )
		
			local spaceId = albumDoc._spaceId
	
			if not spaceId then
				return Observable.just( false )
			end
			
			return docStore:docValue( spaceId )
		
		end )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the comments posted on the asset from within a shared album.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetId (string|number) The unique ID of the asset. This can either be the local 
	id or the global uuid of the asset.
	
	\return <code>Observable< table ></code> An array of all comments in { assetId, albumId } combination.
	Each item will contain the id (full comment-id), author (full author-id), body (comment-text) and date (updated date)
	
 ]]--
function WFCatCollaborationActivity.getCommentsForAsset( wfCatalog, albumId, assetId )

	check( 'WFCatalog', 'string|number', 'string|number' )

	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
    local ozUserId = wfCatalog:ozUserId()
	
	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )
	local fullAssetIdObservable = maybeGetFullDocId( docStore, assetId )

	return Observable.combineLatest( 
			fullAlbumIdObservable, 
			fullAssetIdObservable, 
			getSpaceDocForAlbum( docStore, albumId ) )
			
		:flatMap( function( fullAlbumId, fullAssetId, maybeSpaceDoc )

			local thisFeedBackUserId
			
			if maybeSpaceDoc then
			
				thisFeedBackUserId = KSTableUtils.derefOpt( maybeSpaceDoc, '_serverProvidedMetadata', 'this_space_user', 'user', 'id' )
			
			end

			return indexStore:query( {
				sql = [[ SELECT
							comment.docId, comment.author_id, comment.body, comment.updateTimeStamp, comment.created
						FROM
							comment
						INNER JOIN
							space_album
						ON
							comment.spaceId == space_album.spaceId
						WHERE
							space_album.albumId == ?
						AND
							comment.assetId == ? ]],
				calm = 3,
				fullAlbumId,
				fullAssetId,
			} )
			:map( function( rows )

				local comments = {}
				
				for _, row in ipairs( rows ) do
					
					-- Oz is supposed to add the author info to the document. When 
					-- a comment/favorite is added from the client, the author info 
					-- will be missing till the new information has been downloaded. 
					-- Hence defaulting to current user when author field is missing.
					
					local feedBackUserId = row[ 2 ]
					
					local isYou = feedBackUserId == thisFeedBackUserId 
							or feedBackUserId == nil
					
					comments[ #comments + 1 ] = {
						id = row[ 1 ],
						author = feedBackUserId or ozUserId,
						body = row[ 3 ],
						updated = row[ 4 ],
						created = row[ 5 ],
						isYou = isYou,
					}

				end
				
				return comments

			end )

		end )

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds a text comment to an asset in a shared album.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetId (string|number) The unique ID of the asset. This can either be the local 
	id or the global uuid of the asset.
	
	\param body (string) The text body of the comment that needs to be added to the asset.
		The body length has a soft limit of 1024 and anything beyond that will get trimmed.
		
	\return <code>Observable</code> Observable sends onCompleted when done
	
 ]]--
function WFCatCollaborationActivity.addCommentForAsset( wfCatalog, albumId, assetId, body )

	check( 'WFCatalog', 'string|number', 'string|number', 'string' )

	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()

	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )

	return Observable.combineLatest( fullAlbumIdObservable, docStore:docValue( assetId ) )
	
		:flatMap( function( fullAlbumId, assetDocValue )
			
			local fullAssetId = assetDocValue._id
			
			local assetSpaceId = assetDocValue._spaceId
			
			body = KSStringUtils.UTF8Substring( body, 1, OZ_MAX_COMMENT_BODY_CHARS )
			
			if assetSpaceId then
			
				-- If the asset has a space Id, we know that this user is a
				-- member of the album, and not the owner. In this case,
				-- we must adjust the format of the document.
			
				local docValue = {
					_id = KSIdGenerator.uuidInLowercaseHex(),
					type = 'comment',
					subtype = 'user',
					_spaceId = assetSpaceId,
					asset = { id = fullAssetId },
					body = body,
				} 
				
				WFCatUtils.addUserCreatedFields( docValue )
				
				return docStore:setDocValue( docValue )
					:ignoreElements()
			
			else
			
				-- The user is the owner of the album.
			
				return indexStore:queryOnce( {
					sql = "SELECT spaceId FROM space_album WHERE albumId == ?",
					singleColumnQuery = true,
					fullAlbumId
				} )
				
				:flatMap( function( rows )
					
					local fullSpaceId = rows and rows[ 1 ]

					if fullSpaceId then

						local docValue = {
							_id = KSIdGenerator.uuidInLowercaseHex(),
							type = 'comment',
							subtype = 'user',
							space = { id = fullSpaceId },
							asset = { id = fullAssetId },
							body = body,
						} 
						
						WFCatUtils.addUserCreatedFields( docValue )
						
						return docStore:setDocValue( docValue )
							:ignoreElements()
						
					end

					return Observable.empty()

				end )
				
			end

		end )
		
end

--------------------------------------------------------------------------------
--[[!
	\brief Deletes the comment.
	
	\param commentId (string) The unique global uuid of the comment to be deleted.
	
	\return <code>Observable</code> Observable sends onCompleted when done
	
 ]]--
function WFCatCollaborationActivity.deleteComment( wfCatalog, commentId )

	check( 'WFCatalog', 'string' )

	local docStore = wfCatalog:docStore()

	return docStore:deleteDoc( commentId )
		:ignoreElements()
end

--------------------------------------------------------------------------------
--[[!
	\brief Returns array of user-ids that favorited the asset from within a shared album.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetId (string|number) The unique ID of the asset. This can either be the local 
	id or the global uuid of the asset.
	
	\return <code>Observable< array ></code> An array of all full user-ids who marked the 
		{ assetId, albumId } combination as favorite.
	
 ]]--
function WFCatCollaborationActivity.getFavoritesForAsset( wfCatalog, albumId, assetId )
	
	check( 'WFCatalog', 'string|number', 'string|number' )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	local ozUserId = wfCatalog:ozUserId()
	
	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )
	local fullAssetIdObservable = maybeGetFullDocId( docStore, assetId )

	return Observable.combineLatest( 
			fullAlbumIdObservable, 
			fullAssetIdObservable,
			getSpaceDocForAlbum( docStore, albumId ) )
	
		:flatMap( function( fullAlbumId, fullAssetId, maybeSpaceDoc )
		
			local thisFeedBackUserId
			
			if maybeSpaceDoc then
			
				thisFeedBackUserId = KSTableUtils.derefOpt( maybeSpaceDoc, '_serverProvidedMetadata', 'this_space_user', 'user', 'id' )
			
			end
		
			return indexStore:query( {
				sql = [[ SELECT
							spacefavorite.author_id
						FROM
							spacefavorite
						INNER JOIN
							space_album
						ON
							spacefavorite.spaceId == space_album.spaceId
						WHERE
							space_album.albumId == ?
						AND
							spacefavorite.assetId == ?
						ORDER BY
							spacefavorite.created ]],
				calm = 3,
				fullAlbumId,
				fullAssetId,
			} )
			:map( function( rows )
				
				local favoritesArray = {}
				local favoritesSet = {}
				
				for _, row in ipairs( rows ) do
				
					-- Oz is supposed to add the author info to the document. When 
					-- a comment/favorite is added from the client, the author info 
					-- will be missing till the new information has been downloaded. 
					-- Hence defaulting to current user when author field is missing.
					-- Only a single document should be in this state.
					
					local feedBackUserId = row[ 1 ] or thisFeedBackUserId or ozUserId
					
					local isYou = feedBackUserId == thisFeedBackUserId 
							or feedBackUserId == ozUserId
					
					if not favoritesSet[ feedBackUserId ] then
					
						favoritesArray[ #favoritesArray + 1 ] = {
							userId = feedBackUserId,
							isYou = isYou
						}
						
						favoritesSet[ feedBackUserId ] = true
						
					end
					
				end
				
				return favoritesArray
				
			end )
			
		end )
end

--------------------------------------------------------------------------------
--[[!
	\brief Marks the asset in a shared album as favorite for current user.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetId (string|number) The unique ID of the asset. This can either be the local 
	id or the global uuid of the asset.
	
	\return <code>Observable</code> Observable sends onCompleted when done
	
 ]]--
function WFCatCollaborationActivity.addFavorite( wfCatalog, albumId, assetId )

	check( 'WFCatalog', 'string|number', 'string|number' )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )

	return Observable.combineLatest( fullAlbumIdObservable, docStore:docValue( assetId ) )
	
		:flatMap( function( fullAlbumId, assetDocValue )
		
			local fullAssetId = assetDocValue._id
			local assetSpaceId = assetDocValue._spaceId
		
			if assetSpaceId then
			
				-- If the asset has a space Id, we know that this user is a
				-- member of the album, and not the owner. In this case,
				-- we must adjust the format of the document.
			
				local docValue = {
					_id = KSIdGenerator.uuidInLowercaseHex(),
					type = 'favorite',
					subtype = 'user',
					_spaceId = assetSpaceId,
					asset = { id = fullAssetId },
				}
				
				WFCatUtils.addUserCreatedFields( docValue )
				
				return docStore:setDocValue( docValue )
					:map( function( ... )
						return ...
					end )
					:ignoreElements()
					
			else
			
				-- User is the album owner.
				
				return indexStore:queryOnce( {
					sql = "SELECT spaceId FROM space_album WHERE albumId == ?",
					singleColumnQuery = true,
					fullAlbumId
				} )
				:flatMap( function( rows )
				
					local fullSpaceId = rows and rows[ 1 ]
					
					if fullSpaceId then
					
						local docValue = {
							_id = KSIdGenerator.uuidInLowercaseHex(),
							type = 'favorite',
							subtype = 'user',
							space = { id = fullSpaceId },
							asset = { id = fullAssetId },
						}
						
						WFCatUtils.addUserCreatedFields( docValue )
						
						return docStore:setDocValue( docValue )
							:ignoreElements()
					end
					
					return Observable.empty()
				
				end )
			
			end
		
		end )
		
end

--------------------------------------------------------------------------------
--[[!
	\brief Removes favorite tag from the asset in a shared album for the current user.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetId (string|number) The unique ID of the asset. This can either be the local 
	id or the global uuid of the asset.
		
	\return <code>Observable</code> Observable sends onCompleted when done
	
 ]]--
function WFCatCollaborationActivity.removeFavorite( wfCatalog, albumId, assetId )
	
	check( 'WFCatalog', 'string|number', 'string|number' )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	local ozUserId = wfCatalog:ozUserId()
	
	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )
	local fullAssetIdObservable = maybeGetFullDocId( docStore, assetId )
	local spaceDocObservable = getSpaceDocForAlbum( docStore, albumId )
	
	return Observable.combineLatest( fullAlbumIdObservable, fullAssetIdObservable, spaceDocObservable )
	
		:flatMap( function( fullAlbumId, fullAssetId, maybeSpaceDoc )
		
			local thisFeedBackUserId
			
			if maybeSpaceDoc then
			
				thisFeedBackUserId = KSTableUtils.derefOpt( maybeSpaceDoc, '_serverProvidedMetadata', 'this_space_user', 'user', 'id' )
			
			end
			
			thisFeedBackUserId = thisFeedBackUserId or ozUserId
			
			return indexStore:queryOnce( {
				sql = [[ SELECT
							spacefavorite.docId
						FROM
							spacefavorite
						INNER JOIN
							space_album
						ON
							spacefavorite.spaceId == space_album.spaceId
						WHERE
							space_album.albumId == ?
						AND
							spacefavorite.assetId == ?
						AND
							( spacefavorite.author_id == ? OR spacefavorite.author_id IS NULL ) ]],
				fullAlbumId,
				fullAssetId,
				thisFeedBackUserId
			} )
			:flatMap( function( rows )
			
				local docId = rows and rows[ 1 ] and rows[ 1 ][ 1 ]
				
				if docId then
					return docStore:deleteDoc( docId )
						:ignoreElements()
				end
				
				return Observable.empty()
			
			end )
		end )
end

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

local function getDocsForAlbumsByAssetId( wfCatalog, albumId, assetIds, sqlStr )

	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	local idsAreNumbers = type( assetIds[ 1 ] ) == 'number'
	
	local fullAlbumIdObservable = maybeGetFullDocId( docStore, albumId )
	local fullAssetIdsObservable = WFCatIdUtils.getOzAssetIdsForLocalIds( wfCatalog, assetIds )

	return Observable.combineLatest( 
			fullAlbumIdObservable, 
			fullAssetIdsObservable )
	
		:flatMap( function( fullAlbumId, fullAssetIds )
		
			local fullIdToLocalIdMap = {}
		
			if idsAreNumbers then
		
				for idx, fullAssetId in ipairs( fullAssetIds ) do
					fullIdToLocalIdMap[ fullAssetId ] = assetIds[ idx ]
				end
				
			end
		
			return indexStore:query( {
				sql = sqlStr,
				fullAssetIds,
				#fullAssetIds,
				fullAlbumId,
				calm = 3
			} )
			:map( function( rows )
				
				local entriesByAssetId = {}

				for _, row in ipairs( rows ) do
				
					local assetId = row[ 1 ]
					
					if idsAreNumbers then
						local localAssetId = fullIdToLocalIdMap[ assetId ]
						entriesByAssetId[ localAssetId ] = true
					else
						entriesByAssetId[ assetId ] = true
					end
					
				end
				
				return entriesByAssetId
				
			end )
			
		end )

end

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

--[[!
	\brief Returns a table of asset IDs, from the provided list, that have been favorited
		within a particular album.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetIds (table) A list of asset Ids.
	
	\return <code>Observable< map > A map of asset IDs that contain favorite for
		the specificed album/assets.</code>
 ]]--
 
function WFCatCollaborationActivity.getFavoritesForAlbumByAssetId( wfCatalog, albumId, assetIds )
	
	check( 'WFCatalog', 'string|number', 'table' )
	
	local sqlStr = [[ SELECT
							spacefavorite.assetId
						FROM
							spacefavorite
						INNER JOIN
							space_album
						ON
							spacefavorite.spaceId == space_album.spaceId
						INNER JOIN
							CARRAY( ?, ?, 'char*' ) AS fullAssetIds
						ON
							fullAssetIds.value == spaceFavorite.assetId
						WHERE
							space_album.albumId == ? ]]
			
	return getDocsForAlbumsByAssetId( wfCatalog, albumId, assetIds, sqlStr )
	
end

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

--[[!
	\brief Returns a table of asset IDs, from the provided list, that have been commented on
		for a particular album.
	
	\param albumId (string|number) The unique ID of the album. This can either be the local 
	id or the global uuid of the album.
	
	\param assetIds (table) A list of asset Ids.
	
	\return <code>Observable< map ></code> A map of asset IDs that contain comments for
		the specificed album/assets.
 ]]--
 
function WFCatCollaborationActivity.getCommentsForAlbumByAssetId( wfCatalog, albumId, assetIds )
	
	check( 'WFCatalog', 'string|number', 'table' )
	
	local sqlStr = [[ SELECT
							comment.assetId
						FROM
							comment
						INNER JOIN
							space_album
						ON
							comment.spaceId == space_album.spaceId
						INNER JOIN
							CARRAY( ?, ?, 'char*' ) AS fullAssetIds
						ON
							fullAssetIds.value == comment.assetId
						WHERE
							space_album.albumId == ? ]]
			
	return getDocsForAlbumsByAssetId( wfCatalog, albumId, assetIds, sqlStr )

end

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

function WFCatCollaborationActivity.getCommentedAlbumsForAssetId( wfCatalog, assetId )

	check( 'WFCatalog', 'string|number' )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	return maybeGetFullDocId( docStore, assetId )
	
		:flatMap( function( fullAssetId )
			
			local sqlString = [[ SELECT DISTINCT 
							albums.docId, albums.name, count(DISTINCT comment.docId) AS comment_count
						FROM 
							comment
						INNER JOIN 
							space_album 
						ON 
							comment.spaceId == space_album.spaceId
						LEFT JOIN 
							albums 
						ON 
							space_album.albumId = %s
						WHERE
							comment.assetId == ?
						GROUP BY 
							albums.docId ]]
			
			local albumIdField = "albums.albumId"
			
			-- Because mobile uses 'albums.docId' for the global id
			if require "WFPlatformInfo".isMobile() then
				albumIdField = "albums.docId"
			end
			
			return indexStore:query( {
				sql = string.format( sqlString, albumIdField ),
				calm = 3,
				fullAssetId,
			} )
			:map( function( rows )
				
				local commentedAlbumsArray = {}
				
				for _, row in ipairs( rows ) do
					
					local albumInfo = {}
					
					albumInfo.docId = row[ 1 ]
					albumInfo.albumName = row[ 2 ]
					albumInfo.commentsCount = row[ 3 ]
					
					commentedAlbumsArray[ #commentedAlbumsArray + 1 ] = albumInfo
					
				end
				
				return commentedAlbumsArray
				
			end )
			
			
		end )
end


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

function WFCatCollaborationActivity.getFavoritedAlbumsForAssetId( wfCatalog, assetId )

	check( 'WFCatalog', 'string|number' )
	
	local docStore = wfCatalog:docStore()
	local indexStore = wfCatalog:indexStore()
	
	return maybeGetFullDocId( docStore, assetId )
	
		:flatMap( function( fullAssetId )
			
			local sqlString = [[ SELECT DISTINCT 
							albums.docId, albums.name, count(DISTINCT spacefavorite.docId) AS favorite_count
						FROM 
							spacefavorite
						INNER JOIN 
							space_album 
						ON 
							spacefavorite.spaceId == space_album.spaceId
						LEFT JOIN 
							albums 
						ON 
							space_album.albumId = %s
						WHERE
							spacefavorite.assetId == ?
						GROUP BY 
							albums.docId ]]
			
			local albumIdField = "albums.albumId"

			-- Because mobile uses 'albums.docId' for the global id
			if require "WFPlatformInfo".isMobile() then
				albumIdField = "albums.docId"
			end
			
			return indexStore:query( {
				sql = string.format( sqlString, albumIdField ),
				calm = 3,
				fullAssetId,
			} )
			:map( function( rows )
				
				local favoritedAlbumsArray = {}
				
				for _, row in ipairs( rows ) do
					
					local albumInfo = {}
					
					albumInfo.docId = row[ 1 ]
					albumInfo.albumName = row[ 2 ]
					albumInfo.favoritesCount = row[ 3 ]
					
					favoritedAlbumsArray[ #favoritedAlbumsArray + 1 ] = albumInfo
					
				end
				
				return favoritedAlbumsArray
				
			end )
			
		end )
end

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

return WFCatCollaborationActivity
