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

namespace Raytheon.Instruments
{
   /// <summary>
   /// </summary>
   public class SigGenKeysightScpi : ISignalGenerator
	{
		#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:LEV";
		private const string _OUTPUTON = ":OUTPut ON";
		private const string _OUTPUTOFF = ":OUTPut OFF";

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

		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>
		~SigGenKeysightScpi()
		{
			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>
		/// SigGenKeysightScpi factory constructor
		/// </summary>
		/// <param name="deviceName"></param>
		/// <param name="configurationManager"></param>
		public SigGenKeysightScpi(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("SigGenKeysightScpi", "Address", "127.0.0.1");
			m_PORT = _configuration.GetConfigurationValue("SigGenKeysightScpi", "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 SigGenKeysightScpi(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>
		public string DetailedStatus
		{
			get
			{
				return "This is a Scpi Signal Generator";
			}
		}

		/// <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>
		/// <param name="enable"></param>
		/// <returns></returns>
		public void Enable(bool enable)
		{
			if (enable == true)
			{
				string command = _OUTPUTON + "\n";
				IOWrite(command);
			}
			else
			{
				string command = _OUTPUTOFF + "\n";
				IOWrite(command);
			}
		}

		/// <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)
		{
			// 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("SigGenKeysightScpi:IOQuery() - Sig Gen " + _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("SigGenKeysightScpi:IOWrite() - Device " + _name + " returned error code: " + err.ToString() + "," + errorMessage);
				}
			}
		}

		/// <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 sig gen
			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 void SetFrequency(UnitizedValue frequency)
		{
			int freq = Convert.ToInt32(frequency.Value);

			string command = _FREQCMD + " " + freq.ToString() + "\n";
			IOWrite(command);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="powerLevel"></param>
		public void SetPowerLevel(UnitizedValue powerLevel)
		{
			int power = Convert.ToInt32(powerLevel.Value);

			string command = _PWRLEVELMD + " " + power.ToString() + "dBm\n";
			IOWrite(command);
		}

		/// <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);
			}
		}

	}
}
