using System;
using System.Text;
using System.Threading;
using System.ComponentModel;
using System.Collections.Generic;

using LibUsbDotNet;
using LibUsbDotNet.Info;
using LibUsbDotNet.Main;
using LibUsbDotNet.WinUsb;
using LibUsbDotNet.Descriptors;
using LibUsbDotNet.DeviceNotify;


namespace USB_Comunication
{
    public class USB_Device
    {
		public USB_Device(UsbRegistry usbReg)
		{
			this.IsOpen = false;
			this.FullName = usbReg.SymbolicName;
			this.UsbReg = usbReg;
		}

		public override string ToString()
		{
			return this.UsbReg.FullName;
		}

		public void OpenDevice()
		{
			if (this.IsOpen)
			{
				throw new Exception("This device is opened in this moment.");
			};

			try
			{
				// open 
				if (!UsbReg.Open(out thisDevice))
				{
					throw new Exception("Failed to open this device.");
				}
			}
			catch(Exception e)
			{
				IsOpen = false;
				throw new Exception("Failed to open this device.\n\n" + e.Message);
			}

			IUsbDevice wholeUSBdev = thisDevice as IUsbDevice;
			if (ReferenceEquals(wholeUSBdev, null))
			{
                thisDevice.Close();
                IsOpen = false;
				throw new Exception("This USB device isn't supported by your driver.");
			}

			try
			{
				wholeUSBdev.SetConfiguration(1);
			}
			catch(Exception e)
			{
                thisDevice.Close();
                IsOpen = false;
				throw new Exception("Failed to set USB configuration.\n\n" + e.Message);
			}

			try
			{
				wholeUSBdev.ClaimInterface(0);
			}
			catch (Exception e)
			{
                thisDevice.Close();
                IsOpen = false;
				throw new Exception("Failed to claim USB interface.\n\n" + e.Message);
			}

            GetFreq();
            GetBits();
            GetDevs();
            GetMems();

            IsOpen = true;

            if (DeviceOpened != null)
            {
                DeviceOpened(this, UsbReg.Name);
            }
        }


        public object CloseDevice()
        {
            UsbRegistry tmpReg = this.UsbReg;
            thisDevice.Close();
            thisDevice = null;
            IsOpen = false;

            return tmpReg;
        }



        public event EventHandler<String> DeviceOpened;

        private byte REQDIR_DEVICETOHOST =  (1 << 7);
        private byte REQDIR_HOSTTODEVICE =  (0 << 7);
        private byte REQTYPE_VENDOR =       (2 << 5);
        private byte REQREC_DEVICE =        (0 << 0);

        private byte GET_DA_BITS =          0x50;
        private byte GET_DA_FREQ =          0x51;
        private byte GET_DA_DEVS =          0x52;
        private byte GET_DA_MEMS =          0x53;

        private byte SET_BF_LENGTH =        0x60;
        private byte SET_DIVIDER =          0x61;
        private byte SET_DCOFFSET =         0x62;

        public void GetFreq()
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_DEVICETOHOST | REQTYPE_VENDOR | REQREC_DEVICE), GET_DA_FREQ, 0, 0, 4);

            byte[] buffer = new byte[4];
            int len = 0;
            while(!thisDevice.ControlTransfer(ref setup, buffer, 4, out len));

            frequency = BitConverter.ToUInt32(buffer, 0);
        }

        public void GetBits()
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_DEVICETOHOST | REQTYPE_VENDOR | REQREC_DEVICE), GET_DA_BITS, 0, 0, 4);

            byte[] buffer = new byte[4];
            int len = 0;
            while (!thisDevice.ControlTransfer(ref setup, buffer, 4, out len));

            bitDepth = BitConverter.ToUInt32(buffer, 0);
        }

        public void GetDevs()
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_DEVICETOHOST | REQTYPE_VENDOR | REQREC_DEVICE), GET_DA_DEVS, 0, 0, 4);

            byte[] buffer = new byte[4];
            int len = 0;
            while (!thisDevice.ControlTransfer(ref setup, buffer, 4, out len));

            devider = BitConverter.ToUInt32(buffer, 0);
        }

        public void GetMems()
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_DEVICETOHOST | REQTYPE_VENDOR | REQREC_DEVICE), GET_DA_MEMS, 0, 0, 4);

            byte[] buffer = new byte[4];
            int len = 0;
            while (!thisDevice.ControlTransfer(ref setup, buffer, 4, out len));

            memorySize = BitConverter.ToUInt32(buffer, 0);
        }

        public bool SetBufferLen(UInt16 bLength)
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_HOSTTODEVICE | REQTYPE_VENDOR | REQREC_DEVICE), SET_BF_LENGTH, 0, 0, 2);
            int len = 0;
            
            return thisDevice.ControlTransfer(ref setup, bLength, 2, out len);
        }

        public bool SetDivider(UInt16 divider)
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_HOSTTODEVICE | REQTYPE_VENDOR | REQREC_DEVICE), SET_DIVIDER, 0, 0, 2);
            int len = 0;

            return thisDevice.ControlTransfer(ref setup, divider, 2, out len);
        }

        public bool SetOffset(UInt16 offset)
        {
            UsbSetupPacket setup = new UsbSetupPacket((byte)(REQDIR_HOSTTODEVICE | REQTYPE_VENDOR | REQREC_DEVICE), SET_DCOFFSET, 0, 0, 2);
            int len = 0;

            return thisDevice.ControlTransfer(ref setup, offset, 2, out len);
        }


        public void SendToneBuffer(uint dev, uint length, uint offset, UInt16[] buffer)
        {
            if (!this.IsOpen)
                return;

            if (SetBufferLen((UInt16)length))
            {
                throw new Exception("Error: Sending buffer length fault.");
            }
            Thread.Sleep(100);
            if (SetDivider((UInt16)dev))
            {
                throw new Exception("Error: Sending divider fault.");
            }
            Thread.Sleep(100);
            if (SetOffset((UInt16)offset))
            {
                throw new Exception("Error: Sending offset fault.");
            }
            Thread.Sleep(100);


            UsbEndpointWriter usbw = thisDevice.OpenEndpointWriter(WriteEndpointID.Ep03);

            UsbTransfer usbwt;

            int wtransfered;

            ErrorCode ecw = usbw.SubmitAsyncTransfer(buffer, 0, buffer.Length * sizeof(UInt16), 5000, out usbwt);

            WaitHandle.WaitAll(new WaitHandle[] { usbwt.AsyncWaitHandle }, 5000, false);
            if (!usbwt.IsCompleted)
                usbwt.Cancel();

            ecw = usbwt.Wait(out wtransfered);

            usbwt.Dispose();
        }




        public uint bitDepth { private set; get; }
        public uint frequency { private set; get; }
        public uint devider { private set; get; }
        public uint memorySize { private set; get; }

        public string FullName { private set; get; }
		public bool IsOpen { private set; get; }
		public UsbRegistry UsbReg { private set; get; }

		private UsbDevice thisDevice;

    }
}
