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

WFRxDatabaseImp.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2020 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
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.

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

	-- Rx
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.logging
local WFLogger = require 'WFLogger'

	-- WF.core.rxlua
local WFRxScheduler = require 'WFRxScheduler'
local WFRxControlledObservableWithBuffer = require 'WFRxControlledObservableWithBuffer'
local WFRxDisposeOnUnsubscribeObservable = require 'WFRxDisposeOnUnsubscribeObservable'

	-- WF.core.lua.threading
local WFRequestDispatcher = require 'WFRequestDispatcher'

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

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

	-- WF.sqlite.database
local WFSQLiteDatabase = require 'WFSQLiteDatabase'
local WFSQLiteDatabaseSchema = require 'WFSQLiteDatabaseSchema'
local WFSQLiteUtils = require 'WFSQLiteUtils'

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

local check = assert( WFArguments.check, "Cannot find WFArguments.check" )

local kClosed = { __closed = "CLOSED" }

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

local kSemiColonByte = 59

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


local function identifyRelevantTables( tableNames, sql )

	-- Identify all of our tables that might be relevant for this query.
	-- Naive implementation: Simply search for each table name in query string.

	local result = {}

	for tableName in pairs( tableNames ) do

		-- Naive search, too: Since SQL table names can't contain Lua pattern
		-- special characters, just use the table name as search pattern.

		if sql:find( "[^a-zA-Z]" .. tableName .. "[^a-zA-Z]" ) then
			result[ tableName ] = true
		end

	end

	return result

end

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

local function throttledMap( observable, timeRatio, mapFn )

	local getTimeNow = KSDate.timeNowForAnimation

	local lastDelayCount = 1

	local noSoonerSb = Rx.Subject()

	local noSooner = noSoonerSb
		:replay( nil, 1 )
	noSooner:connect()

	noSoonerSb:onNext( getTimeNow() )

	noSooner = noSooner:map( function( nextTime )
		local now = getTimeNow()
		local delay = nextTime - now
		if delay > 0 then
			return Rx.Observable.timer( delay, WFRxScheduler.sharedScheduler() )
		else
			return Rx.Observable.just( true )
		end
	end )
	:switch()
	:map( function()
		lastDelayCount = lastDelayCount + 1
		return lastDelayCount
	end )

	local count = 1
	local lastProcessed = 1

	local updateCount = observable:map( function()
		count = count + 1
		return count
	end )

	local rateLimited = noSooner
		:combineLatest( function( delayCount, count, ... )

			if count <= lastProcessed then
				return Rx.Observable.never()
			end

			if lastDelayCount == delayCount then
				return Rx.Observable.just( ... )
			end
			return Rx.Observable.never()

		end, updateCount, observable )
		:switch()

	local function timeIt( start, ... )
		lastProcessed = count

		local noSooner = getTimeNow()
		noSooner = noSooner + ( noSooner - start ) * timeRatio
		noSoonerSb:onNext( noSooner )

		return ...
	end

	return rateLimited:map( function( ... )

		local start = getTimeNow()
		return timeIt( start, mapFn( ... ) )

	end )

end

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

local WFRxDatabaseImp = {

	_path = false,
		-- (string) path to DB file
	
	_db = false,
		-- (WFSQLiteDatabase)
		
	_schema = false,
		-- (WFSQLiteDatabaseSchema)
		
	_needsUpdateFromTable = false,
		-- (table) reactive chain to know if table is updated
			-- key: (string) name of table
			-- value: (observable) signal when table is updated
	
	_tableInfo = false,
		-- (table)
			-- key (string): table name as declared in SQL
			-- value (table): args
				-- nColumns (number): column count of the table
				-- indexInfo (table): a list of SQL index names and their corresponding SQL strings
					
	
	_queryRequests = false,
		-- (table) current query Observable requests
			-- key: (Observable< table >) stream returned to caller
			-- value: (table) arguments to :query, plus
				-- relevantTables: (table) set of table names to update
				-- nUpdatesSent: (number) how many times have we broadcasted results?
				
	_log = false,
		-- (WFLogger)

	WFCloseSupport.addCloseSupportMethods,

}

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

--[[!
	\brief Create a new wfRxDatabase implementation.

	\param path (string) path for the database
	\param script (string) The name of table and index definition script for this rxDatabase
	
--]]

function WFRxDatabaseImp:__init( databasePath, script, version )

	check( 'WFRxDatabaseImp', '+string', '+string', '+string' )
	
	self._log = WFLogger( string.format( 'WFRxDatabaseImp %s', databasePath ) )
		--:setLevel 'debug'

	self._log:debugf( "initializing with path = %s", databasePath )

	WFCloseSupport.mustCloseAtThreadTermination( self )

	self._tableInfo = {}
	self._needsUpdateFromTable = {}
	self._queryRequests = {}
	
	-- Create database in appropriate location.
	
	local dbPath

	if databasePath:sub( 1, 8 ) == ':memory:' then
		dbPath = ':memory:'
	elseif databasePath:sub( 1, 6 ) == ':temp:' then
		dbPath = ':temp:'
	else
		dbPath = databasePath
	end

	self._path = dbPath

	self._log:debugf( "WFRxDatabaseImp.start, dbPath = %s", dbPath )

	self._schema = WFSQLiteDatabaseSchema( version )
		:useWAL()
		:setSynchronous( 1 )
		
	script = string.gsub( script, "%.lua$", "" )
	local scriptNameSpace = require( script )
		
	scriptNameSpace.generateSchema( self )

	local function openOrCreateDatabaseAtPath( path )
		if #path > 0 and not path:match( '^:' ) then
			WFFileUtils.mkdir( KSPathUtils.parent( path ) )
		end
	
		local db = WFSQLiteDatabase( path, self._schema )
		return db
	end


	local db
	if not pcall( function()

		db = openOrCreateDatabaseAtPath( dbPath )

	end ) then

		self._log:warn( "WFRxDatabase was corrupt. Deleting and rebuilding." )

		-- Trash the rxDatabase DB and its possible sidecars.

		WFFileUtils.deleteFile( dbPath )
		WFFileUtils.deleteFile( dbPath .. '-shm' )
		WFFileUtils.deleteFile( dbPath .. '-wal' )

		-- Try again. If we fail this time, that's it, man, game over, it's game over.

		db = openOrCreateDatabaseAtPath( dbPath )

	end

	self:setOwned { _db = db }
	
end

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

--[[!
	\brief Adds a table to the schema of the database. Should be called in script.

	\param sql: (string) a <code>CREATE TABLE</code> SQL statement
	
	Remember that SQL table names are case-insensitive, so any case variation
	of those names are also reserved.
]]--
function WFRxDatabaseImp:addTable( sql )

	check( 'WFRxDatabaseImp', 'string' )

	local tableName = WFSQLiteUtils.tableNameFromCreateTableStatement( sql )

	if not tableName then
		error( "WFRxDatabaseImp:addTable: could not find table name in SQL " .. sql, 2 )
	end
	
	local columns = sql:match( "%((.*)%)" )
	local _, nCommas = columns:replace( ',', ',' )
	local nColumns = nCommas + 1
	
	local args = {
		nColumns = nColumns,
		indexInfo = {}
	}

	self._needsUpdateFromTable[ tableName ] = Rx.ReplaySubject( 1 )
			
	self._needsUpdateFromTable[ tableName ]:onNext( true )
	
	self._tableInfo[ tableName ] = args

	return self._schema:addTable( sql ):onConflict( 'replace' )
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Adds an index of a table to the schema.

	\param sql: (string) a <code>CREATE INDEX</code> SQL statement
	
	Remember that SQL table names are case-insensitive, so any case variation
	of those names are also reserved.
]]--

function WFRxDatabaseImp:addIndex( sql )

	check( 'WFRxDatabaseImp', 'string' )

	local _, _, _, tableName = sql:find( "([%a_][%w_]*)%s+[Oo][Nn]%s+([%a_][%w_]*)" )

	if not tableName or #tableName < 1 then
		error( "WFRxDatabaseImp:addIndex: could not find table name in " .. sql, 5 )
	end
	
	local indexEntry = self._schema:addIndex( sql )
	
	if self._tableInfo[ tableName ] then
		self._tableInfo[ tableName ].indexInfo[ indexEntry._indexName ] = indexEntry._sql
	else
		error( "WFRxDatabaseImp:addIndex: table in database schema not declared " .. tableName, 5 )
	end

	return indexEntry
	
end

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

local function getTableNames( self )

	local result = {}

	for k in pairs( self._tableInfo ) do
		result[ k ] = true
	end

	return result

end 

--------------------------------------------------------------------------------
--[[!
	\brief Get the list of the table names
	
	\return a table of talbeNames
]]--

function WFRxDatabaseImp:DEBUG_tableNames()

	check( 'WFRxDatabaseImp' )

	return getTableNames( self )
	
end

--------------------------------------------------------------------------------
--[[!
	\brief Retrieves the set of SQL indices declared for this schema.

	\return (table)
		- key (string): A table name as declared in SQL
		- value (table): \c a list of SQL index names and their corresponding SQL strings
]]--

function WFRxDatabaseImp:DEBUG_indexInfos()

	check( 'WFRxDatabaseImp' )

	local result = {}
	
	for k, v in pairs( self._tableInfo ) do
		if v.indexInfo then
			result[ k ] = v.indexInfo
		end
	end

	return result

end

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

--[[!
	\brief Create an Observable that tracks the results of a SQL query on the database

	\param args (table) arguments for query
		- \c sql: (string) SQL query to be performed
		- \c queryOnce: (boolean) only get immediate result from SQL query 
		- 1 .. n: (optional) query arguments to be substituted for ? placeholders in query

	\return B< table > query results (array of arrays syntax)
--]]

function WFRxDatabaseImp:query( --[[table]] args )

	check( 'WFRxDatabaseImp', 'table' )

		-- Validate args.

	local sql = args.sql

	if string.byte( sql, -1 ) ~= kSemiColonByte then
		sql = sql .. ';'
	end

	-- Identify relevant tables.

	local tableNames = getTableNames( self )
	local relevantTablesSet = identifyRelevantTables( tableNames, sql )

	local nTables = 0
	local relevantTables = {}

	for tableName in pairs( relevantTablesSet ) do
		nTables = nTables + 1
		relevantTables[ nTables ] = tableName
	end

	args.nUpdatesSent = 0
	args.relevantTables = relevantTables

	-- Do we have new data from any of the tables?

	local tablesUpdated = {}

	for i, relevantTable in ipairs( relevantTables ) do
		tablesUpdated[ i ] = self._needsUpdateFromTable[ relevantTable ]
	end
	
	nTables = #tablesUpdated

	if nTables == 0 then
		error( "WFRxDatabaseImp:query: did not find any relevant tables in query " .. sql, 2 )
	end

	local anyTableUpdated = Observable.combineLatest( unpack( tablesUpdated, 1, nTables ) )
	
	local needsToDeliverFirstResult = true
	
	-- Fetch query results.
	
	local nQueryArgs = args.nArgs or #args
	local queryResultsNow = function()
	
		needsToDeliverFirstResult = false
		args.nUpdatesSent = args.nUpdatesSent + 1
		
		return self._db:gatherArrayOfArray( sql, unpack( args, 1, nQueryArgs ) )
		
	end
	
	anyTableUpdated = anyTableUpdated:where( function( ... )
		
		if needsToDeliverFirstResult then
			return true
		end
		
		local n = select( '#', ... )
		
		for i = 1, n do
			local tableUpdated = select( i, ... )
			if tableUpdated then
				return true
			end
		end
		
		return false
		
	end )
	
	if not args.queryOnce then
		anyTableUpdated = anyTableUpdated:debounce( .125, WFRxScheduler.sharedScheduler() )
	end

	local rawQuery = throttledMap( anyTableUpdated, 4, queryResultsNow )

	local previousResult, previousRowCount
		-- Squelch events if the actual query results are unchanged.
	local resultQuery = rawQuery:filter( function( newValue, rowCount )

		if previousRowCount == rowCount
					and KSTableUtils.deepEquals( previousResult, newValue ) then
			return false
		else
			previousResult = newValue
			previousRowCount = rowCount
			return true
		end

	end )
	
	local bufferSize = 1
	local initialRequestCount = math.huge

	local controlledObservable = resultQuery:call( WFRxControlledObservableWithBuffer, bufferSize, initialRequestCount )
	
	local unsubscribeObservable = controlledObservable:call( WFRxDisposeOnUnsubscribeObservable )
	
	args._controlledObservable = controlledObservable

	unsubscribeObservable:addDisposable( Rx.Disposable.create( function()
	
		args._controlledObservable = nil
		self._queryRequests[ unsubscribeObservable ] = nil
		
	end ) )
	
	-- Remember the query.

	self._queryRequests[ unsubscribeObservable ] = args
	
	return unsubscribeObservable

end

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

--[[!
	\brief Bulk insert rows into database table

	\param tableName: (string) tableName for inserting new rows
	\param rows: (table) rows with each row content in order of table columns
	
	\return Observable of database insert result
--]]

function WFRxDatabaseImp:bulkInsert( tableName, rows )

	check( 'WFRxDatabaseImp', 'string', 'table' )

	if #rows == 0 then
		return Observable.throw( "Empty value" )
	end
	
	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:bulkInsert: cannot find tableName " .. tableName )
	end
	
	local db = self._db
	local exec = db.exec
	
	local nColumns = self._tableInfo[ tableName ].nColumns
	local queryPlaceholders = WFSQLiteUtils.createVariableArgList( nColumns )
	
	local sql = string.format( 'INSERT INTO %s VALUES (%s)', tableName, queryPlaceholders )
	
	local result = db:withTransactionDo( function( db )

		local result = {}

		for i, row in ipairs( rows ) do
			
			local res = exec( db, sql, unpack( row ) )
			
			result[ i ] = res

		end
		
		return result

	end, db )
	
	self._needsUpdateFromTable[ tableName ]
		:onNext( true )

	return Observable.just( result )

end

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

--[[!
	\brief Bulk insert or replace rows into database table

	\param tableName: (string) tableName for inserting or replacing if row already exists
	\param rows: (table) rows with each row content in order of table columns
	
	\return Observable of database insert result
--]]

function WFRxDatabaseImp:bulkInsertOrReplace( tableName, rows )

	check( 'WFRxDatabaseImp', 'string', 'table' )

	if #rows == 0 then
		return Observable.throw( "Empty value" )
	end
	
	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:bulkInsertOrReplace: cannot find tableName " .. tableName )
	end
	
	local db = self._db
	local exec = db.exec
	
	local nColumns = self._tableInfo[ tableName ].nColumns
	local queryPlaceholders = WFSQLiteUtils.createVariableArgList( nColumns )
	
	local sql = string.format( 'INSERT OR REPLACE INTO %s VALUES (%s)', tableName, queryPlaceholders )
	
	local result = db:withTransactionDo( function( db )

		local result = {}

		for i, row in ipairs( rows ) do
			
			local res = exec( db, sql, unpack( row ) )
			
			result[ i ] = res

		end
		
		return result

	end, db )
	
	self._needsUpdateFromTable[ tableName ]
		:onNext( true )

	return Observable.just( result )

end

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

--[[!
	\brief bulk update rows in database table
	
	\param tableName: (string) tableName for updating rows
	\args: (array of tables)
		- \c values: (table) key is column name, value is new value to update
		- \c condition: (string) WHERE statement
		- \c 1 .. n: (optional) query arguments to be substituted for ? placeholders in condition
	
	\return Observable of database update result
--]]

function WFRxDatabaseImp:bulkUpdate( tableName, args )

	check( 'WFRxDatabaseImp', 'string', 'table' )

	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:bulkUpdate: cannot find tableName " .. tableName )
	end
		
	local db = self._db
	local exec = db.exec
	
	local sqlArgs = {}
	
	for _, arg in ipairs( args ) do
	
		local values = arg.values
		local condition = arg.condition or ""
	
		if type(values) ~= 'table' then
			error( "WFRxDatabaseImp:update: values is not a table " .. tableName )
		end
	
		if type(condition) ~= 'string' then
			error( "WFRxDatabaseImp:update: condition is not a string " .. tableName )
		end

		local queryPlaceholders = ""
		local updateValueCount = 0
		local queryInputValues = {}
	
		for k, v in pairs( values ) do
	
			updateValueCount = updateValueCount + 1
		
			if updateValueCount > 1 then
				queryPlaceholders = queryPlaceholders .. ", "
			end
		
			queryPlaceholders = queryPlaceholders .. k .. "=?"
			queryInputValues[updateValueCount] = v
	
		end
	
		if updateValueCount == 0 then
			return Observable.throw( "Empty value" )
		end
	
		local sql = string.format( 'UPDATE %s SET %s %s', tableName, queryPlaceholders, condition )
		local nConditionArgs = arg.nArgs or #arg
	
		for _, v in ipairs { unpack( arg, 1, nConditionArgs ) } do
			queryInputValues[ #queryInputValues + 1 ] = v
		end
	
		sqlArgs[ #sqlArgs + 1 ] = {
			sql = sql,
			queryInputValues = queryInputValues,
		}
		
	end
	
	local result = db:withTransactionDo( function( db )

		local rowsAffected = 0

		for _, arg in ipairs( sqlArgs ) do

			exec( db, arg.sql, unpack( arg.queryInputValues ) )
			rowsAffected = rowsAffected + db:getAffectedRowCount()
			
		end
		
		return rowsAffected
		
	end, db )

	
	self._needsUpdateFromTable[ tableName ]
		:onNext( true )

	return Observable.just( result )

end

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

--[[!
	\brief Bulk delete rows from database table

	\param tableName: (string) tableName for deleting rows
	\param columnName: (string) column name that matches the value for deletion
	\param values: (table) an array of values for the column that would be deleted
	
	\return Observable of database delete result
--]]

function WFRxDatabaseImp:deleteWhereIn( tableName, columnName, values )

	check( 'WFRxDatabaseImp', 'string', 'string' )
	
	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:deleteWhereIn: cannot find tableName " .. tableName )
	end
	
	local queryPlaceholders = WFSQLiteUtils.createVariableArgList( #values )
	
	local whereClause = string.format( 'WHERE %s IN (%s)', columnName, queryPlaceholders )

	return self:deleteFromWhere( tableName, whereClause, values )

end

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

--[[!
	\brief Delete from a table with an optional where clause and arguments
	
	\c tableName: (string) table to delete from
	\c optWhereClause: (string) where clause to use in the delete
	\c optArgs: (table) array of arguments to be substituted for ? placeholders

	\return Observable of SQL statement result
	
--]]
function WFRxDatabaseImp:deleteFromWhere( tableName, optWhereClause, optArgs )

	check( 'WFRxDatabaseImp', '+string', '+string|nil', '?table' )
	
	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:deleteFromWhere: cannot find tableName " .. tableName )
	end
	
	local sql = string.format( 'DELETE FROM %s %s', tableName, optWhereClause or '' )

	if string.byte( sql, -1 ) ~= kSemiColonByte then
		sql = sql .. ';'
	end
	
	local db = self._db
	local exec = db.exec
	
	local result = db:withTransactionDo( function( db )
		if optArgs then
			return exec( db, sql, unpack( optArgs, 1, #optArgs ) )
		else
			return exec( db, sql )
		end
		
	end, db )
	
	self._needsUpdateFromTable[ tableName ]
		:onNext( true )
		
	return Observable.just( result )

end

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

--[[!
	\brief Clear tables in database

	\param tableName: (string) tableName for clearing the table
	
	\return Observable of database clear table result
--]]

function WFRxDatabaseImp:clearTable( tableName )

	check( 'WFRxDatabaseImp', 'string' )

	if not self._tableInfo[ tableName ] then
		error( "WFRxDatabaseImp:clearTable: cannot find tableName " .. tableName )
	end
	
	local db = self._db
	local exec = db.exec
	
	local sql = string.format( 'DELETE FROM %s', tableName )
	
	local result = db:withTransactionDo( function( db )

		return exec( db, sql )
		
	end, db )
	
	self._needsUpdateFromTable[ tableName ]
		:onNext( true )

	return Observable.just( result )

end

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

--[[!
	\brief Permanently shut down this rxDatabase and delete all related files.
--]]

function WFRxDatabaseImp:haltAndDelete()

	local path = self._path

	self:close() -- should cause DB object and friends to close

	if path and path ~= ':memory:' then
		WFFileUtils.deleteFile( path )
		WFFileUtils.deleteFile( path .. '-shm' )
		WFFileUtils.deleteFile( path .. '-wal' )
	end

	return Observable.empty()

end

--------------------------------------------------------------------------------
--[[!
	\brief Permanently shut down this rxDatabase.
--]]

function WFRxDatabaseImp:halt()

	self:close() -- should cause DB object and friends to close

	return Observable.empty()

end

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

function WFRxDatabaseImp:_DEBUG_simulateCorruption()

	self._db:_DEBUG_simulateCorruption()

end

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

WFRxDatabaseImp = Rx.Class 'WFRxDatabaseImp'( WFRxDatabaseImp )

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

local initMessageProcessor = function( databaseNameAndScriptAndVersion )

	local _, _, databasePath, script, version = string.find( databaseNameAndScriptAndVersion, "^([^=]+)=(.*)=(.*)$" )
	
	local rxDatabase = WFRxDatabaseImp( databasePath, script, version )

	WFRequestDispatcher.onMessage( 'initDatabase', function( databasePath, script )

		return true
			-- noOp

	end )

	local messageForwarder = setmetatable({}, {
		__index = function( _, methodName )
			return function( self, ... )
				assert( rxDatabase, 'database not yet inited' )
				assert( rxDatabase ~= kClosed, 'database closed' )
				local method = assert( rxDatabase[ methodName ], 'No such method on database' )
				return method( rxDatabase, ... )
			end
		end,
	})

	WFRequestDispatcher.onMessage( 'close', function( rxDatabase )

		assert( rxDatabase, 'database not yet inited' )
		assert( rxDatabase ~= kClosed, 'database closed' )
		local oldRxDatabase = rxDatabase
		rxDatabase = kClosed
		return oldRxDatabase:close()

	end )

	WFRequestDispatcher.onMessageDo {
		message = 'addTable',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'addIndex',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'DEBUG_tableNames',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'DEBUG_indexInfos',
		target = messageForwarder,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'query',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'bulkInsert',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'bulkInsertOrReplace',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'update',
		target = messageForwarder,
	}

	WFRequestDispatcher.onMessageDo {
		message = 'bulkUpdate',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'deleteWhereIn',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'deleteFromWhere',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'clearTable',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'haltAndDelete',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = 'halt',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.onMessageDo {
		message = '_DEBUG_simulateCorruption',
		target = messageForwarder,
	}
	
	WFRequestDispatcher.enableDiagnosticLogForRequestStats()

end

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

return {
	initMessageProcessor = initMessageProcessor,
}
