using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using FunctionCalls = Teradyne.Instruments.Logging.Adapters.FunctionCalls;
using Messaging = Teradyne.Instruments.Logging.Adapters.Messaging;
using Teradyne.Instruments.Logging.Client;

namespace Raytheon.GuidedElectronicsUnit
{
    public class CallLoggingHelper : IDisposable
    {
		private static string s_source = " Raytheon.GuidedElectronicsUnit";
		private static CallLoggingHelper s_helper = null;
		private FunctionCalls.ProducerAdapter _callAdapter;
		private Messaging.ProducerAdapter _messageAdapter;

		static CallLoggingHelper()
		{
			if (s_helper != null)
				return;

			s_helper = new CallLoggingHelper();
		}

		private CallLoggingHelper()
		{
			_callAdapter = new FunctionCalls.ProducerAdapter();
			_messageAdapter = new Messaging.ProducerAdapter();
		}

		public static bool IsLoggingEnabled()
		{
			return
				s_helper._callAdapter.IsFunctionCallLoggingEnabled(s_source) ||
				s_helper._callAdapter.IsFunctionReturnLoggingEnabled(s_source) ||
				s_helper._messageAdapter.IsMessageLoggingEnabled(s_source);
		}

		private bool _enumHandlerRegistered = false;
		private static void RegisterEnumHandler()
		{
			if (s_helper._enumHandlerRegistered)
				return;

			var assembly = typeof(CallLoggingHelper).Assembly;
			//var resourceNames = assembly.GetManifestResourceNames();
			var stream = assembly.GetManifestResourceStream("Raytheon.GuidedElectronicsUnit.CallLoggingEnumMap.xml");
			var doc = new System.Xml.XmlDocument();
			try
			{
				if (stream != null)
					doc.Load(stream);
			}
			catch
			{
			}
			var xml = doc.OuterXml;

			LoggingTimestamp ts = new LoggingTimestamp();
			FunctionCalls.LoggingDataEnumTranslationConfiguration enumConfig =
				new FunctionCalls.LoggingDataEnumTranslationConfiguration(ts, xml);
			s_helper._callAdapter.ConfigureEnumTranslationForConsumers(enumConfig, s_source);

			s_helper._enumHandlerRegistered = true;
		}

		private static Tuple<FunctionCalls.LoggingDataParameterType, byte[], string, int> ConvertToLoggingData(object value)
		{
			FunctionCalls.LoggingDataParameterType loggingType = FunctionCalls.LoggingDataParameterType.None;
			byte[] loggingData;
			string enumName = string.Empty;
			int arrayLength = 0;

			if (value == null)
			{
				loggingData = null;
			}
			else
			{
				Type t = value.GetType();

				if (t.IsArray)
				{
					var arrayValue = value as Array;

					//if (t.GetElementType() == typeof(Channel))
					//{
					//	Channel[] channels = arrayValue as Channel[];
					//	int[] channelIndexes = new int[channels.Length];
					//	for (int c = 0; c < channels.Length; c++)
					//		channelIndexes[c] = channels[c].IndexOnInstrument;

					//	loggingType = FunctionCalls.LoggingDataParameterType.ViInt32;
					//	loggingData = new byte[arrayValue.Length * sizeof(int)];
					//	Buffer.BlockCopy(channelIndexes, 0, loggingData, 0, loggingData.Length);
					//}
					//else
					{
						int sizeOfElement = sizeof(int);
						var elementType = t.GetElementType().UnderlyingSystemType;
						loggingType = FunctionCalls.LoggingDataParameterType.ViInt32;

						if (elementType == typeof(bool))
						{
							sizeOfElement = sizeof(short);
							loggingType = FunctionCalls.LoggingDataParameterType.ViBoolean;
						}
						else if (elementType == typeof(double))
						{
							sizeOfElement = sizeof(double);
							loggingType = FunctionCalls.LoggingDataParameterType.ViReal64;
						}

						loggingData = new byte[arrayValue.Length * sizeOfElement];
						Buffer.BlockCopy(arrayValue, 0, loggingData, 0, loggingData.Length);
						arrayLength = arrayValue.Length;
					}
				}
				else
				{
					//if (t == typeof(Channel))
					//{
					//	loggingType = FunctionCalls.LoggingDataParameterType.ViInt32;
					//	loggingData = BitConverter.GetBytes(((Channel)value).IndexOnInstrument);
					//}
					/*else*/ if (t.IsEnum)
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViInt32;
						loggingData = BitConverter.GetBytes((int)value);
						enumName = t.Name;
					}
					else if (t.UnderlyingSystemType == typeof(int))
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViInt32;
						loggingData = BitConverter.GetBytes((int)value);
					}
					else if (t.UnderlyingSystemType == typeof(bool))
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViBoolean;
						short booleanValue = (short)(((bool)value) ? 1 : 0);
						loggingData = BitConverter.GetBytes(booleanValue);
					}
					else if (t.UnderlyingSystemType == typeof(double))
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViReal64;
						loggingData = BitConverter.GetBytes((double)value);
					}
					else if (t.UnderlyingSystemType == typeof(string))
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViString;
						loggingData = Encoding.ASCII.GetBytes(value.ToString());
					}
					else
					{
						loggingType = FunctionCalls.LoggingDataParameterType.ViString;
						loggingData = Encoding.ASCII.GetBytes(value.ToString());
					}
				}
			}

			return new Tuple<FunctionCalls.LoggingDataParameterType, byte[], string, int>(loggingType, loggingData, enumName, arrayLength);
		}
		private static List<FunctionCalls.LoggingDataParameter> CreateParameteList(object[] args)
		{
			RegisterEnumHandler();

			List<FunctionCalls.LoggingDataParameter> parameters = new List<FunctionCalls.LoggingDataParameter>();

			for (int i = 0; i < args.Length; i += 2)
			{
				string name = args[i].ToString();
				object value = args[i + 1];

				var loggingData = ConvertToLoggingData(value);
				if (value.GetType().IsArray)
				{
					var parameter = new FunctionCalls.LoggingDataArrayParameter(name,
						loggingData.Item1, loggingData.Item4, loggingData.Item2, loggingData.Item3);
					parameters.Add(parameter);
				}
				else
				{
					var parameter = new FunctionCalls.LoggingDataParameter(name,
						loggingData.Item1, loggingData.Item2, loggingData.Item3);
					parameters.Add(parameter);
				}
			}

			return parameters;
		}

		public static void FunctionStart(Type t, string functionName, params object[] args)
		{
			if (!s_helper._callAdapter.IsFunctionCallLoggingEnabled(s_source))
				return;

			LoggingTimestamp ts = new LoggingTimestamp();
			List<FunctionCalls.LoggingDataParameter> parameters = CreateParameteList(args);
			FunctionCalls.LoggingDataFunctionCall functionCall = new FunctionCalls.LoggingDataFunctionCall($"{t.FullName}.{functionName}", ts, parameters);
			s_helper._callAdapter.NotifyConsumers(functionCall, s_source);
		}

		public static void FunctionEnd(Type t, string functionName, object returnValue, params object[] outOrRefArgs)
		{
			if (!s_helper._callAdapter.IsFunctionReturnLoggingEnabled(s_source))
				return;

			LoggingTimestamp ts = new LoggingTimestamp();
			List<FunctionCalls.LoggingDataParameter> parameters = CreateParameteList(outOrRefArgs);
			var loggingReturnData = ConvertToLoggingData(returnValue);
			FunctionCalls.LoggingDataFunctionReturn functionReturn = new FunctionCalls.LoggingDataFunctionReturn($"{t.FullName}.{functionName}", ts,
				loggingReturnData.Item1, loggingReturnData.Item2, parameters, loggingReturnData.Item3);
			s_helper._callAdapter.NotifyConsumers(functionReturn, s_source);
		}

		public static void Message(string message)
		{
			if (!s_helper._messageAdapter.IsMessageLoggingEnabled(s_source))
				return;

			s_helper._messageAdapter.NotifyConsumers(message, s_source);
		}

		private bool _disposedValue;

		protected virtual void Dispose(bool disposing)
		{
			if (!_disposedValue)
			{
				if (disposing)
				{
					_callAdapter?.Shutdown();
				}

				s_helper = null;
				_disposedValue = true;
			}
		}

		~CallLoggingHelper()
		{
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
			Dispose(disposing: false);
		}

		public void Dispose()
		{
			// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
			Dispose(disposing: true);
			GC.SuppressFinalize(this);
		}
	}
}
