// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma warning disable 1634, 1691

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace System.Management.Automation
{
    /// <summary>
    /// Represents the interface to the PowerShell eventing functionality.
    ///  This class allows you to subscribe to, and receive events.
    /// </summary>
    public abstract class PSEventManager
    {
        /// <summary>Sequential event ID</summary>
        private int _nextEventId = 1;

        /// <summary>
        /// Returns a sequential event ID.
        /// </summary>
        protected int GetNextEventId()
        {
            return _nextEventId++;
        }

        /// <summary>
        /// Represents the interface to the PowerShell event queue.
        /// </summary>
        public PSEventArgsCollection ReceivedEvents { get; } = new PSEventArgsCollection();

        /// <summary>
        /// Gets the list of event subscribers.
        /// </summary>
        public abstract List<PSEventSubscriber> Subscribers { get; }

        /// <summary>
        /// Creates a PowerShell event.
        /// </summary>
        /// <param name="sourceIdentifier">
        /// An optional identifier that identifies the source event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="args">
        /// Any event-specific data associated with the event.
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        protected abstract PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData);

        /// <summary>
        /// Generate a PowerShell event.
        /// </summary>
        /// <param name="sourceIdentifier">
        /// An optional identifier that identifies the source event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="args">
        /// Any event-specific data associated with the event.
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
        {
            return this.GenerateEvent(sourceIdentifier, sender, args, extraData, false, false);
        }

        /// <summary>
        /// Generate a PowerShell event.
        /// </summary>
        /// <param name="sourceIdentifier">
        /// An optional identifier that identifies the source event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="args">
        /// Any event-specific data associated with the event.
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="processInCurrentThread">
        /// True if the event should be triggered in current thread. False for the event
        /// to be triggered in a separate thread.
        /// </param>
        /// <param name="waitForCompletionInCurrentThread">
        /// Wait for the event and associated action to be processed and completed.
        /// </param>
        public PSEventArgs GenerateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData,
            bool processInCurrentThread, bool waitForCompletionInCurrentThread)
        {
            PSEventArgs newEvent = CreateEvent(sourceIdentifier, sender, args, extraData);
            ProcessNewEvent(newEvent, processInCurrentThread, waitForCompletionInCurrentThread);

            return newEvent;
        }

        /// <summary>
        /// Adds a forwarded event to the current event manager.
        /// </summary>
        internal abstract void AddForwardedEvent(PSEventArgs forwardedEvent);

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
        protected abstract void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread);

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "InCurrent")]
        protected internal virtual void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread,
                                                         bool waitForCompletionWhenInCurrentThread)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Get the event subscription that corresponds to an identifier
        /// </summary>
        /// <param name="sourceIdentifier">
        /// The identifier that identifies the source of the events
        /// </param>
        public abstract IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier);

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent);

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount);

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent);

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public abstract PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount);

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="shouldQueueAndProcessInExecutionThread">
        /// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// The default value is zero
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        internal virtual PSEventSubscriber SubscribeEvent(object source,
            string eventName,
            string sourceIdentifier,
            PSObject data,
            PSEventReceivedEventHandler handlerDelegate,
            bool supportEvent,
            bool forwardEvent,
            bool shouldQueueAndProcessInExecutionThread,
            int maxTriggerCount = 0)
        {
            return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
        }

        /// <summary>
        /// Unsubscribes from an event on an object.
        /// </summary>
        /// <param name="subscriber">
        /// The subscriber associated with the event subscription
        /// </param>
        public abstract void UnsubscribeEvent(PSEventSubscriber subscriber);

        /// <summary>
        /// This event is raised by the event manager to forward events.
        /// </summary>
        internal abstract event EventHandler<PSEventArgs> ForwardEvent;
    }

    /// <summary>
    /// Implementation of the PSEventManager for local runspaces.
    /// </summary>
    internal class PSLocalEventManager : PSEventManager, IDisposable
    {
        /// <summary>
        /// Creates a new instance of the PSEventManager class.
        /// </summary>
        internal PSLocalEventManager(ExecutionContext context)
        {
            _eventSubscribers = new Dictionary<PSEventSubscriber, Delegate>();
            _engineEventSubscribers = new Dictionary<string, List<PSEventSubscriber>>(StringComparer.OrdinalIgnoreCase);
            _actionQueue = new Queue<EventAction>();
            _context = context;
        }

        private readonly Dictionary<PSEventSubscriber, Delegate> _eventSubscribers;
        private readonly Dictionary<string, List<PSEventSubscriber>> _engineEventSubscribers;
        private readonly Queue<EventAction> _actionQueue;
        private readonly ExecutionContext _context;
        private int _nextSubscriptionId = 1;
        private readonly double _throttleLimit = 1;
        private int _throttleChecks = 0;

        // The assembly and module to hold our event registrations
        private AssemblyBuilder _eventAssembly = null;
        private ModuleBuilder _eventModule = null;
        private int _typeId = 0;

        /// <summary>
        /// Gets the list of event subscribers.
        /// </summary>
        public override List<PSEventSubscriber> Subscribers
        {
            get
            {
                List<PSEventSubscriber> subscribers = new List<PSEventSubscriber>();

                lock (_eventSubscribers)
                {
                    foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)
                    {
                        subscribers.Add(currentSubscriber);
                    }
                }

                return subscribers;
            }
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent)
        {
            return SubscribeEvent(source, eventName, sourceIdentifier, data, action, supportEvent, forwardEvent, 0);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount)
        {
            // Record this subscriber. This may just be a registration for engine events.
            PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, action, supportEvent, forwardEvent, maxTriggerCount);
            ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);
            subscriber.RegisterJob();

            return subscriber;
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="shouldQueueAndProcessInExecutionThread">
        /// True, if the handlerDelegate should be processed in the pipeline execution thread (if possible).
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// The default value is zero
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        internal override PSEventSubscriber SubscribeEvent(object source,
            string eventName,
            string sourceIdentifier,
            PSObject data,
            PSEventReceivedEventHandler handlerDelegate,
            bool supportEvent,
            bool forwardEvent,
            bool shouldQueueAndProcessInExecutionThread,
            int maxTriggerCount = 0)
        {
            PSEventSubscriber newSubscriber = SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
            newSubscriber.ShouldProcessInExecutionThread = shouldQueueAndProcessInExecutionThread;
            return newSubscriber;
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent)
        {
            return SubscribeEvent(source, eventName, sourceIdentifier, data, handlerDelegate, supportEvent, forwardEvent, 0);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount)
        {
            // Record this subscriber. This may just be a registration for engine events.
            PSEventSubscriber subscriber = new PSEventSubscriber(_context, _nextSubscriptionId++, source, eventName, sourceIdentifier, handlerDelegate, supportEvent, forwardEvent, maxTriggerCount);
            ProcessNewSubscriber(subscriber, source, eventName, sourceIdentifier, data, supportEvent, forwardEvent);
            subscriber.RegisterJob();

            return subscriber;
        }

        #region OnIdleProcessing

        private Timer _timer = null;
        private bool _timerInitialized = false;
        private bool _isTimerActive = false;
        /// <summary>
        /// We sample every 100ms to check if the engine is idle (currentlyRunningPipeline == null). If it's "idle"
        /// in four consecutive samples, then we believe it's actually idle. In this way we can avoid capturing possible
        /// pipeline transitions.
        /// </summary>
        private int _consecutiveIdleSamples = 0;

        /// <summary>
        /// Send on-idle event if the engine is idle. The property "AutoReset" of the timer is always false,
        /// so only one handler will be running at anytime. The timer will be enabled again if we can meet
        /// the following two conditions.
        ///   1. No PowerShell.OnIdle event is sent out
        ///   2. A PowerShell.OnIdle event is sent out, and there are still subscribers to the on-idle event.
        /// </summary>
        private void OnElapsedEvent(object source)
        {
            var localRunspace = _context.CurrentRunspace as LocalRunspace;

            if (localRunspace == null)
            {
                // This should never happen, the context should always reference to the local runspace
                _consecutiveIdleSamples = 0;
                return;
            }

            if (localRunspace.GetCurrentlyRunningPipeline() == null)
            {
                _consecutiveIdleSamples++;
            }
            else
            {
                _consecutiveIdleSamples = 0;
            }

            if (_consecutiveIdleSamples == 4)
            {
                _consecutiveIdleSamples = 0;
                lock (_engineEventSubscribers)
                {
                    List<PSEventSubscriber> subscribers = null;
                    if (_engineEventSubscribers.TryGetValue(PSEngineEvent.OnIdle, out subscribers) && subscribers.Count > 0)
                    {
                        // We send out on-idle event and keep enabling the timer only if there still are subscribers to the on-idle event
                        GenerateEvent(PSEngineEvent.OnIdle, null, Array.Empty<object>(), null, false, false);
                        EnableTimer();
                    }
                    else
                    {
                        _isTimerActive = false;
                    }
                }
            }
            else
            {
                EnableTimer();
            }
        }

        private void InitializeTimer()
        {
            try
            {
                _timer = new Timer(OnElapsedEvent, null, Timeout.Infinite, Timeout.Infinite);
            }
            catch (ObjectDisposedException)
            {
                // The PSLocalEventManager is disposed, do nothing
            }
        }

        private void EnableTimer()
        {
            try
            {
                _timer.Change(100, Timeout.Infinite);
            }
            catch (ObjectDisposedException)
            {
                // The PSLocalEventManager is disposed, do nothing
            }
        }

        #endregion OnIdleProcessing

        private static readonly Dictionary<string, Type> s_generatedEventHandlers = new Dictionary<string, Type>();

        private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent)
        {
            Delegate handlerDelegate = null;

            if (_eventAssembly == null)
            {
                _eventAssembly = AssemblyBuilder.DefineDynamicAssembly(
                    new AssemblyName("PSEventHandler"),
                    AssemblyBuilderAccess.Run);
                _eventModule = _eventAssembly.DefineDynamicModule("PSGenericEventModule");
            }

            string engineEventSourceIdentifier = null;
            bool isOnIdleEvent = false;
            // If we are subscribing to an event on an object, generate the supporting delegate
            // for that object.
            if (source != null)
            {
                // If the identifier starts with "PowerShell.", then it will collide with engine
                // events
                if ((sourceIdentifier != null) &&
                    (sourceIdentifier.StartsWith("PowerShell.", StringComparison.OrdinalIgnoreCase)))
                {
                    string errorMessage = StringUtil.Format(EventingResources.ReservedIdentifier, sourceIdentifier);

                    throw new ArgumentException(errorMessage, nameof(sourceIdentifier));
                }

                EventInfo eventInfo = null;
                Type sourceType = source as Type ?? source.GetType();

                // PowerShell does not support WinRT events.
                if (WinRTHelper.IsWinRTType(sourceType))
                {
                    throw new InvalidOperationException(EventingResources.WinRTEventsNotSupported);
                }

                // Retrieve the event from the object
                const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;
                eventInfo = sourceType.GetEvent(eventName, bindingFlags);

                // If we can't find the event, throw an exception
                if (eventInfo == null)
                {
                    string errorMessage = StringUtil.Format(EventingResources.CouldNotFindEvent, eventName);
                    throw new ArgumentException(errorMessage, nameof(eventName));
                }

                // Try to set the EnableRaisingEvents property if it defines one
                PropertyInfo eventProperty = sourceType.GetProperty("EnableRaisingEvents");
                if (eventProperty != null && eventProperty.CanWrite)
                {
                    try
                    {
                        object targetObject = eventProperty.SetMethod.IsStatic ? null : source;
                        eventProperty.SetValue(targetObject, true);
                    }
                    catch (TargetInvocationException e)
                    {
                        if (e.InnerException != null)
                        {
                            throw e.InnerException;
                        }
                        else
                        {
                            throw;
                        }
                    }
                }

                // Get its invoke method, and register ourselves as a handler
                MethodInfo invokeMethod = eventInfo.EventHandlerType.GetMethod("Invoke");

                // We don't support non-void delegates, as the user has no way
                // to influence the result. In the .NET Framework, the only
                // events that return values are for extremely advanced scenarios:
                // System.ResolveEventHandler, and System.Reflection.ModuleResolveEventHandler.
                // For these, the Add-Type cmdlet will suffice.
                if (invokeMethod.ReturnType != typeof(void))
                {
                    string errorMessage = EventingResources.NonVoidDelegateNotSupported;
                    throw new ArgumentException(errorMessage, nameof(eventName));
                }

                // Cache generated event handlers (by type and event name) so that they don't bloat our
                // working set by recompiling event handlers for the same event.

                string eventHandlerKey = source.GetType().FullName + "|" + eventName;
                Type handlerType;

                lock (s_generatedEventHandlers)
                {
                    if (!s_generatedEventHandlers.TryGetValue(eventHandlerKey, out handlerType))
                    {
                        handlerType = GenerateEventHandler(invokeMethod);
                        s_generatedEventHandlers[eventHandlerKey] = handlerType;
                    }
                }

                // And create an instance of the type
                ConstructorInfo constructor =
                    handlerType.GetConstructor(new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) });
                object handler = constructor.Invoke(new object[] { this, source, sourceIdentifier, data });
                MethodInfo eventDelegate = handlerType.GetMethod("EventDelegate", BindingFlags.Public | BindingFlags.Instance);
                handlerDelegate = eventDelegate.CreateDelegate(eventInfo.EventHandlerType, handler);

                eventInfo.AddEventHandler(source, handlerDelegate);
            }
            else
            {
                if (PSEngineEvent.EngineEvents.Contains(sourceIdentifier))
                {
                    engineEventSourceIdentifier = sourceIdentifier;
                    isOnIdleEvent = string.Equals(engineEventSourceIdentifier, PSEngineEvent.OnIdle, StringComparison.OrdinalIgnoreCase);
                }
            }

            lock (_eventSubscribers)
            {
                _eventSubscribers[subscriber] = handlerDelegate;
                if (engineEventSourceIdentifier == null)
                {
                    return;
                }

                lock (_engineEventSubscribers)
                {
                    if (isOnIdleEvent && !_timerInitialized)
                    {
                        InitializeTimer();
                        _timerInitialized = true;
                    }

                    List<PSEventSubscriber> subscribers = null;
                    if (!_engineEventSubscribers.TryGetValue(engineEventSourceIdentifier, out subscribers))
                    {
                        subscribers = new List<PSEventSubscriber>();
                        _engineEventSubscribers.Add(engineEventSourceIdentifier, subscribers);
                    }

                    subscribers.Add(subscriber);

                    // This subscriber is the only one in the idle event list, we enable the timer
                    // since the subscriber could be the first one.
                    if (isOnIdleEvent && !_isTimerActive)
                    {
                        EnableTimer();
                        _isTimerActive = true;
                    }
                }
            }
        }

        /// <summary>
        /// Unsubscribes from an event on an object.
        /// </summary>
        /// <param name="subscriber">
        /// The subscriber associated with the event subscription
        /// </param>
        public override void UnsubscribeEvent(PSEventSubscriber subscriber)
        {
            UnsubscribeEvent(subscriber, false);
        }

        /// <summary>
        /// Unsubscribes from an event on an object.
        /// </summary>
        /// <param name="subscriber">
        /// The subscriber associated with the event subscription
        /// </param>
        /// <param name="skipDraining">
        /// Indicate if we should skip draining
        /// </param>
        private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining)
        {
            ArgumentNullException.ThrowIfNull(subscriber);

            Delegate existingSubscriber = null;
            lock (_eventSubscribers)
            {
                if (subscriber.IsBeingUnsubscribed || !_eventSubscribers.TryGetValue(subscriber, out existingSubscriber))
                {
                    // Already unsubscribed by another thread or the subscriber doesn't exist
                    return;
                }

                subscriber.IsBeingUnsubscribed = true;
            }

            if ((existingSubscriber != null) && (subscriber.SourceObject != null))
            {
                // Fire the unregistration handler
                subscriber.OnPSEventUnsubscribed(subscriber.SourceObject,
                    new PSEventUnsubscribedEventArgs(subscriber));

                EventInfo eventInfo = null;

                Type sourceType = subscriber.SourceObject as Type ?? subscriber.SourceObject.GetType();

                const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase;
                eventInfo = sourceType.GetEvent(subscriber.EventName, bindingFlags);

                if ((eventInfo != null) && (existingSubscriber != null))
                {
                    eventInfo.RemoveEventHandler(subscriber.SourceObject, existingSubscriber);
                }
            }

            // We don't need to drain pending actions when remove an auto-unregister subscriber
            // from ProcessPendingAction method
            if (!skipDraining)
            {
                // Drain any actions pending for this subscriber
                DrainPendingActions(subscriber);
            }

            // Stop the job
            subscriber.Action?.NotifyJobStopped();

            lock (_eventSubscribers)
            {
                _eventSubscribers.Remove(subscriber);
                if (PSEngineEvent.EngineEvents.Contains(subscriber.SourceIdentifier))
                {
                    lock (_engineEventSubscribers)
                    {
                        _engineEventSubscribers[subscriber.SourceIdentifier].Remove(subscriber);
                    }
                }
            }
        }

        /// <summary>
        /// Creates a PowerShell event.
        /// </summary>
        /// <param name="sourceIdentifier">
        /// An optional identifier that identifies the source event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="args">
        /// Any event-specific data associated with the event.
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
        {
            return new PSEventArgs(null, _context.CurrentRunspace.InstanceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);
        }

        /// <summary>
        /// Adds a forwarded event to the current event manager.
        /// </summary>
        internal override void AddForwardedEvent(PSEventArgs forwardedEvent)
        {
            forwardedEvent.EventIdentifier = GetNextEventId();

            ProcessNewEvent(forwardedEvent, false);
        }

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
        {
            ProcessNewEvent(newEvent, processInCurrentThread, false);
        }

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        protected internal override void ProcessNewEvent(PSEventArgs newEvent,
            bool processInCurrentThread,
            bool waitForCompletionWhenInCurrentThread)
        {
            if (processInCurrentThread)
            {
                ProcessNewEventImplementation(newEvent, true);
                ManualResetEventSlim waitHandle = newEvent.EventProcessed;
                if (waitHandle != null)
                {
                    // Win8: 738767 In Win7, the processInCurrentThread parameter was used to be
                    // called processSynchronously. Even though the parameter was called "processSynchronously",
                    // the event and associated action were not processed synchronously..the event manager
                    // just added the associated action into event queue. The action can get executed at a later
                    // time depending on the throttle checks etc. In Win8, to support routing of ScriptBlock
                    // invocation to the current runspace, we took dependency on eventing infrastructure and
                    // this required ensuring the event and associated action be processed in the current thread
                    // synchronously. The below while loop was added for that (win8: 530495). However, fix for
                    // 530495 resulted in not responding for icm | % { icm } case and dynamic event/subscriptions scenarios.
                    // To overcome that, changed "processSynchronously" parameter to "processInCurrentThread" and added
                    // a new parameter "waitForCompletionWhenInCurrentThread" to trigger blocking for ScriptBlock
                    // case.
                    while (waitForCompletionWhenInCurrentThread && !waitHandle.Wait(250))
                    {
                        this.ProcessPendingActions();
                    }

                    waitHandle.Dispose();
                }
            }
            else
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback((_) => ProcessNewEventImplementation(newEvent, false)));
            }
        }

        /// <summary>
        /// Called from ProcessNewEvent to actually process the event.
        /// </summary>
        private void ProcessNewEventImplementation(PSEventArgs newEvent, bool processSynchronously)
        {
            // Get the subscriber(s) for this event
            bool capturedEvent = false;
            List<PSEventSubscriber> actionsHandledInCurrentThread = new List<PSEventSubscriber>();
            List<PSEventSubscriber> subscribersWithoutActionOrHandler = new List<PSEventSubscriber>();
            foreach (PSEventSubscriber subscriber in GetEventSubscribers(newEvent.SourceIdentifier, true))
            {
                newEvent.ForwardEvent = subscriber.ForwardEvent;

                // If we found a subscriber and it has an action, queue it up
                if (subscriber.Action != null)
                {
                    AddAction(new EventAction(subscriber, newEvent), processSynchronously);
                    capturedEvent = true;
                }
                else if (subscriber.HandlerDelegate != null)
                {
                    if (subscriber.ShouldProcessInExecutionThread)
                    {
                        AddAction(new EventAction(subscriber, newEvent), processSynchronously);
                    }
                    else
                    {
                        actionsHandledInCurrentThread.Add(subscriber);
                    }

                    capturedEvent = true;
                }
                else
                {
                    subscribersWithoutActionOrHandler.Add(subscriber);
                }
            }

            foreach (PSEventSubscriber subscriber in actionsHandledInCurrentThread)
            {
                subscriber.HandlerDelegate(newEvent.Sender, newEvent);
                AutoUnregisterEventIfNecessary(subscriber);
            }

            // Otherwise, add it to the queue of unprocessed items, unless we are forwarding the event
            if (!capturedEvent)
            {
                if (newEvent.ForwardEvent)
                {
                    OnForwardEvent(newEvent);
                }
                else
                {
                    lock (ReceivedEvents.SyncRoot)
                    {
                        ReceivedEvents.Add(newEvent);
                    }
                }

                foreach (PSEventSubscriber subscriber in subscribersWithoutActionOrHandler)
                {
                    AutoUnregisterEventIfNecessary(subscriber);
                }
            }
        }

        // Add an action to the event queue
        private void AddAction(EventAction action, bool processSynchronously)
        {
            if (processSynchronously)
            {
                // This mutex will get set after the event is processed.
                action.Args.EventProcessed = new ManualResetEventSlim();
            }

            lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
            {
                // If the engine isn't active, pulse the pipeline.
                // When the engine starts up, it will pick up the pending events
                _actionQueue.Enqueue(action);
            }

            PulseEngine();
        }

        // PowerShell support for async notifications happen through the
        // CheckForInterrupts() method on ParseTreeNode. These are only called when
        // the engine is active (and processing,) so the Pulse() method
        // executes the equivalent of a NOP so that async events
        // can be processed when the engine is idle.
        private void PulseEngine()
        {
            try
            {
                ((LocalRunspace)_context.CurrentRunspace).Pulse();
            }
            catch (ObjectDisposedException)
            { }
        }

        /// <summary>
        /// Process any pending actions. The parser calls this method before it executes any
        /// parse tree node. If we are processing pending actions, we must block the parser.
        /// To prevent starvation of the foreground script, we throttle the number of events
        /// that we process while the parser is waiting. If the parser is not waiting, we
        /// do not throttle the event processing.
        /// </summary>
        internal void ProcessPendingActions()
        {
            // We do this check quickly outside of the lock so that the
            // main-line scenario is as fast as possible.  Also, do the real work
            // in a different method so that this method could be inlined.
            if (_actionQueue.Count == 0)
                return;

            ProcessPendingActionsImpl();
        }

        private void ProcessPendingActionsImpl()
        {
            // Now, process pending actions
            if (IsExecutingEventAction)
                return;

            try
            {
                lock (_actionProcessingLock)
                {
                    if (IsExecutingEventAction)
                        return;

                    int processed = 0;
                    _throttleChecks++;
                    EventAction nextAction;

                    while ((_throttleLimit * _throttleChecks) >= processed)
                    {
                        // Now, check for (and process) pending actions.
                        // Lock the collection so that it doesn't change from
                        // beneath us.
                        lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
                        {
                            int queueCount = _actionQueue.Count;

                            // Exit if the actions have already been processed
                            if (queueCount == 0)
                                return;

                            nextAction = _actionQueue.Dequeue();
                        }

                        bool addActionBackToActionQueue = false;
                        InvokeAction(nextAction, out addActionBackToActionQueue);
                        processed++;

                        if (!addActionBackToActionQueue)
                        {
                            AutoUnregisterEventIfNecessary(nextAction.Sender);
                        }
                    }

                    if (processed > 0)
                        _throttleChecks = 0;
                }
            }
            finally
            {
                if (_actionQueue.Count > 0)
                {
                    // If we still have work remaining, sleep a bit (to give
                    // other pipelines a chance to interrupt,) and try again.
                    // This is done on another thread, since we own the runspace lock
                    // at this point if we're being called from the pipeline
                    // teardown event. That can result in starvation of
                    // foreground threads that also want to use the runspace.
                    ThreadPool.QueueUserWorkItem(new WaitCallback(
                        (_) =>
                        {
                            System.Threading.Thread.Sleep(100);
                            this.PulseEngine();
                        }));
                }
            }
        }

        /// <summary>
        /// Auto unregister the subscriber if both 'RemainingTriggerCount' and 'RemainingActionsToProcess' become zero.
        /// </summary>
        private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber)
        {
            bool removeSubscriber = false;
            if (subscriber.AutoUnregister)
            {
                lock (subscriber)
                {
                    subscriber.RemainingActionsToProcess--;
                    removeSubscriber = subscriber.RemainingTriggerCount == 0 &&
                                       subscriber.RemainingActionsToProcess == 0;
                }
            }

            if (removeSubscriber)
            {
                UnsubscribeEvent(subscriber, true);
            }
        }

        private readonly object _actionProcessingLock = new object();
        private EventAction _processingAction = null;

        /// <summary>
        /// Drain any pending actions for a given subscriber.
        /// This is a synchronous (and expensive) operation, but is
        /// required so that unregistering for an event / stopping the
        /// event job truly discontinues those event arrivals.
        /// </summary>
        internal void DrainPendingActions(PSEventSubscriber subscriber)
        {
            // We do this check quickly outside of the lock so that the
            // main-line scenario is as fast as possible.
            if (_actionQueue.Count == 0)
                return;

            // Now, process pending actions
            lock (_actionProcessingLock)
            {
                lock (((System.Collections.ICollection)_actionQueue).SyncRoot)
                {
                    int queueCount = _actionQueue.Count;

                    // Exit if the actions have already been processed
                    if (queueCount == 0)
                        return;

                    bool needToScanAgain = false;

                    do
                    {
                        EventAction[] pendingActions = _actionQueue.ToArray();
                        _actionQueue.Clear();

                        foreach (EventAction pendingAction in pendingActions)
                        {
                            // Make sure an event action can unregister itself.
                            if ((pendingAction.Sender == subscriber) &&
                                (pendingAction != _processingAction))
                            {
                                while (IsExecutingEventAction)
                                    System.Threading.Thread.Sleep(100);

                                bool addActionBackToActionQueue = false;
                                InvokeAction(pendingAction, out addActionBackToActionQueue);

                                if (addActionBackToActionQueue)
                                {
                                    needToScanAgain = true;
                                }
                            }
                            else
                            {
                                _actionQueue.Enqueue(pendingAction);
                            }
                        }
                    } while (needToScanAgain);
                }
            }
        }

        private void InvokeAction(EventAction nextAction, out bool addActionBack)
        {
            lock (_actionProcessingLock)
            {
                _processingAction = nextAction;
                addActionBack = false;

                // Invoke the action in its own session state
                SessionStateInternal oldSessionState = _context.EngineSessionState;
                if (nextAction.Sender.Action != null)
                {
                    _context.EngineSessionState = nextAction.Sender.Action.ScriptBlock.SessionStateInternal;
                }

                Runspace oldDefault = Runspace.DefaultRunspace;

                try
                {
                    // Set the engine's session state to be
                    // the session state of the action. Also update the default runspace that the
                    // scriptblock will execute under, so that scriptblocks will stay in the same
                    // runspace they were invoked from.
                    Runspace.DefaultRunspace = _context.CurrentRunspace;
                    if (nextAction.Sender.Action != null)
                    {
                        nextAction.Sender.Action.Invoke(nextAction.Sender, nextAction.Args);
                    }
                    else
                    {
                        nextAction.Sender.HandlerDelegate(nextAction.Sender, nextAction.Args);
                    }
                }
                catch (Exception e)
                {
                    // Catch-all OK. This is a third-party call-out.
                    if (e is PipelineStoppedException)
                    {
                        // Enqueue the action again, as we weren't able to process it.
                        // It is possible that the PipelineStoppedException gets generated
                        // to interrupt _our_ event, but is much more likely to get generated
                        // when somebody wants to interrupt the pipeline that we are interrupting
                        // (such as a long directory listing.)
                        AddAction(nextAction, processSynchronously: false);
                        addActionBack = true;
                    }
                }
                finally
                {
                    var eventProcessed = nextAction.Args.EventProcessed;
                    if (!addActionBack && eventProcessed != null)
                    {
                        eventProcessed.Set();
                    }

                    Runspace.DefaultRunspace = oldDefault;
                    _context.EngineSessionState = oldSessionState;
                    _processingAction = null;
                }
            }
        }

        internal bool IsExecutingEventAction
        {
            get { return (_processingAction != null); }
        }

        /// <summary>
        /// Get the event subscription that corresponds to an identifier
        /// </summary>
        /// <param name="sourceIdentifier">
        /// The identifier that identifies the source of the events
        /// </param>
        public override IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier)
        {
            return GetEventSubscribers(sourceIdentifier, false);
        }

        /// <summary>
        /// If we add event filter feature in the future, the filtering work should be done in this method,
        /// so that we only return a list of subscribers that will be invoked.
        /// </summary>
        private IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier, bool forNewEventProcessing)
        {
            List<PSEventSubscriber> returnedSubscribers = new List<PSEventSubscriber>();
            List<PSEventSubscriber> subscribersToBeRemoved = new List<PSEventSubscriber>();

            lock (_eventSubscribers)
            {
                foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys)
                {
                    bool takeActionForEvent = false;
                    if (string.Equals(currentSubscriber.SourceIdentifier, sourceIdentifier, StringComparison.OrdinalIgnoreCase))
                    {
                        if (forNewEventProcessing)
                        {
                            // The caller tries to process the event
                            if (!currentSubscriber.AutoUnregister || currentSubscriber.RemainingTriggerCount > 0)
                            {
                                // If we need the event filter feature, it should be added here.
                                takeActionForEvent = true;
                                returnedSubscribers.Add(currentSubscriber);
                            }
                        }
                        else
                        {
                            // The caller tries to get all subscribers for this event but it's NOT for the event processing purpose
                            returnedSubscribers.Add(currentSubscriber);
                        }

                        // Handle auto-unregister subscribers here
                        if (forNewEventProcessing && currentSubscriber.AutoUnregister && currentSubscriber.RemainingTriggerCount > 0)
                        {
                            lock (currentSubscriber)
                            {
                                currentSubscriber.RemainingTriggerCount--;
                                // For now, 'takeActionForEvent' is always True when we get to this point.
                                // But once the event filter feature is added, it could be False when we get to this point.
                                if (takeActionForEvent)
                                {
                                    currentSubscriber.RemainingActionsToProcess++;
                                }

                                // This condition can only happen after the event filter feature is introduced
                                if (currentSubscriber.RemainingTriggerCount == 0 && currentSubscriber.RemainingActionsToProcess == 0)
                                {
                                    subscribersToBeRemoved.Add(currentSubscriber);
                                }
                            }
                        }
                    }
                }
            }

            if (subscribersToBeRemoved.Count > 0)
            {
                foreach (PSEventSubscriber subscriber in subscribersToBeRemoved)
                {
                    UnsubscribeEvent(subscriber, true);
                }
            }

            return returnedSubscribers;
        }

        // Generates a type and method to handle a strongly-typed event from an object
        // The event handler itself does as little as possible, as dynamically emitted IL
        // is error prone and difficult to read. When possible, functionality enhancements
        // should go in the PSEventHandler class, from which every event handler type derives.
        private Type GenerateEventHandler(MethodInfo invokeSignature)
        {
            int parameterCount = invokeSignature.GetParameters().Length;

            // Define the type that will respond to the event. It
            // derives from PSEventHandler so that complex
            // functionality can go into its base class.
            TypeBuilder eventType =
                _eventModule.DefineType("PSEventHandler_" + _typeId, TypeAttributes.Public, typeof(PSEventHandler));
            _typeId++;

            // Retrieve the existing constructor
            ConstructorInfo existingConstructor =
                typeof(PSEventHandler).GetConstructor(
                    new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) });

            // Define the new constructor
            // public TestEventHandler(PSEventManager eventManager, Object sender, string sourceIdentifier, PSObject extraData)
            // : base(eventManager, sender, sourceIdentifier, extraData)
            ConstructorBuilder eventConstructor =
                eventType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
                    new Type[] { typeof(PSEventManager), typeof(object), typeof(string), typeof(PSObject) });
            ILGenerator extendedConstructor = eventConstructor.GetILGenerator();
            extendedConstructor.Emit(OpCodes.Ldarg_0);
            extendedConstructor.Emit(OpCodes.Ldarg_1);
            extendedConstructor.Emit(OpCodes.Ldarg_2);
            extendedConstructor.Emit(OpCodes.Ldarg_3);
            extendedConstructor.Emit(OpCodes.Ldarg, 4);
            extendedConstructor.Emit(OpCodes.Call, existingConstructor);
            extendedConstructor.Emit(OpCodes.Ret);

            // Go through each of the parameters in the event signature, and store their types
            Type[] parameterTypes = new Type[parameterCount];
            int parameterCounter = 0;
            foreach (ParameterInfo parameter in invokeSignature.GetParameters())
            {
                parameterTypes[parameterCounter] = parameter.ParameterType;
                parameterCounter++;
            }

            // public void EventDelegate(object sender, FileSystemEventArgs e)
            MethodBuilder eventMethod = eventType.DefineMethod("EventDelegate",
                MethodAttributes.Public, CallingConventions.Standard, invokeSignature.ReturnType, parameterTypes);

            // Create new parameters that mimic the parameters of the event method
            parameterCounter = 1;
            foreach (ParameterInfo parameter in invokeSignature.GetParameters())
            {
                ParameterBuilder builder = eventMethod.DefineParameter(
                    parameterCounter, parameter.Attributes, parameter.Name);
                parameterCounter++;
            }

            ILGenerator methodContents = eventMethod.GetILGenerator();

            // Declare a local variable of the type 'object[]' at index 0, say 'object[] args'
            methodContents.DeclareLocal(typeof(object[]));

            methodContents.Emit(OpCodes.Ldc_I4, parameterCount);
            methodContents.Emit(OpCodes.Newarr, typeof(object));

            // Store the new array to the local variable 'args'
            methodContents.Emit(OpCodes.Stloc_0);

            // Inline, this converts into a series of setting args[n] to
            // the argument at the same parameter index
            for (int counter = 1; counter <= parameterCount; counter++)
            {
                methodContents.Emit(OpCodes.Ldloc_0);
                methodContents.Emit(OpCodes.Ldc_I4, counter - 1);
                methodContents.Emit(OpCodes.Ldarg, counter);

                // Box the value type if necessary
                if (parameterTypes[counter - 1].IsValueType)
                {
                    methodContents.Emit(OpCodes.Box, parameterTypes[counter - 1]);
                }

                methodContents.Emit(OpCodes.Stelem_Ref);
            }

            // Gain access to "this"
            methodContents.Emit(OpCodes.Ldarg_0);

            // Load the "eventManager" private field, and push it onto the stack
            FieldInfo eventManagerField = typeof(PSEventHandler).GetField("eventManager",
                BindingFlags.NonPublic | BindingFlags.Instance);
            methodContents.Emit(OpCodes.Ldfld, eventManagerField);

            // Then the "sourceIdentifier" private field
            methodContents.Emit(OpCodes.Ldarg_0);
            FieldInfo identifierField = typeof(PSEventHandler).GetField("sourceIdentifier",
                BindingFlags.NonPublic | BindingFlags.Instance);
            methodContents.Emit(OpCodes.Ldfld, identifierField);

            // Then the "sender" private field
            methodContents.Emit(OpCodes.Ldarg_0);
            FieldInfo senderField = typeof(PSEventHandler).GetField("sender",
                BindingFlags.NonPublic | BindingFlags.Instance);
            methodContents.Emit(OpCodes.Ldfld, senderField);

            // Then push the args variable onto the stack
            methodContents.Emit(OpCodes.Ldloc_0);

            // Then push the "extraData" private field
            methodContents.Emit(OpCodes.Ldarg_0);
            FieldInfo extraDataField = typeof(PSEventHandler).GetField("extraData",
                BindingFlags.NonPublic | BindingFlags.Instance);
            methodContents.Emit(OpCodes.Ldfld, extraDataField);

            // Finally, invoke the method
            MethodInfo generateEventMethod = typeof(PSEventManager).GetMethod(
                nameof(PSEventManager.GenerateEvent),
                new Type[] { typeof(string), typeof(object), typeof(object[]), typeof(PSObject) });

            methodContents.Emit(OpCodes.Callvirt, generateEventMethod);

            // Discard the return value, and return
            methodContents.Emit(OpCodes.Pop);
            methodContents.Emit(OpCodes.Ret);

            return eventType.CreateTypeInfo().AsType();
        }

        /// <summary>
        /// This event is raised by the event manager to forward events.
        /// </summary>
        internal override event EventHandler<PSEventArgs> ForwardEvent;

        /// <summary>
        /// Raises the ForwardEvent event.
        /// </summary>
        protected virtual void OnForwardEvent(PSEventArgs e)
        {
            ForwardEvent?.Invoke(this, e);
        }

        /// <summary>
        /// Disposes the EventManager class.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Stop the timer if it's not null.
        /// Unsubscribes from all events.
        /// </summary>
        /// <param name="disposing">
        /// Whether to actually dispose the object.
        /// </param>
        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (_eventSubscribers)
                {
                    _timer?.Dispose();

                    foreach (PSEventSubscriber currentSubscriber in _eventSubscribers.Keys.ToArray())
                    {
                        UnsubscribeEvent(currentSubscriber);
                    }
                }
            }
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="PSLocalEventManager"/> class.
        /// </summary>
        ~PSLocalEventManager()
        {
            Dispose(false);
        }
    }

    /// <summary>
    /// Implementation of PSEventManager for remote runspaces.
    /// </summary>
    internal class PSRemoteEventManager : PSEventManager
    {
        /// <summary>Computer on which the event was generated</summary>
        private readonly string _computerName;

        /// <summary>Runspace on which the event was generated</summary>
        private readonly Guid _runspaceId;

        /// <summary>
        /// Creates an event manager for the given runspace.
        /// </summary>
        /// <param name="computerName">Computer on which the event was generated.</param>
        /// <param name="runspaceId">Runspace on which the event was generated.</param>
        internal PSRemoteEventManager(string computerName, Guid runspaceId)
        {
            _computerName = computerName;
            _runspaceId = runspaceId;
        }

        /// <summary>
        /// Gets the list of event subscribers.
        /// </summary>
        public override List<PSEventSubscriber> Subscribers
        {
            get
            {
                throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
            }
        }

        /// <summary>
        /// Creates a PowerShell event.
        /// </summary>
        /// <param name="sourceIdentifier">
        /// An optional identifier that identifies the source event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="args">
        /// Any event-specific data associated with the event.
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        protected override PSEventArgs CreateEvent(string sourceIdentifier, object sender, object[] args, PSObject extraData)
        {
            // note that this is a local call, so we use null for the computer name
            return new PSEventArgs(null, _runspaceId, GetNextEventId(), sourceIdentifier, sender, args, extraData);
        }

        /// <summary>
        /// Adds a forwarded event to the current event manager.
        /// </summary>
        internal override void AddForwardedEvent(PSEventArgs forwardedEvent)
        {
            forwardedEvent.EventIdentifier = GetNextEventId();
            forwardedEvent.ForwardEvent = false;

            // The computer name will be null the first time the event is forwarded; in this case we need to override with the
            // remote computer this event manager is associated to. If the event has travelled multiple hops then we do not
            // want to override this value since we want to preserve the original computer.
            if (forwardedEvent.ComputerName == null || forwardedEvent.ComputerName.Length == 0)
            {
                forwardedEvent.ComputerName = _computerName;
                forwardedEvent.RunspaceId = _runspaceId;
            }

            ProcessNewEvent(forwardedEvent, false);
        }

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        protected override void ProcessNewEvent(PSEventArgs newEvent, bool processInCurrentThread)
        {
            ProcessNewEvent(newEvent, processInCurrentThread, false);
        }

        /// <summary>
        /// Processes new events (which have either been generated by this instance or forwarded to it)
        /// </summary>
        protected internal override void ProcessNewEvent(PSEventArgs newEvent,
            bool processInCurrentThread, bool waitForCompletionInCurrentThread)
        {
            lock (ReceivedEvents.SyncRoot)
            {
                if (newEvent.ForwardEvent)
                {
                    OnForwardEvent(newEvent);
                }
                else
                {
                    ReceivedEvents.Add(newEvent);
                }
            }
        }

        /// <summary>
        /// Get the event subscription that corresponds to an identifier
        /// </summary>
        /// <param name="sourceIdentifier">
        /// The identifier that identifies the source of the events
        /// </param>
        public override IEnumerable<PSEventSubscriber> GetEventSubscribers(string sourceIdentifier)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="action">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// Subscribes to an event on an object.
        /// </summary>
        /// <param name="source">
        /// The source object that defines the event
        /// </param>
        /// <param name="eventName">
        /// The event to subscribe
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier to help identify this event subscription
        /// </param>
        /// <param name="data">
        /// Any additional data you wish to attach to the event
        /// </param>
        /// <param name="handlerDelegate">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="supportEvent">
        /// Any action to be invoked when the event arrives
        /// </param>
        /// <param name="forwardEvent">
        /// Whether events in this subscriber should be forwarded to the client PowerShell during remote executions
        /// </param>
        /// <param name="maxTriggerCount">
        /// Indicate how many times the subscriber should be triggered before auto-unregister it
        /// If the value is equal or less than zero, there is no limit on the number of times the event can be triggered without being unregistered
        /// </param>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public override PSEventSubscriber SubscribeEvent(object source, string eventName, string sourceIdentifier, PSObject data, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// Unsubscribes from an event on an object.
        /// </summary>
        /// <param name="subscriber">
        /// The subscriber associated with the event subscription
        /// </param>
        public override void UnsubscribeEvent(PSEventSubscriber subscriber)
        {
            throw new NotSupportedException(EventingResources.RemoteOperationNotSupported);
        }

        /// <summary>
        /// This event is raised by the event manager to forward events.
        /// </summary>
        internal override event EventHandler<PSEventArgs> ForwardEvent;

        /// <summary>
        /// Raises the ForwardEvent event.
        /// </summary>
        protected virtual void OnForwardEvent(PSEventArgs e)
        {
            ForwardEvent?.Invoke(this, e);
        }
    }

    /// <summary>
    /// Constants that represent PowerShell engine events.
    /// </summary>
    // Note: If you generate a new engine event that happens frequently,
    // (i.e.: variable changes), the user should be required to enable
    // that engine event before they make it into the ReceivedEvents channel.
    public sealed class PSEngineEvent
    {
        private PSEngineEvent() { }

        /// <summary>
        /// Called when the PowerShell engine is exiting.
        /// </summary>
        public const string Exiting = "PowerShell.Exiting";

        /// <summary>
        /// Call when the PowerShell engine is idle.
        /// </summary>
        public const string OnIdle = "PowerShell.OnIdle";

        /// <summary>
        /// Called during scriptblock invocation.
        /// </summary>
        internal const string OnScriptBlockInvoke = "PowerShell.OnScriptBlockInvoke";

        /// <summary>
        /// Called during scriptblock invocation.
        /// </summary>
        internal const string GetCommandInfoParameterMetadata = "PowerShell.GetCommandInfoParameterMetadata";

        /// <summary>
        /// A HashSet that contains all engine event names.
        /// </summary>
        internal static readonly HashSet<string> EngineEvents = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke };
    }

    /// <summary>
    /// Represents a subscriber to an event.
    /// </summary>
    public class PSEventSubscriber : IEquatable<PSEventSubscriber>
    {
        /// <summary>
        /// Creates an instance of the PSEventSubscriber class for a given source object, event name,
        /// and optional source identifier.
        /// </summary>
        internal PSEventSubscriber(ExecutionContext context, int id, object source,
            string eventName, string sourceIdentifier, bool supportEvent, bool forwardEvent, int maxTriggerCount)
        {
            _context = context;

            SubscriptionId = id;
            SourceObject = source;
            EventName = eventName;
            SourceIdentifier = sourceIdentifier;
            SupportEvent = supportEvent;
            ForwardEvent = forwardEvent;

            IsBeingUnsubscribed = false;
            RemainingActionsToProcess = 0;

            if (maxTriggerCount <= 0)
            {
                AutoUnregister = false;
                RemainingTriggerCount = -1;
            }
            else
            {
                AutoUnregister = true;
                RemainingTriggerCount = maxTriggerCount;
            }
        }

        /// <summary>
        /// Creates an instance of the PSEventSubscriber
        /// class. Additionally supports an Action scriptblock.
        /// </summary>
        internal PSEventSubscriber(
            ExecutionContext context,
            int id,
            object source,
            string eventName,
            string sourceIdentifier,
            ScriptBlock action,
            bool supportEvent,
            bool forwardEvent,
            int maxTriggerCount)
            : this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount)
        {
            // Create the bound scriptblock, and job.
            if (action != null)
            {
                ScriptBlock newAction = CreateBoundScriptBlock(action);
                Action = new PSEventJob(context.Events, this, newAction, sourceIdentifier);
            }
        }

        internal void RegisterJob()
        {
            // Add this event subscriber to the job repository if it's not a support event.
            if (!SupportEvent)
            {
                if (this.Action != null)
                {
                    JobRepository jobRepository = ((LocalRunspace)_context.CurrentRunspace).JobRepository;
                    jobRepository.Add(Action);
                }
            }
        }

        /// <summary>
        /// Creates an instance of the PSEventSubscriber
        /// class. Additionally supports an Action scriptblock.
        /// </summary>
        internal PSEventSubscriber(
            ExecutionContext context,
            int id,
            object source,
            string eventName,
            string sourceIdentifier,
            PSEventReceivedEventHandler handlerDelegate,
            bool supportEvent,
            bool forwardEvent,
            int maxTriggerCount)
            : this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount)
        {
            HandlerDelegate = handlerDelegate;
        }

        private readonly ExecutionContext _context;

        /// <summary>
        /// Create a bound script block.
        /// </summary>
        private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction)
        {
            ScriptBlock newAction = _context.Modules.CreateBoundScriptBlock(_context, scriptAction, true);

            // Create a new Error variable so that it doesn't pollute the global errors.
            PSVariable errorVariable = new PSVariable("script:Error", new ArrayList(), ScopedItemOptions.Constant);
            SessionStateInternal sessionState = newAction.SessionStateInternal;
            SessionStateScope scriptScope = sessionState.GetScopeByID("script");
            scriptScope.SetVariable(errorVariable.Name, errorVariable, false, true, sessionState, CommandOrigin.Internal);

            return newAction;
        }

        /// <summary>
        /// Get the identifier of this event subscription.
        /// </summary>
        public int SubscriptionId { get; set; }

        /// <summary>
        /// The object to which this event subscription applies.
        /// </summary>
        public object SourceObject { get; }

        /// <summary>
        /// The event object to which this event subscription applies.
        /// </summary>
        public string EventName { get; }

        /// <summary>
        /// The identifier that identifies the source of these events.
        /// </summary>
        public string SourceIdentifier { get; }

        /// <summary>
        /// The action invoked when this event arrives.
        /// </summary>
        public PSEventJob Action { get; }

        /// <summary>
        /// The delegate invoked when this event arrives.
        /// </summary>
        public PSEventReceivedEventHandler HandlerDelegate { get; } = null;

        /// <summary>
        /// Get the flag that marks this event as a supporting event.
        /// </summary>
        public bool SupportEvent { get; }

        /// <summary>
        /// Gets whether to forward the event to the PowerShell client during a remote execution.
        /// </summary>
        public bool ForwardEvent { get; }

        /// <summary>
        /// Gets whether the event should be processed in the pipeline execution thread.
        /// </summary>
        internal bool ShouldProcessInExecutionThread { get; set; }

        /// <summary>
        /// Gets whether the event should be unregistered.
        /// </summary>
        internal bool AutoUnregister { get; }

        /// <summary>
        /// Indicate how many new should be added to the action queue.
        /// e.g. NumberOfTimesToBeInvoked = 3 means that this subscriber only responses to
        /// the first triggered event. So three new actions will be added to the action
        /// queue, and the following events will be ignored.
        /// </summary>
        internal int RemainingTriggerCount { get; set; }

        /// <summary>
        /// Indicate how many actions from this subscriber should be processed.
        /// The method "ProcessPendingAction" will unsubscribe this subscriber
        /// if it's marked as auto-unregister and this property becomes 0.
        /// </summary>
        internal int RemainingActionsToProcess { get; set; }

        /// <summary>
        /// Indicate if the subscriber is being subscribed by a thread.
        /// </summary>
        internal bool IsBeingUnsubscribed { get; set; }

        /// <summary>
        /// The event generated when this event subscriber is unregistered.
        /// </summary>
        public event PSEventUnsubscribedEventHandler Unsubscribed;

        #region IComparable<PSEventSubscriber> Members

        /// <summary>
        /// Determines whether the specified object is equal to the current object.
        /// </summary>
        /// <param name="obj">The object to compare with the current object.</param>
        /// <returns>
        /// <see langword="true"/> if the specified object is equal to the current object;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return obj is PSEventSubscriber es && Equals(es);
        }
        
        /// <summary>
        /// Determines if two PSEventSubscriber instances are equal
        /// </summary>
        /// <param name="other">
        /// The PSEventSubscriber to which to compare this instance
        /// </param>
        public bool Equals(PSEventSubscriber other)
        {
            if (other == null)
            {
                return false;
            }

            return (string.Equals(SubscriptionId, other.SubscriptionId));
        }

        /// <summary>
        /// Gets the hashcode that represents this PSEventSubscriber instance.
        /// </summary>
        public override int GetHashCode()
        {
            return SubscriptionId;
        }
        #endregion

        internal void OnPSEventUnsubscribed(object sender, PSEventUnsubscribedEventArgs e)
        {
            Unsubscribed?.Invoke(sender, e);
        }
    }

    /// <summary>
    /// The generic event handler from which specific event handlers extend. When possible,
    /// add functionality to this class instead of the IL generated by the GenerateEventHandler()
    /// method.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
    public class PSEventHandler
    {
        /// <summary>
        /// Creates a new instance of the PsEventHandler class.
        /// </summary>
        public PSEventHandler()
        {
        }

        /// <summary>
        /// Creates a new instance of the PsEventHandler class for a given
        /// event manager, source identifier, and extra data
        /// </summary>
        /// <param name="eventManager">
        /// The event manager to which we forward events.
        /// </param>
        /// <param name="sender">
        /// The object that generated this event.
        /// </param>
        /// <param name="sourceIdentifier">
        /// An optional subscription identifier that identifies the
        /// source of the event
        /// </param>
        /// <param name="extraData">
        /// Any additional data you wish to attach to the event
        /// </param>
        public PSEventHandler(PSEventManager eventManager, object sender, string sourceIdentifier, PSObject extraData)
        {
            this.eventManager = eventManager;
            this.sender = sender;
            this.sourceIdentifier = sourceIdentifier;
            this.extraData = extraData;
        }

        /// <summary>
        /// The event manager to which we forward events.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        protected PSEventManager eventManager;

        /// <summary>
        /// The sender of the event.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        protected object sender;

        /// <summary>
        /// An optional identifier that identifies the source of the event.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        protected string sourceIdentifier = null;

        /// <summary>
        /// Any additional data you wish to attach to the event.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
        protected PSObject extraData = null;
    }

    /// <summary>
    /// Event argument associated with the event fired in a remote runspace and forwarded to the current runspace.
    /// </summary>
    public class ForwardedEventArgs : EventArgs
    {
        internal ForwardedEventArgs(PSObject serializedRemoteEventArgs)
        {
            SerializedRemoteEventArgs = serializedRemoteEventArgs;
        }

        /// <summary>
        /// Serialized event arguments from the event fired in a remote runspace.
        /// </summary>
        public PSObject SerializedRemoteEventArgs { get; }

        internal static bool IsRemoteSourceEventArgs(object argument)
        {
            return Deserializer.IsDeserializedInstanceOfType(argument, typeof(EventArgs));
        }
    }

    /// <summary>
    /// PowerShell event arguments
    /// This class provides a generic wrapper for event arguments.
    /// </summary>
    /// <typeparam name="T">event argument type</typeparam>
    internal class PSEventArgs<T> : EventArgs
    {
        /// <summary>
        /// Event arguments.
        /// </summary>
        internal T Args;

        /// <summary>
        /// Class constructor.
        /// </summary>
        /// <param name="args">Event arguments.</param>
        public PSEventArgs(T args)
        {
            Args = args;
        }
    }

    /// <summary>
    /// The event arguments associated with an event.
    /// </summary>
    public class PSEventArgs : EventArgs
    {
        /// <summary>
        /// Create a new instance of the PSEventArgs type.
        /// </summary>
        /// <param name="computerName">
        /// Computer on which this event was generated
        /// </param>
        /// <param name="runspaceId">
        /// Runspace on which this event was generated
        /// </param>
        /// <param name="eventIdentifier">
        /// The unique identifier of the event
        /// </param>
        /// <param name="sourceIdentifier">
        /// The source of the event
        /// </param>
        /// <param name="sender">
        /// The object that generated this event
        /// </param>
        /// <param name="originalArgs">
        /// The arguments associated with the handler of the original event.
        /// </param>
        /// <param name="additionalData">
        /// Additional data attached by the user to this event.
        /// </param>
        internal PSEventArgs(string computerName, Guid runspaceId, int eventIdentifier, string sourceIdentifier, object sender, object[] originalArgs, PSObject additionalData)
        {
            // Capture the first EventArgs as SourceEventArgs
            if (originalArgs != null)
            {
                foreach (object argument in originalArgs)
                {
                    EventArgs sourceEventArgs = argument as EventArgs;
                    if (sourceEventArgs != null)
                    {
                        SourceEventArgs = sourceEventArgs;
                        break;
                    }

                    if (ForwardedEventArgs.IsRemoteSourceEventArgs(argument))
                    {
                        SourceEventArgs = new ForwardedEventArgs((PSObject)argument);
                        break;
                    }
                }
            }

            ComputerName = computerName;
            RunspaceId = runspaceId;
            EventIdentifier = eventIdentifier;
            Sender = sender;
            SourceArgs = originalArgs;
            SourceIdentifier = sourceIdentifier;
            TimeGenerated = DateTime.Now;
            MessageData = additionalData;
            ForwardEvent = false;
        }

        /// <summary>
        /// Gets the name of the computer on which this event was generated; the value of this property is null for events generated on the local machine.
        /// </summary>
        public string ComputerName { get; internal set; }

        /// <summary>
        /// Gets the unique identifier of this event.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")]
        public Guid RunspaceId { get; internal set; }

        /// <summary>
        /// Gets the unique identifier of this event.
        /// </summary>
        public int EventIdentifier { get; internal set; }

        /// <summary>
        /// Gets the object that generated this event.
        /// </summary>
        public object Sender { get; }

        /// <summary>
        /// Gets the first argument from the original event source that
        /// derives from EventArgs.
        /// </summary>
        public EventArgs SourceEventArgs { get; }

        /// <summary>
        /// Gets the list of arguments captured by the original event source.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
        public object[] SourceArgs { get; }

        /// <summary>
        /// Gets the identifier associated with the source of this event.
        /// </summary>
        public string SourceIdentifier { get; }

        /// <summary>
        /// Gets the time and date that this event was generated.
        /// </summary>
        public DateTime TimeGenerated
        {
            // internal setter using during deserialization
            get; internal set;
        }

        /// <summary>
        /// Gets the additional user data associated with this event.
        /// </summary>
        public PSObject MessageData { get; }

        /// <summary>
        /// Gets whether to forward the event to the PowerShell client during a remote execution.
        /// </summary>
        internal bool ForwardEvent { get; set; }

        /// <summary>
        /// When processing synchronous events, this mutex is set so we know when we can safely return.
        /// </summary>
        internal ManualResetEventSlim EventProcessed { get; set; }
    }

    /// <summary>
    /// The delegate that handles notifications of new events
    /// added to the collection.
    /// </summary>
    public delegate void PSEventReceivedEventHandler(object sender, PSEventArgs e);

    /// <summary>
    /// The event arguments associated with unsubscribing from an event.
    /// </summary>
    public class PSEventUnsubscribedEventArgs : EventArgs
    {
        /// <summary>
        /// Create a new instance of the PSEventUnsubscribedEventArgs type.
        /// </summary>
        /// <param name="eventSubscriber">
        /// The event subscriber being unregistered
        /// </param>
        internal PSEventUnsubscribedEventArgs(PSEventSubscriber eventSubscriber)
        {
            EventSubscriber = eventSubscriber;
        }

        /// <summary>
        /// The event subscriber being unregistered.
        /// </summary>
        public PSEventSubscriber EventSubscriber { get; internal set; }
    }

    /// <summary>
    /// The delegate that handles notifications of the event being unsubscribed.
    /// </summary>
    public delegate void PSEventUnsubscribedEventHandler(object sender, PSEventUnsubscribedEventArgs e);

    /// <summary>
    /// This class contains the collection of events received by the
    /// execution context.
    /// </summary>
    public class PSEventArgsCollection : IEnumerable<PSEventArgs>
    {
        /// <summary>
        /// The event generated when a new event is received.
        /// </summary>
        public event PSEventReceivedEventHandler PSEventReceived;

        private readonly List<PSEventArgs> _eventCollection = new List<PSEventArgs>();

        /// <summary>
        /// Add add an event to the collection.
        /// </summary>
        /// <param name="eventToAdd">
        /// The PSEventArgs instance that represents this event
        /// </param>
        /// <remarks>Don't add events to the collection directly; use the EventManager instead</remarks>
        internal void Add(PSEventArgs eventToAdd)
        {
            ArgumentNullException.ThrowIfNull(eventToAdd);

            _eventCollection.Add(eventToAdd);

            OnPSEventReceived(eventToAdd.Sender, eventToAdd);
        }

        /// <summary>
        /// Removes an item at a specific index from the collection.
        /// </summary>
        public int Count
        {
            get
            {
                return _eventCollection.Count;
            }
        }

        /// <summary>
        /// Removes an item at a specific index from the collection.
        /// </summary>
        public void RemoveAt(int index)
        {
            _eventCollection.RemoveAt(index);
        }

        /// <summary>
        /// Gets an item at a specific index from the collection.
        /// </summary>
        public PSEventArgs this[int index]
        {
            get
            {
                return _eventCollection[index];
            }
        }

        private void OnPSEventReceived(object sender, PSEventArgs e)
        {
            PSEventReceived?.Invoke(sender, e);
        }

        /// <summary>
        /// Get the enumerator of this collection.
        /// </summary>
        public IEnumerator<PSEventArgs> GetEnumerator()
        {
            return _eventCollection.GetEnumerator();
        }

        /// <summary>
        /// Get the enumerator of this collection.
        /// </summary>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _eventCollection.GetEnumerator();
        }

        /// <summary>
        /// Get the synchronization root for this collection.
        /// </summary>
        public object SyncRoot { get; } = new object();
    }

    /// <summary>
    /// The combination of an event subscriber, and the event that was fired.
    /// This is to support the arguments to script blocks that we invoke automatically
    /// as a response to some events.
    /// </summary>
    internal class EventAction
    {
        public EventAction(PSEventSubscriber sender, PSEventArgs args)
        {
            Sender = sender;
            Args = args;
        }

        /// <summary>
        /// Get the sender of this event (the event subscriber)
        /// </summary>
        public PSEventSubscriber Sender { get; }

        /// <summary>
        /// Get the arguments of this event (the event that was fired)
        /// </summary>
        public PSEventArgs Args { get; }
    }

    /// <summary>
    /// A class to give a job-like interface to event actions.
    /// </summary>
    public class PSEventJob : Job
    {
        /// <summary>
        /// Creates a new instance of the PSEventJob class.
        /// </summary>
        /// <param name="eventManager">
        /// The event manager that controls the event subscriptions
        /// </param>
        /// <param name="subscriber">
        /// The subscriber that generates these actions
        /// </param>
        /// <param name="action">
        /// The script block invoked for this event
        /// </param>
        /// <param name="name">
        /// The name of the job
        /// </param>
        public PSEventJob(
            PSEventManager eventManager,
            PSEventSubscriber subscriber,
            ScriptBlock action,
            string name)
            : base(action?.ToString(), name)
        {
            ArgumentNullException.ThrowIfNull(eventManager);

            ArgumentNullException.ThrowIfNull(subscriber);

            UsesResultsCollection = true;
            ScriptBlock = action;
            _eventManager = eventManager;
            _subscriber = subscriber;
        }

        private readonly PSEventManager _eventManager = null;
        private readonly PSEventSubscriber _subscriber = null;
        private int _highestErrorIndex = 0;

        /// <summary>
        /// Gets dynamic module where the action is invoked.
        /// </summary>
        public PSModuleInfo Module
        {
            get { return ScriptBlock.Module; }
        }

        /// <summary>
        /// Stop Job.
        /// </summary>
        public override void StopJob()
        {
            _eventManager.UnsubscribeEvent(_subscriber);
        }

        /// <summary>
        /// Message indicating status of the job.
        /// </summary>
        public override string StatusMessage { get; } = null;

        /// <summary>
        /// Indicates if more data is available.
        /// </summary>
        /// <remarks>
        /// This has more data if any of the child jobs have more data.
        /// </remarks>
        public override bool HasMoreData
        {
            get
            {
                return _moreData;
            }
        }

        private bool _moreData = false;

        /// <summary>
        /// Location in which this job is running.
        /// </summary>
        public override string Location
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// The scriptblock that defines the action.
        /// </summary>
        internal ScriptBlock ScriptBlock { get; }

        /// <summary>
        /// Invoke the script block
        /// </summary>
        /// <param name="eventSubscriber">
        /// The subscriber that generated this event
        /// </param>
        /// <param name="eventArgs">
        /// The context of this event
        /// </param>
        internal void Invoke(PSEventSubscriber eventSubscriber, PSEventArgs eventArgs)
        {
            if (IsFinishedState(JobStateInfo.State))
                return;

            SetJobState(JobState.Running);

            // Prepare the automatic variables
            SessionState actionState = ScriptBlock.SessionStateInternal.PublicSessionState;

            // $psEventSubscriber = The subscriber
            // that generated this event
            actionState.PSVariable.Set("eventSubscriber", eventSubscriber);
            // $psEvent = The extended event information
            actionState.PSVariable.Set("event", eventArgs);

            // $sender = $psEvent.Sender
            actionState.PSVariable.Set("sender", eventArgs.Sender);
            // $eventArgs = $psEvent.SourceEventArgs
            actionState.PSVariable.Set("eventArgs", eventArgs.SourceEventArgs);

            List<object> results = new List<object>();

            // $args = $psEventArgs.SourceArgs (for PARAM statement)
            try
            {
                Pipe outputPipe = new Pipe(results);
                ScriptBlock.InvokeWithPipe(
                    useLocalScope: false,
                    errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,
                    dollarUnder: AutomationNull.Value,
                    input: AutomationNull.Value,
                    scriptThis: AutomationNull.Value,
                    outputPipe: outputPipe,
                    invocationInfo: null,
                    args: eventArgs.SourceArgs);
            }
            catch (Exception e)
            {
                // Catch-all OK. This is a third-party call-out.
                if (e is not PipelineStoppedException)
                {
                    LogErrorsAndOutput(results, actionState);
                    SetJobState(JobState.Failed);
                }

                throw;
            }
            /*
             * Not clearing the variables because of Win8: 738767 (Event actions no longer have persistent event variables)
             */

            LogErrorsAndOutput(results, actionState);
            _moreData = true;
        }

        internal void NotifyJobStopped()
        {
            SetJobState(JobState.Stopped);
            _moreData = false;
        }

        private void LogErrorsAndOutput(List<object> results, SessionState actionState)
        {
            // Add the output to the job
            for (int i = 0; i < results.Count; i++)
            {
                this.WriteObject(results[i]);
            }

            // And the errors
            Error.Clear();
            int currentErrorIndex = 0;
            var errors = (ArrayList)actionState.PSVariable.Get("error").Value;
            errors.Reverse();

            for (int i = 0; i < errors.Count; i++)
            {
                var error = (ErrorRecord)errors[i];
                if (currentErrorIndex == _highestErrorIndex)
                {
                    this.WriteError(error);
                    _highestErrorIndex++;
                }

                currentErrorIndex++;
            }
        }
    }
}
