//>>***************************************************************************
//                              UNCLASSIFIED
//
//                COPYRIGHT 2017 RAYTHEON MISSILE SYSTEMS 
//                         ALL RIGHTS RESERVED
//   This data was developed pursuant to Contract Number HQ0147-12-C-0004/1088370
//     with the US Government. The US Government's rights in and to this 
//        copyrighted data are as specified in DFAR 252.227-7013 
//               which was made part of the above contract.
//
//    Distribution Statement: D -Distribution authorized to the DoD and DoD 
//       contractors only based on Critical Technology Requirements, May 2001.
//       Other requests shall be referred to PEO THEATER AIR DEFENSE (PMS 422).
//    Warning: - This document contains technical data whose export is restricted
//       by the Arms Export Control Act (Title 22, U.S.C.) or Export 
//       Administration Act of 1979, as amended (Title 50, U.S.C.). Violations 
//       of these laws are subject to severe criminal penalties. Disseminate in 
//       accordance with provisions of DoD 5230.25.
//    Destruction Notice: - For unclassified, limited distribution information, 
//       destroy by any method that will prevent disclosure of contents or 
//       reconstruction of the document. Classified information, destroy in 
//       accordance with DoD-5220.22-M or OPNAVINST 5510.1h.
//>>***************************************************************************

using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using NLog;
using Raytheon.Common;
using Raytheon.Units;

namespace Raytheon.Instruments
{
   /// <summary>
   /// </summary>
   public class PowerMeterKeysightScpi : IPowerMeter
   {
      #region PublicMembers
      #endregion

      #region PrivateMembers

      private const string _READ_ERROR_CODE_CMD = "SYST:ERR?";
      private const string _RESETCMD = "*RST";
      private const string _ISOPCCMD = "*STB? ";
      private const string _SELFTESTCMD = "*TST? ";
      private const string _MEASURE = "MEAS?";
      private const string _FREE_RUN_MODE = "INIT:CONT ON";
      private const string _SENSE = "SENS";
      private const string _AVERAGE = ":AVER";
      private const string _STATE = ":STAT";
      private const string _DISPLAY = "DISP";
      private const string _WINDOW = ":WIND";
      private const string _NUMERIC = ":NUM";
      private const string _RESOLUTION = ":RES";
      private const string _ZERO = "CALibration:ZERO:AUTO ONCE";
      private const string _AUTO = ":AUTO";
      private const string _CAL = "CAL";
      private const string _ONCE = "ONCE";

      private int m_PORT = 5025;
      private const int m_READ_TIMEOUT = 10000;

      private readonly string _address;
      private byte[] _readBuffer;
      private NetworkStream _tcpStream;

      private State _state;
      private SelfTestResult _selfTestResult;
      private string _name;

      private readonly ILogger _logger;

      private readonly IConfigurationManager _configurationManager;
      private readonly IConfiguration _configuration;

      #endregion

      #region PrivateFunctions

      /// <summary>
      /// The Finalizer which will release resources if required
      /// </summary>
      ~PowerMeterKeysightScpi()
      {
         Dispose(false);
      }

      /// <summary>
      /// Calibrate Chanel A or B
      /// Channel A = 1
      /// Channel B = 2
      /// </summary
      /// <param name="channelNumber"></param>
      /// <returns></returns>
      private void Calibrate(int channelNumber)
      {
         if ((channelNumber < 1) || (channelNumber > 2))
         {
            string errMsg = "Channel [" + channelNumber.ToString() + "] Invalid ";
            throw new Exception("" + errMsg);
         }

         // write calibrate
         string command = _CAL + channelNumber.ToString() + _AUTO + " " + _ONCE;
         IOWrite(command);
      }

      /// <summary>
      /// Convert scpi data to string
      /// </summary>
      /// <param name="data"></param>
      /// <returns></returns>
      private string ConvertToString(ref byte[] data)
      {
         string rsp = System.Text.Encoding.ASCII.GetString(data);
         return rsp;
      }

      /// <summary>
      /// Dispose of this object
      /// </summary>
      /// <param name="disposing"></param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_state == State.Ready)
            {
               Reset();

               _tcpStream.Close();

               _tcpStream.Dispose();

               _state = State.Uninitialized;
            }
         }
      }

      /// <summary>
      /// Get the error code.
      /// </summary>
      /// <param name="errorCode">The error code.</param>
      /// <returns>The error description.</returns>
      private string GetErrorCode(out int errorCode)
      {
         // not calling IOQuery() here so IOQuery() can call GetErrorCode() after each query

         string command = _READ_ERROR_CODE_CMD + "\n";

         byte[] commandBuffer = Encoding.ASCII.GetBytes(command);

         // send the data out
         _tcpStream.Write(commandBuffer, 0, commandBuffer.Length);

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

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

         // convert to a string
         string rspStr = ConvertToString(ref _readBuffer);

         // parse the response
         string[] tokens = rspStr.Split(',');

         errorCode = Util.ConvertStringToInt32(tokens[0]);

         // it should always be 2
         if (tokens.Length >= 2)
         {
            return tokens[1];
         }
         else
         {
            return "";
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      private bool HasItTriggered()
      {
         return false;
         // Check the Trigger Event register to see if the event has properly triggered
         /*string command = m_ISITCOMPLETEDCMD + "\n";
			string rspStr = IOQuery(command);
			bool isTriggered = false;

			string[] tokens = rspStr.Split('\n');

			// check to see if it is triggered.
			if (IsOperationCompleted(tokens[0]) == true)
			{
			   isTriggered = true;
			}

			return isTriggered;*/
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="command"></param>
      /// <returns></returns>
      private string IOQuery(string command)
      {
         // send the command
         IOWrite(command, false);

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

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

         // convert response to a string
         string rspStr = ConvertToString(ref _readBuffer);

         // check for errors
         int err = 0;
         string message = GetErrorCode(out err);
         if (err != 0)
         {
            throw new Exception("PowerMeterKeysightScpi:IOQuery() - Power Meter " + _name + " returned error code: " + err.ToString() + ", " + message);
         }

         return rspStr;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="command"></param>
      private void IOWrite(string command, bool shallWeCheckForError = true)
      {
         // convert to a byte array
         byte[] commandBuffer = Encoding.ASCII.GetBytes(command);

         // send the data out
         _tcpStream.Write(commandBuffer, 0, commandBuffer.Length);

         // check for errors
         if (shallWeCheckForError == true)
         {
            int err = 0;
            string errorMessage = GetErrorCode(out err);
            if (err != 0)
            {
               throw new Exception("PowerMeterKeysightScpi:IOWrite() - Device " + _name + " returned error code: " + err.ToString() + "," + errorMessage);
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="statusString"></param>
      /// <param name="expectedResult"></param>
      /// <returns></returns>
      private bool IsOperationCompleted(string statusString, string expectedResult = "1")
      {
         bool completed = false;

         // If Scope returns '+x' instead of 'x', where 'x' is any character, trim it.
         if (statusString.IndexOf('+') == 0)
         {
            statusString = statusString.TrimStart('+');
         }

         if (statusString.Equals(expectedResult) == true)
         {
            completed = true;
         }

         return completed;
      }

      /// <summary>
      /// - Set auto averaging
      /// - enable or disable filtering
      /// </summary>
      /// <param></param>
      /// <returns></returns>
      private void SetAverage(int SenseCount, bool enableAutoAverage)
      {
         string errMsg;
         if ((SenseCount < 1) || (SenseCount > 2))
         {
            errMsg = "Sensor Count [" + SenseCount.ToString() + "] Invalid ";
            throw new Exception(errMsg);
         }

         string command = _SENSE + SenseCount.ToString() + _AVERAGE + _STATE + enableAutoAverage.ToString();

         IOWrite(command);
      }

      /*@@@ Not Needed I don't think
		/// <summary>
		/// Set Power Meter Interface
		/// </summary>
		/// <param name="IPAddress"></param> 
		/// <param name="GPIOBAddress"></param>
		/// <returns></returns>
		private void SetInterface(uint IPAddress, uint GPIBAddress)
		{
		   // UP Address
		   // strip off all but last byte
		   const UInt32 Mask = 0x00000000FF;

		   byte[] ByteArray = new byte[sizeof(uint)]; // 4 bytes in a Uint32
		   for (int i = 0; i < sizeof(uint); i++)
		   {
			  ByteArray[i] = (byte)(IPAddress & Mask);
			  IPAddress = (IPAddress >> 8); // right shift to next byte
		   }

		   // output SYSTem:COMMunicaiton:LAN:ADDRess + IP Address formatted 0.0.0.0 to 255.255.255.255
		   string command = m_SysComm + m_IPAddr + ByteArray[3].ToString() + '.' + ByteArray[2].ToString() + '.' + ByteArray[1].ToString() + '.' + ByteArray[0].ToString();
		   IOWrite(command);

		   //GPIB Address 
		   if (GPIBAddress > 30)
		   {
			  string GPIB_errMsg = "GPIB Address: [" + GPIBAddress + "] Out of Range";
			  throw new Exception(GPIB_errMsg);
		   }

		   command = m_SysComm + m_GPIB + GPIBAddress.ToString();
		   IOWrite(command);
		}*/

      /// <summary>
      /// 
      /// </summary>
      /// <param name="windowNumber"></param>
      /// <param name="numValue"></param>
      /// <param name="resolution"></param>
      private void SetResolution(int windowNumber, int numValue, int resolution)
      {

         string errMsg;
         if ((windowNumber < 1) || (windowNumber > 2))
         {
            errMsg = "Window Number[" + windowNumber.ToString() + "] Invalid ";
            throw new Exception(errMsg);
         }

         if ((numValue < 1) || (numValue > 2))
         {
            errMsg = "Numeric [" + numValue.ToString() + "] Invalid ";
            throw new Exception(errMsg);
         }

         if ((resolution < 1) || (resolution > 4))
         {
            errMsg = "Resolution Level [" + resolution.ToString() + "] Invalid ";
            throw new Exception(errMsg);
         }

         string command = _DISPLAY + _WINDOW + windowNumber.ToString() + _NUMERIC + numValue.ToString() + _RESOLUTION + " " + resolution.ToString();

         IOWrite(command);
      }

      #endregion

      /// <summary>
      /// PowerMeterKeysightScpi factory constructor
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="configurationManager"></param>
      public PowerMeterKeysightScpi(string deviceName, IConfigurationManager configurationManager)
      {
         const int READ_BUFFER_SIZE = 512;

         Name = deviceName;

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

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

         _selfTestResult = SelfTestResult.Unknown;
         _state = State.Uninitialized;

         _readBuffer = new byte[READ_BUFFER_SIZE];

         _address = _configuration.GetConfigurationValue("PowerMeterKeysightScpi", "Address", "127.0.0.1");
         m_PORT = _configuration.GetConfigurationValue("PowerMeterKeysightScpi", "Address", 5025);

         TcpClient scopeSocketConn = new TcpClient(_address, m_PORT);

         _tcpStream = scopeSocketConn.GetStream();
         _tcpStream.ReadTimeout = m_READ_TIMEOUT;

         _state = State.Uninitialized;
         _selfTestResult = SelfTestResult.Unknown;
      }


      /// <summary>
      /// 
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="address"></param>
      public PowerMeterKeysightScpi(string deviceName, string address)
      {
         const int READ_BUFFER_SIZE = 512;
         _name = deviceName;
         _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}");
         _address = address;
         _readBuffer = new byte[READ_BUFFER_SIZE];
         TcpClient scopeSocketConn = new TcpClient(_address, m_PORT);
         _tcpStream = scopeSocketConn.GetStream();
         _tcpStream.ReadTimeout = m_READ_TIMEOUT;
         _state = State.Uninitialized;
         _selfTestResult = SelfTestResult.Unknown;
      }

      /// <summary>
      /// 
      /// </summary>
      public int Channels
      {
         get
         {
            throw new NotImplementedException();
         }

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public bool ClearErrors()
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            return "This is a Scpi Power Meter";
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public bool DisplayEnabled
      {
         get
         {
            throw new NotImplementedException();
         }

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// Dispose of this objects resources
      /// </summary>
      public void Dispose()
      {
         Dispose(true);

         GC.SuppressFinalize(this);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="channel"></param>
      /// <param name="enable"></param>
      /// <returns></returns>
      public bool EnableChannel(int channel, bool enable)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      public bool FrontPanelEnabled
      {
         get
         {
            throw new NotImplementedException();
         }

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public InstrumentMetadata Info
      {
         get
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Initialize()
      {
         // if we have not yet been initialized, go ahead and create the socket
         if (_state == State.Uninitialized)
         {
            Reset();

            // we already connected to the instrument in the constructor
            _state = State.Ready;
         }
         else
         {
            throw new Exception("expected the state of System " + _name + " to be Uninitialized, state was: " + _state.ToString());
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="channel"></param>
      /// <returns></returns>
      public Power GetMeasurement(int channel)
      {
         // for now, just use free running mode with default expected power levels
         const string EXPECTED_POWER = "DEF";
         const string RESOLUTION = "DEF";

         //@@@ TBD if this is needed turn off system header
         // for now, just use free running mode with default expected power levels
         // if we need better resolution, we will extend the IPowerInterface
         string command = _FREE_RUN_MODE + "\n";
         IOWrite(command);

         //MEAS1? DEF,DEF,(@1)
         //MEAS? DEF,DEF,(@1)
         command = _MEASURE + " " + EXPECTED_POWER + "," + RESOLUTION + ",(@" + channel.ToString() + ")" + "\n";
         string rspStr = IOQuery(command);

         string[] tokens = rspStr.Split('\n');

         double power = Util.ConvertStringToDouble(tokens[0]);

         return Power.FromWatts(power);
      }

      /// <summary>
      /// 
      /// </summary>
      public string Name
      {
         get
         {
            return _name;
         }
         set { _name = value; }
      }

      /// <summary>
      /// 
      /// </summary>
      public SelfTestResult SelfTestResult
      {
         get
         {
            return _selfTestResult;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public State Status
      {
         get
         {
            return _state;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public SelfTestResult PerformSelfTest()
      {
         try
         {
            // change the timeout to account for the long self test
            _tcpStream.ReadTimeout = 90000;

            // send the command and get the response 
            string command = _SELFTESTCMD + "\n";
            string rspStr = IOQuery(command);

            // parse the response
            string[] tokens = rspStr.Split('\n');

            //Check if self test is completed; returns status of "0".
            if (IsOperationCompleted(tokens[0], "0") == false)
            {
               string errorMsg = "Power Meter " + _name + " returned an error: " + tokens[0];
               throw new Exception(errorMsg);
            }

            _selfTestResult = SelfTestResult.Pass;

            return SelfTestResult.Pass;
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // restore the timeout
            _tcpStream.ReadTimeout = m_READ_TIMEOUT;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Reset()
      {
         // Resets the power meter
         string command = _RESETCMD + "\n";
         IOWrite(command);

         Thread.Sleep(3000);

         /*command = _ISOPCCMD + "\n";
			string rspStr = IOQuery(command);

			// parse the response
			string[] tokens = rspStr.Split('\n');

			if (IsOperationCompleted(tokens[0]) == false)
			{
			   throw new Exception("Reset was not completed.");
			}*/
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="channel"></param>
      /// <param name="frequency"></param>
      /// <returns></returns>
      public bool SetFrequency(int channel, Frequency frequency)
      {
         int freq = Convert.ToInt32(frequency.Hertz);

         string command = "SENSe1:FREQuency " + freq.ToString() + "HZ" + "\n";
         IOWrite(command);

         return true;
      }

      /// <summary>
      /// 
      /// </summary>
      public void Shutdown()
      {
         string errorMsg = "";

         if (_state == State.Ready)
         {
            try
            {
               //Reset System
               Reset();
            }
            catch (Exception err)
            {
               errorMsg += err.Message + " ";
            }

            _state = State.Uninitialized;
         }

         // the stream was created in the constructor, dispose of it here
         try
         {
            if (_tcpStream != null)
            {
               _tcpStream.Dispose();
               _tcpStream = null;
            }
         }
         catch (Exception err)
         {
            errorMsg += err.Message + " ";
         }

         if (errorMsg != "")
         {
            throw new Exception("System " + _name + " Had an error: " + errorMsg);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public bool ZeroMeter()
      {
         throw new NotImplementedException();

         // zero the power meter
         /*
			string command = _ZERO + "\n";
			IOWrite(command);

			// or
			if ((channelNumber < 1) || (channelNumber > 4))
			{
			   string errMsg = "Channel [" + channelNumber.ToString() + "] Invalid ";
			   throw new Exception(errMsg);
			}

			string command = m_CAL + channelNumber.ToString() + m_Zero + m_Auto + m_Space + m_Once;
			IOWrite(command);*/
      }
   }
}
