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

namespace Raytheon.Instruments
{
	/// <summary>
	/// A class for controlling a scope via ZBind Teradyne Library
	/// </summary>
	public class ScopeZBind : IOscilloScope
	{
		#region PublicMembers
		#endregion

		#region PrivateMembers

		// Maximum number of channels provided by scope.
		private readonly string _address;
		private IntPtr _handle;

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

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

						ScopeZBindNativeMethods.zbind_remove(_handle);

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

		#endregion

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

			_logger = logger;

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

			_address = _configuration.GetConfigurationValue("ScopeZBind", "Address", "");

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

		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <param name="address"></param>
		public ScopeZBind(string name, string address)
		{
			_name = name;
			_logger = LogManager.GetCurrentClassLogger();
			_address = address;
			_state = State.Uninitialized;
			_selfTestResult = SelfTestResult.Unknown;
		}

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

		/// <summary>
		/// 
		/// </summary>
		/// <param name="channelNumber"></param>
		/// <param name="timePerDivison">Horizontal</param>
		/// <param name="timeOffset"></param>
		/// <param name="voltageOffset"></param>
		/// <param name="voltageScale">Verticle Voltage Scale</param>
		/// <param name="inputImpedance"></param>
		public void ConfigureChannel(int channelNumber, double timePerDivison, double timeOffset, double voltageOffset, double voltageScale, string inputImpedance)
		{
			const int SWEEP_POINTS = 2000;

			Reset();
			IOWrite("INP1 ON");
			IOWrite("INP2 ON");
			IOWrite("SENSe:SWEep:POINts " + SWEEP_POINTS.ToString());       // Options defined in zScopeM
			IOWrite("SENSe:SWEep:TIME " + timePerDivison.ToString()); // SWEEP_TIME.ToString());           // In seconds
			IOWrite("SWE:OFFS:TIME 0.0");
			IOWrite("AVER:STAT 0");
			IOWrite("INIT:CONT 0");
			IOWrite("SWE:MODE NORM");
			IOWrite("VOLT1:RANG:OFFS " + voltageOffset.ToString());
			IOWrite("VOLT1:RANG:PTP " + voltageScale.ToString());               // 10 division on screen so V/Div will be 1/10th of input
			IOWrite("INP1:FILT 0");

			// start wave form collection
			IOWrite("INIT");
			Thread.Sleep(1000);
		}

		/// <summary>
		/// 
		/// </summary>
		public string DetailedStatus
		{
			get
			{
				return "This is a Teradyne ZBind Scope";
			}
		}

		/// <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>
		/// <returns></returns>
		public bool HasItTriggered()
		{
			// Did not find support for this in the API, just return true
			return true;
		}

		/// <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)
			{
				int ZT_LAN = 0x0004;
				int err = ScopeZBindNativeMethods.zbind_add(_address, ZT_LAN, out _handle);

				// check for errors
				if (err != 0)
				{
					throw new Exception("ScopeZBind:Initialize() - Scope " + _name + " returned error code: " + err.ToString());
				}

				// reset the scope
				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);

			// read from the response
			StringBuilder rspStr = new StringBuilder(512);

			int err = ScopeZBindNativeMethods.zbind_receive(_handle, 0, "%[ -~]", rspStr);

			// check for errors
			if (err != 0)
			{
				throw new Exception("ScopeZBind:IOQuery() - Scope " + _name + " returned error code: " + err.ToString());
			}

			return rspStr.ToString();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="command"></param>
		public void IOWrite(string command, bool shallWeCheckForError = true)
		{
			int err = ScopeZBindNativeMethods.zbind_send(_handle, 0, command);

			// check for errors
			if (shallWeCheckForError == true)
			{
				if (err != 0)
				{
					throw new Exception("ScopeZBind:IOWrite() - Scope " + _name + " returned error code: " + err.ToString());
				}
			}

			// wait a bit for processing
			Thread.Sleep(50);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasureFallTime(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="channelNumber"></param>
		/// <returns></returns>
		public double MeasureFrequency(int channelNumber)
		{
			IOWrite("INIT");

			IOWrite("SENSe:SWEep:MODE NORM");

			string enableCommand = "SENSe:INPut" + channelNumber.ToString() + ":STATe 1";
			IOWrite(enableCommand);

			// stop the waveform
			IOWrite("ABORt");

			// wait a bit for the wave form to settle
			Thread.Sleep(1000);

			// The scope will respond with a string like "60.6575321, 0"
			string response = IOQuery("MEASure:VOLTage:FREQuency? INPut" + channelNumber.ToString());

			double result = Double.Parse(response.Split(Convert.ToChar(","))[0], System.Globalization.NumberStyles.Float);

			return result;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasureMaxVoltage(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasureMinVoltage(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasurePulseWidth(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasureRiseTime(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public double MeasureVoltageLevel(int channelNumber)
		{
			throw new NotImplementedException();
		}

		/// <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()
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		public void Reset()
		{
			// Resets the oscilloscope
			string command = "*RST";
			IOWrite(command);

			// just a swag
			Thread.Sleep(3000);
		}

		/// <summary>
		/// 
		/// </summary>
		public void SaveImage(string fileName)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 
		/// </summary>
		public void SetupTrigger(bool useRisingEdge, int channelNumber, double triggerLevel)
		{
			IOWrite("TRIG:TYPE EDGE");
			IOWrite("TRIG:SOUR INP1");
			IOWrite("TRIG:INP1:LEV " + triggerLevel.ToString());

			if (useRisingEdge == true)
			{
				IOWrite("TRIG:SLOP POS");
			}
			else
			{
				IOWrite("TRIG:SLOP NEG");
			}
		}

		/// <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
			{
				int err = ScopeZBindNativeMethods.zbind_remove(_handle);

				// check for errors
				if (err != 0)
				{
					throw new Exception("ScopeZBind:Shutdown() - Scope " + _name + " returned error code: " + err.ToString());
				}
			}
			catch (Exception err)
			{
				errorMsg += err.Message + " ";
			}

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

		#endregion
	}
}
