// **********************************************************************************************************
//    BITCOEDeviceInstrument.cs
//    6/21/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 System;
using Raytheon.Instruments.MessagingUtilities;
using System.Xml.XPath;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Threading;
using Raytheon.Common;
using Raytheon.Instruments.coeCSharp;
using Raytheon.Instruments.Exceptions;
using System.Collections.Concurrent;
using static Raytheon.Instruments.MessagingUtilities.Message;
using System.IO;
using System.Reflection;
using System.Globalization;
using System.Runtime.CompilerServices;
using NLog;

[assembly: InternalsVisibleTo("BITCOEDeviceNode.Tests")]
namespace Raytheon.Instruments
{
	/// <summary>
	/// This device supports different ways of communicating with other COE nodes
	/// TCP
	/// UDP
	/// Serial
	/// </summary>
	public enum DriverType
	{
		Undefined,
		TCP,
		UDP,
		Serial
	};

	/// <summary>
	/// Implementation of the IBit interface
	/// </summary>
	public class BITCOEDeviceInstrument : IBit
	{
		/// <summary>
		/// Nlog logger
		/// </summary>
		private readonly ILogger _logger;

		/// <summary>
		/// Raytheon configuration
		/// </summary>
		private readonly IConfigurationManager _configurationManager;
		private readonly IConfiguration _configuration;

		/// <summary>
		/// reference to the main wrapper class for Common Operating Environment
		/// </summary>
		private readonly coe _coe;

		/// <summary>
		/// COE endpoint
		/// </summary>
		private coeEndpoint _endpoint;

		/// <summary>
		/// cancellation token for stopping reading thread
		/// </summary>
		private CancellationTokenSource _cancellationTokenSource = null;

		/// <summary>
		/// collection of the messages received
		/// </summary>
		private readonly Dictionary<string, ConcurrentQueue<Tuple<DateTime, OeMessage>>> _messages;

		/// <summary>
		/// UDP, TCP, Serial or Undefined
		/// </summary>
		private DriverType _driverType;

		/// <summary>
		/// dictionary of options when initializing COE endpoint and router
		/// </summary>
		private readonly Dictionary<string, List<KeyValuePair<string, string>>> _options = new Dictionary<string, List<KeyValuePair<string, string>>>();

		/// <summary>
		/// used for initialization of the endpoint
		/// </summary>
		private uint _maxMessageSize;
		private uint _epQueueDepth;

		/// <summary>
		/// Number of milliseconds to wake up and check the message when receiving
		/// </summary>
		private int _checkForMessageIntervalMs;

		/// <summary>
		/// collection of all labels with message names per every XML file
		/// </summary>
		private readonly Dictionary<string, Dictionary<uint, string>> _icds = new Dictionary<string, Dictionary<uint, string>>();

		/// <summary>
		/// collection of response labels or messages that COE endpoint should be registered for
		/// </summary>
		private readonly List<uint> _responseLabels = new List<uint>();

		/// <summary>
		/// collection of message XML documents (processed XML files) used in COE communications
		/// </summary>
		private readonly Dictionary<string, MessageXmlDocument> _xmlDocs = new Dictionary<string, MessageXmlDocument>();

		/// <summary>
		/// when set to true the instrument will check every value and if empty 
		/// will populate it with the default value
		/// </summary>
		private bool _alwaysSendDefaults;

		/// <summary>
		/// instrument constructor
		/// </summary>
		public BITCOEDeviceInstrument(string name, IConfigurationManager configurationManager, DriverType driverType = DriverType.Undefined, ILogger logger = null)
		{
			Info = new InstrumentMetadata
			{
				ModelNumber = "COECommDevice"
			};

			if (logger == null)
				logger = LogManager.GetCurrentClassLogger();

			_logger = logger;

			Status = State.Uninitialized;
			DetailedStatus = "COE Uninitialized";

			Name = name;

			if (LogManager.Configuration == null)
			{
				var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
				LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(assemblyFolder + "\\nlog.config");
			}

			if (configurationManager == null)
			{
				_logger.Error($"Cannot create {Name} without a configuration manager");
				return;
			}

			_configurationManager = configurationManager;
			_configuration = _configurationManager.GetConfiguration(Name);

			_messages = new Dictionary<string, ConcurrentQueue<Tuple<DateTime, OeMessage>>>();

			_driverType = driverType;

			_coe = new coe();
		}

		public string DetailedStatus { get; protected set; }

		public bool DisplayEnabled { get => false; set => throw new NotImplementedException(); }
		public bool FrontPanelEnabled { get => false; set => throw new NotImplementedException(); }

		public InstrumentMetadata Info { get; set; }

		public string Name { get; protected set; }

		public SelfTestResult SelfTestResult => PerformSelfTest();

		public State Status { get; set; }

		public bool ClearErrors()
		{
			return true;
		}

		/// <summary>
		/// Initializes COE instrument
		/// </summary>
		public void Initialize()
		{
			_logger.Trace($"{Name}({_driverType}) Initializing...");

			if (_driverType == DriverType.Undefined)
				_driverType = _configuration.GetConfigurationValue<DriverType>("Parameters", "DriverType", "TCP");

			_alwaysSendDefaults = _configuration.GetConfigurationValue("Parameters", "AlwaysSendDefaults", false);

			_options.Clear();
			_options.Add("ROUTER_CONFIG", new List<KeyValuePair<string, string>>
			{
				{ new KeyValuePair<string, string>("NODE_ID", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "NODE_ID", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_STATE", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_STATE", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_LABEL_MESSAGE", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_LABEL_MESSAGE", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_BRIDGE_REGISTRATION", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_BRIDGE_REGISTRATION", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_ROUTER_DATABASE", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_ROUTER_DATABASE", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_SEND", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "DISPLAY_DEBUG_RECV", "0")) },
				{ new KeyValuePair<string, string>("BUFFER_SIZE", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "BUFFER_SIZE", "256")) },
				{ new KeyValuePair<string, string>("ENABLE_REGISTRATION_MESSAGES", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "ENABLE_REGISTRATION_MESSAGES", "1")) },
				{ new KeyValuePair<string, string>("THREAD_STACK_SIZE", _configuration.GetConfigurationValue<string>("ROUTER_CONFIG", "THREAD_STACK_SIZE", "16384")) },
			});

			_options.Add("ROUTER_PROTOCOL_CONFIG", new List<KeyValuePair<string, string>>
			{
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("ROUTER_PROTOCOL_CONFIG", "DISPLAY_DEBUG_SEND", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("ROUTER_PROTOCOL_CONFIG", "DISPLAY_DEBUG_RECV", "0")) },
				{ new KeyValuePair<string, string>("THREAD_STACK_SIZE", _configuration.GetConfigurationValue<string>("ROUTER_PROTOCOL_CONFIG", "THREAD_STACK_SIZE", "16384")) },
			});

			var poolEntry = _configuration.GetConfigurationValue<string>("ROUTER_BUFFER_POOLS", "POOL_ENTRY", "100,32|50,128|100,384|150,1536|10,65535");
			if (!string.IsNullOrEmpty(poolEntry))
			{
				var poolEntries = poolEntry.Split('|');
				if (poolEntries.Any())
				{
					var entries = new List<KeyValuePair<string, string>>();
					foreach (var entry in poolEntries)
					{
						entries.Add(new KeyValuePair<string, string>("POOL_ENTRY", entry));
					}
					_options.Add("ROUTER_BUFFER_POOLS", entries);
				}
			}

			_options.Add("BASIC_REGISTRATION_CONFIG", new List<KeyValuePair<string, string>>
			{
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_SEND", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND_BUFFER", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_SEND_BUFFER", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_RECV", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV_BUFFER", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_RECV_BUFFER", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_STATE", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_STATE", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_PING_SEND", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_PING_SEND", "0")) },
				{ new KeyValuePair<string, string>("DISPLAY_DEBUG_PING_RECV", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_PING_RECV", "0")) },
				{ new KeyValuePair<string, string>("THREAD_STACK_SIZE", _configuration.GetConfigurationValue<string>("BASIC_REGISTRATION_CONFIG", "THREAD_STACK_SIZE", "16384")) },
			});

			switch (_driverType)
			{
				case DriverType.UDP:
					_options.Add("UDP_MEDIA_BINDING_CONFIG", new List<KeyValuePair<string, string>>
					{
						{ new KeyValuePair<string, string>("LOCAL_IP_ADDRESS", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "LOCAL_IP_ADDRESS", "127.0.0.1")) },
						{ new KeyValuePair<string, string>("REMOTE_IP_ADDRESS", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "REMOTE_IP_ADDRESS", "127.0.0.1")) },
						{ new KeyValuePair<string, string>("LOCAL_SEND_PORT", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "LOCAL_SEND_PORT", "32010")) },
						{ new KeyValuePair<string, string>("LOCAL_RECV_PORT", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "LOCAL_RECV_PORT", "32020")) },
						{ new KeyValuePair<string, string>("REMOTE_SEND_PORT", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "REMOTE_SEND_PORT", "32011")) },
						{ new KeyValuePair<string, string>("REMOTE_RECV_PORT", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "REMOTE_RECV_PORT", "32021")) },
						{ new KeyValuePair<string, string>("RECV_TIMEOUT", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "RECV_TIMEOUT", "200")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "0")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "0")) },
						{ new KeyValuePair<string, string>("MTU_SIZE", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "MTU_SIZE", "1472")) },
						{ new KeyValuePair<string, string>("THREAD_STACK_SIZE", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "THREAD_STACK_SIZE", "16384")) },
						{ new KeyValuePair<string, string>("THREAD_NAME", _configuration.GetConfigurationValue<string>("UDP_MEDIA_BINDING_CONFIG", "THREAD_NAME", "UDP_MB_RCV")) },
					});
					break;

				case DriverType.TCP:
					_options.Add("TCP_MEDIA_BINDING_CONFIG", new List<KeyValuePair<string, string>>
					{
						{ new KeyValuePair<string, string>("LOCAL_PORT", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "LOCAL_PORT", "9990")) },
						{ new KeyValuePair<string, string>("NUM_PORTS", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "NUM_PORTS", "32")) },
						{ new KeyValuePair<string, string>("NUM_DYNAMIC_NODES", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "NUM_DYNAMIC_NODES", "32")) },
						{ new KeyValuePair<string, string>("SERVER_ADDRESS", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "SERVER_ADDRESS", "127.0.0.1:9990")) },
						{ new KeyValuePair<string, string>("UDP_TX_BUFFER_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "UDP_TX_BUFFER_SIZE", "5000")) },
						{ new KeyValuePair<string, string>("UDP_RX_BUFFER_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "UDP_RX_BUFFER_SIZE", "32768")) },
						{ new KeyValuePair<string, string>("TCP_TX_BUFFER_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "TCP_TX_BUFFER_SIZE", "5000")) },
						{ new KeyValuePair<string, string>("TCP_RX_BUFFER_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "TCP_RX_BUFFER_SIZE", "4096")) },
						{ new KeyValuePair<string, string>("PACKET_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "PACKET_SIZE", "5128")) },
						{ new KeyValuePair<string, string>("TCP_SELECT_VALUE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "TCP_SELECT_VALUE", "1")) },
						{ new KeyValuePair<string, string>("DISABLE_NAG_DELAY", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISABLE_NAG_DELAY", "1")) },
						{ new KeyValuePair<string, string>("TIMER_RATE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "TIMER_RATE", "1000")) },
						{ new KeyValuePair<string, string>("CONNECT_KA_RATE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "CONNECT_KA_RATE", "1")) },
						{ new KeyValuePair<string, string>("RECV_KA_RATE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "RECV_KA_RATE", "1")) },
						{ new KeyValuePair<string, string>("SERVER_CONNECT_RATE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "SERVER_CONNECT_RATE", "1")) },
						{ new KeyValuePair<string, string>("RECV_THREAD_STACK_SIZE", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_STACK_SIZE", "4096")) },
						{ new KeyValuePair<string, string>("RECV_THREAD_PRIORITY", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_PRIORITY", "0")) },
						{ new KeyValuePair<string, string>("RECV_THREAD_AFFINITY", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_AFFINITY", "0")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND_BUFFER", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND_BUFFER", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_UDP_RECV", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_UDP_RECV", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_UDP_RECV_BUFFER", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_UDP_RECV_BUFFER", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_TCP_RECV", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_TCP_RECV", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_TCP_RECV_BUFFER", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_TCP_RECV_BUFFER", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "1")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV_BUFFER", _configuration.GetConfigurationValue<string>("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV_BUFFER", "1")) },
					});
					break;

				case DriverType.Serial:
					_options.Add("SERIAL_MEDIA_BINDING_CONFIG", new List<KeyValuePair<string, string>>
					{
						{ new KeyValuePair<string, string>("DEVICE_NAME", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "DEVICE_NAME", "\\\\.\\COM1")) },
						{ new KeyValuePair<string, string>("BAUD_RATE", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "BAUD_RATE", "9600")) },
						{ new KeyValuePair<string, string>("DATA_BITS", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "DATA_BITS", "8")) },
						{ new KeyValuePair<string, string>("STOP_BITS", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "STOP_BITS", "1")) },
						{ new KeyValuePair<string, string>("PARITY", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "PARITY", "0")) },
						{ new KeyValuePair<string, string>("FLOW_CONTROL", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "FLOW_CONTROL", "0")) },
						{ new KeyValuePair<string, string>("MTU_SIZE", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "MTU_SIZE", "256")) },
						{ new KeyValuePair<string, string>("RECV_PROCESSING_DELAY", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "RECV_PROCESSING_DELAY", "100")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "0")) },
						{ new KeyValuePair<string, string>("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue<string>("SERIAL_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "0")) },
					});
					break;

				default:
					_logger.Error($"{Name}({_driverType}) Configured driver type not valid");
					break;
			}

			_maxMessageSize = _configuration.GetConfigurationValue<uint>("Parameters", "MaxMessageSize", "5000");
			_epQueueDepth = _configuration.GetConfigurationValue<uint>("Parameters", "EPQueueDepth", "5000");
			_checkForMessageIntervalMs = _configuration.GetConfigurationValue<int>("Parameters", "CheckForMessageIntervalMs", "100");

			var responseLabels = _configuration.GetConfigurationListValue("ResponseMessageIds", "ResponseLabel", new List<string> { "1", "2" });

			var bitFilePaths = _configuration.GetConfigurationListValue("BitFilePaths", "FilePath", new List<string> { "File1", "File2" });

			_xmlDocs.Clear();
			foreach (var path in bitFilePaths)
			{
				_xmlDocs.Add(path, new MessageXmlDocument(path, _logger));
			}

			_icds.Clear();
			foreach (var path in bitFilePaths)
			{
				_icds.Add(path, ProcessFileForNamesAndLabels(path));
			}

			foreach (var strLabel in responseLabels)
			{
				uint label = GetLabelFromMessageId(strLabel);
				if (label > 0)
					_responseLabels.Add(label);
			}

			DetailedStatus = "COE Initialized";
			Status = State.Ready;
		}

		/// <summary>
		/// performs self-test
		/// </summary>
		/// <returns></returns>
		public SelfTestResult PerformSelfTest()
		{
			_logger.Trace($"{Name}({_driverType}) Performing Self Test...");

			// TODO implement method
			return SelfTestResult.Pass;
		}

		/// <summary>
		/// Resets COE device comms
		/// </summary>
		/// <exception cref="NotImplementedException"></exception>
		public void Reset()
		{
			_logger.Trace($"{Name}({_driverType}) Resetting...");

			Close();

			Open();
		}

		/// <summary>
		/// Shuts down COE device
		/// </summary>
		public void Shutdown()
		{
			_logger.Trace($"{Name}({_driverType}) Shutting Down...");
			try
			{
				Close();
				//coe.UnloadImportedDll("coeWindows-shared.dll");
			}
			catch (Exception ex)
			{
				_logger.Error(ex, $"{Name}({_driverType}) Error while closing");
			}
		}

		#region IBit functions
		/// <summary>
		/// Opens COE connection
		/// </summary>
		/// <exception cref="NotImplementedException"></exception>
		public void Open()
		{
			_logger.Trace($"{Name}({_driverType}) Opening...");

			try
			{
				switch (_driverType)
				{
					case DriverType.TCP:

						if (_coe.tcp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS)
						{
							_logger.Error($"{Name}({_driverType}) COE TCP media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision.");
							Status = State.CommunicationFailure;
							throw new BitNotConnectedException($"{Name}({_driverType}) COE TCP media binding initialization failure");
						}
						_logger.Trace($"{Name}({_driverType}) COE TCP media binding initialization, {_coe.ProtocolCmitName}");
						break;
					case DriverType.UDP:
						if (_coe.udp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS)
						{
							_logger.Error($"{Name}({_driverType}) COE UDP media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision.");
							Status = State.CommunicationFailure;
							throw new BitNotConnectedException($"{Name}({_driverType}) COE UDP media binding initialization failure");
						}
						_logger.Trace($"{Name}({_driverType}) COE UDP media binding initialization, Local: {_coe.ProtocolCmitName} Remote: {_coe.ProtocolName}");
						break;
					case DriverType.Serial:
						if (_coe.serial_media_binding_configure(_options, _logger) != coe.Status.SUCCESS)
						{
							_logger.Error($"{Name}({_driverType}) COE Serial media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision.");
							Status = State.CommunicationFailure;
							throw new BitNotConnectedException($"{Name}({_driverType}) COE Serial media binding initialization failure");
						}
						_logger.Trace($"{Name}({_driverType}) COE Serial media binding initialization, {_coe.ProtocolCmitName}");
						break;
					default:
						_logger.Error($"{Name}({_driverType}) Configured driver type not valid");
						throw new BitNotConnectedException($"{Name}({_driverType}) Configured driver type not valid");
				}

				foreach (var item in _options)
				{
					_logger.Trace($"{item.Key}:");
					foreach (var pair in item.Value)
					{
						_logger.Trace(string.Format("{0,-50} {1, -40}", pair.Key, pair.Value));
					}
				}

			}
			catch (Exception ex)
			{
				_logger.Error(ex);
				Status = State.CommunicationFailure;
				DetailedStatus = "Unable to Open";
				throw;
			}

			try
			{
				_coe.SetConnected(true);

				//_endpoint = new coeEndpoint(_maxMessageSize, _epQueueDepth, _coe.Router);
				_endpoint = new coeEndpoint(_maxMessageSize, _epQueueDepth);

				_logger.Info($"{Name}({_driverType}) Endpoint Created, Max Message Size: {_maxMessageSize}, Queue Depth: {_epQueueDepth}");

				foreach (var item in _responseLabels)
				{
					var fileName = WhichFileContainsTheLabel(item);
					if (!string.IsNullOrEmpty(fileName))
					{
						var msgName = _icds[fileName][item];
						if (!string.IsNullOrEmpty(msgName))
						{
							_endpoint.Register(item);
							_logger.Debug($"{Name}({_driverType}) Registering new message with the endpoint, {item}: {msgName}");
						}
						else
						{
							_logger.Warn($"{Name}({_driverType}) Message with label {item} is not located in file {fileName}");
						}
					}
					else
					{
						_logger.Warn($"{Name}({_driverType}) Unable to locate label {item} in any of the XML files registered for COE device");
					}
				}

				_cancellationTokenSource = new CancellationTokenSource();
				Task.Run(() => ReadMessages(_cancellationTokenSource.Token));

				Status = State.Ready;
				DetailedStatus = "Opened";
			}
			catch (Exception ex)
			{
				Status = State.CommunicationFailure;
				DetailedStatus = "Unable to Open";
				_logger.Error(ex);
				throw;
			}
		}

		/// <summary>
		/// Close COE endpoint
		/// </summary>
		/// <exception cref="NotImplementedException"></exception>
		public void Close()
		{
			_logger.Trace($"{Name}({_driverType}) Closing ...");

			Status = State.Uninitialized;

			_cancellationTokenSource?.Cancel();

			Thread.Sleep(1000);

			if (_messages.Any())
			{
				foreach (var queue in _messages)
				{
					while (queue.Value.TryDequeue(out Tuple<DateTime, OeMessage> throwAway))
					{
						_logger.Warn($"Message {throwAway.Item2.Label} ({throwAway.Item2.XmlMessage.Name}) received at {throwAway.Item1:hh.mm.ss.fff} was unclaimed");
					}
				}
			}

			_messages.Clear();

			_coe.SetConnected(false);
			var status = _driverType == DriverType.TCP ?
				_coe.TCP_media_binding_shutdown(_logger) : _driverType == DriverType.UDP ?
				_coe.UDP_media_binding_shutdown(_logger) : _coe.SERIAL_media_binding_shutdown(_logger);

			if (status != coe.Status.SUCCESS)
			{
				_logger.Error($"{_driverType} media binding shutdown failure, status {status}");
			}
			else
			{
				_logger.Debug($"{_driverType} shutdown was successful");
			}

			_endpoint?.Dispose();

			_cancellationTokenSource?.Dispose();
			_cancellationTokenSource = null;

			DetailedStatus = "Closed";
		}

		/// <summary>
		/// runs single BIT test request, no waiting for response
		/// expecting user to run 
		/// </summary>
		/// <param name="messageId"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		/// <exception cref="NotImplementedException"></exception>
		public bool RunBIT(string messageId, uint timeoutInMs, IEnumerable<KeyValuePair<string, string>> messageParams)
		{
			_logger.Trace($"{Name}({_driverType}) Running BIT for {messageId} with timeout {timeoutInMs} ...");

			if (!_coe.IsConnected)
			{
				_logger.Error("Error sending COE message, COE not connected");
				throw new BitNotConnectedException();
			}
			if (Status != State.Ready)
			{
				_logger.Warn("Exiting RunBIT due to status");
				throw new BitNotConnectedException();
			}

			var label = GetLabelFromMessageId(messageId);

			var path = WhichFileContainsTheLabel(label);
			if (string.IsNullOrEmpty(path))
			{
				var msg = $"Message Id {messageId} not found in any of the BIT files";
				_logger.Error(msg);
				throw new BitParseException(msg);
			}

			try
			{
				var message = GetOeMessageWithParameters(label, messageParams, path);

				_logger.Info("Sending ...");
				message.XmlMessage.SendToLog(EnumerationType.EXPANDED_FIELDS, MessageDirection.Out);

				var status = _endpoint.Send(message);

				if (status != coe.Status.SUCCESS)
				{
					_logger.Error($"Error sending COE message, error code: {status}");
				}

				return true;
			}
			catch (Exception ex)
			{
				_logger.Error(ex);
				return false;
			}
		}

		/// <summary>
		/// Runs a BIT and expects a result
		/// </summary>
		/// <param name="messageIdOut"></param>
		/// <param name="messageIdIn"></param>
		/// <param name="timeoutInMs"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		/// <exception cref="NotImplementedException"></exception>
		public BitTestResults RunBITWaitForResults(string messageIdOut, string messageIdIn, uint timeoutInMs, IEnumerable<KeyValuePair<string, string>> messageParams)
		{
			_logger.Trace($"{Name}({_driverType}) Running BIT for {messageIdOut} and waiting for result {messageIdIn} with timeout {timeoutInMs}...");

			if (!_coe.IsConnected)
			{
				_logger.Error("Error sending COE message, COE not connected");
				throw new BitNotConnectedException();
			}

			if (Status != State.Ready)
			{
				_logger.Warn("Exiting RunBITWaitForResults due to status");
				throw new BitNotConnectedException();
			}

			if (RunBIT(messageIdOut, timeoutInMs, messageParams))
			{
				if (string.IsNullOrEmpty(messageIdIn))
				{
					messageIdIn = "0";
				}

				string[] multipleIds = messageIdIn.Split(',');

				var totalWaitTimeMs = 0;

				BitTestResults results = null;
				do
				{
					foreach (var id in multipleIds)
					{
						results = GetBITResults(id);

						if (results != null)
						{
							break;
						}
					}

					if (results != null || Status != State.Ready)
					{
						break;
					}
					else 
					{
						Thread.Sleep(_checkForMessageIntervalMs);
						totalWaitTimeMs += _checkForMessageIntervalMs;
					}

				} while (results == null && totalWaitTimeMs < timeoutInMs);

				if (results != null)
				{
					_logger.Debug($"-- Successfully retrieved result message, totalWaitTimeMs = {totalWaitTimeMs}");
					return results;
				}
				else
					throw new BitTimeoutException();
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Reads BIT results
		/// </summary>
		/// <param name="messageId"></param>
		/// <returns></returns>
		/// <exception cref="NotImplementedException"></exception>
		public BitTestResults GetBITResults(string messageId)
		{
			if (!_coe.IsConnected)
			{
				_logger.Error("Error reading COE message, COE not connected");
				throw new BitNotConnectedException();
			}

			uint label = 0;
			// empty string or zero means first available message from the top
			if (!string.IsNullOrEmpty(messageId) && messageId != "0")
			{
				label = GetLabelFromMessageId(messageId);
				if (label == 0)
				{
					_logger.Error($"{Name}({_driverType}) Unable to match message {messageId} with anything in the dictionary. Check your configuration");
					return null;
				}
			}

			string strLabel = label.ToString();

			ConcurrentQueue<Tuple<DateTime, OeMessage>> queue;

			lock (this)
			{
				queue = label == 0 ?
					_messages.Any() ? _messages.FirstOrDefault().Value : null :
					_messages.ContainsKey(strLabel) ? _messages[strLabel] : null;
			}

			if (queue != null && queue.TryDequeue(out Tuple<DateTime, OeMessage> message))
			{
				var oeMessage = message.Item2;

				if (queue.IsEmpty)
				{
					lock (this)
					{
						_messages.Remove(oeMessage.Label);
					}
				}

				// make a copy of the buffer
				var xmlMessage = oeMessage.XmlMessage;
				if (xmlMessage != null)
				{
					var results = new BitTestResults
					{
						Label = oeMessage.Label,
						Time = message.Item1,

						// parse message result into list of results
						Results = FromXmlToBitTestResults(xmlMessage)
					};

					return results;
				}
				else
				{
					var msg = $"Found a message with label {label}, but the Buffer is empty";
					_logger.Error(msg);
					throw new BitParseException(msg);
				}
			}
			else
			{
				// message not found
				return null;
			}
		}

		#endregion

		#region Private Functions

		/// <summary>
		/// keep reading messages and stash them in _messages dictionary with label and timestamp as a key
		/// </summary>
		/// <param name="cancellationToken"></param>
		/// <returns></returns>
		private void ReadMessages(CancellationToken cancellationToken)
		{
			_logger.Debug($"{Name}({_driverType}) Starting to read messages.");

			try
			{
				while (!cancellationToken.IsCancellationRequested)
				{
					//_logger.Debug($"{Name}({_driverType}) Checking for messages...");
					var status = _endpoint.Wait(1000);

					if (status == coe.Status.SUCCESS)
					{
						_logger.Debug("Message Received...");

						while (_endpoint.Peek(out uint label, out uint size, out int priority) == coe.Status.SUCCESS)
						{
							var hexLabel = $"0x{label:X}";
							_logger.Debug($"{Name}({_driverType}) Identified message by peeking... {label} ({hexLabel})");

							var xmlDoc = WhichFileContainsTheLabel(label);

							var message = new OeMessage((int)size + 1)
							{
								XmlMessage = new Message(_xmlDocs[xmlDoc], label.ToString())
							};

							status = _endpoint.Receive(message);

							if (status == coe.Status.SUCCESS)
							{
								_logger.Debug($"{Name}({_driverType}) Successfully read message... Label: {hexLabel} ({message.XmlMessage?.Name})");

								message.XmlMessage.SendToLog(EnumerationType.EXPANDED_FIELDS, MessageDirection.In);

								ConcurrentQueue<Tuple<DateTime, OeMessage>> queue;

								lock (this)
								{
									if (!_messages.ContainsKey(message.Label))
									{
										_messages.Add(message.Label, new ConcurrentQueue<Tuple<DateTime, OeMessage>>());
									}
									queue = _messages[message.Label];
								}

								queue.Enqueue(new Tuple<DateTime, OeMessage>(DateTime.Now, message));
							}
							else
							{
								_logger.Error($"{Name}({_driverType}) Endpoint Receive Failed. Status = {status}");
							}
						}
					}
					// If not timeout and no cancellation requested
					else if (status != coe.Status.FAILED_TIMEOUT && !cancellationToken.IsCancellationRequested)
					{
						_logger.Error($"{Name}({_driverType}) Event Flag Wait Failed. Status = {status}");
					}
				}
				_logger.Debug($"{Name}({_driverType}) Stopping to read messages. Cancellation was requested.");
			}
			catch (Exception ex)
			{
				_logger.Error(ex);
			}
		}

		/// <summary>
		/// if message id can be converted to uint returns the value
		/// otherwise returns the related label from the message id by dictionary lookup
		/// </summary>
		/// <param name="label"></param>
		/// <returns></returns>
		private uint GetLabelFromMessageId(string messageId)
		{
			uint labelId = FromStringToUint(messageId);

			if (labelId == 0)
			{
				foreach (var file in _icds)
				{
					var item = file.Value.FirstOrDefault(l => l.Value == messageId);
					if (!string.IsNullOrEmpty(item.Value))
						return item.Key;
				}
			}

			return labelId;
		}
		/// <summary>
		/// return file path for the file that contains the label
		/// </summary>
		/// <param name="label"></param>
		/// <returns></returns>
		private string WhichFileContainsTheLabel(uint label)
		{
			foreach (var item in _icds)
			{
				if (item.Value.Keys.Contains(label))
				{
					return item.Key;
				}
			}
			return string.Empty;
		}

		/// <summary>
		/// convert from Message to list of BItTestResult fields
		/// </summary>
		/// <param name="message"></param>
		/// <returns></returns>
		private IList<BitTestResult> FromXmlToBitTestResults(Message message)
		{
			if (message == null)
				return null;

			var result = FromMessageArrayToBitResult(message.MessageDataArray);

			return result;
		}

		/// <summary>
		/// recursive function for getting results out
		/// </summary>
		/// <param name="messages"></param>
		/// <returns></returns>
		private IList<BitTestResult> FromMessageArrayToBitResult(MessageData[] messages)
		{
			if (messages == null || messages.Length == 0)
				return null;

			var result = new List<BitTestResult>();

			foreach (var item in messages)
			{
				result.Add(FromMessageDataToBitTestResult(item));

				if (item.MessageArray != null && item.MessageArray.Length > 0)
				{
					var moreResults = FromMessageArrayToBitResult(item.MessageArray);
					result.AddRange(moreResults);
				}
			}

			return result;
		}

		/// <summary>
		/// copy message data fields to BitTestResult
		/// </summary>
		/// <param name="from"></param>
		/// <returns></returns>
		private static BitTestResult FromMessageDataToBitTestResult(MessageData from)
		{
			if (from == null)
				return null;

			return new BitTestResult
			{
				FieldArrayValue = from.FieldArrayValue,
				FieldBitValue = from.FieldBitValue,
				FieldDefaultValue = from.FieldDefaultValue,
				FieldInstruType = from.FieldInstruType,
				FieldMaxValue = from.FieldMaxValue,
				FieldMinValue = from.FieldMinValue,
				FieldName = from.FieldName,
				FieldType = from.FieldType,
				FieldValue = from.FieldValue,
				Variable = from.Variable,
				MaxOffset = from.MaxOffset,
				MinOffset = from.MinOffset,
				VerifyType = from.VerifyType,
				IsSelected = from.isSelected,
				IsArray = from.isArray,
				IsStructure = from.isStructure,
				IsArrayOfStructures = from.isArrayOfStructures,
				IsEnum = from.isEnum,
				UsesRegister = from.usesRegister,
				IsValid = from.isValid,
				UseRange = from.useRange,
				ArrayLength = from.arrayLength,
				ImageWidth = from.imageWidth,
				ImageHeight = from.imageHeight,
				ImagePixelSize = from.imagePixelSize,
				BitMask = from.bitMask,
				Expanded = from.expanded,
				Depth = from.depth,
				ImageBuffer = from.imageBuffer?.ToArray(),
				ImageBufferSize = from.imageBufferSize,
			};
		}

		/// <summary>
		/// Build OeMessage from messageId and provided parameters 
		/// </summary>
		/// <param name="messageId"></param>
		/// <param name="messageParams"></param>
		/// <returns></returns>
		private OeMessage GetOeMessageWithParameters(uint messageId, IEnumerable<KeyValuePair<string, string>> messageParams, string bitFilePath)
		{
			var messageName = _icds[bitFilePath][messageId];
			var message = new OeMessage(new Message(messageName, new MessageXmlDocument(bitFilePath, _logger)));

			if (messageParams != null)
			{
				message.XmlMessage.MessageDataArray = PopulateParameters(message.XmlMessage.MessageDataArray, 0, messageParams);
			}

			return message;
		}

		/// <summary>
		/// recursive function to populate parameters
		/// </summary>
		/// <param name="data">message data array</param>
		/// <param name="level">indicates how deep in the tree we are populating parameters</param>
		/// <param name="messageParams">message parameters</param>
		/// <returns></returns>
		internal MessageData[] PopulateParameters(MessageData[] data, int level, IEnumerable<KeyValuePair<string, string>> messageParams)
		{
			// only get parameters from the same level
			var levelParams = messageParams.Where(m => m.Key.Where(c => c == '.' || c == ']').Count() == level);

			foreach (var item in data)
			{
				if (item.FieldName.StartsWith("$"))
					continue;

				var messageParam = levelParams.FirstOrDefault(m => m.Key.EndsWith(item.FieldName));
				if (!string.IsNullOrEmpty(messageParam.Key) && !string.IsNullOrEmpty(messageParam.Value))
				{
					item.FieldValue = messageParam.Value;
				}
				// always send defaults means that even if parameter was not provided use the default value to populate field value
				else if (_alwaysSendDefaults)
				{
					item.FieldValue = item.FieldDefaultValue;
				}

				// if there are more levels, update recursively
				if (item.MessageArray != null && item.MessageArray.Length > 0)
					item.MessageArray = PopulateParameters(item.MessageArray, level + 1, messageParams);
			}

			return data;
		}

		/// <summary>
		/// reads xml file and extracts all message names with associated labels
		/// </summary>
		/// <param name="filePath"></param>
		/// <returns></returns>
		private Dictionary<uint, string> ProcessFileForNamesAndLabels(string filePath)
		{
			var doc = new XPathDocument(filePath);

			XPathNavigator node = doc.CreateNavigator();
			XPathNodeIterator nodeset = node.Select("interface/message");

			var result = new Dictionary<uint, string>();

			while (nodeset.MoveNext())
			{
				var children = nodeset.Current.SelectChildren(XPathNodeType.Element);
				if (children.Count > 0)
				{
					string strName = string.Empty;
					string strLabel = string.Empty;

					while (children.MoveNext())
					{
						if (children.Current.Name == "name")
						{
							strName = children.Current.Value;
							if (!string.IsNullOrEmpty(strName))
								strName = strName.Trim();
						}
						else if (children.Current.Name == "label")
						{
							strLabel = children.Current.Value;
							if (!string.IsNullOrEmpty(strLabel))
								strLabel = strLabel.Trim();
						}
					}

					uint iLabel = FromStringToUint(strLabel);

					if (!string.IsNullOrEmpty(strName) && iLabel > 0)
					{
						result.Add(iLabel, strName);
					}
				}
			}
			return result;
		}

		/// <summary>
		/// converts from string representation of a label to uint value
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		private uint FromStringToUint(string data)
		{
			if (!string.IsNullOrEmpty(data))
			{
				if (data.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase) || data.StartsWith("&H", StringComparison.CurrentCultureIgnoreCase))
				{
					if (uint.TryParse(data.Substring(2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uint uiValue))
						return uiValue;
				}
				else
				{
					if (uint.TryParse(data, out uint uiValuel))
						return uiValuel;
				}
			}
			return 0;
		}

		#endregion
	}
}
