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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 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 KSTableUtils 	= require 'KSTableUtils'
local WFCatOzErrors = require 'WFCatOzErrors'
local WFSQLiteUtils = require 'WFSQLiteUtils'

local LrAssetUtils = require 'LrAssetUtils'
local LrmAssetUtils = require 'LrmAssetUtils'

	-- WF.oz_client.managed_catalog
local WFCatAssetAux = require 'WFCatAssetAux'
local WFCatAssetUtils = require 'WFCatAssetUtils'

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

local LrmCatSyncStatus = {}

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

-- Assets

-- While this list of sync statuses might look like a subset of the
-- asst sync statuses in WFCatSyncStatus, there are some differences.

	-- regular statuses
local kAssetNeedsMetadataUpload 			= 'needsMetadataUpload'
local kAssetNeedsMasterUpload 				= 'needsMasterUpload'
local kAssetNeedsDevelopXmpUpload			= 'needsDevelopXmpUpload'
local kAssetNeedsProxyOrMasterDownload		= 'needsProxyOrMaster'
local kAssetNeedsDevelopXmpDownload			= 'needsDevelopXmp'
local kAssetComplete 						= 'complete'
local kAssetCompleteNoMaster 				= 'completeNoMaster'
local kAssetCompleteNoNegatives 			= 'completeNoNegatives'
local kAssetStackComplete 					= 'stackComplete'

	-- error statuses
local kAssetStorageLimitExceeded			= 'storageLimitExceeded'
local kAssetTrialOrSubscriptionExpired		= 'trialOrSubscriptionExpired'
local kAssetMultipartCompleteError			= 'multipartCompleteError'
local kAssetInsufficientPermissionError		= 'insufficientPermissionError'
local kAssetInvalidAux						= 'invalidAuxError'
local kAssetInvalidXmpDevelop				= 'invalidXmpDevelopError'

	-- pending statuses
local kAssetPendingCommit					= 'pendingCommit'
	-- A status used to indicate that an asset was imported, but should not yet sync.
	-- This status is only assigned in BYOCR workflows, and is a temporary state.
	-- It should only exist in the window where a camera roll asset is being actively
	-- edited.

local kAssetSyncStatusById = {
	kAssetNeedsMetadataUpload, 				-- 1
	kAssetNeedsMasterUpload, 				-- 2
	kAssetNeedsDevelopXmpUpload,			-- 3
	kAssetNeedsProxyOrMasterDownload,		-- 4
	kAssetNeedsDevelopXmpDownload,			-- 5
	kAssetComplete, 						-- 6
	kAssetCompleteNoMaster, 				-- 7
	kAssetStackComplete, 					-- 8
	kAssetStorageLimitExceeded,				-- 9
	kAssetTrialOrSubscriptionExpired,		-- 10
	kAssetMultipartCompleteError,			-- 11
	kAssetCompleteNoNegatives,				-- 12
	kAssetInsufficientPermissionError,		-- 13
	kAssetInvalidAux,						-- 14
	kAssetPendingCommit,					-- 15
	kAssetInvalidXmpDevelop,				-- 16
}

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

-- Albums

local kAssetSyncStatus = KSTableUtils.invert( kAssetSyncStatusById )

local kAlbumSyncing = 'syncing'
local kAlbumComplete = 'synced'
local kAlbumSyncingForOfflineUse = 'syncingForOfflineUse'
local kAlbumCompleteForOfflineUse = 'syncedForOfflineUse'

local kAlbumSyncStatusById = {
	kAlbumSyncing,									-- 1
	kAlbumComplete,									-- 2
	kAlbumSyncingForOfflineUse, 					-- 3
	kAlbumCompleteForOfflineUse,					-- 4
}

local kAlbumSyncStatus = KSTableUtils.invert( kAlbumSyncStatusById )

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

local function getSyncingStatusIdsForAssets( allSyncStatuses )

	-- SPAM [jandrew 2021-06-08]: on mobile the kAssetNeedsProxyOrMasterDownload and
	-- kAssetNeedsDevelopXmpDownload sync statuses don't mean sync of proxy/master/xmp
	-- is imminent so don't count them as syncing.

	local syncingStates = {
		kAssetNeedsMetadataUpload,
		kAssetNeedsMasterUpload,
		kAssetNeedsDevelopXmpUpload
	}

	-- Copy status ids into a table that can be unpacked.

	local syncingStatusIds = {}

	for _, syncingState in pairs( syncingStates ) do

		if allSyncStatuses[ syncingState ] then
			table.insert( syncingStatusIds, allSyncStatuses[ syncingState ] )
		end

	end

	return syncingStatusIds

end

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

local function getErrorStatusIdsForAssets( allSyncStatuses )

	local errorSyncStates = {
		kAssetStorageLimitExceeded,
		kAssetTrialOrSubscriptionExpired,
		kAssetMultipartCompleteError,
		kAssetInsufficientPermissionError,
		kAssetInvalidAux,
		kAssetInvalidXmpDevelop,
	}

	local errorStatusIds = {}

	for _, errorState in ipairs( errorSyncStates ) do

		if allSyncStatuses[ errorState ] then
			table.insert( errorStatusIds, allSyncStatuses[ errorState ] )
		end

	end

	return errorStatusIds

end

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

local function isPendingCommit( docValue )

	return docValue._labels and docValue._labels[ '_pendingCommit' ]

end

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

function LrmCatSyncStatus.getAssetSyncStatus( docValue )

	local syncStatusId = kAssetNeedsMetadataUpload

	if isPendingCommit( docValue ) then

		syncStatusId = kAssetPendingCommit

	elseif LrmAssetUtils.isStack( docValue ) then

		if not LrAssetUtils.isLatestMetadataUploaded( docValue ) then
			syncStatusId = kAssetNeedsMetadataUpload
		else
			syncStatusId = kAssetStackComplete
		end

	else

		if not LrAssetUtils.isLatestMetadataUploaded( docValue ) then

			if WFCatOzErrors.hasStorageExceededError( docValue ) then
				syncStatusId = kAssetStorageLimitExceeded
			elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue ) then
				syncStatusId = kAssetTrialOrSubscriptionExpired
			elseif WFCatOzErrors.hasMultipartCompleteError( docValue ) then
				syncStatusId = kAssetMultipartCompleteError
			elseif WFCatOzErrors.hasInsufficientPermissionError( docValue ) then
				syncStatusId = kAssetInsufficientPermissionError
			else
				syncStatusId = kAssetNeedsMetadataUpload
			end

		elseif WFCatAssetAux.hasLocalAuxErrorForCurrentDevelopSettings( docValue ) then

			syncStatusId = kAssetInvalidAux

		elseif WFCatAssetUtils.hasLocalXmpDevelopError( docValue ) then

			syncStatusId = kAssetInvalidXmpDevelop

		elseif LrmAssetUtils.doesAssetNeedMasterUploaded( docValue ) then

			if WFCatOzErrors.hasStorageExceededError( docValue ) then
				syncStatusId = kAssetStorageLimitExceeded
			elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue ) then
				syncStatusId = kAssetTrialOrSubscriptionExpired
			elseif WFCatOzErrors.hasMultipartCompleteError( docValue ) then
				syncStatusId = kAssetMultipartCompleteError
			elseif WFCatOzErrors.hasInsufficientPermissionError( docValue ) then
				syncStatusId = kAssetInsufficientPermissionError
			else
				syncStatusId = kAssetNeedsMasterUpload
			end

		elseif WFCatAssetUtils.doesAssetNeedDevelopSettingsUploaded( docValue )
				or WFCatAssetAux.hasUnsyncedDevelopAuxLocally( docValue ) then

			-- For the purpose of sync status, ths develop settings state considers
			-- externalized develop settings XMP, as well as any referenced aux objects.

			if WFCatOzErrors.hasStorageExceededError( docValue ) then
				syncStatusId = kAssetStorageLimitExceeded
			elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue ) then
				syncStatusId = kAssetTrialOrSubscriptionExpired
			elseif WFCatOzErrors.hasMultipartCompleteError( docValue ) then
				syncStatusId = kAssetMultipartCompleteError
			else
				syncStatusId = kAssetNeedsDevelopXmpUpload
			end

		elseif LrmAssetUtils.doesAssetNeedProxyOrMasterDownloaded( docValue ) then

			syncStatusId = kAssetNeedsProxyOrMasterDownload

		elseif LrmAssetUtils.doesAssetNeedDevelopXmpDownloaded( docValue )
				or WFCatAssetAux.needsAuxDownloadForDevelop( docValue ) then

			syncStatusId = kAssetNeedsDevelopXmpDownload

		else

			if LrmAssetUtils.hasMasterLocally( docValue ) then

				syncStatusId = kAssetComplete

			elseif LrmAssetUtils.hasProxyLocally( docValue ) then

				if docValue._spaceId and docValue.subtype == 'video' then

					-- This addresses bug https://jira.corp.adobe.com/browse/WF-1611
					-- The kAssetCompleteNoMaster sync status maps to "Synced from Lr Classic"
					-- in client UI. This is not true for shared-with-you video assets.
					-- Shared-with-you video assets will not have an accessible original
					-- or a true proxy. Videos, in general, use the 720p as the 'proxy'. This
					-- 720p rendition is available for all shared-with-you album members.
					--
					-- In order to prevent shared-with-you videos from showing up as
					-- "Synced from Lr Classic", we do not treat the presence of the 720p
					-- rendition as if the asset has a proxy for shared videos.
					--
					-- Note: The bug only applies to shared-with-you viewers/contributors
					-- and not editors because editors have access to the original.

					syncStatusId = kAssetCompleteNoNegatives

				else

					syncStatusId = kAssetCompleteNoMaster

				end

			else

				syncStatusId = kAssetCompleteNoNegatives

			end

		end

	end

	return kAssetSyncStatus[ syncStatusId ]

end

function LrmCatSyncStatus.getAlbumSyncStatus( docValue )

	local syncStatusId = kAlbumComplete

	local isMarkedForOffline = false
	if KSTableUtils.derefOpt( docValue, '_localOnly', 'useOffline' ) then
		isMarkedForOffline = true
	end

	--  LrAssetUtils.isLatestMetadataUploaded() is applicable to albums as well
	if LrAssetUtils.isLatestMetadataUploaded( docValue ) then

		if isMarkedForOffline then
			syncStatusId = kAlbumCompleteForOfflineUse
		else
			syncStatusId = kAlbumComplete
		end

	else

		if isMarkedForOffline then
			syncStatusId = kAlbumSyncingForOfflineUse
		else
			syncStatusId = kAlbumSyncing
		end

	end

	return kAlbumSyncStatus[ syncStatusId ]

end

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

function LrmCatSyncStatus.isAssetCompleted( statusOrId )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAssetSyncStatusById[ statusOrId ]
	end

	return statusOrId == kAssetComplete
		or statusOrId == kAssetCompleteNoMaster
		or statusOrId == kAssetStackComplete
		or statusOrId == kAssetCompleteNoNegatives

end

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

function LrmCatSyncStatus.isAlbumCompleted( statusOrId )

	if type( statusOrId ) ~= 'string' then
		statusOrId = kAlbumSyncStatusById[ statusOrId ]
	end

	return statusOrId == kAlbumComplete
		or statusOrId == kAlbumCompleteForOfflineUse

end

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

function LrmCatSyncStatus.getAssetSyncStatusList()

	return kAssetSyncStatus

end

function LrmCatSyncStatus.getAlbumSyncStatusList()

	return kAlbumSyncStatus

end

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

-- Returns a map of albumIds and their isCompleted syncstatus of the parent collections of the albumIds passed.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfParentCollectionOfAlbums()
function LrmCatSyncStatus.getSyncStatusOfParentCollectionOfAlbums( wfCatalog, albumIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT a.docId, a.syncStatus
						FROM albums as a
						INNER JOIN albums as b ON b.parentId == a.docId
						WHERE b.docId IN CARRAY(?,?,'char*')]],

			calm = 2,
			albumIds,
			#albumIds
		}

		:map( function( albumStatuses )

			local syncedAlbumsById = {}

			for _, albumStatus in ipairs( albumStatuses ) do

				local albumId = albumStatus[ 1 ]
				local statusId = albumStatus[ 2 ]

				syncedAlbumsById[ albumId ] = LrmCatSyncStatus.isAlbumCompleted( statusId )

			end

			return syncedAlbumsById

		end )

end

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

-- Returns a map of albumsIds and their isCompleted syncstatus of the albums asscociated
-- with passed albumAssetIds.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfAlbumsOfAlbumAssets()
function LrmCatSyncStatus.getSyncStatusOfAlbumsOfAlbumAssets( wfCatalog, albumAssetIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT albums.docId, albums.syncStatus
						FROM albums
						INNER JOIN album_asset ON album_asset.albumId == albums.docId
						WHERE album_asset.docId IN CARRAY(?, ?, 'char*') ]],

			calm = 2,
			albumAssetIds,
			#albumAssetIds
		}

		:map( function( albumStatuses )

			local syncedAlbumsById = {}

			for _, albumStatus in ipairs( albumStatuses ) do

				local albumId = albumStatus[ 1 ]
				local statusId = albumStatus[ 2 ]

				syncedAlbumsById[ albumId ] = LrmCatSyncStatus.isAlbumCompleted( statusId )

			end

			return syncedAlbumsById

		end )

end

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

local function parseAssetStatuses( assetStatuses )

	local syncedAssetsById = {}

	for _, assetStatus in ipairs( assetStatuses ) do

		local assetId = assetStatus[ 1 ]
		local statusId = assetStatus[ 2 ]

		syncedAssetsById[ assetId ] = LrmCatSyncStatus.isAssetCompleted( statusId )

	end

	return syncedAssetsById

end

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

-- Returns a map of assetIds and their isCompleted syncstatus of the assets asscociated
-- with passed albumAssetIds.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfAssetsOfAlbumAssets()
function LrmCatSyncStatus.getSyncStatusOfAssetsOfAlbumAssets( wfCatalog, albumAssetIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT coreInfo.docId, coreInfo.syncStatus
						FROM coreInfo
						INNER JOIN album_asset ON album_asset.assetId == coreInfo.docId
						WHERE album_asset.docId IN CARRAY(?, ?, 'char*')

					UNION
					SELECT trashAssets.docId, trashAssets.syncStatus
						FROM trashAssets
						INNER JOIN album_asset ON album_asset.assetId == trashAssets.docId
						WHERE album_asset.docId IN CARRAY(?, ?, 'char*')

					]],
			calm = 2,
			albumAssetIds,
			#albumAssetIds,
			albumAssetIds,
			#albumAssetIds
		}

		:map( parseAssetStatuses )

end

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

-- Returns a map of assetIds and their isCompleted syncstatus of the assets asscociated
-- with passed versionIds.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfAssetsOfVersions()
function LrmCatSyncStatus.getSyncStatusOfAssetsOfVersions( wfCatalog, versionIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT coreInfo.docId, coreInfo.syncStatus
						FROM coreInfo
						INNER JOIN versions ON versions.assetId == coreInfo.docId
						WHERE versions.docId IN CARRAY(?,?, 'char*')

					UNION
					SELECT trashAssets.docId, trashAssets.syncStatus
						FROM trashAssets
						INNER JOIN versions ON versions.assetId == trashAssets.docId
						WHERE versions.docId IN CARRAY(?,?, 'char*')

					]],
			calm = 2,
			versionIds,
			#versionIds,
			versionIds,
			#versionIds
		}

		:map( parseAssetStatuses )

end

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

-- Returns a map of assetIds and their isCompleted syncstatus of the assets asscociated
-- with passed favoriteIds.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfAssetsOfFavorites()
function LrmCatSyncStatus.getSyncStatusOfAssetsOfFavorites( wfCatalog, favoriteIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT coreInfo.docId, coreInfo.syncStatus
						FROM coreInfo
						INNER JOIN spacefavorite ON spacefavorite.assetId == coreInfo.docId
						WHERE spacefavorite.docId IN CARRAY(?,?, 'char*')

					UNION
					SELECT trashAssets.docId, trashAssets.syncStatus
						FROM trashAssets
						INNER JOIN spacefavorite ON spacefavorite.assetId == trashAssets.docId
						WHERE spacefavorite.docId IN CARRAY(?,?, 'char*')
					]],
			calm = 2,
			favoriteIds,
			#favoriteIds,
			favoriteIds,
			#favoriteIds
		}

		:map( parseAssetStatuses )

end

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

-- Returns a map of assetIds and their isCompleted syncstatus of the assets asscociated
-- with passed commentIds.
-- This method is the Lrm variant of WFCatSyncStatus.getSyncStatusOfAssetsOfComments()
function LrmCatSyncStatus.getSyncStatusOfAssetsOfComments( wfCatalog, commentIds )

	return wfCatalog:indexStore():query {
			sql = [[SELECT coreInfo.docId, coreInfo.syncStatus
						FROM coreInfo
						INNER JOIN comment ON comment.assetId == coreInfo.docId
						WHERE comment.docId IN CARRAY(?,?, 'char*')

					UNION
					SELECT trashAssets.docId, trashAssets.syncStatus
						FROM trashAssets
						INNER JOIN comment ON comment.assetId == trashAssets.docId
						WHERE comment.docId IN CARRAY(?,?, 'char*')
					]],
			calm = 2,
			commentIds,
			#commentIds,
			commentIds,
			#commentIds
		}

		:map( parseAssetStatuses )

end

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

function LrmCatSyncStatus.getDocIdsAndStatusWithSyncPendingOrError( wfCatalog )

	local syncingStatusIds = getSyncingStatusIdsForAssets( kAssetSyncStatus )

	local errorStatusIds = getErrorStatusIdsForAssets( kAssetSyncStatus )

	local syncingOrErrorStatusIds = KSTableUtils.appendArray( syncingStatusIds, errorStatusIds )

	syncingOrErrorStatusIds[ #syncingOrErrorStatusIds + 1 ]
			= kAssetSyncStatus[ kAssetPendingCommit ]
		-- Add in the pending commit status, which is technically not a sync or error
		-- status, but is useful to include as this function is only used by the
		-- diagnostic log.

	local assetsWithSyncPendingOrErrorSQL = string.format(
		[[SELECT docId, syncStatus
			FROM coreInfo
			WHERE coreInfo.syncStatus IN ( %s )]],
		WFSQLiteUtils.createVariableArgList( #syncingOrErrorStatusIds ) )

	return wfCatalog:indexStore():query {
			sql = assetsWithSyncPendingOrErrorSQL,
			unpack( syncingOrErrorStatusIds )
		}
		:map( function( rows )
			local results = {}
			for _, row in ipairs( rows ) do
				table.insert( results, { assetId = row[ 1 ], status = row[ 2 ], statusString = kAssetSyncStatusById[ row[ 2 ] ] } )
			end
			return results
		end )
end

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

function LrmCatSyncStatus.getDownloadRenditionNames()

	local masterRenditionNames = { 'thumbnail2x', 'preview', 'proxyWithoutMaster', 'master', 'xmp_develop', 'aux-original' }
	local proxyRenditionNames = { 'thumbnail2x', 'preview', 'proxy', 'xmp_develop', 'aux-proxy-2560' }

	return masterRenditionNames, proxyRenditionNames

end

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

return LrmCatSyncStatus
