// **********************************************************************************************************
//    BitGenSoftMeasurementManager.cs
//    7/28/2022
//    NGI - Next Generation Interceptor
//    
//    Contract No. HQ0856-21-C-0003/1022000209
//    
//    THIS DOCUMENT DOES NOT CONTAIN TECHNOLOGY OR TECHNICAL DATA CONTROLLED UNDER EITHER THE U.S. 
//    INTERNATIONAL TRAFFIC IN ARMS REGULATIONS OR THE U.S. EXPORT ADMINISTRATION REGULATIONS.
//    
//    RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION PROPRIETARY TO RAYTHEON 
//    COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. 
//    DISCLOSURE TO UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO RAYTHEON 
//    COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS CONTENTS SHALL BE FURNISHED OR DISCLOSED 
//    TO OR COPIED OR USED BY PERSONS OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF 
//    RAYTHEON COMPANY.
//    
//    UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
//    
//    DESTRUCTION NOTICE: FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN DOD 5220.22-M, 
//    NATIONAL INDUSTRIAL SECURITY PROGRAM OPERATING MANUAL, FEBRUARY 2006, 
//    INCORPORATING CHANGE 1, MARCH 28, 2013, CHAPTER 5, SECTION 7, OR DODM 5200.01-VOLUME 3, 
//    DOD INFORMATION SECURITY PROGRAM: PROTECTION OF CLASSIFIED INFORMATION, ENCLOSURE 3, 
//    SECTION 17. FOR CONTROLLED UNCLASSIFIED INFORMATION FOLLOW THE PROCEDURES IN DODM 5200.01-VOLUME 4, 
//    INFORMATION SECURITY PROGRAM: CONTROLLED UNCLASSIFIED INFORMATION.
//    
//    CONTROLLED BY: MISSILE DEFENSE AGENCY
//    CONTROLLED BY: GROUND-BASED MIDCOURSE DEFENSE PROGRAM OFFICE
//    CUI CATEGORY: CTI 
//    DISTRIBUTION/DISSEMINATION CONTROL: F 
//    POC: Alex Kravchenko (1118268)
// **********************************************************************************************************

using NLog;
using Raytheon.Common;
using Raytheon.Instruments;
using Raytheon.Instruments.Exceptions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using BitGenSoftMeasurementManagerLib;

namespace MeasurementManagerLib
{
	/// <summary>
	/// Bit Gen Soft Measurement Manager class
	/// </summary>
	public class BitGenSoftMeasurementManager : IDisposable
	{
		private readonly Logger _logger = LogManager.GetCurrentClassLogger();

		private readonly Dictionary<string, IBit> _bitNodes = new Dictionary<string, IBit>();

		private readonly IInstrumentManager _instrumentManager;
		private IConfigurationManager _configurationManager;
		private int _checkForMessageIntervalMs = 10;
		/// <summary>
		/// constructor that will create a list of BIT instruments
		/// </summary>
		/// <param name="instrumentManager"></param>
		/// <param name="instrumentNames"></param>
		public BitGenSoftMeasurementManager(IInstrumentManager instrumentManager, List<string> instrumentNames)
			: this(instrumentManager)
		{
			try
			{
				foreach (string instrumentName in instrumentNames)
				{
					var bitNode = (IBit)_instrumentManager.GetGenericInstrument(instrumentName);
					if (bitNode == null)
					{
						_logger.Error("Error creating TCP BIT device, check your settings");
					}
					else
					{
						_bitNodes.Add(instrumentName, bitNode);
					}
				}
			}
			catch (Exception ex)
			{
				_logger.Error($"Unable to create BIT instrument\n{ex.Message}");
			}
		}

		/// <summary>
		/// in case instrument manager was passed from upper level
		/// </summary>
		/// <param name="instrumentManager"></param>
		private BitGenSoftMeasurementManager(IInstrumentManager instrumentManager)
		{
			_instrumentManager = instrumentManager;
			InitializeConfigurationManager();
		}

		/// <summary>
		/// initializes configuration manager, 
		/// will use configuration path from General Instrument Manager if available
		/// otherwise will try to use the instruments path
		/// </summary>
		private void InitializeConfigurationManager()
		{
			string defaultPath;
			if (_instrumentManager is GeneralInstrumentManager generalinstrumentManager)
			{
				defaultPath = generalinstrumentManager._configLocation;
			}
			else
			{
				defaultPath = _instrumentManager._partsLocation;
			}

			_configurationManager = new RaytheonConfigurationManager(defaultPath);

			var config = _configurationManager.GetConfiguration("BitGenSoftMeasurementManager");

			_checkForMessageIntervalMs = config.GetConfigurationValue("Settings", "CheckForMessageIntervalMs", 10);
		}

		/// <summary>
		/// initialize COE nodes based on test type
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="testType"></param>
		public void InitNodes()
		{
			foreach (var node in _bitNodes)
			{
				try
				{
					IBit bitNode = node.Value;
					bitNode.Initialize();
					bitNode.Open();
				}
				catch (Exception ex)
				{
					_logger.Error(ex, ex.Message);
				}
			}
		}

		/// <summary>
		/// close all connections
		/// </summary>
		/// <exception cref="NotImplementedException"></exception>
		public void Dispose()
		{
			foreach (var bitNode in _bitNodes)
			{
				bitNode.Value?.Shutdown();
			}
		}

		/// <summary>
		/// straight pass-through the message and parameters
		/// </summary>
		/// <param name="messageId"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		public bool RunBIT(string messageId, uint timeoutInMs, IEnumerable<KeyValuePair<string, string>> messageParams = null)
		{
			if (_bitNodes.Any())
			{
				IBit bitNode = _bitNodes.First().Value;
				return bitNode.RunBIT(messageId, timeoutInMs, messageParams);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node. No nodes defined");
				return false;
			}
		}

		/// <summary>
		/// straight pass-through the message and parameters
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="messageId"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		public bool RunBIT(string instrumentName, string messageId, uint timeoutInMs, IEnumerable<KeyValuePair<string, string>> messageParams = null)
		{
			if (_bitNodes.ContainsKey(instrumentName))
			{
				return _bitNodes[instrumentName].RunBIT(messageId, timeoutInMs, messageParams);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node {instrumentName}");
				return false;
			}
		}

		/// <summary>
		/// always runs the request on the first node, 
		/// </summary>
		/// <param name="messageIdOut"></param>
		/// <param name="messageIdIn"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		public BitTestResults RunBITWaitForResults(string messageIdOut,
													string messageIdIn,
													uint timeoutInMs,
													IEnumerable<KeyValuePair<string, string>> messageParams = null)
		{
			if (_bitNodes.Any())
			{
				IBit bitNode = _bitNodes.First().Value;
				return bitNode.RunBITWaitForResults(messageIdOut, messageIdIn, timeoutInMs, messageParams);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node. No nodes defined");
				return null;
			}
		}

		/// <summary>
		/// run BIT message and wait for result from the instrument based on the name
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="messageIdOut"></param>
		/// <param name="messageIdIn"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		public BitTestResults RunBITWaitForResults(string instrumentName,
													string messageIdOut,
													string messageIdIn,
													uint timeoutInMs,
													IEnumerable<KeyValuePair<string, string>> messageParams = null)
		{
			if (_bitNodes.ContainsKey(instrumentName))
			{
				return _bitNodes[instrumentName].RunBITWaitForResults(messageIdOut, messageIdIn, timeoutInMs, messageParams);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node {instrumentName}");
				return null;
			}

		}

		/// <summary>
		/// RunBITWaitForResults for the whole list of BIT Commands
		/// runs on the first node
		/// </summary>
		/// <param name="commands"></param>
		/// <returns></returns>
		public List<BitTestResults> RunBITWaitForResultsList(List<BITCommand> commands)
		{
			if (_bitNodes.Any())
			{
				return RunBITWaitForResultsList(commands, _bitNodes.First().Value);
			}
			else
			{
				_logger.Error("Unable to locate BIT node. No nodes defined");
				return null;
			}
		}

		/// <summary>
		/// RunBITWaitForResults for the whole list of BIT Commands
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="commands"></param>
		/// <returns></returns>
		public List<BitTestResults> RunBITWaitForResultsList(string instrumentName, List<BITCommand> commands)
		{
			if (_bitNodes.ContainsKey(instrumentName))
			{
				return RunBITWaitForResultsList(commands, _bitNodes[instrumentName]);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node {instrumentName}");
				return null;
			}

		}

		/// <summary>
		/// Runs BIT command and returns the results as a list
		/// </summary>
		/// <param name="commands"></param>
		/// <returns></returns>
		public async Task<List<BitTestResults>> RunBITWaitForResultsListAsync(List<BITCommand> commands)
		{
			if (_bitNodes.Any())
			{
				return await RunBITWaitForResultsListAsync(commands, _bitNodes.First().Value);
			}
			else
			{
				_logger.Error("Unable to locate BIT node. No nodes defined");
				return null;
			}
		}

		/// <summary>
		/// Runs BIT command for specific instrument and returns the results as a list
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="commands"></param>
		/// <returns></returns>
		public async Task<List<BitTestResults>> RunBITWaitForResultsListAsync(string instrumentName, List<BITCommand> commands)
		{
			if (_bitNodes.ContainsKey(instrumentName))
			{
				return await RunBITWaitForResultsListAsync(commands, _bitNodes[instrumentName]);
			}
			else
			{
				_logger.Error($"Unable to locate BIT node {instrumentName}");
				return null;
			}
		}

		/// <summary>
		/// asynchronously runs BIT command and waits for multiple results including parametric messages
		/// </summary>
		/// <param name="instrumentName">BIT instrument to run command</param>
		/// <param name="bitCommandId">main command</param>
		/// <param name="timeout">timeout value in ms for the main response</param>
		/// <param name="commandParams">optional parameters for the main command</param>
		/// <param name="parametricResponseIds">list of additional response messages with their respective timeouts to wait for</param>
		/// <param name="timeout2">parametric timeout value in ms</param>
		/// <param name="errorMessageId">nack response id when provided will be expected as a possible response for either main response or parametric response</param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public async Task<List<BitTestResults>> RunBITWaitForResultsAsync(  string instrumentName,
																			string bitCommandId,
																			int retries,
																			uint timeout,
																			IEnumerable<KeyValuePair<string, string>> commandParams = null,
																			List<string> parametricResponseIds = null,
																			uint timeout2 = 0,
																			string errorMessageId = null)
		{
			IBit bitGenSoftManager = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation.");

			BITCommand command = BuildBITCommand(bitCommandId, retries, timeout, commandParams, parametricResponseIds, timeout2, errorMessageId);

			return await RunBITWaitForResultsListAsync(new List<BITCommand> { command }, bitGenSoftManager);
		}

		/// <summary>
		/// keep reading any of the messages from the list
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeOut"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public async Task KeepReadingBitResultsListAsync(string instrumentName, List<string> parametricResponseIds, CancellationToken token)
		{
			IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation.");
			await KeepReadingBitResultsListAsync(parametricResponseIds, token, bitNode);
		}

		/// <summary>
		/// keep reading any of the messages from the list from the first node
		/// </summary>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeOut"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public async Task KeepReadingBitResultsListAsync(List<string> parametricResponseIds, CancellationToken token)
		{
			IBit bitNode = _bitNodes.First().Value;
			await KeepReadingBitResultsListAsync(parametricResponseIds, token, bitNode);
		}

		/// <summary>
		/// looking for a any of the messages from the list
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeOut"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public async Task<List<BitTestResults>> GetBitResultsListAsync(string instrumentName, List<string> parametricResponseIds, int timeOut)
		{
			IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation.");
			return await GetBitResultsListAsync(parametricResponseIds, timeOut, bitNode);
		}

		/// <summary>
		/// looking for a any of the messages from the list on the first node
		/// </summary>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeOut"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public async Task<List<BitTestResults>> GetBitResultsListAsync(List<string> parametricResponseIds, int timeOut)
		{
			IBit bitNode = _bitNodes.First().Value;
			return await GetBitResultsListAsync(parametricResponseIds, timeOut, bitNode);
		}

		/// <summary>
		/// look for results in the node until either result was found or canceled
		/// </summary>
		/// <param name="token"></param>
		/// <param name="responseId"></param>
		/// <returns></returns>
		public async Task<BitTestResults> GetBITResultsAsync(CancellationToken token, string responseId)
		{
			IBit bitNode = _bitNodes.First().Value;
			return await GetBITResultsAsync(token, responseId, bitNode);
		}

		/// <summary>
		/// look for results in the node until either result was found or canceled
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="token"></param>
		/// <param name="responseId"></param>
		/// <returns></returns>
		public async Task<BitTestResults> GetBITResultsAsync(string instrumentName, CancellationToken token, string responseId)
		{
			IBit bitNode = _bitNodes[instrumentName] ?? throw new Exception("BitGenSoftManager is null. Unable to perform operation.");
			return await GetBITResultsAsync(token, responseId, bitNode);
		}

		/// <summary>
		/// look for results in either node
		/// </summary>
		/// <param name="messageId"></param>
		/// <returns></returns>
		public BitTestResults GetBITResults(string messageId)
		{
			_logger.Trace($"Getting BIT result for: {messageId}.");
			if (_bitNodes.Any())
			{
				IBit bitNode = _bitNodes.First().Value;
				return bitNode.GetBITResults(messageId);
			}
			else
				return null;
		}

		/// <summary>
		/// look for results in either node
		/// </summary>
		/// <param name="instrumentName"></param>
		/// <param name="messageId"></param>
		/// <returns></returns>
		public BitTestResults GetBITResults(string instrumentName, string messageId)
		{
			_logger.Trace($"{instrumentName} Getting BIT result for: {messageId}.");
			return _bitNodes.ContainsKey(instrumentName) ? _bitNodes[instrumentName].GetBITResults(messageId) : null;
		}

		/// <summary>
		/// opens a folder parse XML files for BITCommand definitions and returns a list
		/// </summary>
		/// <param name="dir"></param>
		/// <returns></returns>
		public Dictionary<string, List<BITCommand>> GetBITCommands(string directoryPath)
		{
			Dictionary<string, List<BITCommand>> bitCommands = new Dictionary<string, List<BITCommand>>();

			try
			{
				string[] xmlFiles;
				if (directoryPath.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase))
				{
					xmlFiles = new string[] {directoryPath};
				}
				else
				{
					// Get all XML files recursively in the specified directory
					xmlFiles = Directory.GetFiles(directoryPath, "*.xml", SearchOption.TopDirectoryOnly);
				}

				foreach (string xmlFile in xmlFiles)
				{
					ConfigurationFile configFile = new ConfigurationFile(xmlFile);

					List<string> sections = configFile.ReadAllSections();

					foreach (string section in sections)
					{
						List<BITCommand> commands = configFile.ReadList<BITCommand>(section, "BIT_CMD");
						if(bitCommands.ContainsKey(section))
						{
							_logger.Warn($"Found a duplicate BIT configuration section {section}, overwriting from {xmlFile} file.");
						}
                        else
                        {
							_logger.Trace($"Adding {section} commands from {xmlFile} file");
                        }
                        bitCommands[section] = commands;
					}
				}
			}
			catch (Exception ex)
			{
				_logger.Error(ex, $"Error reading BIT Command definitions in {directoryPath}");
				throw;
			}

			return bitCommands;
		}

		#region Private functions

		/// <summary>
		/// will keep pumping messages from the receiving queue until canceled
		/// </summary>
		/// <param name="parametricResponseIds"></param>
		/// <param name="token"></param>
		/// <param name="bitNode"></param>
		/// <returns></returns>
		private async Task KeepReadingBitResultsListAsync(List<string> parametricResponseIds, CancellationToken token, IBit bitNode)
		{
			_logger.Trace($"{bitNode.Name} Start continuously reading these messages: {string.Join(", ", parametricResponseIds)}.");

			Dictionary<uint, int> score = new Dictionary<uint, int>();
			do
			{
				foreach (var responseId in parametricResponseIds)
				{
					BitTestResults bitTestresults = await GetBITResultsAsync(token, responseId, bitNode);

					if (bitTestresults != null && !string.IsNullOrEmpty(bitTestresults.Label))
					{
						if(uint.TryParse(bitTestresults.Label, out uint label))
						{
							if(!score.ContainsKey(label))
							{
								score.Add(label, 1);
							}
							else 
							{
								score[label]++;
							}
						}
					}
				}
				//await Task.Delay(_checkForMessageIntervalMs);
			} while (!token.IsCancellationRequested);

			foreach (var item in score)
			{
				_logger.Trace($"{bitNode.Name} Dequeued the total of {item.Value} BIT messages for 0x{item.Key:X} label");
			}
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeOut"></param>
		/// <param name="node"></param>
		/// <returns></returns>
		private async Task<List<BitTestResults>> GetBitResultsListAsync(List<string> parametricResponseIds, int timeOut, IBit node)
		{
			_logger.Trace($"{node.Name} Start continuously getting BIT results for these messages: {string.Join(", ", parametricResponseIds)}.");

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

			CancellationTokenSource tokenSource = new CancellationTokenSource();
			List<Task<BitTestResults>> responseTasks = new List<Task<BitTestResults>>();

			foreach (var responseId in parametricResponseIds)
			{
				responseTasks.Add(GetBITResultsAsync(tokenSource.Token, responseId, node));
			}

			Task timeoutTask = Task.Delay(timeOut);
			var completedTask = await Task.WhenAny(responseTasks.Concat(new[] { timeoutTask }));

			tokenSource.Cancel();
			tokenSource.Dispose();

			if (completedTask == timeoutTask)
			{
				_logger.Warn($"Timed out after {timeOut} ms while waiting on parametrized response message(s) {string.Join(", ", parametricResponseIds)}");
			}
			else
			{
				var completedResults = responseTasks.Where(t => t.Status == TaskStatus.RanToCompletion && t.Result != null).Select(t => t.Result);
				results.AddRange(completedResults);
			}

			_logger.Trace($"{node.Name} Completed getting BIT results for these messages: {string.Join(", ", parametricResponseIds)}, found {results?.Count} results");

			return results;
		}

		/// <summary>
		/// look for results in the node until either result was found or canceled
		/// </summary>
		/// <param name="token"></param>
		/// <param name="responseId"></param>
		/// <param name="node"></param>
		/// <returns></returns>
		private async Task<BitTestResults> GetBITResultsAsync(CancellationToken token, string responseId, IBit node)
		{
			//_logger.Trace($"{node.Name} Start waiting for BIT message: {responseId}.");

			BitTestResults bitTestResults = null;
			do
			{
				await Task.Delay(_checkForMessageIntervalMs);
				try
				{
					bitTestResults = node.GetBITResults(responseId);
				}
				catch (Exception ex)
				{
					_logger.Error(ex, ex.Message);
					break;
				}
			} while (bitTestResults == null && !token.IsCancellationRequested);

			//_logger.Trace($"{node.Name} Done waiting for BIT message: {responseId}.");

			return bitTestResults;
		}

		/// <summary>
		/// builds a command from parameters
		/// </summary>
		/// <param name="bitCommandId"></param>
		/// <param name="retries"></param>
		/// <param name="timeout"></param>
		/// <param name="commandParams"></param>
		/// <param name="parametricResponseIds"></param>
		/// <param name="timeout2"></param>
		/// <param name="errorMessageId"></param>
		/// <returns></returns>
		private static BITCommand BuildBITCommand(string bitCommandId,
												  int retries,
												  uint timeout,
												  IEnumerable<KeyValuePair<string, string>> commandParams = null,
												  List<string> parametricResponseIds = null,
												  uint timeout2 = 0,
												  string errorMessageId = null)
		{
			BITCommand bitCommand = new BITCommand
			{
				Command = bitCommandId,
				CommandTimeout = timeout,
				CommandParameters = new List<BITParameter>(),
				CommandResponses = new List<BITResponseMsg>(),
				ParametricResponse = retries.ToString(),
				ParametricTimeout = timeout2
			};

			if (commandParams != null)
			{
				foreach (var commandParameter in commandParams)
				{
					bitCommand.CommandParameters.Add(new BITParameter
					{
						Key = commandParameter.Key,
						Value = commandParameter.Value,
						ParameterType = ParameterType.U
					});
				}
			}
			if(parametricResponseIds != null)
			{
				foreach (var response in parametricResponseIds)
				{
					bitCommand.CommandResponses.Add(new BITResponseMsg
					{
						Name = response,
						ResponseType = ResponseMessageType.P
					});
				}
			}
			if(!string.IsNullOrEmpty(errorMessageId))
			{
				bitCommand.CommandResponses.Add(new BITResponseMsg
				{
					Name = errorMessageId,
					ResponseType = ResponseMessageType.E
				});
			}

			return bitCommand;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="commands"></param>
		/// <param name="node"></param>
		/// <returns></returns>
		private List<BitTestResults> RunBITWaitForResultsList(List<BITCommand> commands, IBit node)
		{
			List<BitTestResults> results = new List<BitTestResults>();

			foreach (var command in commands)
			{
				string bitCommand = command.Command;
				string bitResponseId = ResponseGroupForFirstBITCall(command);
				uint timeout = command.CommandTimeout;
				uint timeout2 = command.ParametricTimeout;
				string strParamerticResponse = command.ParametricResponse;
				int retries = 0;
				if (int.TryParse(strParamerticResponse, out int tmpretries))
				{
					retries = tmpretries;
				}
				List<KeyValuePair<string, string>> commandParams = ConvertCommandParameters(command.CommandParameters);
				int runIndex = 0;
				do
				{
					runIndex++;
					try
					{
						var result = node.RunBITWaitForResults(bitCommand, bitResponseId, timeout, commandParams);
						results.Add(result);
					}
					catch (BitTimeoutException)
					{
						_logger.Warn($"Timeout after {timeout} ms on BIT command {bitCommand} waiting for result {bitResponseId}, retry number {runIndex}");
						if (runIndex == retries)
						{
							throw;
						}
					}
				} while (results == null && runIndex <= retries);

				var responseGroup = ResponseGroupListForParameterBITCall(command);

				if (responseGroup == null || !responseGroup.Any())
				{
					continue;
				}

				CancellationTokenSource cts = new CancellationTokenSource();
				ParallelOptions options = new ParallelOptions
				{
					CancellationToken = cts.Token,
					MaxDegreeOfParallelism = Environment.ProcessorCount
				};

				try
				{
					Parallel.ForEach(responseGroup, options, item =>
					{
						var totalWaitTimeMs = _checkForMessageIntervalMs;
						BitTestResults parametricresults;
						do
						{
							parametricresults = node.GetBITResults(item);

							if (parametricresults != null)
							{
								break;
							}
							else
							{
								Thread.Sleep(_checkForMessageIntervalMs);
								totalWaitTimeMs += _checkForMessageIntervalMs;
							}
						} while (totalWaitTimeMs < timeout2 && !cts.IsCancellationRequested);

						if (parametricresults == null && !cts.IsCancellationRequested)
						{
							_logger.Warn($"Timed out while waiting on parametrized response message(s) for {command.Command} command");
						}

						if (parametricresults != null)
						{
							// replace earlier results (confirmation message) with parametric results
							results.Add(parametricresults);
							cts.Cancel();
						}
					});

				}
				catch (OperationCanceledException)
				{
					_logger.Trace($"Done waiting on parametrized response message(s) for {command.Command} command");
				}
				finally
				{
					cts.Dispose();
				}

			}

			return results;
		}

		/// <summary>
		/// runs bit command and then 
		/// </summary>
		/// <param name="commands"></param>
		/// <param name="node"></param>
		/// <returns></returns>
		private async Task<List<BitTestResults>> RunBITWaitForResultsListAsync(List<BITCommand> commands, IBit node)
		{
			List<BitTestResults> results = new List<BitTestResults>();

			foreach (var command in commands)
			{
				string bitCommand = command.Command;
				string bitResponseId = ResponseGroupForFirstBITCall(command);
				uint timeout = command.CommandTimeout;
				uint timeout2 = command.ParametricTimeout;
				string strParamerticResponse = command.ParametricResponse;
				int retries = 0;
				if (int.TryParse(strParamerticResponse, out int tmpretries))
				{
					retries = tmpretries;
				}
				List<KeyValuePair<string, string>> commandParams = ConvertCommandParameters(command.CommandParameters);
				int runIndex = 0;
				do
				{
					runIndex++;
					try
					{
						var result = await Task.Run(() => node.RunBITWaitForResults(bitCommand, bitResponseId, timeout, commandParams));
						results.Add(result);
					}
					catch (BitTimeoutException)
					{
						_logger.Warn($"Timeout after {timeout} ms on BIT command {bitCommand} waiting for result {bitResponseId}, retry number {runIndex}");
						if (runIndex == retries)
						{
							throw;
						}
					}
				} while (results == null && runIndex <= retries);

				var responseGroup = ResponseGroupListForParameterBITCall(command);

				if (responseGroup == null || !responseGroup.Any())
				{
					continue;
				}

				var paramResults = await GetBitResultsListAsync(responseGroup, (int)timeout2, node);
				if (paramResults != null)
				{
					results.AddRange(paramResults);
				}
			}

			return results;
		}

		/// <summary>
		/// for the initial BIT message expect to find R (main Response)
		/// if any responses marked as E (error) add it to the list as alternative expected response, separated by comma
		/// </summary>
		/// <param name="command"></param>
		/// <returns></returns>
		private static string ResponseGroupForFirstBITCall(BITCommand command)
		{
			StringBuilder resp = new StringBuilder();
			var tempMainResponseGroup = command.CommandResponses.Where(m => m.ResponseType != ResponseMessageType.E).ToList();
			if (tempMainResponseGroup != null)
			{
				// check for Rs first and if no messages marked with Rs than check for Us
				var mainResponse = tempMainResponseGroup.FirstOrDefault(m => m.ResponseType == ResponseMessageType.R);
				if (mainResponse != null)
				{
					resp.Append(mainResponse.Name);
				}
			}

			// append all error message ids
			foreach (var item in command.CommandResponses.Where(m => m.ResponseType == ResponseMessageType.E))
			{
				resp.Append(',');
				resp.Append(item.Name);
			}

			return resp.ToString();
		}
		/// <summary>
		/// for subsequent parameter response look for either P (parameterized) or U (undefined) 
		/// if any responses marked as E (error) add it to the list as alternative expected response
		/// </summary>
		/// <param name="command"></param>
		/// <returns></returns>
		private static List<string> ResponseGroupListForParameterBITCall(BITCommand command)
		{
			List<string> resp = new List<string>();
			bool parameterizedRespExpected = false;
			var tempMainResponseGroup = command.CommandResponses.Where(m => m.ResponseType != ResponseMessageType.E).ToList();
			if (tempMainResponseGroup != null)
			{
				// check for Rs first and if no messages marked with Rs than check for Us
				var mainResponse = tempMainResponseGroup.FirstOrDefault(m => m.ResponseType == ResponseMessageType.P || m.ResponseType == ResponseMessageType.U);
				if (mainResponse != null)
				{
					resp.Add(mainResponse.Name);
					parameterizedRespExpected = true;
				}
			}
			if (parameterizedRespExpected)
			{
				// append all error message ids
				foreach (var item in command.CommandResponses.Where(m => m.ResponseType == ResponseMessageType.E))
				{
					resp.Add(item.Name);
				}
			}
			return resp;
		}

		private static List<KeyValuePair<string, string>> ConvertCommandParameters(List<BITParameter> @in)
		{
			List<KeyValuePair<string, string>> parameters = new List<KeyValuePair<string, string>>();
			foreach (var parameter in @in)
			{
				parameters.Add(new KeyValuePair<string, string>(parameter.Key, parameter.Value));
			}
			return parameters;
		}

		#endregion
	}
}
