//>>***************************************************************************
//                              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 NLog;
using Raytheon.Common;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;

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

		#region PrivateMembers
		private const string _READ_ERROR_CODE_CMD = "SYST:ERR?";
		private const string _SELFTESTCMD = "*TST? ";
		private const string _RESETCMD = "*RST";
		/*private const string _ISOPCCMD = "*STB? ";
		private const string _FREQCMD = ":FREQ:FIX";
		private const string _PWRLEVELMD = "POW:REF";
		private const string _OUTPUTON = ":OUTPut ON";
		private const string _OUTPUTOFF = ":OUTPut OFF";*/


		private const string _INIT_IMMEDIATE = "INIT:IMM";
		private const string _MARKER_SET_MAX = "CALC:MARK:MAX";
		private const string _MARKER_QUERY_MAX_X = "CALC:MARK:X?";
		private const string _MARKER_QUERY_MAX_Y = "CALC:MARK:Y?";
		private const string _AVG_OFF = "AVER OFF";
		private const string _AVG_ON = "AVER ON";
		private const string _AVG_LOG = "AVER:TYPE LOG";
		private const string _AVG_RMS = "AVER:TYPE RMS";
		//private const string _AVG_SCAL = "AVER:TYPE SCAL";
		private const string _AVG_COUNT = "AVER:COUN";
		private const string _ATTENUATION_AUTO_ON = "POWer:ATTenuation:AUTO ON";
		private const string _ATTENUATION_AUTO_OFF = "POWer:ATTenuation:AUTO OFF";
		private const string _ATTENUATION_SET = "POWer:ATTenuation";

		//private const string _POWER_LEVEL = "POWer:LEVel";
		//private const string _POWER_LEVEL_AUTO_ON = "POWer:LEVel:AUTO ON";
		//private const string _POWER_LEVEL_AUTO_OFF = "POWer:LEVel:AUTO OFF";

		private const string _FREQ_CENTER = "SENS:FREQ:CENT";
		private const string _FREQ_SPAN = "SENS:FREQ:SPAN";

		private const string _RBW_SET = "BWID";
		private const string _RBW_AUTO_ON = "BWID:AUTO On";
		private const string _RBW_AUTO_OFF = "BWID:AUTO Off";
		private const string _VBW_SET = "BWID:VID";
		private const string _VBW_AUTO_ON = "BWID:VID:AUTO On";
		private const string _VBW_AUTO_OFF = "BWID:VID:AUTO Off";


		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;

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

		#endregion

		#region PrivateFunctions

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

		/// <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)
		{
			try
			{
				if (disposing)
				{
					if (_state == State.Ready)
					{
						Reset();

						_tcpStream.Close();

						_tcpStream.Dispose();

						_state = State.Uninitialized;
					}
				}
			}
			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>
		/// 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>
		/// <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;
		}

		#endregion

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

			Name = deviceName;

			_logger = logger;

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

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

			_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>
		/// <param name="name"></param>
		/// <param name="address"></param>
		public SpecAnKeysightScpi(string name, string address)
		{
			const int READ_BUFFER_SIZE = 512;
			_name = name;
			_logger = LogManager.GetCurrentClassLogger();
			_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>
		/// <returns></returns>
		public bool ClearErrors()
		{
			throw new NotImplementedException();
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="type"></param>
		/// <param name="numOfAverages"></param>
		public void ConfigureAveraging(SpecAnAveragingType type, int numOfAverages)
		{
			if (type == SpecAnAveragingType.NONE)
			{
				string command = _AVG_OFF + "\n";
				IOWrite(command);
			}
			else
			{
				string onCommand = _AVG_ON + "\n";
				IOWrite(onCommand);

				string countCommand = _AVG_COUNT + " " + numOfAverages.ToString() + "\n";
				IOWrite(onCommand);

				if (type == SpecAnAveragingType.LOG)
				{
					string logCommand = _AVG_LOG + "\n";
					IOWrite(logCommand);
				}
				else if (type == SpecAnAveragingType.RMS)
				{
					string rmsCommand = _AVG_RMS + "\n";
					IOWrite(rmsCommand);
				}
				/*else if (type == SpecAnAveragingType.SCAL)
				{
				//@@@ resulting in error -224
				   string scalCommand = _AVG_SCAL + "\n";
				   IOWrite(scalCommand);
				}*/
				else
				{
					throw new Exception("unsupported averaging type: " + type.ToString());
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="rbw"></param>
		/// <param name="vbw"></param>
		public void ConfigureBandwidth(int rbw, int vbw)
		{
			if (rbw == -99)
			{
				string command = _RBW_AUTO_ON + "\n";
				IOWrite(command);
			}
			else
			{
				string command = _RBW_AUTO_OFF + "\n";
				IOWrite(command);

				command = _RBW_SET + " " + rbw.ToString() + "\n";
				IOWrite(command);
			}

			if (vbw == -99)
			{
				string command = _VBW_AUTO_ON + "\n";
				IOWrite(command);
			}
			else
			{
				string command = _VBW_AUTO_OFF + "\n";
				IOWrite(command);

				command = _VBW_SET + " " + vbw.ToString() + "\n";
				IOWrite(command);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="centerFreq"></param>
		/// <param name="span"></param>
		public void ConfigureFrequency(int centerFreq, int span)
		{
			string command = _FREQ_CENTER + " " + centerFreq.ToString() + "\n";
			IOWrite(command);

			command = _FREQ_SPAN + " " + span.ToString() + "\n";
			IOWrite(command);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="referanceLevel"></param>
		/// <param name="attenuation"></param>
		public void ConfigurePowerLevel(/*int referanceLevel, */int attenuation)
		{
			/*if (referanceLevel == -99)
			{
			   string command = _POWER_LEVEL_AUTO_ON + " " + referanceLevel.ToString() + "\n";
			   IOWrite(command);
			}
			else
			{
			   string command = _POWER_LEVEL_AUTO_OFF + " " + referanceLevel.ToString() + "\n";
			   IOWrite(command);

			   command = _POWER_LEVEL + " " + referanceLevel.ToString() + "\n";
			   IOWrite(command);
			}*/

			if (attenuation == -99)
			{
				string command = _ATTENUATION_AUTO_ON + "\n";
				IOWrite(command);
			}
			else
			{
				string command = _ATTENUATION_AUTO_OFF + "\n";
				IOWrite(command);

				command = _ATTENUATION_SET + " " + attenuation.ToString() + "\n";
				IOWrite(command);
			}
		}

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

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

			set
			{
				throw new NotImplementedException();
			}
		}

		/// <summary>
		/// Dispose of this objects resources
		/// </summary>
		public void Dispose()
		{
			try
			{
				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>
		/// 
		/// </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="command"></param>
		/// <returns></returns>
		public string IOQuery(string command, int timeout = 10000)
		{
			_tcpStream.ReadTimeout = timeout;

			// 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("SpecAnKeysightScpi:IOQuery() - Spec An " + _name + " returned error code: " + err.ToString() + ", " + message);
			}

			return rspStr;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="command"></param>
		public 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("SpecAnKeysightScpi:IOWrite() - Device " + _name + " returned error code: " + err.ToString() + "," + errorMessage);
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasurePeakAmplitude()
		{
			//Initiate a single sweep
			string command = _INIT_IMMEDIATE + "\n";
			IOWrite(command);

			//@@@ probably need to wait until sweep is completed
			//although examples dont show that..have to try it out with a real signal

			// Set the marker to the maximum peak
			command = _MARKER_SET_MAX + "\n";
			IOWrite(command);

			//Query and read the marker frequency
			command = _MARKER_QUERY_MAX_Y + "\n";
			string rspStr = IOQuery(command);

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

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

			return amp;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasurePeakFrequency()
		{
			//Initiate a single sweep
			string command = _INIT_IMMEDIATE + "\n";
			IOWrite(command);

			//@@@ probably need to wait until sweep is completed
			//although examples dont show that..have to try it out with a real signal

			// Set the marker to the maximum peak
			command = _MARKER_SET_MAX + "\n";
			IOWrite(command);

			//Query and read the marker frequency
			command = _MARKER_QUERY_MAX_X + "\n";
			string rspStr = IOQuery(command);

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

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

			return freq;
		}

		/// <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 = 120000;

				// send the command and get the rsponse 
				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 = "System " + _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 instrument
			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>
		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);
			}
		}

	}
}
