import { ArrayExt , each , every , retro , some } from '@lumino/algorithm' ; import { LinkedList } from '@lumino/collections' ; /**
 * A message which can be delivered to a message handler.
 *
 * #### Notes
 * This class may be subclassed to create complex message types.
 */ export class Message { param type constructor ( type ) { this . type = type ; } /**
     * The type of the message.
     *
     * #### Notes
     * The `type` of a message should be related directly to its actual
     * runtime type. This means that `type` can and will be used to cast
     * the message to the relevant derived `Message` subtype.
     */ readonly type : string ; /**
     * Test whether the message is conflatable.
     *
     * #### Notes
     * Message conflation is an advanced topic. Most message types will
     * not make use of this feature.
     *
     * If a conflatable message is posted to a handler while another
     * conflatable message of the same `type` has already been posted
     * to the handler, the `conflate()` method of the existing message
     * will be invoked. If that method returns `true`, the new message
     * will not be enqueued. This allows messages to be compressed, so
     * that only a single instance of the message type is processed per
     * cycle, no matter how many times messages of that type are posted.
     *
     * Custom message types may reimplement this property.
     *
     * The default implementation is always `false`.
     */ get isConflatable ( ) : boolean { return false ; } param other returns conflate ( other ) { return false ; } } /**
 * A convenience message class which conflates automatically.
 *
 * #### Notes
 * Message conflation is an advanced topic. Most user code will not
 * make use of this class.
 *
 * This message class is useful for creating message instances which
 * should be conflated, but which have no state other than `type`.
 *
 * If conflation of stateful messages is required, a custom `Message`
 * subclass should be created.
 */ export class ConflatableMessage extends Message { /**
     * Test whether the message is conflatable.
     *
     * #### Notes
     * This property is always `true`.
     */ get isConflatable ( ) : boolean { return true ; } /**
     * Conflate this message with another message of the same `type`.
     *
     * #### Notes
     * This method always returns `true`.
     */ conflate ( other ) { return true ; } } /**
 * An object which handles messages.
 *
 * #### Notes
 * A message handler is a simple way of defining a type which can act
 * upon on a large variety of external input without requiring a large
 * abstract API surface. This is particularly useful in the context of
 * widget frameworks where the number of distinct message types can be
 * unbounded.
 */ export interface IMessageHandler { param msg processMessage ( msg ) : void ; } /**
 * An object which intercepts messages sent to a message handler.
 *
 * #### Notes
 * A message hook is useful for intercepting or spying on messages
 * sent to message handlers which were either not created by the
 * consumer, or when subclassing the handler is not feasible.
 *
 * If `messageHook` returns `false`, no other message hooks will be
 * invoked and the message will not be delivered to the handler.
 *
 * If all installed message hooks return `true`, the message will
 * be delivered to the handler for processing.
 *
 * **See also:** [[installMessageHook]] and [[removeMessageHook]]
 */ export interface IMessageHook { param handler param msg returns messageHook ( handler , msg ) : boolean ; } /**
 * A type alias for message hook object or function.
 *
 * #### Notes
 * The signature and semantics of a message hook function are the same
 * as the `messageHook` method of [[IMessageHook]].
 */ export type MessageHook = IMessageHook | ( ( handler : IMessageHandler , msg : Message ) => boolean ) ; /**
 * The namespace for the global singleton message loop.
 */ export namespace MessageLoop { param handler param msg export function sendMessage ( handler , msg ) { let hooks = messageHooks . get ( handler ) ; if ( ! hooks || hooks . length === 0 ) { invokeHandler ( handler , msg ) ; return ; } let passed = every ( retro ( hooks ) , hook => { return hook ? invokeHook ( hook , handler , msg ) : true ; } ) ; if ( passed ) { invokeHandler ( handler , msg ) ; } } param handler param msg export function postMessage ( handler , msg ) { if ( ! msg . isConflatable ) { enqueueMessage ( handler , msg ) ; return ; } let conflated = some ( messageQueue , posted => { if ( posted . handler !== handler ) { return false ; } if ( ! posted . msg ) { return false ; } if ( posted . msg . type !== msg . type ) { return false ; } if ( ! posted . msg . isConflatable ) { return false ; } return posted . msg . conflate ( msg ) ; } ) ; if ( ! conflated ) { enqueueMessage ( handler , msg ) ; } } param handler param hook export function installMessageHook ( handler , hook ) { let hooks = messageHooks . get ( handler ) ; if ( hooks && hooks . indexOf ( hook ) !== - 1 ) { return ; } if ( ! hooks ) { messageHooks . set ( handler , [ hook ] ) ; } else { hooks . push ( hook ) ; } } param handler param hook export function removeMessageHook ( handler , hook ) { let hooks = messageHooks . get ( handler ) ; if ( ! hooks ) { return ; } let i = hooks . indexOf ( hook ) ; if ( i === - 1 ) { return ; } hooks [ i ] = null ; scheduleCleanup ( hooks ) ; } param handler export function clearData ( handler ) { let hooks = messageHooks . get ( handler ) ; if ( hooks && hooks . length > 0 ) { ArrayExt . fill ( hooks , null ) ; scheduleCleanup ( hooks ) ; } each ( messageQueue , posted => { if ( posted . handler === handler ) { posted . handler = null ; posted . msg = null ; } } ) ; } /**
     * Process the pending posted messages in the queue immediately.
     *
     * #### Notes
     * This function is useful when posted messages must be processed
     * immediately, instead of on the next animation frame.
     *
     * This function should normally not be needed, but it may be
     * required to work around certain browser idiosyncrasies.
     *
     * Recursing into this function is a no-op.
     */ export function flush ( ) { if ( flushGuard || loopTaskID === 0 ) { return ; } unschedule ( loopTaskID ) ; flushGuard = true ; runMessageLoop ( ) ; flushGuard = false ; } /**
     * A type alias for the exception handler function.
     */ export type ExceptionHandler = ( err : Error ) => void ; returns export function getExceptionHandler ( ) { return exceptionHandler ; } param handler returns export function setExceptionHandler ( handler ) { let old = exceptionHandler ; exceptionHandler = handler ; return old ; } /**
     * A type alias for a posted message pair.
     */ type PostedMessage = { handler : IMessageHandler | null ; msg : Message | null ; } ; /**
     * The queue of posted message pairs.
     */ const messageQueue = new LinkedList < PostedMessage > ( ) ; /**
     * A mapping of handler to array of installed message hooks.
     */ const messageHooks = new WeakMap < IMessageHandler , Array < MessageHook | null > > ( ) ; /**
     * A set of message hook arrays which are pending cleanup.
     */ const dirtySet = new Set < Array < MessageHook | null > > ( ) ; /**
     * The message loop exception handler.
     */ let exceptionHandler = ( err ) => { console . error ( err ) ; } ; type ScheduleHandle = number | any ; /**
     * The id of the pending loop task animation frame.
     */ let loopTaskID = 0 ; /**
     * A guard flag to prevent flush recursion.
     */ let flushGuard = false ; /**
     * A function to schedule an event loop callback.
     */ const schedule = ( ( ) : ScheduleHandle => { let ok = typeof requestAnimationFrame === 'function' ; return ok ? requestAnimationFrame : setImmediate ; } ) ( ) ; /**
     * A function to unschedule an event loop callback.
     */ const unschedule = ( ( ) => { let ok = typeof cancelAnimationFrame === 'function' ; return ok ? cancelAnimationFrame : clearImmediate ; } ) ( ) ; /**
     * Invoke a message hook with the specified handler and message.
     *
     * Returns the result of the hook, or `true` if the hook throws.
     *
     * Exceptions in the hook will be caught and logged.
     */ function invokeHook ( hook , handler , msg ) { let result = true ; try { if ( typeof hook === 'function' ) { result = hook ( handler , msg ) ; } else { result = hook . messageHook ( handler , msg ) ; } } catch ( err ) { exceptionHandler ( err ) ; } return result ; } /**
     * Invoke a message handler with the specified message.
     *
     * Exceptions in the handler will be caught and logged.
     */ function invokeHandler ( handler , msg ) { try { handler . processMessage ( msg ) ; } catch ( err ) { exceptionHandler ( err ) ; } } /**
     * Add a message to the end of the message queue.
     *
     * This will automatically schedule a run of the message loop.
     */ function enqueueMessage ( handler , msg ) { messageQueue . addLast ( { handler , msg } ) ; if ( loopTaskID !== 0 ) { return ; } loopTaskID = schedule ( runMessageLoop ) ; } /**
     * Run an iteration of the message loop.
     *
     * This will process all pending messages in the queue. If a message
     * is added to the queue while the message loop is running, it will
     * be processed on the next cycle of the loop.
     */ function runMessageLoop ( ) { loopTaskID = 0 ; if ( messageQueue . isEmpty ) { return ; } let sentinel = { handler : null , msg : null } ; messageQueue . addLast ( sentinel ) ; while ( true ) { let posted = messageQueue . removeFirst ( ) ! ; if ( posted === sentinel ) { return ; } if ( posted . handler && posted . msg ) { sendMessage ( posted . handler , posted . msg ) ; } } } /**
     * Schedule a cleanup of a message hooks array.
     *
     * This will add the array to the dirty set and schedule a deferred
     * cleanup of the array contents. On cleanup, any `null` hook will
     * be removed from the array.
     */ function scheduleCleanup ( hooks ) { if ( dirtySet . size === 0 ) { schedule ( cleanupDirtySet ) ; } dirtySet . add ( hooks ) ; } /**
     * Cleanup the message hook arrays in the dirty set.
     *
     * This function should only be invoked asynchronously, when the
     * stack frame is guaranteed to not be on the path of user code.
     */ function cleanupDirtySet ( ) { dirtySet . forEach ( cleanupHooks ) ; dirtySet . clear ( ) ; } /**
     * Cleanup the dirty hooks in a message hooks array.
     *
     * This will remove any `null` hook from the array.
     *
     * This function should only be invoked asynchronously, when the
     * stack frame is guaranteed to not be on the path of user code.
     */ function cleanupHooks ( hooks ) { ArrayExt . removeAllWhere ( hooks , isNull ) ; } /**
     * Test whether a value is `null`.
     */ function isNull < T > ( value ) { return value === null ; } }