using System;
using System.Collections.Generic;
using Win_API;
using Raytheon.Instruments.GeneralIO;
using CP210xRuntime_DLL;
using NLog;
using Raytheon.Common;

namespace Raytheon.Instruments
{
	/// <summary>
	/// 
	/// </summary>
	public class ECPDIOSiCp210x : DIOSiCp210x
	{
		private string _comPort = string.Empty;

		/// <summary>
		/// ECPDIOSiCp210x factory constructor
		/// </summary>
		/// <param name="deviceName"></param>
		/// <param name="configurationManager"></param>
		public ECPDIOSiCp210x(string deviceName, IConfigurationManager configurationManager, ILogger logger)
			: base(deviceName, configurationManager, logger)
		{
			_comPort = _dioModuleConfig.ReadValue(Name, ConfigIni.COM_PORT.ToString());
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="deviceName"></param>
		/// <param name="deviceNum"></param>
		/// <param name="inputPins"></param>
		/// <param name="outputPins"></param>
		public ECPDIOSiCp210x(string deviceName, uint deviceNum) 
			: base(deviceName, deviceNum)
		{ }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="deviceName"></param>
		/// <param name="comPort"></param>
		/// <param name="inputPins"></param>
		/// <param name="outputPins"></param>
		public ECPDIOSiCp210x(string deviceName, string comPort)
			: base(deviceName, 0)
		{
			_comPort = comPort;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="comPort"></param>
		public void SetComPort(string comPort)
		{
			_comPort = comPort;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		private IntPtr GetHandle()
		{
			var comString = $"\\\\.\\{_comPort}";
			var securityAttbs = NativeMethods.InitWithDefaultAttributes();

			//Open a handle the device specified
			IntPtr hDevice = NativeMethods.CreateFileA(comString,
                         NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
								 0,
								 ref securityAttbs,
								 3,
								 NativeMethods.FILE_ATTRIBUTE_NORMAL | NativeMethods.FILE_FLAG_OVERLAPPED,
								 IntPtr.Zero);

			if (hDevice != NativeMethods.INVALID_HANDLE_VALUE)
			{
				return hDevice;
			}
			else
			{
				throw new Exception($"Unable to get a valid handle using COM port {_comPort}");
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public ushort ReadLatch()
		{
			var handle = GetHandle();
			ushort latch = 0;

			var errCode = CP210xRuntime.CP210xRT_ReadLatch(handle, ref latch);

         NativeMethods.CloseHandle(handle);

			if (errCode == SI_SUCCESS)
			{
				return latch;
			}
			else
			{
				throw new Exception($"Error when reading CP210X latch. Error code returned: {errCode}");
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="bit"></param>
		/// <returns></returns>
		public override IODatatypes.BitState GetBitState(string signalName)
		{
			lock (_syncObj)
			{
            if (!_signalNameToChannelMap.ContainsKey(signalName))
               throw new Exception($"Signal name {signalName} doesn't exist for card: " + _name);

            if (_signalNameToChannelMap[signalName].channelNumber >= _numInputChannels || _signalNameToChannelMap[signalName].channelNumber < _channelStartIndex)
            {
               throw new Exception($"The input channel number {_signalNameToChannelMap[signalName].channelNumber} specified must be >= {_channelStartIndex} and < {_numInputChannels + _channelStartIndex} on card " + _name);
            }

            int bitIndex = (int)_signalNameToChannelMap[signalName].channelNumber - _channelStartIndex;

            ushort mask = (ushort)(0x1 << (int)bitIndex);

            var latch = ReadLatch();

            return (IODatatypes.BitState)(latch & mask);
         }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="bit"></param>
		/// <param name="state"></param>
		public override void SetBit(string signalName, IODatatypes.BitState state)
		{
			lock (_syncObj)
			{
            if (!_signalNameToChannelMap.ContainsKey(signalName))
               throw new Exception($"Signal name {signalName} doesn't exist for card: " + _name);

            if (_signalNameToChannelMap[signalName].channelNumber >= _numOutputChannels || _signalNameToChannelMap[signalName].channelNumber < _channelStartIndex)
            {
               throw new Exception($"The output channel number {_signalNameToChannelMap[signalName].channelNumber} specified must be >= {_channelStartIndex} and < {_numOutputChannels + _channelStartIndex} on card " + _name);
            }

            int bitIndex = (int)_signalNameToChannelMap[signalName].channelNumber - _channelStartIndex;

            ushort mask = (ushort)(0x1 << (int)bitIndex);

				if (state == IODatatypes.BitState.High)
				{
					WriteLatch(mask, mask);
				}
				else
				{
					WriteLatch(mask, 0);
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="mask"></param>
		/// <param name="latch"></param>
		/// <returns></returns>
		protected override void WriteLatch(ushort mask, ushort latch)
		{
			var handle = GetHandle();

			int ret = CP210xRuntime.CP210xRT_WriteLatch(handle, mask, latch);

			NativeMethods.CloseHandle(handle);

			if (ret != SI_SUCCESS)
			{
				throw new Exception("call to write latch returned error: " + ret.ToString() + " on card: " + _name);
			}
		}

	}
}
