//                           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.Threading;
using NLog;
using Raytheon.Common;

namespace Raytheon.Instruments
{
   /// <summary>
   /// Flow Meter class used to interact with flow meter.
   /// It supports the Omega DPF20 when configured to slave mode (where data is pulled as opposed to pushed). 
   /// Set to slave mode on the front panel
   /// </summary>
   public class FlowMeterOmegaDPF20 : IFlowMeter
   {
      #region PublicMembers
      public enum FlowMeterMode
      {
         MASTER,
         SLAVE
      };
      #endregion
      #region PrivateMembers

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

      //Ethernet Communication
      private readonly string _ipAddr;
      private readonly int _port;
      private readonly byte _flowMeterAddress;
      private readonly FlowMeterOmegaDPF20.FlowMeterMode _mode;
      private const int _READ_BUFFER_SIZE = 1024;
      private const int _READ_TIMEOUT = 5500;
      private byte[] _readBuffer;
      private NetworkStream _tcpStream;
      TcpClient _flowSocket;

      private readonly ILogger _logger;

      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>
      ~FlowMeterOmegaDPF20()
      {
         Dispose(false);
      }

      /// <summary>
      /// Dispose of this object's resources
      /// </summary>
      /// <param name="disposing">Currently disposing</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            //Close Connection (if available)
            _tcpStream?.Close();
            _flowSocket?.Close();
         }
      }

      #endregion

      #region PublicFunctions

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

         _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}");

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


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

         _readBuffer = new byte[_READ_BUFFER_SIZE];

         //Create and open a socket to flow meter
         _flowSocket = new TcpClient(_ipAddr, _port);
         _tcpStream = _flowSocket.GetStream();
         _tcpStream.ReadTimeout = _READ_TIMEOUT;
      }

      /// <summary>
      /// Constructor for the DPF20 flow meter. It makes a socket connection upon construction
      /// </summary>
      /// <param name="deviceName">The name. Anything the hosts wants it to be</param>
      /// <param name="ipAddress">IP Address of the flow meter server</param>
      /// <param name="port">Port of the flow meter server</param>
      /// <param name="flowMeterAddress">The address on the front display of the flow meter</param>
      /// <param name="mode">The mode on the front display of the flow meter</param>
      public FlowMeterOmegaDPF20(string deviceName, string ipAddress, int port, byte flowMeterAddress, FlowMeterOmegaDPF20.FlowMeterMode mode)
      {
         Name = deviceName;
         _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}");

         _ipAddr = ipAddress;
         _port = port;
         _flowMeterAddress = flowMeterAddress;
         _mode = mode;

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

         //Create and open a socket to flow meter
         _flowSocket = new TcpClient(_ipAddr, _port);
         _tcpStream = _flowSocket.GetStream();
         _tcpStream.ReadTimeout = _READ_TIMEOUT;
      }

      /// <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>
      /// <param name="flowMeterAddress">The address on the front display of the flow meter</param>
      /// <param name="mode">The mode on the front display of the flow meter</param>
      public FlowMeterOmegaDPF20(string name, string comPortName, uint delayBeforeReadMs, int baudRate = 115200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One, byte flowMeterAddress = 128, FlowMeterOmegaDPF20.FlowMeterMode mode = FlowMeterMode.SLAVE)
      {
         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 ex)
         {
            _logger.Error(ex.Message);
         }
      }

      /// <summary>
      /// Query the flow rate from the flow meter
      /// </summary>
      /// <returns>the flow rate.</returns>
      public double ReadFlow()
      {
         lock (_sync)
         {
            if (_mode == FlowMeterMode.SLAVE)
            {
               byte[] cmd = FlowMeterOmegaDPF20DataParser.CreateReadFlowCmd(_flowMeterAddress);

               _tcpStream.Write(cmd, 0, cmd.Length);

               int numBytesRead = _tcpStream.Read(_readBuffer, 0, _readBuffer.Length);

               double flow = FlowMeterOmegaDPF20DataParser.ParseReadFlowRsp(_readBuffer, numBytesRead);

               return flow;
            }
            else if (_mode == FlowMeterMode.MASTER)
            {
               const int EXPECTED_DATA_LEN = 18;

               // ensure we have at least one full message
               // need at least 35 bytes (worst case partial msg plus a full message)
               int numBytesAvailable = _flowSocket.Available;
               while (numBytesAvailable < (EXPECTED_DATA_LEN * 2) - 1)
               {
                  Thread.Sleep(5);
                  numBytesAvailable = _flowSocket.Available;
               }

               byte[] readBuf = new byte[numBytesAvailable];

               int numBytesRead = _tcpStream.Read(readBuf, 0, readBuf.Length);

               double flow = FlowMeterOmegaDPF20DataParser.ParseMasterFlowMsg(readBuf, numBytesRead);

               return flow;
            }
            else
            {
               throw new Exception("unknown mode: " + _mode.ToString());
            }
         }
      }

      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
   }
}
