using Phidget22;
using Phidget22.ExampleUtils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace DataAdapter_Example {
	public partial class Form1 : Form {
		CommandLineOpen openArgs;
		Phidget22.DataAdapter toOpen = null;
		Phidget22.DataAdapter device = null;
		private ErrorEventBox errorBox;
		formCleaner cleaner;

		public Form1() {
			InitializeComponent();
			openArgs = new CommandLineOpen(this);
			cleaner = new formCleaner(this);
			Size = PreferredSize;
		}

		public Form1(Phidget22.DataAdapter ch) {
			InitializeComponent();
			toOpen = ch;
			cleaner = new formCleaner(this);
			Size = PreferredSize;
		}

		private void Form1_Load(object sender, EventArgs e) {
			errorBox = new ErrorEventBox(this);
			device = toOpen ?? openArgs.makeChannel<Phidget22.DataAdapter>();

			device.Attach += device_attach;
			device.Detach += device_detach;
			device.Error += device_error;

			commandsTable.CellContentClick += sendCommand_click;

			try {
				device.Open();
			} catch (PhidgetException ex) { errorBox.addMessage("Error opening device: " + ex.Message); }

		}

		private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
			device.Attach -= device_attach;
			device.Detach -= device_detach;
			device.Error -= device_error;
			device.Close();
		}

		void device_attach(object sender, Phidget22.Events.AttachEventArgs e) {
			Phidget22.DataAdapter attached = (Phidget22.DataAdapter)sender;
			phidgetInfoBox1.FillPhidgetInfo(attached);
			controlBox.Visible = true;
			configBox.Visible = true;

			Enum[] supportedVoltages;
			Enum[] supportedFrequencies;

			switch (attached.DeviceID) {
			case DeviceID.PN_ADP0002: //spi
				supportedVoltages = new Enum[] {DataAdapterVoltage.External,
												DataAdapterVoltage.Volts_2_5,
												DataAdapterVoltage.Volts_3_3,
												DataAdapterVoltage.Volts_5};
				supportedFrequencies = new Enum[] {DataAdapterFrequency.Frequency_188kHz,
												   DataAdapterFrequency.Frequency_375kHz,
												   DataAdapterFrequency.Frequency_750kHz,
												   DataAdapterFrequency.Frequency_1500kHz,
												   DataAdapterFrequency.Frequency_3MHz,
												   DataAdapterFrequency.Frequency_6MHz};
				endiannessPanel.Visible = true;
				spiPanel.Visible = true;
				addressPanel.Visible = false;

				initializeCommandsTable("spi");

				chipSelectBox.SelectedIndex = 0;
				modeBox.SelectedIndex = 0;
				break;
			case DeviceID.PN_ADP0001: //i2c
				supportedVoltages = new Enum[] {DataAdapterVoltage.External,
												DataAdapterVoltage.Volts_2_5,
												DataAdapterVoltage.Volts_3_3,
												DataAdapterVoltage.Volts_5};
				supportedFrequencies = new Enum[] {DataAdapterFrequency.Frequency_10kHz,
												   DataAdapterFrequency.Frequency_100kHz,
												   DataAdapterFrequency.Frequency_400kHz};
				endiannessPanel.Visible = false;
				spiPanel.Visible = false;
				addressPanel.Visible = true;

				initializeCommandsTable("i2c");
				break;
			default:
				supportedVoltages = new Enum[] {DataAdapterVoltage.External,
												DataAdapterVoltage.Volts_2_5,
												DataAdapterVoltage.Volts_3_3,
												DataAdapterVoltage.Volts_5};
				supportedFrequencies = new Enum[] {DataAdapterFrequency.Frequency_10kHz,
												   DataAdapterFrequency.Frequency_100kHz,
												   DataAdapterFrequency.Frequency_400kHz,
												   DataAdapterFrequency.Frequency_188kHz,
												   DataAdapterFrequency.Frequency_375kHz,
												   DataAdapterFrequency.Frequency_750kHz,
												   DataAdapterFrequency.Frequency_1500kHz,
												   DataAdapterFrequency.Frequency_3MHz,
												   DataAdapterFrequency.Frequency_6MHz};
				endiannessPanel.Visible = true;
				spiPanel.Visible = true;
				addressPanel.Visible = true;
				break;
			}

			commandsTable.Rows.Add();


			voltageBox.DataSource = supportedVoltages;
			voltageBox.SelectedIndex = 0;
			freqBox.DataSource = supportedFrequencies;
			if (attached.DeviceID == DeviceID.PN_ADP0002)
				freqBox.SelectedIndex = 3;
			else
				freqBox.SelectedIndex = 1;

			Size = PreferredSize;
		}
		void device_detach(object sender, Phidget22.Events.DetachEventArgs e) {
			controlBox.Visible = false;
			configBox.Visible = false;

			commandsTable.Rows.Clear();
			commandsTable.Columns.Clear();
			Size = PreferredSize;

		}
		void device_error(object sender, Phidget22.Events.ErrorEventArgs e) {
			errorBox.addMessage(e.Description);
		}

		private void voltageBox_SelectedIndexChanged(object sender, EventArgs e) {
			try {
				device.DataAdapterVoltage = (DataAdapterVoltage)Enum.Parse(typeof(DataAdapterVoltage), voltageBox.SelectedValue.ToString());
			} catch (PhidgetException ex) { errorBox.addMessage("Error setting voltage: " + ex.Message); }
		}

		private void freqBox_SelectedIndexChanged(object sender, EventArgs e) {
			try {
				device.Frequency = (DataAdapterFrequency)Enum.Parse(typeof(DataAdapterFrequency), freqBox.SelectedValue.ToString());
			} catch (PhidgetException ex) { errorBox.addMessage("Error setting frequency: " + ex.Message); }
		}

		private void chipSelectBox_SelectedIndexChanged(object sender, EventArgs e) {
			int index = ((ComboBox)sender).SelectedIndex;
			try {
				switch (index) {
				case 0:
					device.SPIChipSelect = DataAdapterSPIChipSelect.ActiveLow;
					break;
				case 1:
					device.SPIChipSelect = DataAdapterSPIChipSelect.ActiveHigh;
					break;
				case 2:
					device.SPIChipSelect = DataAdapterSPIChipSelect.Low;
					break;
				case 3:
					device.SPIChipSelect = DataAdapterSPIChipSelect.High;
					break;
				default:
					break;
				}
			} catch (PhidgetException ex) { errorBox.addMessage("Error setting chip select mode: " + ex.Message); }
		}

		private void modeBox_SelectedIndexChanged(object sender, EventArgs e) {
			int index = ((ComboBox)sender).SelectedIndex;
			try {
				switch (index) {
				case 0:
					device.SPIMode = DataAdapterSPIMode.Mode_0;
					break;
				case 1:
					device.SPIMode = DataAdapterSPIMode.Mode_1;
					break;
				case 2:
					device.SPIMode = DataAdapterSPIMode.Mode_2;
					break;
				case 3:
					device.SPIMode = DataAdapterSPIMode.Mode_3;
					break;
				default:
					break;
				}
			} catch (PhidgetException ex) { errorBox.addMessage("Error setting SPI mode: " + ex.Message); }
		}

		private void MSBSelect_CheckedChanged(object sender, EventArgs e) {
			if (((RadioButton)sender).Checked) {
				try {
					device.Endianness = DataAdapterEndianness.MSB_First;
				} catch (PhidgetException ex) { errorBox.addMessage("Error setting endianness: " + ex.Message); }
			}
		}

		private void LSBSelect_CheckedChanged(object sender, EventArgs e) {
			if (((RadioButton)sender).Checked) {
				try {
					device.Endianness = DataAdapterEndianness.LSB_First;
				} catch (PhidgetException ex) { errorBox.addMessage("Error setting endianness: " + ex.Message); }
			}
		}

		private void sendCommand_click(object sender, DataGridViewCellEventArgs e) {
			DataGridView table = (DataGridView)sender;
			String dataToSend = "";
			try {
				if (table.Rows[e.RowIndex].Cells[0].Value != null) {
					dataToSend = table.Rows[e.RowIndex].Cells[0].Value.ToString();
				}
				if (e.ColumnIndex == 2)
					sendCommand(dataToSend, e.RowIndex);
			} catch (Exception ex) { errorBox.addMessage("Error sending command: " + ex.Message); }

		}

		private void sendCommand(string commandTxt, int rowIndex) {
			byte[] received;

			if (device.DeviceID == DeviceID.PN_ADP0002) {
				byte[] toSend;
				toSend = parseByteCommand(commandTxt);
				if (toSend == null) {
					return;
				}
				//send it
				if (!(Convert.ToBoolean(commandsTable.Rows[rowIndex].Cells["reply"].Value))) {
					try {
						device.SendPacket(toSend);
					} catch (PhidgetException ex) { errorBox.addMessage("Error sending command: " + ex.Message); }

					dataTable.Rows.Add(BitConverter.ToString(toSend), "N/A");
				} else if (Convert.ToBoolean(commandsTable.Rows[rowIndex].Cells["reply"].Value)) {
					try {
						received = device.SendPacketWaitResponse(toSend);

						dataTable.Rows.Add(BitConverter.ToString(toSend), BitConverter.ToString(received));
					} catch (PhidgetException ex) { errorBox.addMessage("Error sending command: " + ex.Message); return; }
				}
			} else if (device.DeviceID == DeviceID.PN_ADP0001) {
				byte[] toSend;
				toSend = parseByteCommand(commandTxt);
				if (toSend == null) {
					return;
				}

				if (i2cAddressTxt.Text.StartsWith("0x")) //remove 0x prefix
					i2cAddressTxt.Text = i2cAddressTxt.Text.Remove(0, 2);
				int addr = int.Parse(i2cAddressTxt.Text, System.Globalization.NumberStyles.HexNumber); //grab the address we are sending to

				//send it
				try {
					if (commandsTable.Rows[rowIndex].Cells["reply"].Value == null) //no value is counted as 0 response bytes
						commandsTable.Rows[commandsTable.RowCount - 1].Cells[1].Value = 0;
					received = device.I2CSendReceive(addr, toSend, int.Parse(commandsTable.Rows[rowIndex].Cells["reply"].Value.ToString()));

					dataTable.Rows.Add(BitConverter.ToString(toSend), BitConverter.ToString(received));
				} catch (PhidgetException ex) { errorBox.addMessage("Error sending command: " + ex.Message); return; }
			}

			dataTable.FirstDisplayedScrollingRowIndex = dataTable.RowCount - 1;
		}

		public static byte[] StringToByteArray(string hex) {
			return Enumerable.Range(0, hex.Length)
							 .Where(x => x % 2 == 0)
							 .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
							 .ToArray();
		}

		private byte[] parseByteCommand(string cmd) {
			byte[] parsed;

			if (cmd.Length == 0)
				return new byte[0];

			if (cmd.Length % 2 != 0) { //check that there are a valid number of characters for a byte array
				errorBox.addMessage("Error parsing command.  Format must be a series of bytes.");
				return null;
			}

			char[] delimiters = { ' ', ',', ';' };
			string[] dataTokens = cmd.Split(delimiters); //check to see if the bytes are split up

			int length = dataTokens.Length;
			if (length > 1) {
				parsed = new byte[length];
				for (int i = 0; i < length; i++) {
					if (dataTokens[i].StartsWith("0x")) //remove 0x prefix
						dataTokens[i] = dataTokens[i].Remove(0, 2);
					try {
						parsed[i] = (byte)Convert.ToInt16(dataTokens[i], 16); //translate string into byte array
					} catch (Exception ex) { errorBox.addMessage("Error parsing command: " + ex.Message); }
				}
			} else if (length == 1) {
				if (dataTokens[0].StartsWith("0x")) //remove 0x prefix
					dataTokens[0] = dataTokens[0].Remove(0, 2);
				int length2 = dataTokens[0].Length;
				if (length2 % 2 != 0) { //check that there are a valid number of characters for a byte array
					errorBox.addMessage("Error parsing command.  Format must be a series of bytes.");
					return null;
				}

				IEnumerable<string> bytes = dataTokens[0].SplitBy(2); //split up string into individual bytes

				string[] bytearray = bytes.ToArray();

				parsed = new byte[bytearray.Length];

				try {
					for (int i = 0; i < parsed.Length; i++) {
						parsed[i] = (BitConverter.GetBytes(Convert.ToUInt16(bytearray[i], 16)))[0]; //translate strings into bytes
					}
				} catch (Exception ex) {
					errorBox.addMessage("Error parsing serial command: " + ex.Message);
					return null;
				}

			} else {
				errorBox.addMessage("Error parsing command.  Format must be a series of bytes.");
				return null;
			}
			return parsed;
		}

		private void clrBtn_Click(object sender, EventArgs e) { //clear datagridview entries
			dataTable.Rows.Clear();
		}

		private void TxTxt_KeyDown(object sender, KeyEventArgs e) {
		}

		private void addRow_Click(object sender, EventArgs e) {
			commandsTable.Rows.Add();
		}

		private void delRow_Click(object sender, EventArgs e) {
			commandsTable.Rows.RemoveAt(commandsTable.Rows.Count - 1);
		}

		private void initializeCommandsTable(string style) {
			DataGridViewTextBoxColumn dataToAdd = new DataGridViewTextBoxColumn();
			dataToAdd.HeaderText = "Data";
			dataToAdd.Name = "data";
			dataToAdd.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
			DataGridViewButtonColumn sendToAdd = new DataGridViewButtonColumn();
			sendToAdd.Text = "Send";
			sendToAdd.Name = "send";
			sendToAdd.UseColumnTextForButtonValue = true;
			sendToAdd.Width = 60;
			if (style == "spi") {
				DataGridViewCheckBoxColumn receiveToAdd = new DataGridViewCheckBoxColumn();
				receiveToAdd.HeaderText = "Wait for Response?";
				receiveToAdd.Name = "reply";
				commandsTable.Columns.Add(dataToAdd);
				commandsTable.Columns.Add(receiveToAdd);
				commandsTable.Columns.Add(sendToAdd);
			} else if (style == "i2c") {
				DataGridViewTextBoxColumn receiveToAdd = new DataGridViewTextBoxColumn();
				receiveToAdd.HeaderText = "Reply Bytes";
				receiveToAdd.Name = "reply";
				commandsTable.Columns.Add(dataToAdd);
				commandsTable.Columns.Add(receiveToAdd);
				commandsTable.Columns.Add(sendToAdd);
			}
		}

		private void commandsTable_DpiChangedAfterParent(object sender, EventArgs e) {
			commandsTable.PerformLayout();
			commandsTable.Invalidate();
		}

		private void dataTable_DpiChangedAfterParent(object sender, EventArgs e) {
			dataTable.PerformLayout();
			dataTable.Invalidate();
		}
	}

	public static class EnumerableEx {
		public static IEnumerable<string> SplitBy(this string str, int chunkLength) {
			if (String.IsNullOrEmpty(str)) throw new ArgumentException();
			if (chunkLength < 1) throw new ArgumentException();

			for (int i = 0; i < str.Length; i += chunkLength) {
				if (chunkLength + i > str.Length)
					chunkLength = str.Length - i;

				yield return str.Substring(i, chunkLength);
			}
		}
	}

	public enum CommandStyle { SINGLE, BATCH }

	public class serialCommand {
		public byte[] Data;
		public int rxCount = 0;
	}
}
