//..............................................................................
//
// Socket & network adapter user interface
//

import "ui_PropertyGrid.jnc"
import "ui_ComboBox.jnc"
import "ui_Action.jnc"
import "ui_Layout.jnc"
import "ui_ToolBar.jnc"
import "doc_Storage.jnc"
import "std_Array.jnc"
import "std_String.jnc"
import "io_base.jncx"
import "io_Serial.jnc"
import "io_SerialPortEnumerator.jnc"

namespace ui {

//! \addtogroup common-ui
//! @{

//..............................................................................

class SerialUi {
	bitflag enum ItemId {
		BaudRate,
		DataBits,
		Parity,
		StopBits,
		FlowControl,
		Dtr,
		Rts,
		BreakCondition,

		All =
			BaudRate |
			DataBits |
			Parity |
			StopBits |
			FlowControl |
			Dtr |
			Rts |
			BreakCondition,
	}

	enum Defaults {
		BaudRate       = 38400,
		DataBits       = 8,
		Parity         = io.SerialParity.None,
		StopBits       = io.SerialStopBits._1,
		FlowControl    = io.SerialFlowControl.None,
		Dtr            = true,
		Rts            = false,
		BreakCondition = false,
	}

	ComboBox* m_baudRateCombo;
	ComboBox* m_dataBitsCombo;
	ComboBox* m_parityCombo;
	ComboBox* m_stopBitsCombo;
	ComboBox* m_flowControlCombo;
	Action* m_dtrAction;
	Action* m_rtsAction;
	Action* m_breakConditionAction;

	ComboProperty* m_baudRateProp;
	EnumProperty* m_dataBitsProp;
	EnumProperty* m_parityProp;
	EnumProperty* m_stopBitsProp;
	EnumProperty* m_flowControlProp;
	BoolProperty* m_dtrProp;
	BoolProperty* m_rtsProp;
	BoolProperty* m_breakConditionProp;

	property m_baudRate {
		uint_t get() {
			return
				m_baudRateCombo ? atoi(m_baudRateCombo.m_editText) :
				m_baudRateProp ? atoi(m_baudRateProp.m_value) :
				Defaults.BaudRate;
		}

		void set(uint_t value) {
			if (m_baudRateCombo)
				m_baudRateCombo.m_editText = $"$value bps";
			else if (m_baudRateProp)
				m_baudRateProp.m_value = $"$value bps";
		}
	}

	property m_dataBits {
		uint_t get() {
			return
				m_dataBitsCombo ? m_dataBitsCombo.m_currentData :
				m_dataBitsProp ? m_dataBitsProp.m_value :
				Defaults.DataBits;
		}

		void set(uint_t value) {
			if (m_dataBitsCombo)
				m_dataBitsCombo.m_currentData = value;
			else if (m_dataBitsProp)
				m_dataBitsProp.m_value = value;
		}
	}

	property m_parity {
		io.SerialParity get() {
			return
				m_parityCombo ? m_parityCombo.m_currentData :
				m_parityProp ? m_parityProp.m_value :
				Defaults.Parity;
		}

		void set(io.SerialParity value) {
			if (m_parityCombo)
				m_parityCombo.m_currentData = value;
			else if (m_parityProp)
				m_parityProp.m_value = value;
		}
	}

	property m_stopBits {
		io.SerialStopBits get() {
			return
				m_stopBitsCombo ? m_stopBitsCombo.m_currentData :
				m_stopBitsProp ? m_stopBitsProp.m_value :
				Defaults.StopBits;
		}

		void set(io.SerialStopBits value) {
			if (m_stopBitsCombo)
				m_stopBitsCombo.m_currentData = value;
			else if (m_stopBitsProp)
				m_stopBitsProp.m_currentIndex = value;
		}
	}

	property m_flowControl {
		io.SerialFlowControl get() {
			return
				m_flowControlCombo ? m_flowControlCombo.m_currentData :
				m_flowControlProp ? m_flowControlProp.m_value :
				Defaults.FlowControl;
		}

		void set(io.SerialFlowControl value) {
			if (m_flowControlCombo)
				m_flowControlCombo.m_currentData = value;
			else if (m_flowControlProp)
				m_flowControlProp.m_value = value;
		}
	}

	property m_dtr {
		bool get() {
			return
				m_dtrAction ? m_dtrAction.m_isChecked :
				m_dtrProp ? m_dtrProp.m_value :
				Defaults.Dtr;
		}

		void set(bool value) {
			if (m_dtrAction)
				m_dtrAction.m_isChecked = value;
			else if (m_dtrProp)
				m_dtrProp.m_value = value;
		}
	}

	property m_rts {
		bool get() {
			return
				m_rtsAction ? m_rtsAction.m_isChecked :
				m_rtsProp ? m_rtsProp.m_value :
				Defaults.Dtr;
		}

		void set(bool value) {
			if (m_rtsAction)
				m_rtsAction.m_isChecked = value;
			else if (m_rtsProp)
				m_rtsProp.m_value = value;
		}
	}

	property m_breakCondition {
		bool get() {
			return
				m_breakConditionAction ? m_breakConditionAction.m_isChecked :
				m_breakConditionProp ? m_breakConditionProp.m_value :
				Defaults.Dtr;
		}

		void set(bool value) {
			if (m_breakConditionAction)
				m_breakConditionAction.m_isChecked = value;
			else if (m_breakConditionProp)
				m_breakConditionProp.m_value = value;
		}
	}

	static EnumPropertyOption const m_baudRateTable[] = {
		{ "110 bps" },
		{ "300 bps" },
		{ "600 bps" },
		{ "1200 bps" },
		{ "2400 bps" },
		{ "4800 bps" },
		{ "9600 bps" },
		{ "14400 bps" },
		{ "19200 bps" },
		{ "38400 bps" },
		{ "56000 bps" },
		{ "57600 bps" },
		{ "115200 bps" },
		{ "128000 bps" },
		{ "153600 bps" },
		{ "230400 bps" },
		{ "256000 bps" },
		{ "460800 bps" },
		{ "921600 bps" },
	}

	static EnumPropertyOption const m_dataBitsTable[] = {
		{ "7 bits", 7 },
		{ "8 bits", 8 },
	}

	static EnumPropertyOption const m_stopBitsTable[] = {
		{ "1 bit",    io.SerialStopBits._1 },
		{ "1.5 bits", io.SerialStopBits._15 },
		{ "2 bits",   io.SerialStopBits._2 },
	}

	static EnumPropertyOption const m_parityTable[] = {
		{ "None",  io.SerialParity.None },
		{ "Odd",   io.SerialParity.Odd },
		{ "Even",  io.SerialParity.Even },
		{ "Mark",  io.SerialParity.Mark },
		{ "Space", io.SerialParity.Space },
	}

	static EnumPropertyOption const m_flowControlTable[] = {
		{ "None",     io.SerialFlowControl.None },
		{ "RTS/CTS",  io.SerialFlowControl.RtsCts },
		{ "XON/XOFF", io.SerialFlowControl.XonXoff },
	}

public:
	void createForm(
		FormLayout* layout,
		ItemId mask = ItemId.All
	);

	void createProperties(
		PropertyGrid* propertyGrid,
		GroupProperty* groupProp = null,
		ItemId mask = ItemId.All
	);

	void load(doc.Storage* storage);
	void save(doc.Storage* storage);
	void updateProperties();
	void applyProperties();
	void restoreDefaultProperties();

protected:
	Action* createControlLineAction(
		ui.ToolBar* toolBar,
		string_t iconFileName,
		string_t text
	);
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

void SerialUi.createForm(
	FormLayout* layout,
	ItemId mask
) {
	if (mask & ItemId.BaudRate) {
		m_baudRateCombo = new ComboBox(m_baudRateTable, countof(m_baudRateTable));
		m_baudRateCombo.m_isEditable = true;
		layout.addRow("Baud rate:", m_baudRateCombo);
	}

	if (mask & ItemId.DataBits) {
		m_dataBitsCombo = new ComboBox(m_dataBitsTable, countof(m_dataBitsTable));
		layout.addRow("Data bits:", m_dataBitsCombo);
	}

	if (mask & ItemId.Parity) {
		m_parityCombo = new ComboBox(m_parityTable, countof(m_parityTable));
		layout.addRow("Parity:", m_parityCombo);
	}

	if (mask & ItemId.StopBits) {
		m_stopBitsCombo = new ComboBox(m_stopBitsTable, countof(m_stopBitsTable));
		layout.addRow("Stop bits:", m_stopBitsCombo);
	}

	if (mask & ItemId.FlowControl) {
		m_flowControlCombo = new ComboBox(m_flowControlTable, countof(m_flowControlTable));
		layout.addRow("Flow control:", m_flowControlCombo);
	}

	if (mask & (ItemId.Dtr | ItemId.Rts | ItemId.BreakCondition)) {
		ui.ToolBar toolBar;
		toolBar.addStretch();

		if (mask & ItemId.Dtr)
			m_dtrAction = createControlLineAction(toolBar, "images/dtr.png", "DTR");

		if (mask & ItemId.Rts)
			m_rtsAction = createControlLineAction(toolBar, "images/rts.png", "RTS");

		if (mask & ItemId.BreakCondition)
			m_breakConditionAction = createControlLineAction(toolBar, "images/break.png", "Break");

		layout.addRow(toolBar);
	}
}

Action* SerialUi.createControlLineAction(
	ui.ToolBar* toolBar,
	string_t iconFileName,
	string_t text
) {
	Action action(iconFileName, text);
	action.m_isCheckable = true;
	toolBar.addAction(action);
	return action;
}

void SerialUi.createProperties(
	PropertyGrid* propertyGrid,
	GroupProperty* groupProp,
	ItemId mask
) {
	if (mask & ItemId.BaudRate)
		m_baudRateProp = propertyGrid.createComboProperty(
			groupProp,,
			"Baud rate",
			"Enter a baud rate...",
			"Specify a serial baud rate",
			m_baudRateTable,
			countof(m_baudRateTable)
		);

	if (mask & ItemId.DataBits)
		m_dataBitsProp = propertyGrid.createEnumProperty(
			groupProp,,
			"Data bits",
			"Serial data bit count (word size)",
			m_dataBitsTable,
			countof(m_dataBitsTable)
		);

	if (mask & ItemId.Parity)
		m_parityProp = propertyGrid.createEnumProperty(
			groupProp,,
			"Parity",
			"Serial parity check",
			m_parityTable,
			countof(m_parityTable)
		);

	if (mask & ItemId.StopBits)
		m_stopBitsProp = propertyGrid.createEnumProperty(
			groupProp,,
			"Stop bits",
			"Serial stop bit count",
			m_stopBitsTable,
			countof(m_stopBitsTable)
		);

	if (mask & ItemId.FlowControl)
		m_flowControlProp = propertyGrid.createEnumProperty(
			groupProp,,
			"Flow control",
			"Serial flow control",
			m_flowControlTable,
			countof(m_flowControlTable)
		);

	if (mask & ItemId.Dtr)
		m_dtrProp = propertyGrid.createBoolProperty(
			groupProp,,
			"DTR",
			"DTR (Data-Terminal-Ready) modem control line"
		);

	if (mask & ItemId.Rts)
		m_rtsProp = propertyGrid.createBoolProperty(
			groupProp,,
			"RTS",
			"RTS (Request-To-Send) modem control line"
		);

	if (mask & ItemId.BreakCondition)
		m_breakConditionProp = propertyGrid.createBoolProperty(
			groupProp,,
			"BREAK",
			"BREAK condition on the TX line"
		);
}

void SerialUi.load(doc.Storage* storage) {
	m_baudRate = storage.readInt("baudRateVal", Defaults.BaudRate);
	m_dataBits = storage.readInt("dataBitsVal", Defaults.DataBits); // 8 bits
	m_parity = (io.SerialParity)storage.readInt("parityVal", Defaults.Parity);
	m_stopBits = (io.SerialStopBits)storage.readInt("stopBitsVal", Defaults.StopBits);
	m_flowControl = (io.SerialFlowControl)storage.readInt("flowControlVal", Defaults.FlowControl);
	m_dtr = (io.SerialFlowControl)storage.readBool("dtr", Defaults.Dtr);
	m_rts = (io.SerialFlowControl)storage.readBool("rts", Defaults.Rts);
}

void SerialUi.save(doc.Storage* storage) {
	storage.writeInt("baudRateVal", m_baudRate);
	storage.writeInt("dataBitsVal", m_dataBits);
	storage.writeInt("parityVal", m_parity);
	storage.writeInt("stopBitsVal", m_stopBits);
	storage.writeInt("flowControlVal", m_flowControl);
	storage.writeBool("dtr", m_dtr);
	storage.writeBool("rts", m_rts);
}

void SerialUi.updateProperties() {
	if (m_baudRateProp && m_baudRateCombo)
		m_baudRateProp.m_value = m_baudRateCombo.m_editText;

	if (m_dataBitsProp && m_dataBitsCombo)
		m_dataBitsProp.m_currentIndex = m_dataBitsCombo.m_currentIndex;

	if (m_parityProp && m_parityCombo)
		m_parityProp.m_currentIndex = m_parityCombo.m_currentIndex;

	if (m_stopBitsProp && m_stopBitsCombo)
		m_stopBitsProp.m_currentIndex = m_stopBitsCombo.m_currentIndex;

	if (m_flowControlProp && m_flowControlCombo)
		m_flowControlProp.m_currentIndex = m_flowControlCombo.m_currentIndex;

	if (m_dtrProp && m_dtrAction)
		m_dtrProp.m_value = m_dtrAction.m_isChecked;

	if (m_rtsProp && m_rtsAction)
		m_rtsProp.m_value = m_rtsAction.m_isChecked;

	if (m_breakConditionProp && m_breakConditionAction)
		m_breakConditionProp.m_value = m_breakConditionAction.m_isChecked;
}

void SerialUi.applyProperties() {
	if (m_baudRateProp && m_baudRateCombo)
		m_baudRateCombo.m_editText = m_baudRateProp.m_value;

	if (m_dataBitsProp && m_dataBitsCombo)
		m_dataBitsCombo.m_currentIndex = m_dataBitsProp.m_currentIndex;

	if (m_parityProp && m_parityCombo)
		m_parityCombo.m_currentIndex = m_parityProp.m_currentIndex;

	if (m_stopBitsProp && m_stopBitsCombo)
		m_stopBitsCombo.m_currentIndex = m_stopBitsProp.m_currentIndex;

	if (m_flowControlProp && m_flowControlCombo)
		m_flowControlCombo.m_currentIndex = m_flowControlProp.m_currentIndex;

	if (m_dtrProp && m_dtrAction)
		m_dtrAction.m_isChecked = m_dtrProp.m_value;

	if (m_rtsProp && m_rtsAction)
		m_rtsAction.m_isChecked = m_rtsProp.m_value;

	if (m_breakConditionProp && m_breakConditionAction)
		m_breakConditionAction.m_isChecked = m_breakConditionProp.m_value;
}

void SerialUi.restoreDefaultProperties() {
	if (m_baudRateProp)
		m_baudRateProp.m_value = $"$(Defaults.BaudRate) bps";

	if (m_dataBitsProp)
		m_dataBitsProp.m_value = Defaults.DataBits;

	if (m_parityProp)
		m_parityProp.m_value = Defaults.Parity;

	if (m_stopBitsProp)
		m_stopBitsProp.m_value = Defaults.StopBits;

	if (m_flowControlProp)
		m_flowControlProp.m_value = Defaults.FlowControl;

	if (m_dtrProp)
		m_dtrProp.m_value = Defaults.Dtr;

	if (m_rtsProp)
		m_rtsProp.m_value = Defaults.Rts;

	if (m_breakConditionProp)
		m_breakConditionProp.m_value = Defaults.BreakCondition;
}

//..............................................................................

bool isSerialPortLess(
	io.SerialPortDesc const* port1,
	io.SerialPortDesc const* port2
) {
	return port1.m_deviceName < port2.m_deviceName;
}

void enumerateSerialPorts(
	ComboBox* portCombo,
	ComboProperty* portProp
) {
	string_t editText = portCombo.m_editText; // save edit text
	portCombo.clear();

	size_t count;
	io.SerialPortDesc const* portList = io.enumerateSerialPorts(io.SerialPortDescFlags.All, &count);

	std.Array portArray;
	portArray.setCount(count);
	io.SerialPortDesc const* port = portList;
	for (size_t i = 0; i < count; i++, port = port.m_next)
		portArray[i] = port;

	portArray.sort(isSerialPortLess);

	EnumPropertyOption* optionArray = new EnumPropertyOption[count];
	EnumPropertyOption* option = optionArray;

	std.StringBuilder description;
	std.StringBuilder toolTip;

	for (size_t i = 0; i < count; i++, option++) {
		port = portArray[i];
		description = port.m_deviceName;
		toolTip = $"<table><tr><td>Device&nbsp;name:</td><td>$(port.m_deviceName)</td></tr>";

		if (port.m_description) {
			description += $" - $(port.m_description)";
			toolTip += $"<tr><td>Description:</td><td>$(port.m_description)</td></tr>";
		}

		if (port.m_manufacturer) {
			description += $" by $(port.m_manufacturer)";
			toolTip += $"<tr><td>Manufacturer:</td><td>$(port.m_manufacturer)</td></tr>";
		}

		if (port.m_hardwareIds)
			toolTip += $"<tr><td>Hardware&nbsp;ID:</td><td>$(port.m_hardwareIds)</td></tr>";

		if (port.m_driver)
			toolTip += $"<tr><td>Driver:</td><td>$(port.m_driver)</td></tr>";

		if (port.m_location)
			toolTip += $"<tr><td>Location:</td><td>$(port.m_location)</td></tr>";

		toolTip.append("</table>");

		string_t text = description.detachString();
		size_t j = portCombo.addItem(text, port.m_deviceName);
		portCombo.m_itemToolTip[j] = toolTip.detachString();

		option.m_text = text;
		option.m_value = port.m_deviceName;
	}

	portCombo.m_editText = editText; // restore edit text
	portProp.setOptions(optionArray, count);
}

//..............................................................................

//! @}

} // namespace ui
