/// <summary>
        /// Create engine
        /// </summary>
        public DataFlowProcessingEngine(IMessageTrigger messageTrigger, IMessageEncoder encoder,
                                        IMessageSink messageSink, IEngineConfiguration engineConfiguration, ILogger logger,
                                        IIdentity identity)
        {
            _config         = engineConfiguration;
            _messageTrigger = messageTrigger;
            _messageSink    = messageSink;
            _messageEncoder = encoder;
            _logger         = logger;
            _identity       = identity;

            if (_config.BatchSize.HasValue && _config.BatchSize.Value > 1)
            {
                _dataSetMessageBufferSize = _config.BatchSize.Value;
            }
            if (_config.MaxMessageSize.HasValue && _config.MaxMessageSize.Value > 0)
            {
                _maxEncodedMessageSize = _config.MaxMessageSize.Value;
            }

            _diagnosticInterval        = _config.DiagnosticsInterval.GetValueOrDefault(TimeSpan.Zero);
            _batchTriggerInterval      = _config.BatchTriggerInterval.GetValueOrDefault(TimeSpan.Zero);
            _diagnosticsOutputTimer    = new Timer(DiagnosticsOutputTimer_Elapsed);
            _batchTriggerIntervalTimer = new Timer(BatchTriggerIntervalTimer_Elapsed);
            _maxOutgressMessages       = _config.MaxOutgressMessages.GetValueOrDefault(4096); // = 1 GB

            _encodingBlock = new TransformManyBlock <DataSetMessageModel[], NetworkMessageModel>(
                async input => {
                try {
                    if (_dataSetMessageBufferSize == 1)
                    {
                        return(await _messageEncoder.EncodeAsync(input, _maxEncodedMessageSize).ConfigureAwait(false));
                    }
                    else
                    {
                        return(await _messageEncoder.EncodeBatchAsync(input, _maxEncodedMessageSize).ConfigureAwait(false));
                    }
                }
                catch (Exception e) {
                    _logger.Error(e, "Encoding failure");
                    return(Enumerable.Empty <NetworkMessageModel>());
                }
            },
                new ExecutionDataflowBlockOptions());

            _batchDataSetMessageBlock = new BatchBlock <DataSetMessageModel>(
                _dataSetMessageBufferSize,
                new GroupingDataflowBlockOptions());

            _batchNetworkMessageBlock = new BatchBlock <NetworkMessageModel>(
                _networkMessageBufferSize,
                new GroupingDataflowBlockOptions());

            _sinkBlock = new ActionBlock <NetworkMessageModel[]>(
                async input => {
                if (input != null && input.Any())
                {
                    _logger.Debug("Sink block in engine {Name} triggered with {count} messages",
                                  Name, input.Length);
                    await _messageSink.SendAsync(input).ConfigureAwait(false);
                }
                else
                {
                    _logger.Warning("Sink block in engine {Name} triggered with empty input",
                                    Name);
                }
            },
                new ExecutionDataflowBlockOptions());
            _batchDataSetMessageBlock.LinkTo(_encodingBlock);
            _encodingBlock.LinkTo(_batchNetworkMessageBlock);
            _batchNetworkMessageBlock.LinkTo(_sinkBlock);

            _messageTrigger.OnMessage      += MessageTriggerMessageReceived;
            _messageTrigger.OnCounterReset += MessageTriggerCounterResetReceived;

            if (_diagnosticInterval > TimeSpan.Zero)
            {
                _diagnosticsOutputTimer.Change(_diagnosticInterval, _diagnosticInterval);
            }
        }