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

WFCatAssetInfoCollector.lua

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

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

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

-- @mark WFCatAssetInfoCollector
--[[!

	\brief Collects values for multiple different fields across multiple different assets.
	Consolodates simple types (ratings, flags, etc.) into a single shared value or 'mixed'.
	Accumulates array types (keywords) into two arrays: values that are shared across all 
	assets and values that are only present on some.

--]]

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

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

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

local kNilValue = { "< Nil >" }
local kMixedValue = { "< Mixed >" }

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

local function alphabeticalSort( t )

	table.sort( t, function( a, b )

		return KSStringUtils.localizedStandardStringCompare( a or "", b or "" ) < 0

	end )
end

--------------------------------------------------------------------------------
-- Helper class that ccumulates array values for a single field across multiple assets 
-- and groups them into two sets: values that are shared across all assets that have been
-- encountered, and values that are not shared by all.

local function makeAccumulator( sortResults )

	return {

		valueCounts = {},		-- counts how many times each value is used

		allValues = {}, 		-- needed to track addition/removal of items
		
 		--------------------------------------------------------------------------------

		accumulateValues = function( self, id, values, sortResults )
	
			if not self.allValues[ id ] then
				self.allValues[ id ] = {}
			end

			local previousValues = KSTableUtils.shallowcopy( self.allValues[ id ] )

			for _, value in ipairs( values ) do

				if not self.allValues[ id ][ value ] then
					self.valueCounts[ value ] = ( self.valueCounts[ value ] or 0 ) + 1
				end

				self.allValues[ id ][ value ] = true
				
				previousValues[ value ] = nil
			
			end
			
			for value, _ in pairs( previousValues ) do

				self.allValues[ id ][ value ] = nil
				
				if self.valueCounts[ value ] then
					self.valueCounts[ value ] = self.valueCounts[ value ] - 1
					
					if self.valueCounts[ value ] == 0 then
						self.valueCounts[ value ] = nil
					end
				end
			end
		end,

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

		getAccumulatedValues = function( self, totalCount )

			local sharedByAll = {}
			local notSharedByAll = {}
			
			for value, count in pairs( self.valueCounts ) do
				if count == totalCount then
					table.insert( sharedByAll, value )
				else
					table.insert( notSharedByAll, value )
				end
			end
			
			if sortResults then
				alphabeticalSort( sharedByAll )
				alphabeticalSort( notSharedByAll )
			end

			return sharedByAll, notSharedByAll
		end,
	}
end

--==============================================================================
--[[!
	\brief Collects values for multiple different fields across multiple different assets.
	  Simple types (ratings, flags, etc.) are consolodated into a single shared value, or 'mixed'.
	  Array types (keywords) are accumulated into two arrats: values that are shared across all assets
	  and values that are only present on some. 

	\param assetId The ID of the asset whose values are currently being accumulated.
	\param sharedKey The key to use the in the result table to store the array of values shared by all assets.
	\param mixedKey The key to use in the result table to store the array of values not shared by all assets.
	\param values The array of values from this asset to accumulate.
	\param param sortResults If true, the resulting arrays will be alphabetically sorted.
--]]

local function WFCatAssetInfoCollector_accumulateValues( self, assetId, sharedKey, mixedKey, values, sortResults )

	if not self._accumulators[ sharedKey ] then

		local accumulator = makeAccumulator( sortResults )

		-- store the mixed key for use when returning results in getCollectedValues
		accumulator.mixedKey = mixedKey			

		self._accumulators[ sharedKey ] = accumulator			
	end
	
	self._accumulators[ sharedKey ]:accumulateValues( assetId, values )
end

--------------------------------------------------------------------------------
--[[!
	\brief Updates the consolidated value for a single field. If the given value is different
	  from the consolidated value it is put into a mixed state.
	  
	  The actual calculation of consolidated values is deferred until getCollectedValues is called.

	\param assetId The ID of the asset whose value is currently being consolidated.
	\param key The key to use in the result table to store the consolidated value.
	\param value The value form this asset to add to the consolidation.
	\param optDefault If given, the value that will be used as the consolidated value if it would 
		otherwise be nil.
--]]

local function WFCatAssetInfoCollector_consolidateValue( self, assetId, key, value, optDefault, consolidateFn )

	if value == nil then
		value = kNilValue
	end

	if not self._allValues[ key ] then
		self._allValues[ key ] = {}
	end							
	
	if not self._allValues[ key ][ assetId ] then
		self._allValues[ key ][ assetId ] = {}
	end
	
	local isNewValue = self._allValues[ key ][ assetId ] == nil
	local isChangedValue = not isNewValue and self._allValues[ key ][ assetId ] ~= value
	
	if consolidateFn and type( consolidateFn ) == "function" then
		consolidateFn(  assetId, key, value, self._consolidatedValues )
	else
		local consolidatedValue = self._consolidatedValues[ key ]
		
		if isNewValue and value ~= consolidatedValue then
			self._consolidatedValues[ key ] = kMixedValue -- we know this is now mixed
		end
		
		if isChangedValue and consolidatedValue ~= kMixedValue then
			self._consolidatedValues[ key ] = nil -- needs recalculation
		end
	end
		
	self._allValues[ key ][ assetId ] = value
	self._defaultValues[ key ] = optDefault

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns all values that have been accumulated and/or consolidated so far.

	\param totalAssetCount The total number of assets (required to separate accumlated values 
		into "shared by all" vs. "not shared by all" sets.)
		
	\return A map of the collected results.
--]]

local function WFCatAssetInfoCollector_getCollectedValues( self, totalAssetCount )

	local result = {
		numberOfAssets = totalAssetCount
	}

	for key, perImageValues in pairs( self._allValues ) do
	
		local consolidatedValue = self._consolidatedValues[ key ]
		local mixed = nil
	
		if consolidatedValue == nil then -- needs calculation
						
			for _, value in pairs( perImageValues ) do
				if consolidatedValue == nil then
					consolidatedValue = value
				elseif consolidatedValue ~= value then
					consolidatedValue = kMixedValue
					break
				end
			end
			
			if consolidatedValue == kNilValue then
				consolidatedValue = nil
			end
			
			if consolidatedValue == kMixedValue then
				consolidatedValue = nil
				mixed = true
			end
		end

		if not mixed then
			result[ key ] = consolidatedValue
		end
		
		if result[ key ] == nil then
			result[ key ] = self._defaultValues[ key ] 
		end

	end
	
	for sharedKey, accumulator in pairs( self._accumulators ) do		

		local sharedValues, notSharedValues = accumulator:getAccumulatedValues( totalAssetCount )

		result[ sharedKey ] = sharedValues

		if accumulator.mixedKey then
			result[ accumulator.mixedKey ] = notSharedValues				
		end
	end

	return result
end

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

local function makeCollector()

	return {
	
		_allValues = {},
		_accumulators = {},
		_consolidatedValues = {},
		_defaultValues = {},
	
		--------------------------------------------------------------------------------
		
		accumulateValues = WFCatAssetInfoCollector_accumulateValues,
		
		consolidateValue = WFCatAssetInfoCollector_consolidateValue,
		
		getCollectedValues = WFCatAssetInfoCollector_getCollectedValues,
	}

end

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

return {

	makeCollector = makeCollector,

}
