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

WFRxDocStoreMessageProcessor.lua

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

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.lua.threading
local WFRequestDispatcher = require 'WFRequestDispatcher'

	-- WF.core.utils
local WFCapabilities = require 'WFCapabilities'
local WFCloseSupport = require 'WFCloseSupport'
local WFArguments = require 'WFArguments'

	-- WF.core.threading
local WFWorkerThread = require 'WFWorkerThread'

	-- WF.sqlite.doc_store
local WFRxDocStoreImp = require 'WFRxDocStoreImp'

local WFReadOnlyDocStore = require 'WFReadOnlyDocStore'

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

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

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

-- @mark WFRxDocStoreMessageProcessor namespace

local WFRxDocStoreMessageProcessor = {}

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

function WFRxDocStoreMessageProcessor.initMessageProcessor( path )

	local docStoreImp = WFRxDocStoreImp( path )

	WFRxDocStoreMessageProcessor.attachMessageHandlers( docStoreImp )

end

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

function WFRxDocStoreMessageProcessor.attachMessageHandlers( docStoreImp )

	WFCloseSupport.mustCloseAtThreadTermination( docStoreImp )

	WFRequestDispatcher.onMessageDo {
		message = 'closeDb',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'docValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'bulkDocValue',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'ongoingDocValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'ongoingLocalDocValue',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'localIdToDocId',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'bulkLocalIdToDocIdAsMap',
		target = docStoreImp,
	}
	WFRequestDispatcher.onMessageDo {
		message = 'docIdToLocalId',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'bulkDocIdToLocalIdAsMap',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'setDocValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'bulkSetDocValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'currentRevisionsForDoc',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'deleteDoc',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'deleteSpace',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'resolveConflictWithDelete',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'deleteRevisionAsNonCurrent',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'deleteRevisionAsNonCurrentAndApplyOverlay',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'setLabel',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'removeLabel',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'getRevisionForLabel',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'updateLabelFromRevisionList',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'getDocsOfType',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'getIdsOfDocsWithLabel',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'countDocsOfType',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'countAllDocs',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'isCorrupt',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'DEBUG_getNumberOfDocsByTypeAndSubtype',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'applyOverlay',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'bulkApplyOverlay',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'applyOverlayToIds',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'changeCounter',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'changeCounterOnce',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'changedDocs',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = '_raw_changesFeed',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'docRevisionHistory',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'localDocValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'setLocalDocValue',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'applyOverlayLocalDocValue',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'deletedDocsSinceNamedChangeCounter',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'updateDeletedDocsSinceNamedChangeCounter',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageWithProgress {
		message = 'compact',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageWithProgress {
		message = 'vacuum',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'dumpDocsToDbAtPath',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'onlineBackup',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'DEBUG_dumpCoverage',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'PERF_collectQueryPerformanceStats',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'PERF_generateQueryStatsReport',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'performIntegrityCheck',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'haltAndDelete',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'halt',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'getSQLiteFatalErrorCodeStream',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = '_DEBUG_simulateCorruption',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = '_DEBUG_bulkRequestInfoStream',
		target = docStoreImp,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = '_DEBUG_clearIdCache',
		target = docStoreImp,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'initializeReadOnlyDocStore',
		handler = function( path, tag )
			return WFReadOnlyDocStore.initializeReadOnlyDocStore( path, tag )
		end
	}

	WFRequestDispatcher.enableDiagnosticLogForRequestStats()

end

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

local _WFRxDocStoreImpBootstrapHooks = {}
local _WFRxDocStoreImpSchemaConflictHooks = {}

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

local _mostRecentWFRxDocStoreImpBootstrapHookId = 0
local _mostRecentWFRxDocStoreImpSchemaConflictHookId = 0

--------------------------------------------------------------------------------
--[[!
	\brief Creates a WFWorkerThread WFRxDocStoreImp instance

	\param path (string) the path to the SQLite db

	\return <code>WFWorkerThread</code> interface to WFRxDocStoreImp( path )
]]--

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

local _bootstrapWFRxDocStoreImpHooksSemaphore
local _schemaConflictWFRxDocStoreImpHooksSemaphore

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

local _shouldBenchmark = 
		WFCapabilities.getCapability(
			"WFRxDocStoreMessageProcessor_shouldBenchmark" )

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

WFRxDocStoreMessageProcessor.bootstrapWFRxDocStoreImp = function( path )

	if not _bootstrapWFRxDocStoreImpHooksSemaphore then

		-- don't run the hooks from a hook, otherwise we can get infinite
		-- recursion
		
		_bootstrapWFRxDocStoreImpHooksSemaphore = true

		for _, packedFuncAndArgsTable in pairs( _WFRxDocStoreImpBootstrapHooks ) do

			local f, packedArgs, nArgs = unpack( packedFuncAndArgsTable )

			local ok, maybeError = 
					pcall( f, path, unpack( packedArgs, 1, nArgs ) )

			if not ok then

				logger:warnf( "error in bootstrapWFRxDocStoreImp hook: %s", 
								maybeError )

			end

		end

	end

	_bootstrapWFRxDocStoreImpHooksSemaphore = nil

	local docStoreImpThread = WFWorkerThread( "WFRxDocStoreMessageProcessor.lua", path )

	docStoreImpThread:threadIsReady()
		
		:take( 1 )

		:tap( nil,
			
			function( errorMessage )
				
				local conflictionDefinitionErrorCode = "CONFLICTING_SCHEMA"
					-- As defined by `conflictingSchemaDefinitionError` in WFSqliteDatabase
					
				-- The errorMessage will contain the conflicting definition error code
				-- if the doc store schema has been altered. This is all done through
				-- string matching. This is because the error is thrown via the standard
				-- `error` method which only allows for passing a single error string.
				
				if string.find( errorMessage, conflictionDefinitionErrorCode ) then
					-- The errorMessage may also contain a description of the precise
					-- error, which is why we use `string.find` and not `==` to perform
					-- the check.
					
					if not _schemaConflictWFRxDocStoreImpHooksSemaphore then
						-- don't run the hooks from a hook
						_schemaConflictWFRxDocStoreImpHooksSemaphore = true
						
						for _, packedFuncAndArgsTable in pairs( _WFRxDocStoreImpSchemaConflictHooks ) do
	
							local f, packedArgs, nArgs = unpack( packedFuncAndArgsTable )
	
							local ok, maybeError = 
									pcall( f, unpack( packedArgs, 1, nArgs ) )
	
							if not ok then
	
								logger:warnf( "error in schemaConflictWFRxDocStoreImp hook: %s", 
												maybeError )
	
							end
	
						end
						
					end
					
					_schemaConflictWFRxDocStoreImpHooksSemaphore = nil
				end
			
			end,
			
			nil )
		
		:call( require 'WFRxRunUntilDone' )
		
	if _shouldBenchmark then

		docStoreImpThread:enablePerClientBenchmarking()

	end

	return docStoreImpThread

end

--------------------------------------------------------------------------------
--[[!
	\brief Call a function when a WFRxDocStoreImp instance is about to be 
		created

	\param f (function) a callback function
	\param ... additional arguments which are retained until
		WFRxDocStoreMessageProcessor.removeBootstrapWFRxDocStoreImpHook is called.

	\return an opaque id that may be passed to 
		WFRxDocStoreMessageProcessor.removeBootstrapWFRxDocStoreImpHook to
		remove the callback
]]--
--------------------------------------------------------------------------------

WFRxDocStoreMessageProcessor.onBootstrapWFRxDocStoreImp = function( f, ... )

	WFArguments.check( "function" )

	_mostRecentWFRxDocStoreImpBootstrapHookId =
		_mostRecentWFRxDocStoreImpBootstrapHookId + 1

	_WFRxDocStoreImpBootstrapHooks[ _mostRecentWFRxDocStoreImpBootstrapHookId ] = 
		{ f, { ... }, select( "#", ... ) }

	return _mostRecentWFRxDocStoreImpBootstrapHookId

end

--------------------------------------------------------------------------------
--[[!
	\brief Removes a bootstrapWFRxDocStoreImp hook

	\param id (opaque) the id returned from
		WFRxDocStoreMessageProcessor.onBootstrapWFRxDocStoreImp
]]--

WFRxDocStoreMessageProcessor.removeBootstrapWFRxDocStoreImpHook = function( id )

	_WFRxDocStoreImpBootstrapHooks[ id ] = nil

end

--------------------------------------------------------------------------------
--[[!
	\brief Call a function when a WFRxDocStoreImp thread fails to be created
		because of a doc store schema conflict.

	\param f (function) a callback function
	\param ... additional arguments which are retained until
		WFRxDocStoreMessageProcessor.removeSchemaConflictWFRxDocStoreImpHook is called.

	\return an opaque id that may be passed to 
		WFRxDocStoreMessageProcessor.removeSchemaConflictWFRxDocStoreImpHook to
		remove the callback
]]--
--------------------------------------------------------------------------------

WFRxDocStoreMessageProcessor.onSchemaConflictWFRxDocStoreImp = function( f, ... )

	WFArguments.check( "function" )
	
	_mostRecentWFRxDocStoreImpSchemaConflictHookId =
		_mostRecentWFRxDocStoreImpSchemaConflictHookId + 1
		
	_WFRxDocStoreImpSchemaConflictHooks[ _mostRecentWFRxDocStoreImpSchemaConflictHookId ] = 
		{ f, { ... }, select( "#", ... ) }
	
	return _mostRecentWFRxDocStoreImpSchemaConflictHookId
end

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

--[[!
	\brief Removes a schemaConflictWFRxDocStoreImp hook
	
	\param id (opaque) the id returned from
		WFRxDocStoreMessageProcessor.onSchemaConflictWFRxDocStoreImp
		
]]--

WFRxDocStoreMessageProcessor.removeSchemaConflictWFRxDocStoreImpHook = function( id )

	_WFRxDocStoreImpSchemaConflictHooks[ id ] = nil
	
end

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

return WFRxDocStoreMessageProcessor
