//                           UNCLASSIFIED
/*-------------------------------------------------------------------------
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.

THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
GOVERNMENT.

UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
-------------------------------------------------------------------------*/

using System;
using System.IO.Ports;
using System.Net.Sockets;
using System.Text;
using NLog;
using Raytheon.Common;

namespace Raytheon.Instruments
{
	/// <summary>
	/// Flow Meter class used to interact with M1676 flow meter
	/// </summary>
	public class FlowMeterOmegaM1676 : IFlowMeter
	{
		#region PrivateMembers
		//Commands
		private const string _CARRIAGE_RTN = "\r";
		private const string _FLOW = "@U?V";
		private const string _GET_VERSION = "@U?SC";
		private const string _SET_CONFIG = "@U?SP2B";
		private const string _SET_DEC_POINT = "@U?SP2A0";
		private const string _SET_SCALE_1ST_BYTE = "@U?SP2F";
		private const string _SET_SCALE_2ND_BYTE = "@U?SP30";
		private const string _SET_SCALE_3RD_BYTE = "@U?SP31";

		//Prevent Collisions
		private static object _sync = new object();

		//Ethernet Communication
		private readonly string _ipAddr;
		private readonly int _port;
		private const int _READ_BUFFER_SIZE = 1024;
		private const int _READ_TIMEOUT = 5000;
		private byte[] _readBuffer;
		private NetworkStream _tcpStream;

		/// <summary>
		/// NLog logger
		/// </summary>
		private readonly ILogger _logger;
		/// <summary>
		/// Raytheon configuration
		/// </summary>
		private readonly IConfigurationManager _configurationManager;
		private readonly IConfiguration _configuration;

		public string DetailedStatus => throw new NotImplementedException();

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

		public InstrumentMetadata Info => throw new NotImplementedException();

		public string Name { get; set;}

		public SelfTestResult SelfTestResult => throw new NotImplementedException();

		public State Status => throw new NotImplementedException();

		#endregion

		#region PrivateFunctions
		/// <summary>
		/// Destructor
		/// </summary>
		~FlowMeterOmegaM1676()
		{
			Dispose(false);
		}

		/// <summary>
		/// Open socket to the flow meter
		/// </summary>
		private void ConnectEthernet()
		{
			//Create and open a socket to flow meter
			TcpClient flowSocket = new TcpClient(_ipAddr, _port);
			_tcpStream = flowSocket.GetStream();
			_tcpStream.ReadTimeout = _READ_TIMEOUT;
		}


		/// <summary>
		/// Turn off the alarm feature of the flow meter
		/// </summary>
		private void ConfigureAlarm()
		{
			lock (_sync)
			{
				//Turn off the Alarm in the response messages -> 000001
				SendMessageNoResponse(_SET_CONFIG + "01");
			}
		}

		/// <summary>
		/// Used to set the decimal place of the measurement and display:
		/// 0 --> Auto Range
		/// 1 --> FFFFFF.
		/// 2 --> FFFFF.F
		/// 3 --> FFFF.FF
		/// 4 --> FFF.FFF
		/// 5 --> FF.FFFF
		/// 6 --> F.FFFFF
		/// </summary>
		/// <param name="option">Set the decimal place of the measurement.</param>
		private void ConfigureDecimalOutput(int option)
		{
			lock (_sync)
			{
				if (option < 0)
				{
					option = 0;
				}
				else if (option > 6)
				{
					option = 0;
				}

				SendMessageNoResponse(_SET_DEC_POINT + option.ToString());
			}
		}

		/// <summary>
		/// Configure the scale factor to .002 (per CIL manual)
		/// </summary>
		private void ConfigureScaleFactor()
		{
			lock (_sync)
			{
				//Per manual .002 --> 0x400002
				//Send a byte at a time (0x40, 0x00, 0x02)
				SendMessageNoResponse(_SET_SCALE_1ST_BYTE + "02");
				SendMessageNoResponse(_SET_SCALE_2ND_BYTE + "00");
				SendMessageNoResponse(_SET_SCALE_3RD_BYTE + "40");
			}
		}

		/// <summary>
		/// Dispose of this object's resources
		/// </summary>
		/// <param name="disposing">Currently disposing</param>
		protected virtual void Dispose(bool disposing)
		{
			try
			{
				if (disposing)
				{
					//Close Connection (if available)
					_tcpStream?.Close();
				}
			}
			catch (Exception)
			{
				try
				{
					//ErrorLogger.Instance().Write(err.Message + "\r\n" + err.StackTrace);
				}
				catch (Exception)
				{
					//Do not rethrow. Exception from error logger that has already been garbage collected
				}
			}
		}

		/// <summary>
		/// Send a command to the flow meter and request a response
		/// </summary>
		/// <param name="cmd">Command to send.</param>
		/// <returns>Response from the flow meter.</returns>
		private string SendMessageGetResponse(string cmd)
		{
			lock (_sync)
			{
				//Send the data
				SendMessageNoResponse(cmd);

				//clear the buffer
				Array.Clear(_readBuffer, 0, _readBuffer.Length);

				//read from the response buffer
				int numBytesRead = _tcpStream.Read(_readBuffer, 0, _readBuffer.Length);

				//convert response to a string
				string rspStr = Encoding.ASCII.GetString(_readBuffer);

				//Check Response
				if (rspStr.Contains(_CARRIAGE_RTN))
				{
					//Parse string ("/r")
					char[] delimit = { '\r' };
					string[] parsed = rspStr.Split(delimit, StringSplitOptions.RemoveEmptyEntries);

					//Return parsed message
					return parsed[0];
				}
				else
				{
					throw new Exception("Command message not successful");
				}
			}
		}

		/// <summary>
		/// Send a command to the flow meter
		/// </summary>
		/// <param name="cmd">The command to send</param>
		private void SendMessageNoResponse(string cmd)
		{
			lock (_sync)
			{
				//Format the command before sending
				string commandString = cmd + _CARRIAGE_RTN;

				//convert to byte array for sending
				byte[] commandBuffer = Encoding.ASCII.GetBytes(commandString);

				//send the data
				_tcpStream.Write(commandBuffer, 0, commandBuffer.Length);
			}
		}
		#endregion

		#region PublicFunctions

		/// <summary>
		/// FlowMeterOmegaM1676 factory constructor
		/// </summary>
		/// <param name="deviceName"></param>
		/// <param name="configurationManager"></param>
		public FlowMeterOmegaM1676(string deviceName, IConfigurationManager configurationManager, ILogger logger)
		{
			Name = deviceName;

			_logger = logger;

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


			_ipAddr = _configuration.GetConfigurationValue("FlowMeterOmegaM1676", "IpAddr", "");
			_port = _configuration.GetConfigurationValue("FlowMeterOmegaM1676", "Port", 0);

			_readBuffer = new byte[_READ_BUFFER_SIZE];
			//Connect to and configure device
			ConnectEthernet();

			//Configuration
			ConfigureAlarm();
			ConfigureDecimalOutput(4);
			ConfigureScaleFactor();
		}

		/// <summary>
		/// Constructor for the M1676 flow meter. Forms a socket connection upon construction
		/// </summary>
		/// <param name="name">The name. Anything the host wants it to be</param>
		/// <param name="ipAddress">IP Address of the flow meter</param>
		/// <param name="port">Port of the flow meter</param>
		public FlowMeterOmegaM1676(string name, string ipAddress, int port)
		{
			Name = name;
			_logger = LogManager.GetCurrentClassLogger();
			_ipAddr = ipAddress;
			_port = port;

			//Initialize read buffer
			_readBuffer = new byte[_READ_BUFFER_SIZE];

			//Connect to and configure device
			ConnectEthernet();

			//Configuration
			ConfigureAlarm();
			ConfigureDecimalOutput(4);
			ConfigureScaleFactor();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="comPortName"></param>
		/// <param name="delayBeforeReadMs"></param>
		/// <param name="baudRate"></param>
		/// <param name="parity"></param>
		/// <param name="dataBits"></param>
		/// <param name="stopBits"></param>
		public FlowMeterOmegaM1676(string name, string comPortName, uint delayBeforeReadMs, int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
		{
			throw new NotImplementedException();
		}

		// This code added to correctly implement the disposable pattern.
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
		public void Dispose()
		{
			try
			{
				lock (_sync)
				{
					Dispose(true);

					GC.SuppressFinalize(this);
				}
			}
			catch (Exception)
			{
				try
				{
					//ErrorLogger.Instance().Write(err.Message + "\r\n" + err.StackTrace);
				}
				catch (Exception)
				{
					//Do not rethrow. Exception from error logger that has already been garbage collected
				}
			}
		}

		/// <summary>
		/// Query the flow rate from the flow meter
		/// </summary>
		/// <returns>the flow rate.</returns>
		public double ReadFlow()
		{
			lock (_sync)
			{
				string rsp = SendMessageGetResponse(_FLOW);

				//Convert string value to double and return
				return double.Parse(rsp);
			}
		}

		public bool ClearErrors()
		{
			throw new NotImplementedException();
		}

		public void Initialize()
		{
			throw new NotImplementedException();
		}

		public SelfTestResult PerformSelfTest()
		{
			throw new NotImplementedException();
		}

		public void Reset()
		{
			throw new NotImplementedException();
		}

		public void Shutdown()
		{
			throw new NotImplementedException();
		}

		#endregion
	}
}
