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

using System;
using System.Management.Automation;

namespace Microsoft.PowerShell.Commands
{
    /// <summary>
    /// Base class for object-based event registration.
    /// </summary>
    public abstract class ObjectEventRegistrationBase : PSCmdlet
    {
        #region parameters

        /// <summary>
        /// Parameter for an identifier for this event subscription.
        /// </summary>
        [Parameter(Position = 100)]
        public string SourceIdentifier
        {
            get
            {
                return _sourceIdentifier;
            }

            set
            {
                _sourceIdentifier = value;
            }
        }

        private string _sourceIdentifier = Guid.NewGuid().ToString();

        /// <summary>
        /// Parameter for any action to be invoked when the event arrives.
        /// </summary>
        [Parameter(Position = 101)]
        public ScriptBlock Action
        {
            get
            {
                return _action;
            }

            set
            {
                _action = value;
            }
        }

        private ScriptBlock _action = null;

        /// <summary>
        /// Parameter for additional data to be associated with this event subscription.
        /// </summary>
        [Parameter]
        public PSObject MessageData
        {
            get
            {
                return _messageData;
            }

            set
            {
                _messageData = value;
            }
        }

        private PSObject _messageData = null;

        /// <summary>
        /// Parameter for the flag that determines if this subscription is used to support
        /// other subscriptions.
        /// </summary>
        [Parameter]
        public SwitchParameter SupportEvent
        {
            get
            {
                return _supportEvent;
            }

            set
            {
                _supportEvent = value;
            }
        }

        private SwitchParameter _supportEvent = new SwitchParameter();

        /// <summary>
        /// Parameter for the flag that determines whether this
        /// subscription will forward its events to the PowerShell client during remote executions.
        /// </summary>
        [Parameter]
        public SwitchParameter Forward
        {
            get
            {
                return _forward;
            }

            set
            {
                _forward = value;
            }
        }

        private SwitchParameter _forward = new SwitchParameter();

        /// <summary>
        /// Parameter to indicate that the subscriber should be auto-unregistered after being triggered for specified times.
        /// 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.
        /// </summary>
        [Parameter]
        public int MaxTriggerCount
        {
            get
            {
                return _maxTriggerCount;
            }

            set
            {
                _maxTriggerCount = value <= 0 ? 0 : value;
            }
        }

        private int _maxTriggerCount = 0;

        #endregion parameters

        /// <summary>
        /// Returns the object that generates events to be monitored.
        /// </summary>
        protected abstract object GetSourceObject();

        /// <summary>
        /// Returns the event name to be monitored on the input object.
        /// </summary>
        protected abstract string GetSourceObjectEventName();

        /// <summary>
        /// Gets the subscriber generated by this command.
        /// </summary>
        protected PSEventSubscriber NewSubscriber
        {
            get { return _newSubscriber; }
        }

        private PSEventSubscriber _newSubscriber;

        /// <summary>
        /// Check arguments.
        /// </summary>
        protected override void BeginProcessing()
        {
            if (((bool)_forward) && (_action != null))
            {
                ThrowTerminatingError(
                    new ErrorRecord(
                        new ArgumentException(EventingResources.ActionAndForwardNotSupported),
                        "ACTION_AND_FORWARD_NOT_SUPPORTED",
                        ErrorCategory.InvalidOperation,
                        null));
            }
        }

        /// <summary>
        /// Subscribe to the event on the object.
        /// </summary>
        protected override void EndProcessing()
        {
            object inputObject = PSObject.Base(GetSourceObject());
            string eventName = GetSourceObjectEventName();

            try
            {
                if (
                    ((inputObject != null) || (eventName != null)) &&
                    (Events.GetEventSubscribers(_sourceIdentifier).GetEnumerator().MoveNext())
                    )
                {
                    // Detect if the event identifier already exists
                    ErrorRecord errorRecord = new ErrorRecord(
                        new ArgumentException(
                            string.Format(
                                System.Globalization.CultureInfo.CurrentCulture,
                                EventingResources.SubscriberExists, _sourceIdentifier)),
                        "SUBSCRIBER_EXISTS",
                        ErrorCategory.InvalidArgument,
                        inputObject);

                    WriteError(errorRecord);
                }
                else
                {
                    _newSubscriber =
                        Events.SubscribeEvent(
                            inputObject,
                            eventName,
                            _sourceIdentifier, _messageData, _action, (bool)_supportEvent, (bool)_forward, _maxTriggerCount);

                    if ((_action != null) && (!(bool)_supportEvent))
                        WriteObject(_newSubscriber.Action);
                }
            }
            catch (ArgumentException e)
            {
                ErrorRecord errorRecord = new ErrorRecord(
                    e,
                    "INVALID_REGISTRATION",
                    ErrorCategory.InvalidArgument,
                    inputObject);

                WriteError(errorRecord);
            }
            catch (InvalidOperationException e)
            {
                ErrorRecord errorRecord = new ErrorRecord(
                    e,
                    "INVALID_REGISTRATION",
                    ErrorCategory.InvalidOperation,
                    inputObject);

                WriteError(errorRecord);
            }
        }
    }
}
