
// 
// CIL bindings for the FTDI library
//
// Copyright (C) 2006 Jelmer Vernooij <jelmer@palmsens.com>
// Licensed under the GNU Lesser Public License
//

using System;
using System.Collections;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
using System.IO;
//using Mono.Unix.Native;



namespace LibFTDI 
{
	public enum ChipType : int
	{
		TYPE_AM    = 0,
		TYPE_BM    = 1,
		TYPE_2232C = 2,
		TYPE_R     = 3,
		TYPE_2232H = 4,
		TYPE_4232H = 5,
		TYPE_232H  = 6
	}
	
	public enum ParityType : int
	{
		NONE  = 0,
		ODD   = 1,
		EVEN  = 2,
		MARK  = 3,
		SPACE =4
	}
	
	public enum StopBitsType : int
	{
		STOP_BIT_1  = 0,
		STOP_BIT_15 = 1,
		STOP_BIT_2  =2
	}
	
	public enum BitsType : int
	{
		BITS_7 = 7,
		BITS_8 = 8
	}
	
	public enum BreakType : int
	{ 
		BREAK_OFF = 0,
		BREAK_ON  = 1
	}

	enum MpsseMode : int 
	{
	    BITMODE_RESET   = 0x00,
	    BITMODE_BITBANG = 0x01,
	    BITMODE_MPSSE   = 0x02,
	    BITMODE_SYNCBB  = 0x04,
	    BITMODE_MCU     = 0x08,
	    BITMODE_OPTO    = 0x10,
	    BITMODE_CBUS    = 0x20,
		BITMODE_SYNCFF  = 0x40
	}

	public enum Interface : int 
	{
	    INTERFACE_ANY = 0,
	    INTERFACE_A   = 1,
	    INTERFACE_B   = 2,
	    INTERFACE_C   = 3,
	    INTERFACE_D   = 4
	}
	
	public enum ModuleDetachMode : int
	{
		AUTO_DETACH_SIO_MODULE = 0,
		DONT_DETACH_SIO_MODULE = 1
	}

	[Flags]
	public enum MPSSEShiftCmds
	{
		MPSSE_WRITE_NEG = 0x01,   /* Write TDI/DO on negative TCK/SK edge*/
		MPSSE_BITMODE   = 0x02,   /* Write bits, not bytes */
		MPSSE_READ_NEG  = 0x04,   /* Sample TDO/DI on negative TCK/SK edge */
		MPSSE_LSB       = 0x08,   /* LSB first */
		MPSSE_DO_WRITE  = 0x10,   /* Write TDI/DO */
		MPSSE_DO_READ   = 0x20,   /* Read TDO/DI */
		MPSSE_WRITE_TMS = 0x40    /* Write TMS/CS */
	}
	
	[Flags]
	public enum MPSSECommands
	{
		SET_BITS_LOW   = 0x80,
		SET_BITS_HIGH  = 0x82,
		GET_BITS_LOW   = 0x81,
		GET_BITS_HIGH  = 0x83,
		LOOPBACK_START = 0x84,
		LOOPBACK_END   = 0x85,
		TCK_DIVISOR    = 0x86,
		SEND_IMMEDIATE = 0x87,
		WAIT_ON_HIGH   = 0x88,
		WAIT_ON_LOW    = 0x89,
		READ_SHORT     = 0x90,
		READ_EXTENDED  = 0x91,
		WRITE_SHORT    = 0x92,
		WRITE_EXTENDED = 0x93
	}

	[StructLayout(LayoutKind.Sequential)]
	public struct ftdi_context
	{
		// USB specific
		public IntPtr usb_dev;
		public int usb_read_timeout;
		public int usb_write_timeout;
	
		// FTDI specific
		private ChipType type;
		private int baudrate;
		private byte bitbang_enabled;
		private IntPtr readbuffer;
		private uint readbuffer_offset;
		private uint readbuffer_remaining;
		private uint readbuffer_chunksize;
		private uint writebuffer_chunksize;
		private uint max_packet_size;
	
		// FTDI FT2232C requirecments
		public  int iface;       // 0 or 1
		private int index;       // 1 or 2
		// Endpoints
		private int in_ep;
		private int out_ep;      // 1 or 2
	
		/* 1: (default) Normal bitbang mode, 2: FT2232C SPI bitbang mode */
		private byte bitbang_mode;

		// EEPROM size. Default is 128 bytes for 232BM and 245BM chips
		private int eeprom_size;
	
		// misc
		private IntPtr error_str; /* const char * */

		// Buffer needed for async communication
		private IntPtr async_usb_buffer;
		// Number of URB-structures we can buffer
		private uint async_usb_buffer_size;
		
		public ModuleDetachMode module_detach_mode;
	}

	[StructLayout(LayoutKind.Sequential)]
	public unsafe struct DeviceList
	{
		// FIXME: IntPtr??
		public DeviceList *next;
		public IntPtr dev;
	}

	[StructLayout(LayoutKind.Sequential)]
	public unsafe struct ftdi_eeprom
	{
		private int vendor_id;
		private int product_id;
	
		private int self_powered;
		private int remote_wakeup;
		private int chip_type;
	
		private int in_is_isochronous;
		private int out_is_isochronous;
		private int suspend_pull_downs;
	
		private int use_serial;
		private int change_usb_version;
		private int usb_version;
		private int max_power;
	
		private IntPtr manufacturer;
		private IntPtr product;
		private IntPtr serial;
		
		private fixed int cbus_function[5];
		private int high_current;
		private int invert;
		
		private int size;
	}


	public struct DeviceInfo
	{
		private IntPtr dev;
		private string manufacturer;
		private	string description;
		private string serial;
		
		public unsafe DeviceInfo(IntPtr dev, sbyte* mnf, sbyte* dsc, sbyte* ser)
		{
			this.dev     = dev;  
			manufacturer = new string(mnf);
			description  = new string(dsc);
			serial       = new string(ser);
		}
		
		public string Manufacturer { get { return manufacturer; } }
		public string Description  { get { return description;  } }
		public string Serial       { get { return serial;       } }				
//		public IntPtr Dev          { get { return dev;          } }				
	}

	
	public class FTDI
	{
		private const string dll = "libftdi-0.20.0.dll";
		private ftdi_context ftdi = new ftdi_context();

		
		[DllImport(dll)]
		internal static extern int ftdi_init(ref ftdi_context ftdi);
		private FTDI()
		{
			ftdi_init(ref this.ftdi);
			this.ftdi.module_detach_mode = ModuleDetachMode.DONT_DETACH_SIO_MODULE;
		}

		[DllImport(dll)]
		internal static extern int ftdi_usb_open(ref ftdi_context ftdi, int vendor, int product);
		public FTDI(int vendor, int product) : this() 
		{
			CheckResult(ftdi_usb_open(ref ftdi, vendor, product));
		}

		[DllImport(dll)]
		internal static extern int ftdi_usb_open_desc(ref ftdi_context ftdi, int vendor, int product, string description, string serial);
		public FTDI(int vendor, int product, string description, string serial) : this() 
		{
			int ret = ftdi_usb_open_desc(ref ftdi, vendor, product, description, serial);
			CheckResult(ret);
		}

		[DllImport(dll)] 
		internal static extern int ftdi_usb_open_dev(ref ftdi_context ftdi, IntPtr dev);
		public FTDI(IntPtr dev) : this()
		{
			CheckResult(ftdi_usb_open_dev(ref ftdi, dev));
		}

		[DllImport(dll)]
		internal static extern void ftdi_deinit(ref ftdi_context ftdi);
		~FTDI() 
		{
			ftdi_deinit(ref ftdi);
		}

		[DllImport(dll)] 
		internal static extern int ftdi_set_baudrate(ref ftdi_context ftdi, int baudrate);
		public int Baudrate
		{ 
			set { CheckResult(ftdi_set_baudrate(ref ftdi, value)); }
		}

		[DllImport(dll)] 
		internal static extern IntPtr ftdi_get_error_string(ref ftdi_context ftdi);
		private void CheckResult(int ret) 
		{
			if (ret >= 0) 
				return;

			string s = Marshal.PtrToStringAnsi(ftdi_get_error_string(ref ftdi));
			Console.WriteLine("Libftdi error: {0} ({1})", ret, s);

			throw new Exception(s);
		}

		[DllImport(dll)]
		internal static extern int ftdi_usb_reset(ref ftdi_context ftdi);
		public void Reset()
		{
			CheckResult(ftdi_usb_reset(ref ftdi));
		}


		[DllImport(dll)]
		internal static extern int ftdi_usb_close(ref ftdi_context ftdi);
		public void Close()
		{
			if (ftdi.usb_dev == IntPtr.Zero)
				return;
			CheckResult(ftdi_usb_close(ref ftdi));
		}

		[DllImport(dll)] internal static extern int ftdi_usb_purge_buffers(ref ftdi_context ftdi);
		public void PurgeBuffers() 
		{
			CheckResult(ftdi_usb_purge_buffers(ref ftdi));
		}

//		[DllImport("libftdi-0.18.0.dll")] internal static extern int ftdi_usb_purge_rx_buffer(ref ftdi_context ftdi);
//		[DllImport("libftdi-0.18.0.dll")] internal static extern int ftdi_usb_purge_tx_buffer(ref ftdi_context ftdi);

		[DllImport(dll)]
		internal static extern int ftdi_read_data_set_chunksize(ref ftdi_context ftdi, uint chunksize);
		[DllImport(dll)]
		internal static extern int ftdi_read_data_get_chunksize(ref ftdi_context ftdi, out uint chunksize);
		public uint ReadChunkSize 
		{
			set 
			{
				CheckResult(ftdi_read_data_set_chunksize(ref ftdi, value)); 
			}
			get 
			{
				uint chunksize;
				CheckResult(ftdi_read_data_get_chunksize(ref ftdi, out chunksize));
				return chunksize;
			}
		}

		[DllImport(dll)] 
		internal static extern int ftdi_write_data_set_chunksize(ref ftdi_context ftdi, uint chunksize);
		[DllImport(dll)]
		internal static extern int ftdi_write_data_get_chunksize(ref ftdi_context ftdi, out uint chunksize);
		public uint WriteChunkSize
		{
			set 
			{
				CheckResult(ftdi_write_data_set_chunksize(ref ftdi, value)); 
			}
			get 
			{
				uint chunksize;
				CheckResult(ftdi_write_data_get_chunksize(ref ftdi, out chunksize));
				return chunksize;
			}
		}


		[DllImport(dll)]
		internal static extern int ftdi_set_latency_timer(ref ftdi_context ftdi, byte latency);
		[DllImport(dll)]
		internal static extern int ftdi_get_latency_timer(ref ftdi_context ftdi, out byte latency);
		public byte LatencyTimer 
		{
			set 
			{ 
				CheckResult(ftdi_set_latency_timer(ref ftdi, value)); 
			}
			get 
			{
				byte latency;
				CheckResult(ftdi_get_latency_timer(ref ftdi, out latency));
				return latency;
			}
		}

//		[DllImport("libftdi-0.19.0.dll")]
//		internal static extern int ftdi_eeprom_build(ref ftdi_eeprom eeprom, ref byte[] output);
//		[DllImport("libftdi-0.19.0.dll")]
//		internal static extern int ftdi_write_eeprom(ref ftdi_context ftdi, ref byte[] eeprom);
//		public void WriteEEPROM(ftdi_eeprom eeprom)
//		{
//			byte[] data = new byte[128];
//			ftdi_eeprom.ftdi_eeprom_build(ref eeprom, ref data); 
//			CheckRet(ftdi_write_eeprom(ref ftdi, ref data));
//		}
//
//		[DllImport("libftdi-0.19.0.dll")] 
//		internal static extern int ftdi_erase_eeprom(ref ftdi_context ftdi);
//		public void EraseEEPROM()
//		{
//			CheckRet(ftdi_erase_eeprom(ref ftdi));
//		}
//
//		[DllImport("libftdi-0.19.0.dll")]
//		internal static extern int ftdi_read_eeprom(ref ftdi_context ftdi, ref byte[] eeprom);
//		public byte[] ReadEEPROM()
//		{
//			/* FIXME */
//			return null;
//		}

		[DllImport(dll)]
		internal static extern int ftdi_read_data(ref ftdi_context ftdi, byte[] buf, int size);
		public int ReadData(ref byte[] buf, int size)
		{
			int ret = ftdi_read_data(ref ftdi, buf, size); 
			CheckResult(ret);
			return ret;
		}
		
		[DllImport(dll)]
		internal static extern int ftdi_write_data(ref ftdi_context ftdi, byte[] buf, int size);
		public int WriteData(byte[] buf, int size)
		{
			int ret = ftdi_write_data(ref ftdi, buf, size); 
			CheckResult(ret);
			return ret;
		}

		[DllImport(dll)]
		internal static extern int ftdi_set_interface(ref ftdi_context ftdi, Interface iface);
		public Interface Interface
		{ 
			set { CheckResult(ftdi_set_interface(ref ftdi, value)); }
		}

		[DllImport(dll)]
		internal static extern int ftdi_read_pins(ref ftdi_context ftdi, out byte pins);
		public byte GetPins()
		{
			byte pins;
			CheckResult(ftdi_read_pins(ref ftdi, out pins));
			return pins;
		}

		[DllImport(dll)]
		internal static extern int ftdi_enable_bitbang(ref ftdi_context ftdi, byte bitmask);
		public void EnableBitBang(byte bitmask)
		{
			CheckResult(ftdi_enable_bitbang(ref ftdi, bitmask));
		}

		[DllImport(dll)] 
		internal static extern int ftdi_disable_bitbang(ref ftdi_context ftdi);
		public void DisableBitBang() 
		{
			CheckResult(ftdi_disable_bitbang(ref ftdi));
		}

		[DllImport(dll)]
		internal static extern int ftdi_set_bitmode(ref ftdi_context ftdi, byte bitmask, byte mode);
		public void SetBitMode(byte bitmask, byte mode) {
			CheckResult(ftdi_set_bitmode(ref ftdi, bitmask, mode));
		}

		[DllImport(dll)]
		internal static extern int ftdi_set_line_property(ref ftdi_context ftdi, BitsType bits, StopBitsType sbit, ParityType parity);
		public void SetLineProperty(BitsType bits, StopBitsType sbit, ParityType parity)
		{
			CheckResult(ftdi_set_line_property(ref ftdi, bits, sbit, parity));
		}


		[DllImport(dll)] 
		internal unsafe static extern int ftdi_usb_find_all(ref ftdi_context ftdi, DeviceList **devlist, int vendor, int product);
		[DllImport(dll)]
		internal unsafe static extern void ftdi_list_free(DeviceList **devlist);
		public static unsafe IntPtr[] GetDeviceList(int vendor, int product)
		{
			ArrayList array_list = new ArrayList();
			DeviceList *devlist, d;
			ftdi_context ftdi = new ftdi_context();
			ftdi_init(ref ftdi);

			int result = ftdi_usb_find_all(ref ftdi, &devlist, vendor, product);
			if (result < 0)
			{
				Console.WriteLine("Error while calling ftdi_usb_find_all()");
				throw new Exception("Error while calling ftdi_usb_find_all()");
			}

			for (d = devlist; d != null; d = d->next) 
			{
				array_list.Add(d->dev);
			}

			ftdi_deinit(ref ftdi);
			ftdi_list_free(&devlist);

			return (IntPtr[])array_list.ToArray(typeof(IntPtr));
		}
		
		
		[DllImport(dll)] 
		private unsafe static extern int ftdi_usb_get_strings(ref ftdi_context ftdi, IntPtr dev, 
		                                                      sbyte[] manufacturer, int mlen,
		                                                      sbyte[] description,  int dlen,
		                                                      sbyte[] serial,       int slen);
		public static unsafe DeviceInfo[] GetDeviceInfoList(int vendor, int product)
		{
			ArrayList array_list = new ArrayList();
			DeviceList *devlist, d;
			ftdi_context ftdi = new ftdi_context();
			ftdi_init(ref ftdi);

			int result = ftdi_usb_find_all(ref ftdi, &devlist, vendor, product);
			if (result < 0)
			{
				Console.WriteLine("Error while calling ftdi_usb_find_all()");
				throw new Exception("Error while calling ftdi_usb_find_all()");
			}
			
			List<DeviceInfo> dev_infos = new List<DeviceInfo>();

			for (d = devlist; d != null; d = d->next)
			{
				array_list.Add(d->dev);
				
				sbyte[] mnf = new sbyte[128];
				sbyte[] dsc = new sbyte[128];
				sbyte[] ser = new sbyte[128];
				
				result = ftdi_usb_get_strings(ref ftdi, d->dev, mnf, 128, dsc, 128, ser, 128);
				if (result < 0)
				{
					Console.WriteLine("Error while calling ftdi_usb_get_strings()");
					throw new Exception("Error while calling ftdi_usb_get_strings()");
				}
					
				fixed (sbyte* pmnf = mnf)
					fixed (sbyte* pdsc = dsc)
						fixed (sbyte* pser = ser)
				{
					dev_infos.Add( new DeviceInfo(d->dev, pmnf, pdsc, pser) );
				}
			}

			ftdi_deinit(ref ftdi);
			ftdi_list_free(&devlist);

			return dev_infos.ToArray();
		}
		
		[DllImport("kernel32.dll")]
		static extern IntPtr LoadLibrary(string lpFileName);
		public static void PreloadLibrary()
		{
			PlatformID osversion = Environment.OSVersion.Platform;
			if (osversion != PlatformID.Win32NT      &&
			    osversion != PlatformID.Win32S       &&
			    osversion != PlatformID.Win32Windows &&
			    osversion != PlatformID.WinCE)
			{
				return;
			}
			
			if (LoadLibrary(dll) == IntPtr.Zero)
			{
				string dll_path;
				
				switch(IntPtr.Size)
				{
				
				case 4:
					dll_path = Path.Combine("x86", dll);
					break;
				
				case 8:
					dll_path = Path.Combine("x64", dll);
					break;
				
				default:
					throw new PlatformNotSupportedException();
				}
				
				if (File.Exists(dll_path))
				{
					Console.WriteLine("Preloading libftdi: " + dll_path);
					LoadLibrary(dll_path);
				}
			}
		}

		[DllImport(dll)] 
		private static extern IntPtr ftdi_new();
		public static void CheckForLibftdi()
		{
			ftdi_new();
		}

		public uint ReadTimeout
		{
			get { return (uint)ftdi.usb_read_timeout; }
			set { ftdi.usb_read_timeout = (int)value; }
		}
	
		public uint WriteTimeout
		{
			get { return (uint)ftdi.usb_write_timeout; }
			set { ftdi.usb_write_timeout = (int)value; }
		}
	}
}
