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

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.reactive
local KSPromises = require 'KSPromises'

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local KSPathUtils = require "KSPathUtils"

local WFTableMerge = require 'WFTableMerge'

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

	-- WF.core.os
local KSDate = require 'KSDate'
local WFDeviceInfo = require 'WFDeviceInfo'
local WFFileUtils = require 'WFFileUtils'

	-- WF.core.rxLua
local WFRxPromiseBridge = require 'WFRxPromiseBridge'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'


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

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

local WFLogger = require 'WFLogger'

local logger = WFLogger( 'LriImport' )
--	:setLevel( 'debug' )


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

local LriImport = {}

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

local derefOpt = assert( KSTableUtils.derefOpt )

local kImportStatusNewAsset = 'new'
local kImportStatusDuplicateAsset = 'duplicate'

local asKSPromise = WFRxPromiseBridge

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

local function applyFilesOverlay( asset, renditionName, catalogPath, filePath )

	if filePath then

		local masterFileAttributes = WFFileUtils.fileAttributes( KSPathUtils.child( catalogPath, filePath ) )
		local masterSize = masterFileAttributes.fileSize

		local contentId

		if renditionName == 'master' or renditionName == 'proxy' then
			contentId = derefOpt(asset.docValue, 'importSource', 'sha256' )
		elseif renditionName == 'thumbnail2x' or renditionName == 'preview' then
			contentId = WFCatFileConductorConstants.renditionContentId( asset.docValue )
		end

		local fileInfo = {}

		fileInfo[ renditionName ] = {
			fileSize = masterSize,
			path = filePath,
			contentId = contentId
		}

		WFTableMerge.applyOverlay( asset.docValue, {
			_localOnly = {
				files = fileInfo
			}
		} )

	end

	return asset

end

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

local function createImportResult( assetId, localUrl, jobAssetId, importStatus )

	return {
		assetId = assetId,
		importStatus = importStatus,
		localUrl = localUrl,
		jobAssetId = jobAssetId,
	}
end

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

local function addAssetToCatalogP( catalog, asset, userId, options )

--	check( 'OzCatalogServiceImp', 'table' )

	-- check KSData
	-- check args

	-- check local url

	local deviceId = asset.deviceId

	local bypassSha256Check = options.bypassSha256Check or false

	if not deviceId then
		deviceId = WFDeviceInfo.uniqueDeviceId()
	end

	local jobAssetId = asset.jobAssetId

	if not jobAssetId then
		return KSPromises.resolvedPromiseP( 'error', 'No valid jobAssetId' )
	end

	local docValue = assert( asset.docValue )
	local sha256 = derefOpt( docValue, 'importSource', 'sha256' )

	if not docValue.captureDate then
		docValue.captureDate = KSDate.timeInIso8601Format( nil, false )
	end

	docValue.originalCaptureDate = docValue.captureDate

	if not docValue.userCreated then
		docValue.userCreated = KSDate.timeInIso8601Format()
		docValue.userUpdated = KSDate.timeInIso8601Format()
	end

	if not docValue.importSource then
		docValue.importSource = {}
	end

	if not docValue._localOnly then
		docValue._localOnly = {}
	end

	local localUrl = derefOpt( docValue, '_localOnly', 'localUrl' )
	local isFileUrl = localUrl and string.find( localUrl, 'file://' ) == 1 or false

	if isFileUrl then
		docValue._localOnly.localUrl = nil
	end

	local isVideo = docValue.video and true or false

	docValue.subtype = isVideo and 'video' or 'image'
	docValue.importSource[ 'type' ] = nil
	docValue.importSource[ 'importedOnDevice' ] = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId )
	docValue.importSource[ 'importedBy' ] = userId

	if not derefOpt( docValue, 'importSource', 'importTimestamp' ) then
		docValue.importSource[ 'importTimestamp' ] = KSDate.timeInIso8601Format( nil, false )
	end

	docValue.importSource[ 'localAssetId' ] = localUrl
	docValue.importSource[ 'uniqueDeviceId' ] = WFDeviceInfo.uniqueDeviceId()

	if docValue.height then
		docValue.importSource[ 'originalHeight' ] = docValue.height
	end

	if docValue.width then
		docValue.importSource[ 'originalWidth' ] = docValue.width
	end

	docValue.height = nil
	docValue.width = nil
	docValue.originalCaptureDate = nil

	local docStore = catalog:docStore()

	local newRevId = asset.revId or KSIdGenerator.uuidInLowercaseHex()

	return LriImport.hasAssetBySha256P( catalog, sha256, bypassSha256Check )

		:mapDataP_new( function( assetId )

			if assetId then

				return createImportResult( assetId, localUrl, jobAssetId, kImportStatusDuplicateAsset )

			end

			return catalog:getOriginalsDocumentsFolderP()

				:mapDataP_new( function( catalogPath )

					local assetId = asset.assetId or KSIdGenerator.uuidInLowercaseHex()

					if asset.master then
						applyFilesOverlay( asset, 'master', catalogPath, asset.master )
					end

					if asset.proxy then
						applyFilesOverlay( asset, 'proxy', catalogPath, asset.proxy )
					end

					if asset.renditions then
						for _, rendition in ipairs( asset.renditions ) do
							applyFilesOverlay( asset, rendition.renditionName, catalogPath, rendition.renditionData )
						end
					end

					asset.docValue._newRevId = newRevId

					return docStore:applyOverlayToDocumentP {
						docId = assetId,
						docType = 'asset',
						overlay = asset.docValue,
					}
					:mapDataP_new( function()

						return createImportResult( assetId, localUrl, jobAssetId, kImportStatusNewAsset )

					end )

				end )

		end )

		:mapP( function( ... )

			local originalData = asset.original

			if originalData and KSData.isData( originalData ) then
				originalData:close()
			end

			local proxyData = asset.proxy

			if proxyData and KSData.isData( proxyData ) then
				proxyData:close()
			end

			return ...

		end )

		:runUntilDoneP()

end

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

function LriImport.addAssetArrayToCatalogP( catalog, assetArray, options )

--	check( 'OzCatalogServiceImp', 'table', 'table' )

	local minimumFreeDiskSpace = assert( options.minimumFreeDiskSpace )

	local function _addAssetToCatalogP( asset )

		if type( asset.docValue ) == 'string' then

			local result, docValue = pcall( KSJsonDecoder.decodeWebSafeJson, asset.docValue )

			asset.docValue = nil

			if result then

				asset.docValue = docValue

			end

		end

		if not asset.docValue or type( asset.docValue ) ~= 'table' then

			return KSPromises.resolvedPromiseP( 'error', 'No valid docValue for asset' )

		end

		local derivedFrom = asset.derivedFrom

		return catalog:getWfCatalog()

			:flatMap( function( wfCatalog )

				local auxInfo = asset.auxInfo or {}
				asset.auxInfo = nil
				asset.docValue.auxInfo = nil -- Just in case clients are doing the wrong thing!

				local auxDataList =  auxInfo or {}

				return WFCatAssetAux.getAuxOverlay( wfCatalog, nil, auxDataList )

			end )

			:call( require 'WFRxPromiseBridge' )

			:mapDataP_new( function( auxOverlay )

				local isVideo = not not KSTableUtils.derefOpt( asset, 'docValue', 'video' )

				if not isVideo then -- Videos do not currently support masks.

					local docValue = asset.docValue
					docValue = WFCatAssetAux.mergeAuxOverlay( docValue, auxOverlay )
					asset.docValue = docValue

				end

				-- INFO [pkamin 2015-08-31]: we don't verify the revisionId as we might not have a revision
				-- due to not yet uploaded metadata

				if derivedFrom and derivedFrom.catalogId and derivedFrom.assetId then

					return catalog:getCatalogIdP()

						:mapDataP_new( function( catalogId )

							if catalogId == derivedFrom.catalogId then

								return catalog:docStore():docValueOnceP( 'asset', derivedFrom.assetId )

									:mapP( function( status, originDocValue )

										local newDocValue = asset.docValue

										if status == 'data' and originDocValue._changeCounter then

											local derivedFromInstance = derivedFrom.revisionId
															and string.format( '/v2/catalogs/%s/assets/%s/revisions/%s',
																				catalogId,
																				originDocValue._docId,
																				derivedFrom.revisionId )
															or nil

											local derivedFromDocument = string.format( '/v2/catalogs/%s/assets/%s',
																			catalogId,
																			originDocValue._docId )

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

											local derivedOverlay = {
												xmp = {
													xmpMM = {
														DerivedFromDocument = derivedFromDocument,
														DerivedFromInstance = derivedFromInstance,
													}
												}
											}

											local originXmp = derefOpt( originDocValue, 'xmp' )

											local copiedXmp = {
												xmp = originXmp,
											}

											WFTableMerge.applyOverlay( newDocValue, copiedXmp )

											WFTableMerge.applyOverlay( newDocValue, derivedOverlay )

										end

										asset.docValue = newDocValue

										return 'data', asset

									end )

									:mapDataP_new( function()
										return catalog:ozUserIdP()
									end )

									:mapDataP_new( function( userId )
										return addAssetToCatalogP( catalog, asset, userId, options )
									end )

							end

							return catalog:ozUserIdP()

								:mapDataP_new( function( userId )
									return addAssetToCatalogP( catalog, asset, userId, options )
								end )

						end )

				end

				return catalog:ozUserIdP()

					:mapDataP_new( function( userId )

						return addAssetToCatalogP( catalog, asset, userId, options )

					end )
			end )
	end


	local syncClient = catalog:getSyncClient()

	-- Pause and lock sync while the import process is taking place. This speeds up Time-To-Loupe on mobile platforms

	-- First, lock the sync client so that the client may not restart sync while WF completes the import
	return syncClient:lockSync()
		:flatMap(function()

			-- Stopping sync in this way will prevent the
			-- sync client from turning the sync process
			-- on until the import is completes. In this
			-- case, the client directed "STOP" action
			-- will be what the syncClient "resumes"
			-- to. See below
			return syncClient:stopSync_INTERNAL()

		end)
		-- LriImport speaks KSPromises - convert the sync client calls to a promise chain
		:call( asKSPromise )
		:mapP( function()

			local fileSystemFreeSpace = WFFileUtils.freeSpaceAtPath( KSPathUtils.getStandardFilePath 'carouselDocuments' )

			local spaceNeeded = minimumFreeDiskSpace - fileSystemFreeSpace

			if spaceNeeded > 0 then

				return KSPromises.resolvedPromiseP( 'error', 'LowDiskSpace' )


			end

			-- Add all assets to the catalog
			return KSPromises.mapArrayP( _addAssetToCatalogP, assetArray, 5 )
				:mapP( function( status, data )

					return status, data

				end )

		end)
		:mapP( function( status, result )

			-- Now that assets have been added, Unlock the
			-- sync process so that the client has
			-- complete and immediate control again

			return syncClient:unlockSync()
			:flatMap( function()
				return syncClient:resume()
			end)
			:call(asKSPromise)
			:mapP( function()
				return status, result
			end)

	end )


end

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

function LriImport.hasAssetBySha256P( catalog, sha256, bypassSha256Check )

	if bypassSha256Check then
		return KSPromises.resolvedPromiseP( 'data', nil )
	end

	if not( type( sha256 ) == 'string' and #sha256 == 64 ) then
		error( "hasAssetBySha256P: sha256 must be a 64-byte hex string", 2 )
	end

	local sql = [[ SELECT docId
					FROM coreInfo
					WHERE sha256 = ?
					AND spaceId IS NULL ]] -- Only consider catalog-based assets.

	local params = { sha256 }

	return catalog:indexStore():queryP( sql, unpack( params ) )
		:mapDataP_new( function( row )

			return row and row[ 1 ] and row[ 1 ][ 1 ]

		end )

end

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

return LriImport
