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

NmbCatWatermark.lua

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

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

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

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local WFCrypto = require 'WFCrypto'
local KSPathUtils = require 'KSPathUtils'
local WFFileUtils = require 'WFFileUtils'
local WFAsyncFileUtils = require 'WFAsyncFileUtils'
local KSTableUtils = require 'KSTableUtils'
local WFFolderWalker = require 'WFFolderWalker'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFCatWatermark = require "WFCatWatermark"


local WFLogger = require 'WFLogger'
local logger = WFLogger( "NmbCatWatermark" )

local check = assert( WFArguments.check )
local child = assert( KSPathUtils.child )

--------------------------------------------------------------------------------
local function getCatalogPath( wfCatalog )
	local catalogPath

	if WFPlatformInfo.isUnitTest() then
		catalogPath = KSPathUtils.getStandardFilePath( 'testdata' )
	else
		catalogPath = wfCatalog and wfCatalog:fileSpec() and wfCatalog:fileSpec():catalogPath()
	end
	return catalogPath
end

-- This function does a bit more than WFFileUtils.createFullPathFromRelativePathAndCatalogPath()
-- which is the reason why this function exists. It checks if the path is already absolute and returns
-- if such is the case.
--------------------------------------------------------------------------------
local function toAbsolutePath( wfCatalog, relativePath )

	if WFFileUtils.isAbsolutePath( relativePath ) then
		return relativePath
	end
	
	return WFFileUtils.createFullPathFromRelativePathAndCatalogPath( wfCatalog, relativePath )
	
end

--------------------------------------------------------------------------------
local function toRelativePath( wfCatalog, absolutePath )

	if not WFFileUtils.isAbsolutePath( absolutePath ) then
		return absolutePath
	end
	
	local catalogPath = getCatalogPath( wfCatalog ) .. WFPlatformInfo.folderSeparator
	local startIndex, endIndex = string.find( absolutePath, catalogPath )
	if startIndex == 1 and string.len( catalogPath ) == endIndex then
		return string.sub(absolutePath, endIndex + 1 )
	end
	
	return nil
	
end

--------------------------------------------------------------------------------
local function getWatermarkInfo( wfCatalog, watermarkId )
	return WFCatWatermark.getWatermarkInfo( wfCatalog, watermarkId )
	:map( function( watermarkInfo )
		
		local newWatermarkInfo = KSTableUtils.deepCopy( watermarkInfo )
		
		if newWatermarkInfo and newWatermarkInfo.filePath and newWatermarkInfo.filePath ~= nil then
			newWatermarkInfo.filePath = toAbsolutePath( wfCatalog, newWatermarkInfo.filePath )
		end
		
		return newWatermarkInfo
	end )
end

--------------------------------------------------------------------------------
local function getAllWatermarksInfo( wfCatalog )
	return WFCatWatermark.getAllWatermarksInfo( wfCatalog )
	:map( function( watermarksInfo )
		
		local newWatermarksInfo = {}
		for _, watermarkInfo in ipairs( watermarksInfo ) do
			local newWatermarkInfo =  KSTableUtils.deepCopy( watermarkInfo )
			if newWatermarkInfo.filePath and newWatermarkInfo.filePath ~= nil then
				newWatermarkInfo.filePath = toAbsolutePath( wfCatalog, newWatermarkInfo.filePath )
			end
			newWatermarksInfo[ #newWatermarksInfo + 1 ] = newWatermarkInfo
		end
		
		return newWatermarksInfo
		
	end )
end


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

local function imageWatermarksFolderName()
	return 'watermarks'
end

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

local function folderForImageWatermarks( baseBinariesPath )
		
	check( '+string' )
	
	local watermarkFolderPath = child( baseBinariesPath, imageWatermarksFolderName() )
	
	if not WFFileUtils.isPathOnOfflineVolume( watermarkFolderPath ) then
		local ok, maybeError = pcall( WFFileUtils.mkdir, watermarkFolderPath )
		if not ok then
			error( string.format( 'Creating directory failed. Path: %s Error:%s',
									  watermarkFolderPath, maybeError ) )
		end
	end
	
	return watermarkFolderPath
	
end

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

local function pathForImageWatermark( baseBinariesPath, watermarkDocId, fileName )
	
	check( '+string', 'string', 'string' )
		
	local imageWatermarkPath = folderForImageWatermarks( baseBinariesPath )
	
	local imageWatermarkFolder = child( imageWatermarkPath, watermarkDocId )
	local extension = fileName:match( "%.([^%.]+)$" ):lower()
	local saveFileName = require 'KSIdGenerator'.uuidInLowercaseHex() .. '.' .. extension
	if not WFFileUtils.exists( imageWatermarkFolder ) then
		local ok, maybeError = pcall( WFFileUtils.mkdir, imageWatermarkFolder )
		if not ok then
			error( string.format( 'Creating directory failed. Path: %s Error:%s',
									  imageWatermarkFolder, maybeError ) )
		end
	end
	
	local fileRelativePath = child( child( imageWatermarksFolderName(), watermarkDocId ), saveFileName )
	local fileAbsolutePath = child( imageWatermarkFolder, saveFileName )

	return fileAbsolutePath, fileRelativePath
end

--------------------------------------------------------------------------------
local function saveImageWatermarkCallbackFunc( wfCatalog, watermarkKSData, docValue )
	check( 'WFCatalog', 'KSData', 'table' )
	
	local watermarkDocId = docValue._id
	
	if not watermarkKSData then
		return Observable.throw(
			string.format( "invalid data for watermark doc %s",
														watermarkDocId ) )
	end
	
	local catalogPath = getCatalogPath( wfCatalog )
	local fileName = KSTableUtils.derefOpt( docValue, 'importSource', 'fileName' )
	local absolutePath, relativePath = pathForImageWatermark( catalogPath, watermarkDocId, fileName )
	return WFAsyncFileUtils.saveToFile( watermarkKSData, absolutePath )
		:map( function( success )
			if success then
				local imageWatermarkSha256 = WFCrypto.digestFile( absolutePath, 'SHA256' )
				local fileSize = WFFileUtils.fileAttributes( absolutePath ).fileSize
				return relativePath, fileSize, imageWatermarkSha256
			else
				error( string.format( "failed to save image watermark to %s", absolutePath ) )
			end
		end )
	
end


--------------------------------------------------------------------------------
local function downloadImageWatermark( wfCatalog, docId )
	return WFCatWatermark.downloadImageWatermark( wfCatalog, docId,	saveImageWatermarkCallbackFunc )
	:map( function( response )
		local newResponse = response
		if response.status == "success" then
			newResponse = KSTableUtils.deepCopy( response )
			newResponse.filePath = toAbsolutePath( wfCatalog, response.filePath )
		end
		return newResponse
	end )
end

--------------------------------------------------------------------------------
local function pathContentsSame( path1, path2 )
	local path1SHA256 = ( path1 ~= nil and WFFileUtils.exists( path1 ) and WFCrypto.digestFile( path1, 'SHA256' ) ) or ""
	local path2SHA256 = ( path2 ~= nil and WFFileUtils.exists( path2 ) and WFCrypto.digestFile( path2, 'SHA256' ) ) or ""
	return path1SHA256 ~= "" and path1SHA256 == path2SHA256
end

--------------------------------------------------------------------------------
-- copies the watermark graphic file from user folder to catalog folder and updates `watermarkImagePath`
local function copyImageWatermarkToCatalog( wfCatalog, srcImageWatermarkPath, uuid )

	if not srcImageWatermarkPath or srcImageWatermarkPath == "" then
		
		return Rx.Observable.just()
		
	end
	
	return getWatermarkInfo( wfCatalog, uuid )
	
		:map( function( watermarkInfo )
			
			local newImageWatermarkPath
			if watermarkInfo and pathContentsSame( srcImageWatermarkPath, watermarkInfo.filePath ) then
				local relativePath = toRelativePath( wfCatalog, watermarkInfo.filePath )
				newImageWatermarkPath = relativePath
			else
				local catalogPath = getCatalogPath( wfCatalog )
				local absoluteImageWatermarkPath, relativeImageWatermarkPath = pathForImageWatermark( catalogPath, uuid, srcImageWatermarkPath )
				WFFileUtils.copy( srcImageWatermarkPath, absoluteImageWatermarkPath )
				newImageWatermarkPath = relativeImageWatermarkPath
			end
			
			return newImageWatermarkPath
			
		end )
end

--------------------------------------------------------------------------------
local function createWatermark( wfCatalog, name, settings, optFilePath, optFileName, optId )
	
	local watermarkId = optId or require "KSIdGenerator".uuidInLowercaseHex()
	
	return copyImageWatermarkToCatalog( wfCatalog, optFilePath, watermarkId )
		
		:flatMapLatest(function( newFilePath )
			
			return WFCatWatermark.createWatermark( wfCatalog, name, settings, newFilePath, optFileName, watermarkId )
		
		end )
end

--------------------------------------------------------------------------------
local function updateWatermark( wfCatalog, id, name, settings, optFilePath, optFileName )
	return copyImageWatermarkToCatalog( wfCatalog, optFilePath, id )
	:flatMapLatest(function( newFilePath )
		return WFCatWatermark.updateWatermark( wfCatalog, id, name, settings, newFilePath, optFileName )
	end)
end

--------------------------------------------------------------------------------
local function purgeOrphanWatermarkAssets( wfCatalog )
	return getAllWatermarksInfo( wfCatalog )
	:take( 1 )
	:map( function( ozWatermarks )
		-- Collect the non-orphan asset list
		
		local catalogPath = getCatalogPath( wfCatalog )
		local imageWatermarksFolder = folderForImageWatermarks( catalogPath )
		local fileType = WFFileUtils.exists( imageWatermarksFolder )
	
		if fileType ~= 'directory' then
			return {}
		end

		local nonOrphanWatermarkAssets = {}
		
		for _, watermark in ipairs( ozWatermarks ) do
			local asset
			asset = watermark.filePath
			nonOrphanWatermarkAssets[ #nonOrphanWatermarkAssets + 1 ] = asset
		end
		
		local invertedNonOrphanWatermarkAssets = KSTableUtils.invert( nonOrphanWatermarkAssets )
		
		-- Collect the orphan asset list now that we have the mapping

		-- Walk thru the <catalog-id>/watermarks directory to see if there are any
		-- 1) folders with orphan watermark ids => these are directory names for which there isn't an entry in nonOrphanWatermarkAssets
		-- 2) orphan files that don't find an entry in nonOrphanWatermarkAssets
		
		local orphanWatermarkAssets = {}
		
		for watermarkFile in WFFolderWalker( imageWatermarksFolder ) do
			if not invertedNonOrphanWatermarkAssets[ watermarkFile ] then
				orphanWatermarkAssets [ #orphanWatermarkAssets + 1 ] = watermarkFile
			end
		end
		
		local parentFoldersOfOrphans = {}
		for _, orphanWatermarkAsset in ipairs( orphanWatermarkAssets ) do
			local parentFolder = KSPathUtils.parent( orphanWatermarkAsset )
			if not parentFoldersOfOrphans[ parentFolder ] then
				parentFoldersOfOrphans[ parentFolder ] = true
			end
			local ok, didDelete = pcall( WFFileUtils.deleteFile, orphanWatermarkAsset )
			if not ok or not didDelete then
				local errMsg = not ok and didDelete or "failed to delete"
				logger:errorf( "Failed to delete '%s': %s", orphanWatermarkAsset, errMsg )
			end
		end
		
		for parentFolder, _ in pairs( parentFoldersOfOrphans ) do
			if WFFileUtils.empty( parentFolder ) then
				WFFileUtils.deleteDirectory( parentFolder )
			end
		end
			
		return orphanWatermarkAssets
	end )
	
end

--------------------------------------------------------------------------------
return {
	
	createWatermark = createWatermark,
	updateWatermark = updateWatermark,
	downloadImageWatermark = downloadImageWatermark,
	getWatermarkInfo = getWatermarkInfo,
	getAllWatermarksInfo = getAllWatermarksInfo,
	purgeOrphanWatermarkAssets = purgeOrphanWatermarkAssets,
	
}