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

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.

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

	-- WF.core.rxlua
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.self_test
local WFAsyncTestCase = require 'WFAsyncTestCase'

	-- WF.core.reactive
local KSPromises = require 'KSPromises'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFTableMerge = require 'WFTableMerge'

	-- WF.core.os
local WFDeviceInfo = require 'WFDeviceInfo'
local WFFileUtils = require 'WFFileUtils'
local KSPathUtils = require 'KSPathUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

local OzFilePaths = require "OzFilePaths"

	-- WF.core.lua
local KSData = require 'KSData'

	-- WF.oz_client
local WFOzPurgeSettings = require 'WFOzPurgeSettings'

	-- WF.web.json
local KSJsonEncoder = require 'KSJsonEncoder'

	-- Lrmobile_models
local LriOzRenditionServerUtils = require "LriOzRenditionServerUtils"	
local LrmCatalogTestHelper = require 'LrmCatalogTestHelper'	
local LriImport = require 'LriImport'

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

local LriImportTest = {}

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

local derefOpt = assert( KSTableUtils.derefOpt )

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

local catalog, catalogId

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

function LriImportTest.setUp()

	local _

	catalog, _, catalogId = LrmCatalogTestHelper.makeCatalogService()
	
end

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

function LriImportTest.tearDown()

	if catalog then
		
		catalog:close()
		
	end
	
end

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

local function makeImportDocValue( assetIdOrNil )

	local assetId = assetIdOrNil or KSIdGenerator.uuidInLowercaseHex()

	local docValue = {
		_docId = assetId,
		subtype = 'image',
		captureDate = '2012-01-02T20:54:00.485',
		importSource = {
			fileName = 'Image01.jpg',
			filePath = '/var/mobile/0001', -- different format?
			fileSize = 10000,
			sha256 = 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c',
			contentType = 'image/jpeg',
			originalWidth = 1200,
			originalHeight = 1000,
		},
		location = {
			longitude = -121.891392,
			latitude = 37.335753,
			altitude = 26,
			direction = 290.00,
			reference = 'C',
		},
		develop = {
			croppedHeight = 150,
			croppedWidth = 150,
			xmpCameraRaw = '<xmp />',
			userOrientation = 2,
		},
		xmp = {
			dc = {
				rights = 'Copyright',
			},
			exif = {
				ExposureTime = { 100, 1 },
				FNumber = { 100, 1 },
				ApertureValue = { 100, 1 },
				FocalLength = { 100, 1 },
				ISOSpeedRatings = 100,
			},
			tiff = {
				Make = 'Make',
				Model = 'Model',
			},
			aux = {
				Lens = 'Lens',
				IsMergedHDR = true,
				IsMergedPanorama = true,
			}
		},
		_localOnly = {
			localUrl = '1234',
		}
	}
	
	return docValue
	
end

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

local function writeRendition( docValue, renditionName, contentOrNil )

	local catalogPath = OzFilePaths.getCatalogBasePath( catalogId )

	local docId = docValue._docId

	local relativePath = OzFilePaths.getRelativeBlobPath( docId, catalogId )

	local contentId = LriOzRenditionServerUtils.renditionContentId( docValue )
	
	if renditionName == 'thumbnail2x' then
		relativePath = KSPathUtils.child( relativePath, 'th_' .. contentId .. '.jpg' )
	elseif renditionName == 'preview' then
		relativePath = KSPathUtils.child( relativePath, 'pv_' .. contentId .. '.jpg' )
	else
		return nil
	end
	
	local renditionData = KSData.make( contentOrNil or renditionName )
	
	local sha256 = renditionData:digest( 'SHA256' )
	
	KSData.saveToFile( renditionData, KSPathUtils.child( catalogPath, relativePath ) )
	
	renditionData:close()
	
	return relativePath, sha256

end

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

local function writeProxyOrMaster( docValue, proxyOrMaster, contentOrFileNameOrNil )

	local originalsFolder = OzFilePaths.getStandardOriginalFolderDocumentsPath( catalogId )

	local captureDate = assert( docValue.captureDate )

	local absolutePath = OzFilePaths.originalsFolderForDate( originalsFolder, captureDate )
	
	WFFileUtils.mkdir( absolutePath )
	
	local fileName = assert( derefOpt( docValue, 'importSource', 'fileName' ) )
	
	if proxyOrMaster == 'master' then
		absolutePath = KSPathUtils.child( absolutePath, fileName )
	elseif proxyOrMaster == 'proxy' then
		absolutePath = KSPathUtils.child( absolutePath, 'proxy_' .. fileName .. '.jpg' )
	else
		return nil
	end
	
	if not contentOrFileNameOrNil or not contentOrFileNameOrNil:find( '/' ) then
	
		local renditionData = KSData.make( contentOrFileNameOrNil or proxyOrMaster )
	
		local sha256 = renditionData:digest( 'SHA256' )
	
	 	absolutePath = KSData.saveToUniquelyNamedFile( renditionData, absolutePath )

		renditionData:close()

		return OzFilePaths.makeRelativeFrom( absolutePath, 'Originals' ), sha256
	
	else
	
		-- contentOrFileNameOrNil is path
				
		absolutePath = WFFileUtils.copyToUniquelyNamedFile( contentOrFileNameOrNil, absolutePath )
		
		local data = KSData.memoryMappedContentsOfFile( absolutePath )
		local sha256 = data:digest( 'SHA256' )
		
		data:close()

		return OzFilePaths.makeRelativeFrom( absolutePath, 'Originals' ), sha256

	end
end

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

local function getShaFromDisk( docValue, renditionName )

	local rendition = derefOpt( docValue, '_localOnly', 'files', renditionName )
	
	if not rendition then
		return nil
	end
	
	local path = OzFilePaths.getAbsolutePathInCatalog( catalogId, rendition.path )
	if not WFFileUtils.exists( path ) then
		return nil
	end
	
	local data = KSData.memoryMappedContentsOfFile( path )
	local shaFromDisk = data:digest( 'SHA256' )
	
	data:close()
					
	return shaFromDisk

end

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

local function removeImportedFiles( docValue )

	local renditions = { 'thumbnail2x', 'preview', 'proxy', 'master' }
	
	for _, renditionName in ipairs( renditions ) do
	
		local rendition = derefOpt( docValue, '_localOnly', 'files', renditionName )
		if rendition then
		
			local path = OzFilePaths.getAbsolutePathInCatalog( catalogId, rendition.path )

			pcall( WFFileUtils.delete, path )
			
		end

	end

end

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

function LriImportTest.testSingleImportWithProxyAndRenditions( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath, thumbnailSha = writeRendition( importDocValue, 'thumbnail2x' )
	
	local previewPath, previewSha = writeRendition( importDocValue, 'preview' )
	
	local proxyPath, proxySha = writeProxyOrMaster( importDocValue, 'proxy' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		proxy = proxyPath,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
			
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxyShaFromDisk = getShaFromDisk( docValue, 'proxy' )
										
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						proxy = {
							sha256 = proxyShaFromDisk
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			return 'data', {
				importSource = {
					importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId ),
					importedBy = userId,
				},
				proxy = {
					sha256 = proxySha
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return getShaFromDisk( docValue, 'thumbnail2x' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnailSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )

					return getShaFromDisk( docValue, 'preview' )

				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local localAssetId = derefOpt( docValue, 'importSource', 'localAssetId' )
					local deviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )
					
					return { id = localAssetId, deviceId = deviceId }
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', { 
			id = '1234', 
			deviceId = WFDeviceInfo.uniqueDeviceId()
		} )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					removeImportedFiles( docValue )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					copyDocValue._docId = importDocValue._docId

					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

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

function LriImportTest.testSingleImportWithRenditionsAndMaster( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath, thumbnailSha = writeRendition( importDocValue, 'thumbnail2x' )
	
	local previewPath, previewSha = writeRendition( importDocValue, 'preview' )
	
	local masterPath, masterSha = writeProxyOrMaster( importDocValue, 'master' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		master = masterPath,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local masterShaFromDisk = getShaFromDisk( docValue, 'master' )
					
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						master = {
							sha256 = masterShaFromDisk,
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			return 'data', {
				importSource = {
					importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId ),
					importedBy = userId,
				},
				master = {
					sha256 = masterSha,
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local thumbnail = derefOpt( docValue, '_localOnly', 'files', 'thumbnail2x' )
					local path = OzFilePaths.getAbsolutePathInCatalog( catalogId, thumbnail.path )
					
					local thumbnailData = KSData.memoryMappedContentsOfFile( path )
					local thumbnailShaFromDisk = thumbnailData:digest( 'SHA256' )
					
					return thumbnailShaFromDisk
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnailSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local preview = derefOpt( docValue, '_localOnly', 'files', 'preview' )
					local path = OzFilePaths.getAbsolutePathInCatalog( catalogId, preview.path )
					
					local previewData = KSData.memoryMappedContentsOfFile( path )
					local previewShaFromDisk = previewData:digest( 'SHA256' )
					
					return previewShaFromDisk
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local localAssetId = derefOpt( docValue, 'importSource', 'localAssetId' )
					local deviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )
					
					return { id = localAssetId, deviceId = deviceId }
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', { 
			id = '1234', 
			deviceId = WFDeviceInfo.uniqueDeviceId()
		} )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					removeImportedFiles( docValue )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					copyDocValue._docId = importDocValue._docId
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

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

function LriImportTest.testSingleImportWithRenditions( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath, thumbnailSha = writeRendition( importDocValue, 'thumbnail2x' )
	
	local previewPath, previewSha = writeRendition( importDocValue, 'preview' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			return 'data', {
				importSource = {
					importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId ),
					importedBy = userId,
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return getShaFromDisk( docValue, 'thumbnail2x' )
									
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnailSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local previewShaFromDisk = getShaFromDisk( docValue, 'preview' )
					
					removeImportedFiles( docValue )

					return previewShaFromDisk
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewSha )
		
end

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

function LriImportTest.testSingleImportWithProxyAndRenditions( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath, thumbnailSha = writeRendition( importDocValue, 'thumbnail2x' )
	local previewPath, previewSha = writeRendition( importDocValue, 'preview' )
	local proxyPath, proxySha = writeProxyOrMaster( importDocValue, 'proxy' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		proxy = proxyPath,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
			
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxyShaFromDisk = getShaFromDisk( docValue, 'proxy' )
					
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						proxy = {
							sha256 = proxyShaFromDisk,
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			return 'data', {
				importSource = {
					importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId ),
					importedBy = userId,
				},
				proxy = {
					sha256 = proxySha,
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return getShaFromDisk( docValue, 'thumbnail2x' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnailSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return getShaFromDisk( docValue, 'preview' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewSha )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local localAssetId = derefOpt( docValue, 'importSource', 'localAssetId' )
					local deviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )
					
					return { id = localAssetId, deviceId = deviceId }
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', { 
			id = '1234', 
			deviceId = WFDeviceInfo.uniqueDeviceId()
		} )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					removeImportedFiles( docValue )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					copyDocValue._docId = importDocValue._docId
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

--------------------------------------------------------------------------------
if not WFPlatformInfo.isAndroid then
-- Android doesn't have the large 12MB.jpg copied to the bundle.

function LriImportTest.testLinkedFiledImport( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
		
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath = writeRendition( importDocValue, 'thumbnail2x' )	
	local previewPath = writeRendition( importDocValue, 'preview' )
	
	local testDataPath = KSPathUtils.getStandardFilePath( "testdata" )
	testDataPath = KSPathUtils.child( testDataPath, 'assets' )
	
	local proxyPath = writeProxyOrMaster( importDocValue, 'proxy', KSPathUtils.child( testDataPath, '1.5MB.jpg' ) )
	local masterPath = writeProxyOrMaster( importDocValue, 'master', KSPathUtils.child( testDataPath, '12MB.jpg' ) )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		proxy = proxyPath,
		master = masterPath,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxyShaFromDisk = getShaFromDisk( docValue, 'proxy' )
					local masterShaFromDisk = getShaFromDisk( docValue, 'master' )
									
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						proxy = {
							existsOnDisk = proxyShaFromDisk and true or false,
						},
						master = {
							existsOnDisk = masterShaFromDisk and true or false,
						}
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			return 'data', {
				importSource = {
					importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId ),
					importedBy = userId,
				},
				proxy = {
					existsOnDisk = true,
				},
				master = {
					existsOnDisk = true,
				}
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
					
					removeImportedFiles( docValue )

					local copyDocValue = KSTableUtils.deepCopy( docValue )
					copyDocValue._docId = importDocValue._docId
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end
end

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

function LriImportTest.testBulkAssetImport( test )

	local assetArray = {}
	
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
		
	local kMaxAssets = 10
	
	for i = 1, kMaxAssets do
	
		local importDocValue = makeImportDocValue()

		local thumbnailPath = writeRendition( importDocValue, 'thumbnail2x', 'thumbnail2x' .. i )
		local previewPath, sha256 = writeRendition( importDocValue, 'preview', 'preview' .. i )
					
		importDocValue.captureDate = string.format( '2015-01-%02d', i )
		importDocValue.importSource.sha256 = sha256
		
		if i % 2 == 0 then
		
			importDocValue = KSJsonEncoder.encodeOzUploadJson( importDocValue )
			
		end
		
		local importAsset = {
			renditions = {
				{
					renditionName = 'thumbnail2x',
					renditionData = thumbnailPath
				},
				{
					renditionName = 'preview',
					renditionData = previewPath
				}
			},
			docValue = importDocValue,
			deviceId = deviceId,
			jobAssetId = i,
		}
		
		assetArray[ #assetArray + 1 ] = importAsset
	
	end
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					return type( assetIds ) == 'table' and #assetIds == kMaxAssets
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:indexStore():queryP( 'SELECT count( docId ) FROM coreInfo' )
				:mapDataP_new( function( rows )
				
					return rows[ 1 ][ 1 ]
					
				end )
			
		end )
		
		:continueWhenValueMatches( 'data', kMaxAssets )
		
end

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

function LriImportTest.testFailingImport_invalidJson( test )

	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local docValue = {
		foo = 'bar',
	}
	
	local docValueAsString = KSJsonEncoder.encodeOzUploadJson( docValue )
	
	docValueAsString = docValueAsString:replace( ':', '=' )
	
	local importAsset = {
		docValue = docValueAsString,
		deviceId = deviceId,
		jobAssetId = 1,
	}
	
	local assetArray = { importAsset }
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
			
		end )
		
		:continueWhenValueMatches( 'error', ':ignore:' )
	
end

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

function LriImportTest.testDerivedFromNonExistingAsset( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local originCatalogId = KSIdGenerator.uuidInLowercaseHex()
	local originAssetId = KSIdGenerator.uuidInLowercaseHex()
	local originRevisionId = KSIdGenerator.uuidInLowercaseHex()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnailPath = writeRendition( importDocValue, 'thumbnail2x' )
	local previewPath = writeRendition( importDocValue, 'preview' )
	local _, sha256 = writeProxyOrMaster( importDocValue, 'master' )
	
	local derivedDocValue = {
		captureDate = '2015-11-09',
		importSource = {
			sha256 = sha256
		},
	}
	
	local originInformation = {
		catalogId = originCatalogId,
		assetId = originAssetId,
		revisionId = originRevisionId,
	}
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnailPath
			},
			{
				renditionName = 'preview',
				renditionData = previewPath,
			}
		},
		docValue = derivedDocValue,
		deviceId = deviceId,
		derivedFrom = originInformation,
		jobAssetId = 1,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:docStore():applyOverlayToDocumentP {
				docType = 'asset',
				docId = originAssetId,
				overlay = importDocValue,
			}
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			return catalog:getCatalogIdP():mapDataP_new( function( catalogId )
			
				WFTableMerge.applyOverlay( originInformation, {
					catalogId = catalogId,
					assetId = { _delete = true },
				} )
				
				return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
					:mapDataP_new( function( assetIds )
					
						assetId = assetIds[ 1 ].assetId
						
						return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
						
					end )
					
			end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			WFTableMerge.applyOverlay( originInformation, {
				assetId = KSIdGenerator.uuidInLowercaseHex(),
			} )
			
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					removeImportedFiles( docValue )
					
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
end

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

function LriImportTest.testDerivedFromValidAsset( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	WFTableMerge.applyOverlay( importDocValue, {
		_serverProvidedMetadata = {
			links = {
				master = '/v1.0/foo',
			},
		},
	} )
	
	local originAssetId = KSIdGenerator.uuidInLowercaseHex()
	local originRevisionId = KSIdGenerator.uuidInLowercaseHex()
	
	local assetId
	
	local thumbnailPath = writeRendition( importDocValue, 'thumbnail2x' )
	local previewPath = writeRendition( importDocValue, 'preview' )
	local _, sha256 = writeProxyOrMaster( importDocValue, 'master' )
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local catalogId
	local derivedDocValue
	
	WFAsyncTestCase( test )
	
		:waitFor( function()
		
			return catalog:getCatalogIdP():mapDataP_new( function( catId )
			
				catalogId = catId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( userId )
			
				return catalog:getCatalogIdP():mapDataP_new( function( catalogId )
				
					derivedDocValue = {
						version = 1,
						captureDate = '2015-11-09',
						importSource = {
							sha256 = sha256,
							importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), WFDeviceInfo.uniqueDeviceId() ),
							uniqueDeviceId = WFDeviceInfo.uniqueDeviceId(),
							importedBy = userId,
						},
						_localOnly = {
							localUrl = 'file://foo'
						}
					}
		
					local importAsset = {
						renditions = {
							{
								renditionName = 'thumbnail2x',
								renditionData = thumbnailPath
							},
							{
								renditionName = 'preview',
								renditionData = previewPath,
							}
						},
						docValue = derivedDocValue,
						derivedFrom = {
							catalogId = catalogId,
							assetId = originAssetId,
							revisionId = originRevisionId,
						},
						jobAssetId = 1,
					}
					
					assetArray[ #assetArray + 1 ] = importAsset
					
					return catalog:docStore():applyOverlayToDocumentP {
						docType = 'asset',
						docId = originAssetId,
						overlay = importDocValue,
					}
					
				end )
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					local importedAsset = assetIds[ 1 ]
					assetId = assetIds[ 1 ].assetId
					
					local localUrl = importedAsset.localUrl
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table',
						localUrl
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true, 'file://foo' )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					removeImportedFiles( docValue )
					
					return derefOpt( docValue, '_localOnly', 'localUrl' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp' )
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			local copiedDocValue = KSTableUtils.deepCopy( importDocValue )
			
			WFTableMerge.applyOverlay( copiedDocValue, derivedDocValue )
			
			WFTableMerge.applyOverlay( copiedDocValue, {
				xmp = {
					xmpMM = {
						DerivedFromDocument = string.format( '/v2/catalogs/%s/assets/%s', catalogId, originAssetId ),
						DerivedFromInstance = string.format( '/v2/catalogs/%s/assets/%s/revisions/%s', catalogId, originAssetId, originRevisionId )
					}
				}
			} )
			
			return 'data', derefOpt( copiedDocValue, 'xmp' )
		
		end )
		
end

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

function LriImportTest.testDuplicateSha256( test )

	local assetId1, assetId2
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local importDocValue = makeImportDocValue()

	local importAsset = {
		docValue = importDocValue,
		deviceId = deviceId,
		jobAssetId = 1,
	}

	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 30 )
	
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, { importAsset }, importOptions )
			
				:mapDataP_new( function( assetIds )
				
					assetId1 = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table' and
						assetIds[ 1 ].importStatus == 'new' and assetIds[ 1 ].localUrl == '1234'
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return LriImport.addAssetArrayToCatalogP( catalog, { importAsset }, importOptions )
			
				:mapDataP_new( function( assetIds )
				
					assetId2 = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table' and
						assetIds[ 1 ].importStatus == 'duplicate'
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return KSPromises.resolvedPromiseP( 'data', assetId1 == assetId2 )
			
		end )
		
		:continueWhenValueMatches( 'data', true )
		
end

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

function LriImportTest.testHasAssetBySha256( test )

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	local sha256_1 = KSData.make( 'hash' ):digest( 'SHA256' )
	local sha256_2 = KSData.make( 'hash1' ):digest( 'SHA256' )
	
	WFAsyncTestCase( test, 30 )
	
		:waitFor( function()
		
			local status = pcall( LriImport.hasAssetBySha256P, catalog, 'hash' )
			
			return KSPromises.resolvedPromiseP( 'error', status )
			
		end )
		
		:continueWhenValueMatches( 'error', false )
		
		:waitFor( function()
		
			local status = pcall( LriImport.hasAssetBySha256P, catalog, 42 )
			
			return KSPromises.resolvedPromiseP( 'error', status )
			
		end )
		
		:continueWhenValueMatches( 'error', false )
		
		:waitFor( function()
		
			return catalog:docStore():applyOverlayToDocumentP {
				docId = assetId,
				docType = 'asset',
				overlay = {
					subtype = 'image',
					importSource = {
						sha256 = sha256_1
					},
					_localOnly = {
						localUrl = '1234',
					}
				}
			}
			
		end )
		
		:waitFor( function()
		
			return LriImport.hasAssetBySha256P( catalog, sha256_1 )
			
		end )
		
		:continueWhenValueMatches( 'data', assetId )
		
		:waitFor( function()
		
			return LriImport.hasAssetBySha256P( catalog, sha256_2 )
			
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
end

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

return WFRxTestCase.wrapSuite( LriImportTest )
