using CareFusion.Lib.StorageSystem.EventArguments;
using CareFusion.Lib.StorageSystem.Helpers.Enums;
using CareFusion.Lib.StorageSystem.Input;
using CareFusion.Lib.StorageSystem.Logging;
using CareFusion.Lib.StorageSystem.Net;
using CareFusion.Lib.StorageSystem.Output;
using CareFusion.Lib.StorageSystem.State;
using CareFusion.Lib.StorageSystem.Stock;
using CareFusion.Lib.StorageSystem.Wwks2;
using CareFusion.Lib.StorageSystem.Wwks2.Messages;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.ArticleInformation;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Configuration;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Hello;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Input;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.KeepAlive;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Output;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.OutputDestination;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Status;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Stock;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Task;
using CareFusion.Lib.StorageSystem.Wwks2.Messages.Unprocessed;
using CareFusion.Lib.StorageSystem.Wwks2.Types;
using Rowa.Lib.Security.Net;
using Rowa.Mosaic.Shared.Converters;
using StorageSystem.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Xml.Serialization;
using UnprocessedMessage = CareFusion.Lib.StorageSystem.Wwks2.Messages.Unprocessed.UnprocessedMessage;

namespace CareFusion.Lib.StorageSystem
{
    /// <summary>
    /// The storage system implementation for a CareFusion | Rowa system.
    /// </summary>
    public class RowaStorageSystem : AsyncEventProvider, IStorageSystem
    {
        #region Constants

        /// <summary>
        /// The subscriber identifier to use on WWKS 2.0 protocol level.
        /// </summary>
        private const int DefaultSubscriberID = 100;

        /// <summary>
        /// The state check default interval in milliseconds.
        /// </summary>
        private const int DefaultStateCheckInterval = 15000;

        /// <summary>
        /// The connect timeout in milliseconds.
        /// </summary>
        private const int ConnectTimeout = 30000;

        /// <summary>
        /// The protocol handshake timeout in seconds.
        /// </summary>
        private const int HandshakeTimeout = 10;

        /// <summary>
        /// The message timeout in milliseconds.
        /// </summary>
        private const int MessageTimeout = 10000;

        #endregion

        #region Members

        /// <summary>
        /// The tcp client of the active storage system connection. 
        /// </summary>
        private TcpClient _client;

        /// <summary>
        /// The object stream which is used to read and write messages.
        /// </summary>
        private XmlObjectStream _messageObjectStream;

        /// <summary>
        /// The subscriber identifier of the storage system which is retrieved during handshake.
        /// </summary>
        private int _destinationId;

        /// <summary>
        /// The tenant identifier to use during WWKS handshake.
        /// </summary>
        private readonly string _tenantId = null;

        /// <summary>
        /// Holds the capabilities of the connected remote host.
        /// </summary>
        private Capability[] _destinationCapabilities;

        /// <summary>
        /// Event which is used to shutdown the background connection check thread.
        /// </summary>
        private ManualResetEvent _shutdownThreadEvent;

        /// <summary>
        /// The message dispatcher instance which reads and dispatches the messages of the active storage system connection.
        /// </summary>
        private readonly MessageDispatcher _messageDispatcher = new MessageDispatcher();

        /// <summary>
        /// Flag whether automatic state observation is enabled.
        /// </summary>
        private bool _enableAutomaticStateObservation = true;

        /// <summary>
        /// The references to the internal event handles which are used for event registration.
        /// </summary>
        private StateChangedEventHandler _stateChanged;
        private Delegates.StateChanged.EventHandler<StateChangedEventArgs> _stateChangedExtended;

        private ComponentStateChangedEventHandler _componentStateChanged;
        private Delegates.ComponentStateChanged.EventHandler<ComponentStateChangedEventArgs> _componentStateChangedExtended;

        private PackDispensedEventHandler _packDispensed;
        private Delegates.PackDispensed.EventHandler<PackDispensedEventArgs> _packDispensedExtended;

        private PackInputRequestEventHandler _packInputRequested;
        private Delegates.PackInputRequested.EventHandler<PackInputRequestedEventArgs> _packInputRequestedExtended;

        private PackStoredEventHandler _packStored;
        private PackStoredEventHandlerEx _packStoredEx;
        private Delegates.PackStored.EventHandler<PackStoredEventArgs> _packStoredExtended;

        private PackInputFinished _packInputFinished;
        private PackInputFinishedEx _packInputFinishedEx;
        private Delegates.PackInputFinished.EventHandler<PackInputFinishedEventArgs> _packInputFinishedExtended;

        private StockUpdatedEventHandler _stockUpdated;
        private Delegates.StockUpdated.EventHandle<StockUpdatedEventArgs> _stockUpdatedExtended;

        private OutputProcessFinishedEventHandler _outputProcessFinished;
        private Delegates.OutputProcessFinished.EventHandler<OutputProcessFinishedEventArgs> _outputProcessFinishedExtended;

        private StorageSystemArticleInfoRequestEventHandler _articleInfoRequested;
        private Delegates.ArticleInfoRequested.EventHandler<ArticleInfoRequestedEventArgs> _articleInfoRequestedExtended;

        private OutputDestinationButtonPressedEventHandler _outputDestinationButtonPressed;

        /// <summary>
        /// Thread synchronization object.
        /// </summary>
        private readonly object _syncLock = new object();

        /// <summary>
        /// Flag whether the WWKS 2.0 related XML serializers were (pre)initialized.
        /// </summary>
        private static bool _serializerInitialized = false;

        #endregion

        #region Properties

        /// <summary>
        /// Gets a flag whether the storage system is currently connected.
        /// </summary>
        public bool Connected
        {
            get
            {
                lock (_syncLock)
                {
                    return (_client == null) ? false : _client.Connected;
                }
            }
        }

        /// <summary>
        /// Gets the subscriber id of the storage system.
        /// </summary>
        public int SubscriberID
        {
            get;

            private set;
        }


        /// <summary>
        /// Gets the current state of the storage system.
        /// </summary>
        public ComponentState State
        {
            get
            {
                if (this.Connected == false)
                {
                    this.Trace($"Storage System current state: {ComponentState.NotConnected}");
                    return ComponentState.NotConnected;
                }

                var request = new StatusRequestEnvelope()
                {
                    StatusRequest = new StatusRequest()
                    {
                        Id = MessageId.Next,
                        Destination = _destinationId,
                        Source = SubscriberID,
                    }
                };

                this.Trace($"Sending StatusRequest to Storage System and waiting for a StatusResponse.");
                var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                        request.StatusRequest.Id,
                                                                        typeof(StatusResponse),
                                                                        MessageTimeout);

                if (message == null)
                {
                    this.Trace($"Storage System current state: {ComponentState.NotConnected}");
                    return ComponentState.NotConnected;
                }

                var statusResponse = (StatusResponse)message;

                this.Trace(
                    $"Storage System current state: {TypeConverter.ConvertEnum(statusResponse.State, ComponentState.NotReady)}");
                return TypeConverter.ConvertEnum(statusResponse.State, ComponentState.NotReady);
            }
        }

        /// <summary>
        /// Gets a list of all components of the storage system with their according states.
        /// </summary>
        public IComponent[] ComponentStates
        {
            get
            {
                if (this.Connected == false)
                {
                    return new IComponent[0];
                }

                var request = new StatusRequestEnvelope()
                {
                    StatusRequest = new StatusRequest()
                    {
                        Id = MessageId.Next,
                        Destination = _destinationId,
                        Source = SubscriberID,
                        IncludeDetails = "True"
                    }
                };

                this.Trace($"Sending StatusRequest to Storage System and waiting for a StatusResponse.");
                var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                        request.StatusRequest.Id,
                                                                        typeof(StatusResponse),
                                                                        MessageTimeout);

                if (message == null)
                {
                    return new IComponent[0];
                }

                var statusResponse = (StatusResponse)message;

                return (statusResponse.Component != null) ? statusResponse.Component : new IComponent[0];
            }
        }

        /// <summary>
        /// Gets the arbitrary configuration content of the storage system.
        /// </summary>
        public string Configuration
        {
            get
            {
                if (this.Connected == false)
                {
                    return string.Empty;
                }

                bool isConfigurationSupported = false;

                if (_destinationCapabilities != null)
                {
                    foreach (var capability in _destinationCapabilities)
                    {
                        if (string.Compare(capability.Name, "Configuration", true) == 0)
                        {
                            isConfigurationSupported = true;
                        }
                    }
                }
                else
                {
                    isConfigurationSupported = true;
                }

                if (isConfigurationSupported == false)
                {
                    return string.Empty;
                }

                var request = new ConfigurationGetRequestEnvelope()
                {
                    ConfigurationGetRequest = new ConfigurationGetRequest()
                    {
                        Id = MessageId.Next,
                        Destination = _destinationId,
                        Source = SubscriberID,
                    }
                };

                var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                        request.ConfigurationGetRequest.Id,
                                                                        typeof(ConfigurationGetResponse),
                                                                        MessageTimeout);

                if (message == null)
                {
                    return string.Empty;
                }

                var configurationResponse = (ConfigurationGetResponse)message;
                return configurationResponse.RawContent;
            }
        }

        /// <summary>
        /// Gets a list of all stock locations that are supported by the storage system.
        /// </summary>
        public IStockLocation[] StockLocations
        {
            get
            {
                if (this.Connected == false)
                {
                    return new IStockLocation[0];
                }

                var request = new StockLocationInfoRequestEnvelope()
                {
                    StockLocationInfoRequest = new StockLocationInfoRequest()
                    {
                        Id = MessageId.Next,
                        Destination = _destinationId,
                        Source = SubscriberID,
                    }
                };

                var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                        request.StockLocationInfoRequest.Id,
                                                                        typeof(StockLocationInfoResponse),
                                                                        MessageTimeout);

                if (message == null)
                {
                    return new IStockLocation[0];
                }

                var response = (StockLocationInfoResponse)message;

                return (response.StockLocation != null) ? response.StockLocation : new IStockLocation[0];
            }
        }

        /// <summary>
        /// Enables or disables the automatic state observation.
        /// If it is enabled, the state of the storage system is requested regularly
        /// and in case of state changes the event "StateChanged" is raised.
        /// Automatic state observation is enabled by default.
        /// </summary>
        public bool EnableAutomaticStateObservation
        {
            get { lock (_syncLock) { return _enableAutomaticStateObservation; } }
            set { lock (_syncLock) { _enableAutomaticStateObservation = value; } }
        }

        /// <summary>
        /// The interval for requesting the state of the storage system in milliseconds.
        /// </summary>
        public int StateCheckInterval { get; set; } = DefaultStateCheckInterval;

        #endregion

        #region Events

        /// <summary>
        /// Event which is raised when the state of a storage system has changed.
        /// </summary>
        public event StateChangedEventHandler StateChanged
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StateChanged as been registered for '{0}'", value.Method.Name);
                    _stateChanged += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StateChanged as been unregistered for '{0}'", value.Method.Name);
                    _stateChanged -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when the state of a storage system has changed.
        /// </summary>
        public event Delegates.StateChanged.EventHandler<StateChangedEventArgs> StateChangedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StateChanged as been registered for '{0}'", value.Method.Name);
                    _stateChangedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StateChanged as been unregistered for '{0}'", value.Method.Name);
                    _stateChangedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when the state of a storage system component has changed.
        /// </summary>
        public event ComponentStateChangedEventHandler ComponentStateChanged
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ComponentStateChanged as been registered for '{0}'", value.Method.Name);
                    _componentStateChanged += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ComponentStateChanged as been unregistered for '{0}'", value.Method.Name);
                    _componentStateChanged -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when the state of a storage system component has changed.
        /// </summary>
        public event Delegates.ComponentStateChanged.EventHandler<ComponentStateChangedEventArgs> ComponentStateChangedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ComponentStateChanged as been registered for '{0}'", value.Method.Name);
                    _componentStateChangedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ComponentStateChanged as been unregistered for '{0}'", value.Method.Name);
                    _componentStateChangedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when a pack was dispensed by an output
        /// process that was not initiated by this storage system connection (e.g. at the UI of the storage system).
        /// </summary>
        public event PackDispensedEventHandler PackDispensed
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackDispensed as been registered for '{0}'", value.Method.Name);
                    _packDispensed += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackDispensed as been unregistered for '{0}'", value.Method.Name);
                    _packDispensed -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when a pack was dispensed by an output
        /// process that was not initiated by this storage system connection (e.g. at the UI of the storage system).
        /// </summary>
        public event Delegates.PackDispensed.EventHandler<PackDispensedEventArgs> PackDispensedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackDispensed as been registered for '{0}'", value.Method.Name);
                    _packDispensedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackDispensed as been unregistered for '{0}'", value.Method.Name);
                    _packDispensedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a connected storage system requests permission for pack input.
        /// </summary>
        public event PackInputRequestEventHandler PackInputRequested
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputRequested as been registered for '{0}'", value.Method.Name);
                    _packInputRequested += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputRequested as been unregistered for '{0}'", value.Method.Name);
                    _packInputRequested -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a connected storage system requests permission for pack input.
        /// </summary>
        public event Delegates.PackInputRequested.EventHandler<PackInputRequestedEventArgs> PackInputRequestedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputRequested as been registered for '{0}'", value.Method.Name);
                    _packInputRequestedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputRequested as been unregistered for '{0}'", value.Method.Name);
                    _packInputRequestedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a new pack was successfully stored in a storage system. 
        /// </summary>
        public event PackStoredEventHandler PackStored
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been registered for '{0}'", value.Method.Name);
                    _packStored += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been unregistered for '{0}'", value.Method.Name);
                    _packStored -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a new pack was successfully stored in a storage system. 
        /// </summary>
        public event PackStoredEventHandlerEx PackStoredEx
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been registered for '{0}'", value.Method.Name);
                    _packStoredEx += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been unregistered for '{0}'", value.Method.Name);
                    _packStoredEx -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a new pack was successfully stored in a storage system. 
        /// </summary>
        public event Delegates.PackStored.EventHandler<PackStoredEventArgs> PackStoredExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been registered for '{0}'", value.Method.Name);
                    _packStoredExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackStored as been unregistered for '{0}'", value.Method.Name);
                    _packStoredExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a connected storage system finished an input.
        /// </summary>
        public event PackInputFinished PackInputFinished
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been registered for '{0}'", value.Method.Name);
                    _packInputFinished += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been unregistered for '{0}'", value.Method.Name);
                    _packInputFinished -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a connected storage system finished an input.
        /// </summary>
        public event PackInputFinishedEx PackInputFinishedEx
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been registered for '{0}'", value.Method.Name);
                    _packInputFinishedEx += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been unregistered for '{0}'", value.Method.Name);
                    _packInputFinishedEx -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a connected storage system finished an input.
        /// </summary>
        public event Delegates.PackInputFinished.EventHandler<PackInputFinishedEventArgs> PackInputFinishedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been registered for '{0}'", value.Method.Name);
                    _packInputFinishedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for PackInputFinished as been unregistered for '{0}'", value.Method.Name);
                    _packInputFinishedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever the stock of the storage system has been updated.
        /// </summary>
        public event StockUpdatedEventHandler StockUpdated
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StockUpdated as been registered for '{0}'", value.Method.Name);
                    _stockUpdated += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StockUpdated as been unregistered for '{0}'", value.Method.Name);
                    _stockUpdated -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever the stock of the storage system has been updated.
        /// </summary>
        public event Delegates.StockUpdated.EventHandle<StockUpdatedEventArgs> StockUpdatedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StockUpdated as been registered for '{0}'", value.Method.Name);
                    _stockUpdatedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for StockUpdated as been unregistered for '{0}'", value.Method.Name);
                    _stockUpdatedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever an output process finished processing.
        /// </summary>
        public event OutputProcessFinishedEventHandler OutputProcessFinished
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputProcessFinished as been registered for '{0}'", value.Method.Name);
                    _outputProcessFinished += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputProcessFinished as been unregistered for '{0}'", value.Method.Name);
                    _outputProcessFinished -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever an output process finished processing.
        /// </summary>
        public event Delegates.OutputProcessFinished.EventHandler<OutputProcessFinishedEventArgs> OutputProcessFinishedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputProcessFinished as been registered for '{0}'", value.Method.Name);
                    _outputProcessFinishedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputProcessFinished as been unregistered for '{0}'", value.Method.Name);
                    _outputProcessFinishedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when detailed information for one or more articles is requested.
        /// </summary>
        public event StorageSystemArticleInfoRequestEventHandler ArticleInfoRequested
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ArticleInfoRequested as been registered for '{0}'", value.Method.Name);
                    _articleInfoRequested += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ArticleInfoRequested as been unregistered for '{0}'", value.Method.Name);
                    _articleInfoRequested -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised when detailed information for one or more articles is requested.
        /// </summary>
        public event Delegates.ArticleInfoRequested.EventHandler<ArticleInfoRequestedEventArgs> ArticleInfoRequestedExtended
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ArticleInfoRequested as been registered for '{0}'", value.Method.Name);
                    _articleInfoRequestedExtended += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for ArticleInfoRequested as been unregistered for '{0}'", value.Method.Name);
                    _articleInfoRequestedExtended -= value;
                }
            }
        }

        /// <summary>
        /// Event which is raised whenever a button is pressed.
        /// </summary>
        public event OutputDestinationButtonPressedEventHandler OutputDestinationButtonPressed
        {
            add
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputDestinationButtonPressed as been registered for '{0}'", value.Method.Name);
                    _outputDestinationButtonPressed += value;
                }
            }
            remove
            {
                lock (_syncLock)
                {
                    this.Info("Eventhandler for OutputDestinationButtonPressed as been unregistered for '{0}'", value.Method.Name);
                    _outputDestinationButtonPressed -= value;
                }
            }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Initializes a new instance of the <see cref="RowaStorageSystem"/> class.
        /// </summary>
        /// <param name="stateCheckInterval">The interval for requesting the state of the storage system in milliseconds.</param>
        /// <param name="subscriberID">Subscriber Id used for connecting to the storage system.</param>
        public RowaStorageSystem(
            int subscriberID = DefaultSubscriberID, 
            int stateCheckInterval = DefaultStateCheckInterval)
        {
            LogManagerProxy.Initialize();
            LogVersion();
            InitializeXmlSerializer();
            _messageDispatcher.MessageArrived += OnMessageArrived;
            _messageDispatcher.MessageStreamDown += OnMessageStreamDown;

            StateCheckInterval = stateCheckInterval;
            SubscriberID = subscriberID;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RowaStorageSystem"/> class.
        /// </summary>
        /// <param name="tenantId">The tenant identifier to use during WWKS handshake.</param>
        /// <param name="stateCheckInterval">The interval for requesting the state of the storage system in milliseconds.</param>
        /// <param name="subscriberID">Subscriber Id used for connecting to the storage system.</param>
        public RowaStorageSystem(
            string tenantId,
            int subscriberID = DefaultSubscriberID,
            int stateCheckInterval = DefaultStateCheckInterval)
            : this(subscriberID, stateCheckInterval)
        {
            if (string.IsNullOrEmpty(tenantId))
            {
                throw new ArgumentException("Invalid tenantId specified.");
            }

            _tenantId = tenantId;
        }

        /// <inheritdoc/>
        public void Connect(string host, ushort port = 6050, EncryptionOptions encryptionOptions = null)
        {
            this.Connect(host, port, this.GetAvailableCapabilities(), encryptionOptions);
        }

        /// <inheritdoc/>
        public void Connect(
            string host,
            ushort port,
            List<Capability> helloRequestCapabilities,
            EncryptionOptions encryptionOptions = null)
        {
            this.ProcessConnectionRequest(host, port, helloRequestCapabilities.ToArray(), encryptionOptions);
        }

        /// <summary>
        /// Performs a graceful shutdown of the storage system connection. 
        /// </summary>
        public void Disconnect()
        {
            lock (_syncLock)
            {
                if (_messageObjectStream == null)
                {
                    return;
                }

                this.Trace("Closing connection to storage system.");

                base.ResetEventQueue();

                if ((_shutdownThreadEvent != null) &&
                    (_shutdownThreadEvent.SafeWaitHandle.IsClosed == false))
                {
                    _shutdownThreadEvent.Set();
                }

                _shutdownThreadEvent = null;

                _messageDispatcher.Stop();
                _messageObjectStream.Dispose();
                _messageObjectStream = null;

                if (_client.Connected)
                {
                    try
                    {
                        this.Trace("Shutting down the connected TCP client");
                        _client.Client.Shutdown(SocketShutdown.Both);
                        this.Trace("The TCP client is shutdown");
                    }
                    catch (Exception ex)
                    {
                        this.Error("Shutdown of the connected TCP client failed!", ex);
                    }
                }

                _client.Close();
                _client = null;
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// 

        protected override void Dispose(bool isDisposing)
        {
            if (_isDisposed)
            {
                return;
            }

            base.Dispose(isDisposing);

            if (isDisposing)
            {
                Disconnect();

                _messageDispatcher.MessageStreamDown -= OnMessageStreamDown;
                _messageDispatcher.MessageArrived -= OnMessageArrived;
                _messageDispatcher.Dispose();
            }

            LogManagerProxy.Cleanup();
        }

        /// <summary>
        /// Gets the complete stock of the connected storage system. 
        /// </summary>
        /// <param name="includePackDetails">
        /// Flag whether the returned article list should also include detailed pack information.
        /// </param>
        /// <param name="includeArticleDetails">
        /// Flag whether the returned article list should also contain detailed article information 
        /// like name, dosage form, packaging unit, etc.
        /// </param>
        /// <returns>List of articles and packs which are currently stored in the storage system.</returns>
        public List<IArticle> GetStock(bool includePackDetails = true,
                                       bool includeArticleDetails = false)
        {
            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new StockInfoRequestEnvelope()
            {
                StockInfoRequest = new StockInfoRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                    IncludePacks = includePackDetails.ToString(),
                    IncludeArticleDetails = includeArticleDetails.ToString()
                }
            };

            var message = (StockInfoResponse)_messageDispatcher.SendAndWaitForResponse(request,
                                                                                       request.StockInfoRequest.Id,
                                                                                       typeof(StockInfoResponse), 20 * MessageTimeout);

            if (message == null)
            {
                throw new ApplicationException("Waiting for the message 'StockInfoResponse' failed.");
            }

            return (message.Article != null) ? new List<IArticle>(message.Article) : new List<IArticle>();
        }

        /// <summary>
        /// Gets the stock which matches the specified filter criteria from the connected storage system.
        /// </summary>
        /// <param name="includePackDetails">
        /// Flag whether the returned article list should also include detailed pack information.
        /// </param>
        /// <param name="includeArticleDetails">
        /// Flag whether the returned article list should also contain detailed article information 
        /// like name, dosage form, packaging unit, etc.
        /// </param>
        /// <param name="articleId">
        /// If this parameter is not null, the requested stock information is filtered by the specified article identifier.
        /// </param>
        /// <param name="batchNumber">
        /// If this parameter is not null, the requested stock information is filtered by the specified batch number.
        /// </param>
        /// <param name="externalId">
        /// If this parameter is not null, the requested stock information is filtered by the specified external pack identifier.
        /// </param>
        /// <returns>
        /// List of articles and packs which are currently stored in the storage system and matches the specified filter.
        /// </returns>
        public List<IArticle> GetStock(bool includePackDetails,
                                       bool includeArticleDetails,
                                       string articleId,
                                       string batchNumber = null,
                                       string externalId = null,
                                       string stockLocationId = null,
                                       string machineLocation = null)
        {
            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new StockInfoRequestEnvelope()
            {
                StockInfoRequest = new StockInfoRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                    IncludePacks = includePackDetails.ToString(),
                    IncludeArticleDetails = includeArticleDetails.ToString()
                }
            };

            if ((string.IsNullOrEmpty(articleId) == false) ||
                (string.IsNullOrEmpty(batchNumber) == false) ||
                (string.IsNullOrEmpty(externalId) == false) ||
                (string.IsNullOrEmpty(stockLocationId) == false) ||
                (string.IsNullOrEmpty(machineLocation) == false))
            {
                var criteria = Criteria.Create(articleId: articleId,
                    batchNumber: batchNumber,
                    externalId: externalId,
                    stockLocationId: stockLocationId,
                    machineLocation: machineLocation);

                request.StockInfoRequest.Criteria = new Criteria[] { criteria };

            }
            var message = (StockInfoResponse)_messageDispatcher.SendAndWaitForResponse(request,
                                                                                       request.StockInfoRequest.Id,
                                                                                       typeof(StockInfoResponse), 3 * MessageTimeout);

            if (message == null)
            {
                throw new ApplicationException("Waiting for the message 'StockInfoResponse' failed.");
            }

            return (message.Article != null) ? new List<IArticle>(message.Article) : new List<IArticle>();
        }

        /// <summary>
        /// Creates a new object instance of a master article.
        /// Master articles are used by the storage system to make self-sufficient decisions 
        /// about which articles are allowed to be stored in the storage system. If the storage
        /// system is configured to use its master article list, it will not send InputRequest
        /// for every scanned pack. Instead it will decide on its own whether to input a pack.
        /// </summary>
        /// <param name="articleId">The unique identifier of the article (e.g. PZN or EAN).</param>
        /// <param name="articleName">The name of the article.</param>
        /// <param name="dosageForm">The dosage form of the article.</param>
        /// <param name="packagingUnit">The packaging unit of the article.</param>
        /// <param name="requiresFridge">The flag whether packs of this article have to be stored in a refrigerator.</param>
        /// <param name="maxSubItemQuantity">
        /// The optional maximum number of elements (e.g. pills or ampoules) which are in one pack of this article.
        /// A value of 0 means "no maximum defined".
        /// </param>
        /// <param name="stockLocationId">Optional stock location to use for packs of this article.</param>
        /// <param name="machineLocation">Optional machine location to use for packs of this article.</param> 
        /// <param name="virtualArticleId">Optionnal: Unique identifier of the virtual article.</param>
        /// <param name="virtualArticleName">Optionnal: Name of the virtual article.</param>
        /// <returns>Newly created master article object instance.</returns>
        public IMasterArticle CreateMasterArticle(string articleId,
            string articleName,
            string dosageForm,
            string packagingUnit,
            bool requiresFridge = false,
            uint maxSubItemQuantity = 0,
            string stockLocationId = null,
            string machineLocation = null,
            string virtualArticleId = null,
            string virtualArticleName = null,
            bool serialTracking = false,
            bool batchTracking = false,
            bool expiryTracking = false,
            List<string> productCodes = null)
        {
            if (string.IsNullOrEmpty(articleId))
            {
                throw new ArgumentException("Invalid articleId specified.");
            }

            if (string.IsNullOrEmpty(articleName))
            {
                throw new ArgumentException("Invalid articleName specified.");
            }

            return new Article
            {
                Id = TextConverter.EscapeInvalidXmlChars(articleId),
                Name = TextConverter.EscapeInvalidXmlChars(articleName),
                DosageForm = string.IsNullOrEmpty(dosageForm) ? string.Empty : TextConverter.EscapeInvalidXmlChars(dosageForm),
                PackagingUnit = string.IsNullOrEmpty(packagingUnit) ? string.Empty : TextConverter.EscapeInvalidXmlChars(packagingUnit),
                RequiresFridge = requiresFridge.ToString(),
                MaxSubItemQuantity = maxSubItemQuantity.ToString(),
                StockLocationId = TextConverter.EscapeInvalidXmlChars(stockLocationId),
                MachineLocation = TextConverter.EscapeInvalidXmlChars(machineLocation),
                VirtualId = TextConverter.EscapeInvalidXmlChars(virtualArticleId),
                VirtualName = TextConverter.EscapeInvalidXmlChars(virtualArticleName),
                SerialTracking = serialTracking.ToString(),
                BatchTracking = batchTracking.ToString(),
                ExpiryTracking = expiryTracking.ToString(),
                ProductCodes = productCodes?.Select(p => new ProductCode { Code = p }).ToList()
            };
        }

        /// <summary>
        /// Updates the list of active master articles which are used by the storage system.
        /// </summary>
        /// <param name="masterArticleList">
        /// The new list of master articles to set. 
        /// If the list is empty, the master article list of the storage system is cleared.
        /// </param>
        public void UpdateMasterArticles(List<IMasterArticle> masterArticleList)
        {
            if (masterArticleList == null)
            {
                throw new ArgumentException("Invalid masterArticleList specified.");
            }

            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new ArticleMasterSetRequestEnvelope()
            {
                ArticleMasterSetRequest = new ArticleMasterSetRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                }
            };

            if (masterArticleList.Count > 0)
            {
                request.ArticleMasterSetRequest.Article = new Article[masterArticleList.Count];

                for (int i = 0; i < masterArticleList.Count; ++i)
                {
                    request.ArticleMasterSetRequest.Article[i] = (Article)masterArticleList[i];
                }
            }

            var message = (ArticleMasterSetResponse)_messageDispatcher.SendAndWaitForResponse(request,
                                                                                              request.ArticleMasterSetRequest.Id,
                                                                                              typeof(ArticleMasterSetResponse), MessageTimeout);

            if (message == null)
            {
                throw new ApplicationException("Waiting for the message 'ArticleMasterSetResponse' failed.");
            }

            if ((message.SetResult != null) &&
                (message.SetResult.Value != SetResultValue.Accepted))
            {
                var errorMessage = string.Format("The specified master articles were rejected by the storage " +
                                                 "system with the reason '{0}'.", TextConverter.UnescapeInvalidXmlChars(message.SetResult.Text));

                throw new ApplicationException(errorMessage);
            }
        }

        /// <summary>
        /// Creates a new object instance of a stock delivery.
        /// Pre-defined stock deliveries are used by the storage system to make self-sufficient decisions 
        /// about which stock deliveries are allowed to be started at the storage system and which
        /// articles are allowed to be stored in combination with a specific stock delivery. 
        /// If the storage system is configured to use pre-defined stock deliveries, it will not 
        /// send InputRequest for every scanned pack that belongs to a stock delivery. 
        /// Instead it will decide on its own whether to input a pack.
        /// </summary>
        /// <param name="deliveryNumber">The delivery number of the stock delivery.</param>
        /// <returns>Newly created stock delivery object instance.</returns>
        public IStockDelivery CreateStockDelivery(string deliveryNumber)
        {
            if (string.IsNullOrEmpty(deliveryNumber))
            {
                throw new ArgumentException("Invalid deliveryNumber specified.");
            }

            return new StockDelivery()
            {
                DeliveryNumber = deliveryNumber,
                Article = new List<Article>()
            };
        }

        /// <summary>
        /// Adds the specified list of stock deliveries to the pre-defined stock delivery list of the storage system.
        /// </summary>
        /// <param name="stockDeliveryList">List of stock deliveries to add.</param>
        public void AddStockDeliveries(List<IStockDelivery> stockDeliveryList)
        {
            if (stockDeliveryList == null)
            {
                throw new ArgumentException("Invalid stockDeliveryList specified.");
            }

            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new StockDeliverySetRequestEnvelope()
            {
                StockDeliverySetRequest = new StockDeliverySetRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                }
            };

            if (stockDeliveryList.Count > 0)
            {
                request.StockDeliverySetRequest.StockDelivery = new StockDelivery[stockDeliveryList.Count];

                for (int i = 0; i < stockDeliveryList.Count; ++i)
                {
                    request.StockDeliverySetRequest.StockDelivery[i] = (StockDelivery)stockDeliveryList[i];
                }
            }

            var message = (StockDeliverySetResponse)_messageDispatcher.SendAndWaitForResponse(request,
                                                                                              request.StockDeliverySetRequest.Id,
                                                                                              typeof(StockDeliverySetResponse), MessageTimeout);

            if (message == null)
            {
                throw new ApplicationException("Waiting for the message 'StockDeliverySetResponse' failed.");
            }

            if ((message.SetResult != null) &&
                (message.SetResult.Value != SetResultValue.Accepted))
            {
                var errorMessage = string.Format("The specified stock deliveries were rejected by the storage " +
                                                 "system with the reason '{0}'.", TextConverter.UnescapeInvalidXmlChars(message.SetResult.Text));

                throw new ApplicationException(errorMessage);
            }
        }

        /// <summary>
        /// Gets detailed information about the specified stock delivery.
        /// </summary>
        /// <param name="deliveryNumber">The delivery number of the stock delivery.</param>
        /// <returns>According stock delivery information.</returns>
        public IStockDeliveryInfo GetStockDeliveryInfo(string deliveryNumber)
        {
            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new TaskInfoRequestEnvelope()
            {
                TaskInfoRequest = new TaskInfoRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                    IncludeTaskDetails = true.ToString()
                }
            };

            request.TaskInfoRequest.Task = new Task[1];
            request.TaskInfoRequest.Task[0] = new Task() { Id = deliveryNumber, Type = TaskType.StockDelivery };

            var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                    request.TaskInfoRequest.Id,
                                                                    typeof(TaskInfoResponse), MessageTimeout);

            var taskInfoResponse = (TaskInfoResponse)message;

            if ((taskInfoResponse == null) ||
                (taskInfoResponse.Task == null) ||
                (taskInfoResponse.Task.Length == 0) ||
                (taskInfoResponse.Task[0] == null))
            {
                this.Trace(string.Format("Requesting information for stock delivery {0} failed.", deliveryNumber));
                return null;
            }
            else
            {
                return taskInfoResponse.Task[0];
            }
        }

        /// <summary>
        /// Creates a new output process which can be used to dispense packs from the connected storage system.
        /// </summary>
        /// <param name="orderNumber">The unique order number which is used to identify an output process.</param>
        /// <param name="outputDestination">The storage system output destination for the dispensed packs.
        /// This number typically refers to a dispense point which has been configured at the storage system
        /// (e.g. 1 = maintenance output).</param>
        /// <param name="outputPoint">A more detailed definition of the storage system output destination (e.g. belt number) for the dispensed packs.</param>
        /// <param name="priority">The priority of this output process.</param>
        /// <param name="boxNumber">The optional box number which belongs to this output process.
        /// This parameter is required for box system scenarios when the IT system
        /// wants to dispense packs to a specific box.</param>
        /// <returns>
        /// Newly created output process to use for dispensing packs.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">The storage system is currently not connected.</exception>
        public IOutputProcess CreateOutputProcess(int orderNumber,
                                                  int outputDestination,
                                                  int outputPoint = 0,
                                                  OutputProcessPriority priority = OutputProcessPriority.Normal,
                                                  string boxNumber = null)
        {
            return CreateOutputProcess(orderNumber.ToString(),
                                       outputDestination,
                                       outputPoint,
                                       priority,
                                       boxNumber);
        }

        /// <summary>
        /// Creates a new output process which can be used to dispense packs from the connected storage system.
        /// </summary>
        /// <param name="orderNumber">The unique order number which is used to identify an output process.</param>
        /// <param name="outputDestination">The storage system output destination for the dispensed packs.
        /// This number typically refers to a dispense point which has been configured at the storage system
        /// (e.g. 1 = maintenance output).</param>
        /// <param name="outputPoint">A more detailed definition of the storage system output destination (e.g. belt number) for the dispensed packs.</param>
        /// <param name="priority">The priority of this output process.</param>
        /// <param name="boxNumber">The optional box number which belongs to this output process.
        /// This parameter is required for box system scenarios when the IT system
        /// wants to dispense packs to a specific box.</param>
        /// <returns>
        /// Newly created output process to use for dispensing packs.
        /// </returns>
        /// <exception cref="System.InvalidOperationException">The storage system is currently not connected.</exception>
        public IOutputProcess CreateOutputProcess(string orderNumber,
                                                  int outputDestination,
                                                  int outputPoint = 0,
                                                  OutputProcessPriority priority = OutputProcessPriority.Normal,
                                                  string boxNumber = null)
        {
            lock (_syncLock)
            {
                if (this.Connected == false)
                {
                    throw new InvalidOperationException("The storage system is currently not connected.");
                }

                var result = new OutputRequest(_messageObjectStream, _messageDispatcher)
                {
                    Id = orderNumber,
                    Source = SubscriberID,
                    Destination = _destinationId
                };

                result.Details = new Details()
                {
                    OutputDestination = outputDestination.ToString(),
                    OutputPoint = outputPoint.ToString(),
                    Priority = priority.ToString()
                };

                result.BoxNumber = boxNumber;
                result.Finished += OnOutputProcess_Finished;
                return result;
            }
        }

        /// <summary>
        /// Gets detailed information about the specified output process.
        /// </summary>
        /// <param name="orderNumber">The unique order number which identifies the output process.</param>
        /// <returns>
        /// According output process information.
        /// </returns>
        public IOutputProcessInfo GetOutputProcessInfo(int orderNumber)
        {
            return GetOutputProcessInfo(orderNumber.ToString());
        }

        /// <summary>
        /// Gets detailed information about the specified output process.
        /// </summary>
        /// <param name="orderNumber">The unique order number which identifies the output process.</param>
        /// <returns>
        /// According output process information.
        /// </returns>
        public IOutputProcessInfo GetOutputProcessInfo(string orderNumber)
        {
            if (string.IsNullOrEmpty(orderNumber))
            {
                throw new ArgumentException("Invalid orderNumber specified.");
            }

            if (this.Connected == false)
            {
                throw new InvalidOperationException("The storage system is currently not connected.");
            }

            var request = new TaskInfoRequestEnvelope()
            {
                TaskInfoRequest = new TaskInfoRequest()
                {
                    Id = MessageId.Next,
                    Destination = _destinationId,
                    Source = SubscriberID,
                    IncludeTaskDetails = true.ToString()
                }
            };

            request.TaskInfoRequest.Task = new Task[1];
            request.TaskInfoRequest.Task[0] = new Task() { Id = orderNumber, Type = TaskType.Output };

            var message = _messageDispatcher.SendAndWaitForResponse(request,
                                                                    request.TaskInfoRequest.Id,
                                                                    typeof(TaskInfoResponse), MessageTimeout);

            var taskInfoResponse = (TaskInfoResponse)message;

            if ((taskInfoResponse == null) ||
                (taskInfoResponse.Task == null) ||
                (taskInfoResponse.Task.Length == 0) ||
                (taskInfoResponse.Task[0] == null))
            {
                var errorMessage = string.Format("Requesting information for output process {0} failed.", orderNumber);
                throw new ApplicationException(errorMessage);
            }

            return taskInfoResponse.Task[0];
        }

        /// <summary>
        /// Creates a new input initiation request which can be used to trigger an input request of the connected storage system.
        /// </summary>
        /// <param name="id">The unique identifier of the initiated input process.</param>
        /// <param name="inputSource">The storage system input source for the pack input.</param>
        /// <param name="destination">The destination identifier of the system that should start the input process.</param>
        /// <param name="deliveryNumber">Delivery number to use when the initiated input is part of a new delivery.
        /// A value of null means that the input is part of a stock return.</param>
        /// <param name="setPickingIndicator">Flag whether the initiated input should request the activation of the picking indicator.</param>
        /// <returns>
        /// Newly created input initiation request if initiation of inputs is supported;null otherwise.
        /// </returns>
        public IInitiateInputRequest CreateInitiateInputRequest(string id,
            int inputSource,
            int inputPoint = 0,
            int destination = 0,
            string deliveryNumber = null,
            bool setPickingIndicator = false)
        {
            lock (_syncLock)
            {
                if (this.Connected == false)
                {
                    throw new InvalidOperationException("The storage system is currently not connected.");
                }

                bool isInitiateInputSupported = false;

                if (_destinationCapabilities != null)
                {
                    foreach (var capability in _destinationCapabilities)
                    {
                        if (string.Compare(capability.Name, "InitiateInput", true) == 0)
                        {
                            isInitiateInputSupported = true;
                        }
                    }
                }
                else
                {
                    isInitiateInputSupported = true;
                }

                if (isInitiateInputSupported == false)
                {
                    return null;
                }

                var result = new InitiateInputRequest(_messageObjectStream, _messageDispatcher, deliveryNumber)
                {
                    Id = id,
                    Source = SubscriberID,
                    Destination = (destination == 0) ? _destinationId : destination
                };

                result.SetPickingIndicator = setPickingIndicator.ToString();
                result.Details = new Details()
                {
                    InputSource = inputSource.ToString(),
                    InputPoint = inputPoint.ToString()
                };

                return result;
            }
        }

        /// <inheritdoc/>
        public IInitiateInputRequest CreateInitiateInputRequest(int id,
            int inputSource,
            int inputPoint = 0,
            int destination = 0,
            string deliveryNumber = null,
            bool setPickingIndicator = false)
        {
            return this.CreateInitiateInputRequest(id.ToString(), inputSource, inputPoint, destination, deliveryNumber,
                setPickingIndicator);
        }

        /// <inheritdoc/>
        public IInfeedInputRequest CreateInfeedInputRequest(string id,
                                                            int infeedNumber,
                                                            int destination = 0,
                                                            string deliveryNumber = null,
                                                            bool setPickingIndicator = false)
        {
            lock (_syncLock)
            {
                if (!this.Connected)
                    throw new InvalidOperationException("The storage system is currently not connected.");

                bool isInfeedInputSupported = false;

                if (_destinationCapabilities != null)
                {
                    foreach (var capability in _destinationCapabilities)
                    {
                        if (string.Compare(capability.Name, "Infeed", true) == 0)
                        {
                            isInfeedInputSupported = true;
                            break;
                        }
                    }
                }
                else
                {
                    isInfeedInputSupported = true;
                }

                if (!isInfeedInputSupported)
                    return null;

                var result = new InfeedInputRequest(_messageObjectStream, _messageDispatcher, deliveryNumber)
                {
                    Id = id,
                    Source = SubscriberID,
                    Destination = (destination == 0) ? _destinationId : destination
                };

                result.SetPickingIndicator = setPickingIndicator.ToString();
                result.Details.InfeedNumber = infeedNumber.ToString();

                return result;
            }
        }

        /// <summary>
        /// Method to be called to change the state of a light at an output destination.
        /// </summary>
        /// <param name="outputDestination">The number of the output destination.</param>
        /// <param name="newState">The new state of the output destination.</param>
        /// <returns></returns>
        /// <exception cref="ApplicationException"></exception>
        public IOutputDestinationStateIndicationSetResponse SetOutputDestinationStateIndication(int outputDestination, OutputDestinationState newState)
        {
            var request = new OutputDestinationStateIndicationSetRequestEnvelope()
            {
                OutputDestinationStateIndicationSetRequest = new OutputDestinationStateIndicationSetRequest()
                {
                    Id = MessageId.Next,
                    Source = SubscriberID,
                    Destination = _destinationId,
                    OutputDestination = outputDestination,
                    State = newState
                }
            };

            var message = (OutputDestinationStateIndicationSetResponse)_messageDispatcher.SendAndWaitForResponse(request,
                                                                                                            request.OutputDestinationStateIndicationSetRequest.Id,
                                                                                                            typeof(OutputDestinationStateIndicationSetResponse), MessageTimeout);

            if (message == null)
            {
                throw new ApplicationException("Waiting for the message 'OutputDestinationStateIndicationSetResponse' failed.");
            }

            return message;
        }

        public IStockUpdate CreateStockUpdate()
        {
            return new StockUpdate(_messageDispatcher, MessageTimeout, _destinationId, SubscriberID);
        }

        /// <summary>
        /// Gets the available capabilities.
        /// </summary>
        /// <returns>A <see cref="List{T}"/> of <see cref="Capability"/></returns>
        private List<Capability> GetAvailableCapabilities()
        {
            return ((MosaicSupportedCapabilities[])Enum
                .GetValues(typeof(MosaicSupportedCapabilities)))
                .OrderBy(x => x).Select(i => new Capability
                {
                    Name = i.ToString()
                }).ToList();
        }

        /// <summary>
        /// Processes the connection request from Connect methods
        /// </summary>
        /// <param name="host">The name or ip address of the storage system.</param>
        /// <param name="port">The port number of the storage system.</param>
        /// <param name="helloRequestCapabilities">The selected capabilities to be included in HelloRequest message.</param>
        /// <param name="encryptionOptions">The encryption options to use on the connection.</param>
        private void ProcessConnectionRequest(
            string host,
            ushort port,
            Capability[] helloRequestCapabilities,
            EncryptionOptions encryptionOptions)
        {
            if (string.IsNullOrEmpty(host))
            {
                throw new ArgumentException("Invalid host specified.");
            }

            if (port == 0)
            {
                throw new ArgumentException("Invalid port specified.");
            }

            this.Disconnect();

            lock (this._syncLock)
            {
                this.Trace("Connecting to storage system '{0}' at port '{1}'.", host, port);

                this._client = new TcpClient();

                var connectResult = this._client.BeginConnect(host, port, null, null);

                if (!connectResult.AsyncWaitHandle.WaitOne(ConnectTimeout))
                {
                    connectResult.AsyncWaitHandle.Close();

                    this._client.Close();
                    this._client = null;

                    this.Error("Connecting to storage system '{0}' at port '{1}' timed out.", host, port);

                    throw new TimeoutException(string.Format("Connecting to storage system '{0}' at port '{1}' timed out.", host, port));
                }

                this._client.EndConnect(connectResult);
                this._client.ReceiveTimeout = 0;
                this._client.IncreaseReadBufferSize();
                this._client.EnableSocketKeepAlive();

                // Setup encryption, if needed.
                System.IO.Stream streamToUse =
                    encryptionOptions != null ?
                        Tls.AuthenticateAsClient(_client.GetStream(), encryptionOptions.TargetHost, encryptionOptions.UseMutualAuthentication) :
                        _client.GetStream();

                // Initialize message stream and register the supported XML message types
                this._messageObjectStream = new XmlObjectStream(streamToUse, "WWKS", "StorageSystem", string.Concat(host, ":", port));
                this._messageObjectStream.AddSupportedType(typeof(KeepAliveRequestEnvelope), nameof(KeepAliveRequest));
                this._messageObjectStream.AddSupportedType(typeof(KeepAliveResponseEnvelope), nameof(KeepAliveResponse));
                this._messageObjectStream.AddSupportedType(typeof(HelloRequestEnvelope), nameof(HelloRequest));
                this._messageObjectStream.AddSupportedType(typeof(HelloResponseEnvelope), nameof(HelloResponse));
                this._messageObjectStream.AddSupportedType(typeof(InputMessageEnvelope), nameof(InputMessage));
                this._messageObjectStream.AddSupportedType(typeof(InputRequestEnvelope), nameof(InputRequest));
                this._messageObjectStream.AddSupportedType(typeof(InputResponseEnvelope), nameof(InputResponse));
                this._messageObjectStream.AddSupportedType(typeof(InitiateInputMessageEnvelope), nameof(InitiateInputMessage));
                this._messageObjectStream.AddSupportedType(typeof(InitiateInputRequestEnvelope), nameof(InitiateInputRequest));
                this._messageObjectStream.AddSupportedType(typeof(InitiateInputResponseEnvelope), nameof(InitiateInputResponse));
                this._messageObjectStream.AddSupportedType(typeof(StatusRequestEnvelope), nameof(StatusRequest));
                this._messageObjectStream.AddSupportedType(typeof(StatusResponseEnvelope), nameof(StatusResponse));
                this._messageObjectStream.AddSupportedType(typeof(StockInfoRequestEnvelope), nameof(StockInfoRequest));
                this._messageObjectStream.AddSupportedType(typeof(TaskCancelRequestEnvelope), nameof(TaskCancelRequest));
                this._messageObjectStream.AddSupportedType(typeof(TaskCancelResponseEnvelope), nameof(TaskCancelResponse));
                this._messageObjectStream.AddSupportedType(typeof(TaskInfoRequestEnvelope), nameof(TaskInfoRequest));
                this._messageObjectStream.AddSupportedType(typeof(TaskInfoResponseEnvelope), nameof(TaskInfoResponse));
                this._messageObjectStream.AddSupportedType(typeof(ArticleMasterSetResponseEnvelope), nameof(ArticleMasterSetResponse));
                this._messageObjectStream.AddSupportedType(typeof(StockDeliverySetResponseEnvelope), nameof(StockDeliverySetResponse));
                this._messageObjectStream.AddSupportedType(typeof(OutputRequestEnvelope), nameof(OutputRequest));
                this._messageObjectStream.AddSupportedType(typeof(OutputResponseEnvelope), nameof(OutputResponse));
                this._messageObjectStream.AddSupportedType(typeof(OutputMessageEnvelope), nameof(OutputMessage));
                this._messageObjectStream.AddSupportedType(typeof(StockInfoResponseEnvelope), nameof(StockInfoResponse));
                this._messageObjectStream.AddSupportedType(typeof(StockInfoMessageEnvelope), nameof(StockInfoMessage));
                this._messageObjectStream.AddSupportedType(typeof(ArticleMasterSetRequestEnvelope), nameof(ArticleMasterSetRequest));
                this._messageObjectStream.AddSupportedType(typeof(StockDeliverySetRequestEnvelope), nameof(StockDeliverySetRequest));
                this._messageObjectStream.AddSupportedType(typeof(ConfigurationGetRequestEnvelope), nameof(ConfigurationGetRequest));
                this._messageObjectStream.AddSupportedType(typeof(ConfigurationGetResponseEnvelope), nameof(ConfigurationGetResponse));
                this._messageObjectStream.AddSupportedType(typeof(StockLocationInfoRequestEnvelope), nameof(StockLocationInfoRequest));
                this._messageObjectStream.AddSupportedType(typeof(StockLocationInfoResponseEnvelope), nameof(StockLocationInfoResponse));
                this._messageObjectStream.AddSupportedType(typeof(ArticleInfoRequestEnvelope), nameof(ArticleInfoRequest));
                this._messageObjectStream.AddSupportedType(typeof(ArticleInfoResponseEnvelope), nameof(ArticleInfoResponse));
                this._messageObjectStream.AddSupportedType(typeof(InfeedInputRequestEnvelope), nameof(InfeedInputRequest));
                this._messageObjectStream.AddSupportedType(typeof(InfeedInputResponseEnvelope), nameof(InfeedInputResponse));
                this._messageObjectStream.AddSupportedType(typeof(InfeedInputMessageEnvelope), nameof(InfeedInputMessage));
                this._messageObjectStream.AddSupportedType(typeof(InfeedInputPlacePackRequestEnvelope), nameof(InfeedInputPlacePackRequest));
                this._messageObjectStream.AddSupportedType(typeof(InfeedInputPlacePackResponseEnvelope), nameof(InfeedInputPlacePackResponse));
                this._messageObjectStream.AddSupportedType(typeof(OutputDestinationButtonPressedMessageEnvelope), nameof(OutputDestinationButtonPressedMessage));
                this._messageObjectStream.AddSupportedType(typeof(OutputDestinationStateIndicationSetRequestEnvelope), nameof(OutputDestinationStateIndicationSetRequest));
                this._messageObjectStream.AddSupportedType(typeof(OutputDestinationStateIndicationSetResponseEnvelope), nameof(OutputDestinationStateIndicationSetResponse));
                this._messageObjectStream.AddSupportedType(typeof(UnprocessedMessageEnvelope), nameof(UnprocessedMessage));
                this._messageObjectStream.AddSupportedType(typeof(StockUpdateRequestEnvelope), nameof(StockUpdateRequest));
                this._messageObjectStream.AddSupportedType(typeof(StockUpdateResponseEnvelope), nameof(StockUpdateResponse));

                var processHandshakeResult = this.ProcessHandshake(helloRequestCapabilities);

                if (!processHandshakeResult)
                {
                    this.Disconnect();

                    throw new ApplicationException("Processing WWKS protocol handshake failed.");
                }

                this._messageDispatcher.Start(this._messageObjectStream);

                this._shutdownThreadEvent = new ManualResetEvent(false);

                if (!ThreadPool.QueueUserWorkItem(new WaitCallback(ConnectionCheck), _shutdownThreadEvent))
                {
                    this.Disconnect();

                    throw new ApplicationException("Starting background connection check thread failed.");
                }
            }
        }

        /// <summary>
        /// Processes the WWKS 2.0 protocol handshake to initialize the connection to the storage system.
        /// </summary>
        /// <param name="capabilities">An array of capabilities that should be included in HelloRequest.</param>
        /// <returns>A boolean which is true if connection succeeded</returns>
        private bool ProcessHandshake(Capability[] capabilities)
        {
            var request = new HelloRequestEnvelope
            {
                HelloRequest = new HelloRequest
                {
                    Id = MessageId.Next,
                    Subscriber = new Subscriber
                    {
                        Id = SubscriberID,
                        Type = SubscriberType.IMS,
                        Manufacturer = "CareFusion Germany 326 GmbH",
                        ProductInfo = "StorageSystem Library",
                        VersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion,
                        Capability = capabilities,
                        TenantId = this._tenantId
                    }
                }
            };

            if (!this._messageObjectStream.Write(request))
            {
                this.Error("Sending the initial 'HelloRequest' message failed!");
                return false;
            }

            object response = this._messageObjectStream.Read(HandshakeTimeout);

            if (response == null)
            {
                this.Error("Waiting for the message 'HelloResponse' failed.");

                return false;
            }

            if (response.GetType() != typeof(HelloResponseEnvelope))
            {
                this.Error("Received unexpected message of type '{0}'.", response.GetType().Name);

                return false;
            }

            HelloResponseEnvelope resp = (HelloResponseEnvelope)response;

            if (resp.HelloResponse.Subscriber == null)
            {
                this.Error("Received a 'HelloResponse' message with invalid subscriber information.");

                return false;
            }

            if (resp.HelloResponse.Id != request.HelloRequest.Id)
            {
                this.Error("Received a 'HelloResponse' message with invalid message identifier.");

                return false;
            }

            this._destinationCapabilities = resp.HelloResponse.Subscriber.Capability;

            this.Info("WWKS 2.0 protocol handshake successfully finished -> ID='{0}' Type='{1}' " +
                      "Manufacturer='{2}' ProductInfo='{3}' VersionInfo='{4}'.",
                      resp.HelloResponse.Subscriber.Id, resp.HelloResponse.Subscriber.Type.ToString(),
                      string.IsNullOrEmpty(resp.HelloResponse.Subscriber.Manufacturer) ? string.Empty : resp.HelloResponse.Subscriber.Manufacturer,
                      string.IsNullOrEmpty(resp.HelloResponse.Subscriber.ProductInfo) ? string.Empty : resp.HelloResponse.Subscriber.ProductInfo,
                      string.IsNullOrEmpty(resp.HelloResponse.Subscriber.VersionInfo) ? string.Empty : resp.HelloResponse.Subscriber.VersionInfo);

            this._destinationId = resp.HelloResponse.Subscriber.Id;

            return true;
        }

        /// <summary>
        /// Event which is raised when the message reader detected a problem while reading messages from the stream.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Always null and not used here.</param>
        private void OnMessageStreamDown(object sender, EventArgs e)
        {
            this.Trace("Message stream connection went down.");
            Disconnect();
        }

        /// <summary>
        /// Event which is raised when a new message arrived that is not handled by any pending message interceptors.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Instance of MessageArrivedArgs with event details.</param>
        private void OnMessageArrived(object sender, EventArgs e)
        {
            if (sender != _messageDispatcher)
            {
                return;
            }

            var eventArgs = e as MessageDispatcher.MessageArrivedArgs;

            if (eventArgs == null)
            {
                return;
            }

            try
            {
                switch (eventArgs.Message.GetType().Name)
                {
                    case "InputRequest": ProcessInputRequest((InputRequest)eventArgs.Message); break;
                    case "InputMessage": ProcessInputMessage((InputMessage)eventArgs.Message); break;
                    case "OutputMessage": ProcessOutputMessage((OutputMessage)eventArgs.Message); break;
                    case "StockInfoMessage": ProcessStockInfoMessage((StockInfoMessage)eventArgs.Message); break;
                    case "ArticleInfoRequest": ProcessArticleInfoRequest((ArticleInfoRequest)eventArgs.Message); break;
                    case "OutputDestinationButtonPressedMessage": ProcessButtonsPressedMessage((OutputDestinationButtonPressedMessage)eventArgs.Message); break;
                }
            }
            catch (Exception ex)
            {
                this.Error("Processing incomming message '{0}' failed!", ex, eventArgs.Message.GetType().Name);
            }
        }

        /// <summary>
        /// Processes an incomming pack input request message.
        /// </summary>
        /// <param name="request">The request message to process.</param>
        private void ProcessInputRequest(InputRequest request)
        {
            lock (_syncLock)
            {
                if (_messageObjectStream == null)
                {
                    return;
                }

                request.MessageObjectStream = _messageObjectStream;
            }

            if (_packInputRequested != null)
            {
                base.Raise("PackInputRequested", _packInputRequested, this, request);
            }

            if (_packInputRequestedExtended != null)
            {
                base.Raise("PackInputRequestedExtended", _packInputRequestedExtended, this, new PackInputRequestedEventArgs(request));
            }
        }

        /// <summary>
        /// Processes a pack input notification message.
        /// </summary>
        /// <param name="request">The pack input notification message to process.</param>
        private void ProcessInputMessage(InputMessage message)
        {
            if ((message.Article == null) || (message.Article.Count == 0))
            {
                return;
            }

            var articles = new List<IArticle>();

            foreach (var article in message.Article)
            {
                if ((article.Pack == null) || (article.Pack.Count == 0))
                {
                    continue;
                }

                foreach (var pack in article.Pack.ToArray())
                {
                    if ((pack.Handling == null) ||
                        (string.Compare(pack.Handling.Input, "Completed", true) != 0))
                    {
                        article.Pack.Remove(pack);
                    }
                }

                if (article.Pack.Count > 0)
                {
                    articles.Add(article);
                }
            }

            if (articles.Count > 0)
            {
                if (_packStored != null)
                {
                    base.Raise("PackStored", _packStored, this, articles.ToArray());
                }

                if (_packStoredExtended != null)
                {
                    base.Raise("PackStoredExtended", _packStoredExtended, this, new PackStoredEventArgs(articles.ToArray(), Convert.ToBoolean(message.IsNewDelivery)));
                }
            }

            if (_packInputFinished != null)
            {
                base.Raise("PackInputFinished",
                           _packInputFinished,
                           this,
                           message.Source,
                           message.Id,
                           (articles.Count > 0) ? InputResult.Completed : InputResult.Aborted,
                           articles.ToArray());
            }

            if (_packInputFinishedExtended != null)
            {
                base.Raise("PackInputFinishedExtended",
                    _packInputFinishedExtended,
                    this, new PackInputFinishedEventArgs(message.Source,
                        int.Parse(message.Id),
                        (articles.Count > 0) ? InputResult.Completed : InputResult.Aborted,
                        articles.ToArray()));
            }
        }

        /// <summary>
        /// Processes a pack output notification message.
        /// </summary>
        /// <param name="message">The pack output notification message to process.</param>
        private void ProcessOutputMessage(OutputMessage message)
        {
            if (_outputProcessFinished != null)
            {
                if (message.Id == "1")
                {
                    if ((message.Article != null) && (message.Article.Length > 0))
                    {
                        if (_packDispensed != null)
                        {
                            base.Raise("PackDispensed", _packDispensed, this, message.Article);
                        }
                        if (_packDispensedExtended != null)
                        {
                            base.Raise("PackDispensedExtended", _packDispensedExtended, this, new PackDispensedEventArgs(message.Article, message.Id));
                        }
                    }
                }
                else
                {
                    base.Raise("OutputProcessFinished", _outputProcessFinished, this, message.Id, message);
                }
            }
            else if (_outputProcessFinishedExtended != null)
            {
                if (message.Id == "1")
                {
                    if ((message.Article != null) && (message.Article.Length > 0))
                    {
                        if (_packDispensed != null)
                        {
                            base.Raise("PackDispensed", _packDispensed, this, message.Article);
                        }
                        if (_packDispensedExtended != null)
                        {
                            base.Raise("PackDispensedExtended", _packDispensedExtended, this, new PackDispensedEventArgs(message.Article, message.Id));
                        }
                    }
                }
                else
                {
                    base.Raise("OutputProcessFinishedExtended", _outputProcessFinishedExtended, this, new OutputProcessFinishedEventArgs(message.Id, message));
                }
            }
            else
            {
                if ((message.Article != null) && (message.Article.Length > 0))
                {
                    if (_packDispensed != null)
                    {
                        base.Raise("PackDispensed", _packDispensed, this, message.Article);
                    }
                    if (_packDispensedExtended != null)
                    {
                        base.Raise("PackDispensedExtended", _packDispensedExtended, this, new PackDispensedEventArgs(message.Article, message.Id));
                    }
                }
            }
        }

        /// <summary>
        /// Called when an output process has finished execution.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void OnOutputProcess_Finished(object sender, EventArgs e)
        {
            var outputRequest = (OutputRequest)sender;
            if (_outputProcessFinished != null)
            {
                base.Raise("OutputProcessFinished", _outputProcessFinished, this, outputRequest.Id, outputRequest);
            }

            if (_outputProcessFinishedExtended != null)
            {
                base.Raise("OutputProcessFinishedExtended", _outputProcessFinishedExtended, this, new OutputProcessFinishedEventArgs(outputRequest.Id, outputRequest));
            }
        }

        /// <summary>
        /// Processes the event of stock updates.
        /// </summary>
        /// <param name="message">The stock update message to process.</param>
        private void ProcessStockInfoMessage(StockInfoMessage message)
        {
            if ((message.Article != null) && (message.Article.Length > 0))
            {
                if (_stockUpdated != null)
                {
                    base.Raise("StockUpdated", _stockUpdated, this, message.Article);
                }

                if (_stockUpdatedExtended != null)
                {
                    base.Raise("StockUpdatedExtended", _stockUpdatedExtended, this, new StockUpdatedEventArgs(message.Article));
                }
            }
        }

        /// <summary>
        /// Processes the event of a pressed button.
        /// </summary>
        /// <param name="message">The buttons pressed message to process.</param>
        private void ProcessButtonsPressedMessage(OutputDestinationButtonPressedMessage message)
        {
            if (_outputDestinationButtonPressed != null)
            {
                base.Raise("OutputDestinationButtonPressed", _outputDestinationButtonPressed, this, message.OutputDestination);
            }
        }

        /// <summary>
        /// Processes an Article Information request message.
        /// </summary>
        /// <param name="request">The request message to process.</param>
        private void ProcessArticleInfoRequest(ArticleInfoRequest request)
        {
            lock (_syncLock)
            {
                if (_messageObjectStream == null)
                {
                    return;
                }

                request.MessageObjectStream = _messageObjectStream;
            }

            if (_articleInfoRequested != null)
            {
                base.Raise("ArticleInfoRequested", _articleInfoRequested, this, request);
            }

            if (_articleInfoRequestedExtended != null)
            {
                base.Raise("ArticleInfoRequestedExtended", _articleInfoRequestedExtended, this, new ArticleInfoRequestedEventArgs(request));
            }
        }

        /// <summary>
        /// Processes regulary tcp connection checks and polls for the state of the storage system.
        /// </summary>
        /// <param name="parameter">Instance of the RowaStorageSystem object to observe.</param>
        private void ConnectionCheck(object parameter)
        {
            this.Trace("Background connection check thread started.");
            var shutdownEvent = parameter as ManualResetEvent;
            var currentState = ComponentState.NotConnected;
            var currentComponents = new IComponent[0];
            var lastState = currentState;

            try
            {
                var lastComponents = currentComponents;
                var stateTime = DateTime.UtcNow;

                if (this.EnableAutomaticStateObservation)
                {
                    if (_stateChanged != null)
                    {
                        currentState = this.State;
                        lastState = currentState;
                        this.Trace($"StateChanged event is raised. The state of Storage System changes to: {currentState}.");
                        base.Raise("StateChanged", _stateChanged, this, currentState);
                    }

                    //if (_stateChangedExtended != null)
                    //{
                    //    currentState = this.State;
                    //    lastState = currentState;
                    //    this.Trace($"StateChanged event is raised. The state of Storage System changes to: {currentState}.");
                    //    base.Raise("StateChangedExtended", _stateChangedExtended, this, new StateChangedEventArgs(currentState));
                    //}

                    if (_componentStateChanged != null)
                    {
                        currentComponents = this.ComponentStates;
                        lastComponents = currentComponents;

                        foreach (var component in currentComponents)
                        {
                            this.Trace(
                                $"ComponentStateChanged event is raised. The state of Storage System component {component.Type} changes to: {component.State}.");
                            base.Raise("ComponentStateChanged", _componentStateChanged, this, component);
                        }
                    }

                    if (_componentStateChangedExtended != null)
                    {
                        currentComponents = this.ComponentStates;
                        lastComponents = currentComponents;

                        foreach (var component in currentComponents)
                        {
                            this.Trace(
                                $"ComponentStateChanged event is raised. The state of Storage System component {component.Type} changes to: {component.State}.");
                            base.Raise("ComponentStateChangedExtended", _componentStateChangedExtended, this, new ComponentStateChangedEventArgs(component));
                        }
                    }
                }

                while (this.Connected)
                {
                    if (this.EnableAutomaticStateObservation)
                    {
                        if ((DateTime.UtcNow - stateTime).TotalMilliseconds >= StateCheckInterval)
                        {
                            if (_stateChanged != null)
                                currentState = this.State;

                            //if (_stateChangedExtended != null)
                            //    currentState = this.State;

                            if (_componentStateChanged != null)
                                currentComponents = this.ComponentStates;

                            if (_componentStateChangedExtended != null)
                                currentComponents = this.ComponentStates;

                            stateTime = DateTime.UtcNow;

                            if ((_stateChanged != null) && (lastState != currentState))
                            {
                                lastState = currentState;
                                this.Trace($"StateChanged event is raised. The state of Storage System changes to: {currentState}.");
                                base.Raise("StateChanged", _stateChanged, this, currentState);
                            }

                            //if ((_stateChangedExtended != null) && (lastState != currentState))
                            //{
                            //    lastState = currentState;
                            //    this.Trace($"StateChanged event is raised. The state of Storage System changes to: {currentState}.");
                            //    base.Raise("StateChangedExtended", _stateChangedExtended, this, new StateChangedEventArgs(currentState));
                            //}

                            if (_componentStateChanged != null)
                            {
                                for (int i = 0; i < lastComponents.Length; ++i)
                                {
                                    if ((currentComponents.Length > i) &&
                                        ((currentComponents[i].Description != lastComponents[i].Description) ||
                                         (currentComponents[i].State != lastComponents[i].State)))
                                    {
                                        this.Trace(
                                            $"ComponentStateChanged event is raised. The state of Storage System component {currentComponents[i].Type} changes to: {currentComponents[i].State}.");
                                        base.Raise("ComponentStateChanged", _componentStateChanged, this, currentComponents[i]);
                                    }
                                }

                                lastComponents = currentComponents;
                            }

                            if (_componentStateChangedExtended != null)
                            {
                                for (int i = 0; i < lastComponents.Length; ++i)
                                {
                                    if ((currentComponents.Length > i) &&
                                        ((currentComponents[i].Description != lastComponents[i].Description) ||
                                         (currentComponents[i].State != lastComponents[i].State)))
                                    {
                                        this.Trace(
                                            $"ComponentStateChanged event is raised. The state of Storage System component {currentComponents[i].Type} changes to: {currentComponents[i].State}.");
                                        base.Raise("ComponentStateChangedExtended", _componentStateChangedExtended, this, new ComponentStateChangedEventArgs(currentComponents[i]));
                                    }
                                }

                                lastComponents = currentComponents;
                            }
                        }
                    }

                    if (shutdownEvent.WaitOne(StateCheckInterval))
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                this.Error("Processing connection and status checks failed!", ex);
            }
            finally
            {
                shutdownEvent.Dispose();

                try
                {
                    if ((_stateChanged != null) && (this.EnableAutomaticStateObservation) && (lastState != ComponentState.NotConnected))
                    {
                        this.Trace($"StateChanged event is raised. The state of Storage System changes to: {ComponentState.NotConnected}.");
                        base.Raise("StateChanged", _stateChanged, this, ComponentState.NotConnected);
                    }

                    //if ((_stateChangedExtended != null) && (this.EnableAutomaticStateObservation) && (lastState != ComponentState.NotConnected))
                    //{
                    //    this.Trace($"StateChanged event is raised. The state of Storage System changes to: {ComponentState.NotConnected}.");
                    //    base.Raise("StateChangedExtended", _stateChangedExtended, this, new StateChangedEventArgs(ComponentState.NotConnected));
                    //}
                }
                catch (Exception ex)
                {
                    this.Error("Raising final StateChanged event failed!", ex);
                }
            }

            this.Trace("Background connection check thread stopped.");
        }

        /// <summary>
        /// Logs the version number of the Dll in a secure manner.
        /// </summary>
        private void LogVersion()
        {
            using (var sha = System.Security.Cryptography.SHA256.Create())
            {
                var today = DateTime.Now;
                var version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
                var hashInput = string.Format($"{version}-{today.Day}.{today.Month}.{today.Year}");
                var hashBytes = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(hashInput));
                var hashString = BitConverter.ToString(hashBytes).Replace("-", "");

                var log = string.Format($"StorageSystem Version: {version} Hash: {hashString}");
                this.Info(log);
            }
        }

        /// <summary>
        /// (Pre)Initializes the XML serializer for the required WWKS 2.0 messages.
        /// This one-time initialization will speed up the execution time later on
        /// when creating new RowaStorageSystem instances.
        /// 
        /// The XmlSerializer COMPILES! temporary intermediate assemblies for every
        /// type you want to serialize/deserialize with it. This happens in the background
        /// when you instantiate the XmlSerializer for a specific type for the very first time.
        /// </summary>
        /// <returns><c>true</c> if initialization was successful; <c>false</c> otherwise.</returns>
        private bool InitializeXmlSerializer()
        {
            if (_serializerInitialized)
            {
                return true;
            }

            this.Trace("Initializing WWKS 2.0 XmlSerializer ...");

            try
            {
                var v1 = new XmlSerializer(typeof(HelloRequestEnvelope));
                var v2 = new XmlSerializer(typeof(HelloResponseEnvelope));
                var v3 = new XmlSerializer(typeof(InputMessageEnvelope));
                var v4 = new XmlSerializer(typeof(InputRequestEnvelope));
                var v5 = new XmlSerializer(typeof(InputResponseEnvelope));
                var v6 = new XmlSerializer(typeof(OutputMessageEnvelope));
                var v7 = new XmlSerializer(typeof(OutputRequestEnvelope));
                var v8 = new XmlSerializer(typeof(OutputResponseEnvelope));
                var v9 = new XmlSerializer(typeof(StatusRequestEnvelope));
                var v10 = new XmlSerializer(typeof(StatusResponseEnvelope));
                var v11 = new XmlSerializer(typeof(StockInfoRequestEnvelope));
                var v12 = new XmlSerializer(typeof(StockInfoResponseEnvelope));
                var v13 = new XmlSerializer(typeof(StockInfoMessageEnvelope));
                var v14 = new XmlSerializer(typeof(ArticleMasterSetRequestEnvelope));
                var v15 = new XmlSerializer(typeof(ArticleMasterSetResponseEnvelope));
                var v16 = new XmlSerializer(typeof(StockDeliverySetRequestEnvelope));
                var v17 = new XmlSerializer(typeof(StockDeliverySetResponseEnvelope));
                var v18 = new XmlSerializer(typeof(TaskCancelRequestEnvelope));
                var v19 = new XmlSerializer(typeof(TaskCancelResponseEnvelope));
                var v20 = new XmlSerializer(typeof(TaskInfoRequestEnvelope));
                var v21 = new XmlSerializer(typeof(TaskInfoResponseEnvelope));
                var v22 = new XmlSerializer(typeof(KeepAliveRequestEnvelope));
                var v23 = new XmlSerializer(typeof(KeepAliveResponseEnvelope));
                var v24 = new XmlSerializer(typeof(InitiateInputRequestEnvelope));
                var v25 = new XmlSerializer(typeof(InitiateInputResponseEnvelope));
                var v26 = new XmlSerializer(typeof(InitiateInputMessageEnvelope));
                var v27 = new XmlSerializer(typeof(ConfigurationGetRequestEnvelope));
                var v28 = new XmlSerializer(typeof(ConfigurationGetResponseEnvelope));
                var v29 = new XmlSerializer(typeof(StockLocationInfoRequestEnvelope));
                var v30 = new XmlSerializer(typeof(StockLocationInfoResponseEnvelope));
                var v31 = new XmlSerializer(typeof(InfeedInputRequestEnvelope));
                var v32 = new XmlSerializer(typeof(InfeedInputResponseEnvelope));
                var v33 = new XmlSerializer(typeof(InfeedInputMessageEnvelope));
                var v34 = new XmlSerializer(typeof(InfeedInputPlacePackRequestEnvelope));
                var v35 = new XmlSerializer(typeof(InfeedInputPlacePackResponseEnvelope));
                var v36 = new XmlSerializer(typeof(OutputDestinationButtonPressedMessageEnvelope));
                var v37 = new XmlSerializer(typeof(OutputDestinationStateIndicationSetRequestEnvelope));
                var v38 = new XmlSerializer(typeof(OutputDestinationStateIndicationSetResponseEnvelope));
                var v39 = new XmlSerializer(typeof(UnprocessedMessageEnvelope));

                this.Trace("Initialization of WWKS 2.0 XmlSerializer finished.");
                _serializerInitialized = true;
                return true;
            }
            catch (Exception ex)
            {
                this.Error("Initializing WWKS 2.0 XmlSerializer failed!", ex);
            }

            return false;
        }

        /// <inheritdoc/>
        public void SendRawXml(byte[] message)
        {
            this.Info("Sends the raw xml");
            this._messageObjectStream.WriteRaw(message);
        }

        #endregion
    }
}
