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

WFCatImportManifestMergeAssetUtils.lua

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

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

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

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

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

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

local kMergeTypes = {
	ignore = "ignore", -- Ignore value from A irrespective of value of B
	add = "add", -- Set value from A if no value is present in B, otherwise use value in B
	override = "override", -- Set non-nil value from A irrespective of value in B
	overrideEvenIfNil = "overrideEvenIfNil", -- Set any value from A irrespective of value in B.
	merge = "merge", -- Use values from both. Applicable only for tables as of now
					 -- Merge cannot be used as default. If merge rule is specified, 
					 -- we will merge the tree based on the first level
	latest = "latest", -- Use latest value. Compare timestamp in the same level as value.
						-- Key for time might be either `date` or `userUpdated`.
}

-- Default rule is override
local kDefaultMergeType = kMergeTypes.override
local kKeyDefaultMergeType = "__defaultRule__"
local kUserIdPlaceHolder = "<user_id>"

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

local function mergeDocImp( userId, mergeSchema, result, a, b, parentDefaultMergeRule )
	
	local keys = {}
	for k, _ in pairs( a ) do
		keys[ k ] = true
	end
	
	for k, _ in pairs( b ) do
		keys[ k ] = true
	end
	
	local defaultMergeRule = mergeSchema[ kKeyDefaultMergeType ] or parentDefaultMergeRule
	
	for k, _ in pairs( keys ) do

		local aVal = a[ k ]
		local bVal = b[ k ]
		local mergeRuleIndex = k == userId and kUserIdPlaceHolder or k
		local mergeRule = mergeSchema[ mergeRuleIndex ] or defaultMergeRule or kDefaultMergeType
		
		if type( aVal ) ~= 'table' and type( bVal ) ~= 'table' then
			
			-- Simple values
			
			if aVal == bVal then
				-- Both are same. Use directly.
				result[ k ] = aVal
			
			elseif not aVal then
				-- A is nil. Use A only if overrideEvenIfNil is specified.
				-- Else use B
				
				if mergeRule ~= kMergeTypes.overrideEvenIfNil then
					result[ k ] = bVal
				end
			elseif not bVal then
				-- B is nil. Use A except when ignore is specified
				
				if mergeRule ~= kMergeTypes.ignore then
					result[ k ] = aVal
				end
			
			else
				-- A and B and different non-nil values.
				if mergeRule == kMergeTypes.override 
					or mergeRule == kMergeTypes.overrideEvenIfNil then
					
					result[ k ] = aVal
					
				elseif mergeRule == kMergeTypes.ignore
					or mergeRule == kMergeTypes.add then
					
					result[ k ] = bVal
				end
			end
			
		else
		
			-- Atleast one of the values are of type table
			
			if not aVal and not bVal then
				-- Both are nil. Nothing to do
				
			elseif not aVal then 
				
				if mergeRule ~= kMergeTypes.overrideEvenIfNil then
					result[ k ] = KSTableUtils.deepCopy( bVal )
				end
			
			elseif not bVal then 
				
				if mergeRule ~= kMergeTypes.ignore then
					result[ k ] = KSTableUtils.deepCopy( aVal )
				end

			elseif aVal == bVal or KSTableUtils.deepEquals( aVal, bVal ) then
				-- Both are same.
				result[ k ] = KSTableUtils.deepCopy( aVal )
			
			elseif aVal[ 1 ] then
				-- Its an array.
				
				if mergeRule == kMergeTypes.override 
					or mergeRule == kMergeTypes.overrideEvenIfNil then

					result[ k ] = KSTableUtils.deepCopy( aVal )
				
				elseif mergeRule == kMergeTypes.ignore 
					or mergeRule == kMergeTypes.add then

					result[ k ] = KSTableUtils.deepCopy( bVal )
					
				elseif mergeRule == 'merge' then
				
					result[ k ] = KSTableUtils.deepCopy( bVal )
					local aCopy = KSTableUtils.deepCopy( aVal )
					for _, v in ipairs( aCopy ) do
						table.insert( result[ k ], v )
					end
				else
					error( "Incorrect merge rule for array" )
				end

				
			else
				-- Unequal tables
				
				if mergeRule == kMergeTypes.latest then
					local timestampA = aVal.date or aVal.userUpdated
					local timestampB = bVal.date or bVal.userUpdated
					
					if timestampA and timestampB then
						local timeA = KSDate.iso8601ToTimeIntervalSinceReference( timestampA )
						local timeB = KSDate.iso8601ToTimeIntervalSinceReference( timestampB )
						if timeA > timeB then
							-- A is latest
							result[ k ] = KSTableUtils.deepCopy( aVal )
						else
							-- B is latest
							result[ k ] = KSTableUtils.deepCopy( bVal )
						end
					else
						-- One of the times, is nil. Whoever has non-nil time wins
						result[ k ] = timestampA and KSTableUtils.deepCopy( aVal )
										or KSTableUtils.deepCopy( bVal )
						
					end
				
				elseif mergeRule == kMergeTypes.merge then
					
					result[ k ] = KSTableUtils.deepCopy( bVal )
					local aCopy = KSTableUtils.deepCopy( aVal )
					for key, val in pairs( aCopy ) do
						result[ k ][ key ] = val
					end

				else
					-- Traverse the remaining tree
					result[ k ] = {}
					mergeDocImp( userId, mergeSchema[ mergeRuleIndex ] or {}, result[ k ], aVal, bVal, defaultMergeRule )
				end
			end
			
		end
	end
	
	return result
end
--------------------------------------------------------------------------------

local function mergeDoc( userId, mergeSchema, incomingDoc, existingDoc )

	return mergeDocImp( userId, mergeSchema, {}, incomingDoc, existingDoc )
end

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

return {
	kMergeTypes = kMergeTypes,
	kKeyDefaultMergeType = kKeyDefaultMergeType,
	kUserIdPlaceHolder = kUserIdPlaceHolder,
	mergeDoc = mergeDoc,
	
}
