//..............................................................................
//
// Serial session
//

import "io_base.jncx"
import "io_Serial.jnc"
import "std_List.jnc"
import "std_Buffer.jnc"
import "sys_Timer.jnc"
import "doc_Plugin.jnc"

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

class SerialSession: doc.Session
{
protected:
	enum Defaults
	{
		BaudRate          = 38400,
		DataBits          = 8,
		StopBits          = io.SerialStopBits._1,
		Parity            = io.SerialParity.None,
		FlowControl       = io.SerialFlowControl.None,
		InterCharDelay    = 200,
		ReadCheckComstat  = false,
		ReadWaitFirstChar = true,
		ReadInterval      = 10,
	}

	enum State
	{
		Closed,
		Opened,
	}

	enum ActionId
	{
		Refresh,
		Open,
		Dtr,
		Rts,
		_Count,
	}

	enum IconId
	{
		Open,
		Close,
		_Count,
	}

	enum StatusPaneId
	{
		PortName,
		State,
		Dtr,
		Rts,
		Dsr,
		Cts,
		Dcd,
		_Count,
	}

protected:
	// serial port

	State bindable m_state;
	io.Serial m_serial;
	SerialLogFilter m_logFilter;
	char const* m_portName;
	io.SerialStatusLines bindable m_serialStatusLines;
	bool bindable m_serialDtr;
	bool bindable m_serialRts;
	std.Buffer m_readBuffer;
	uint_t m_syncId;

	// inter-char delay

	sys.Timer m_delayedWriteTimer;
	std.List m_delayedWriteList;
	size_t m_totalDelayedWriteSize;
	uint64_t m_lastWriteTime;

	// toolbar, status bar & menu

	gui.Action* m_actionTable [ActionId._Count];
	gui.Icon* m_iconTable [IconId._Count];
	gui.StatusPane* m_statusPaneTable [StatusPaneId._Count];
	gui.ComboBox* m_portCombo;
	gui.ComboBox* m_baudRateCombo;

	// serial property grid

	gui.StringProperty* m_portProp;
	gui.EnumProperty* m_baudRateProp;
	gui.EnumProperty* m_dataBitsProp;
	gui.EnumProperty* m_stopBitsProp;
	gui.EnumProperty* m_flowControlProp;
	gui.EnumProperty* m_parityProp;
	gui.BoolProperty* m_useInterCharDelayProp;
	gui.IntProperty* m_interCharDelayProp;
	gui.BoolProperty* m_readCheckComstatProp;
	gui.BoolProperty* m_readWaitFirstCharProp;
	gui.IntProperty* m_readIntervalProp;
	io.BufferPropertySet m_bufferPropertySet;

	// filter property grid

	gui.BoolProperty* m_settingChangedVisibleProp;
	gui.BoolProperty* m_controlLineChangedVisibleProp;
	gui.BoolProperty* m_statusLineChangedVisibleProp;

public:
	construct (doc.PluginHost* pluginHost);

	bool errorcode open ();
	close ();

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

	override size_t errorcode transmit (
		void const* p,
		size_t size
		);

protected:
	onRefresh ();
	onOpen ();
	onDelayedWriteTimer ();
	onBaudRateChanged ();
	onDtrChanged ();
	onRtsChanged ();

	onSerialEvent (
		uint_t syncId,
		io.SerialEvents triggeredEvents
		);

	createUi ();

	gui.ComboBox* createSerialSettingComboBox (
		char const* labelText,
		gui.EnumPropertyOption const* optionTable,
		size_t optionCount,
		function* onChanged ()
		);

	size_t write (
		void const* p,
		size_t size
		);

	size_t addDelayedWrite (
		void const* p,
		size_t size
		);

	waitSerialEvents ();
	setDelayedWriteTimer ();
	flushDelayedWrite ();
	io.SerialOptions getSerialOptions ();

	uint_t getReadInterval ()
	{
		return sys.g_systemInfo.m_osKind != sys.OsKind.Windows || m_readCheckComstatProp.m_value ? 0 : m_readIntervalProp.m_value;
	}

	reactor m_uiReactor ();
}

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

SerialSession.construct (doc.PluginHost* pluginHost)
{
	basetype.construct (pluginHost);

	pluginHost.m_log.attachRepresenter (representSerialLog);
	pluginHost.m_log.attachFilter (m_logFilter);

	m_state = State.Closed;
	m_readBuffer.setSize (io.BufferPropertySet.Defaults.ReadBufferSize);

	createUi ();
}

SerialSession.createUi ()
{
	static gui.EnumPropertyOption baudRateTable [] =
	{
		{ "110 bps",    110 },
		{ "300 bps",    300 },
		{ "600 bps",    600 },
		{ "1200 bps",   1200 },
		{ "2400 bps",   2400 },
		{ "4800 bps",   4800 },
		{ "9600 bps",   9600 },
		{ "14400 bps",  14400 },
		{ "19200 bps",  19200 },
		{ "38400 bps",  38400 },
		{ "56000 bps",  56000 },
		{ "57600 bps",  57600 },
		{ "115200 bps", 115200 },
		{ "128000 bps", 128000 },
		{ "153600 bps", 153600 },
		{ "230400 bps", 230400 },
		{ "256000 bps", 256000 },
		{ "460800 bps", 460800 },
		{ "921600 bps", 921600 },
	}

	static gui.EnumPropertyOption flowControlTable [] =
	{
		{ "None",     io.SerialFlowControl.None },
		{ "RTS/CTS",  io.SerialFlowControl.RtsCts },
		{ "XON/XOFF", io.SerialFlowControl.XOnXOff },
	}

	static gui.EnumPropertyOption dataBitsTable [] =
	{
		{ "7 bits", 7 },
		{ "8 bits", 8 },
	}

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

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

	// toolbar

	m_pluginHost.m_toolBar.addLabel ("Port:");
	m_portCombo = m_pluginHost.m_toolBar.addComboBox ();
	m_portCombo.m_isEditable = true;

	size_t portCount;
	io.SerialPortDesc const* portList = io.createSerialPortDescList (&portCount);
	io.SerialPortDesc const* port = portList;
	for (; port; port = port.m_next)
		m_portCombo.addItem (port.m_deviceName, null);

	m_portCombo.adjustSizeToContents ();
	m_portCombo.m_onEnter += open;

	m_actionTable [ActionId.Refresh]  = createAction ("Refresh port list", "images/refresh.png");
	m_actionTable [ActionId.Refresh].m_onTriggered += onRefresh;

	m_baudRateCombo = createSerialSettingComboBox (
		"Baud:",
		baudRateTable,
		countof (baudRateTable),
		onBaudRateChanged
		);

	m_iconTable [IconId.Open] = new gui.Icon ("images/connect.png");
	m_iconTable [IconId.Close] = new gui.Icon ("images/pause.png");

	m_actionTable [ActionId.Open]  = createAction ("Open", "images/connect.png");
	m_actionTable [ActionId.Open].m_onTriggered += onOpen;

	m_pluginHost.m_menu.addSeparator ();
	m_pluginHost.m_toolBar.addSeparator ();

	m_actionTable [ActionId.Dtr] = createAction ("DTR", "images/dtr.png", true, true);
	m_actionTable [ActionId.Dtr].m_onTriggered += onDtrChanged;

	m_actionTable [ActionId.Rts] = createAction ("RTS", "images/rts.png", true, false);
	m_actionTable [ActionId.Rts].m_onTriggered += onRtsChanged;

	// status bar

	m_statusPaneTable [StatusPaneId.PortName] = m_pluginHost.m_statusBar.addPane (, 50);
	m_statusPaneTable [StatusPaneId.PortName].m_color = gui.StdColor.Blue;
	m_statusPaneTable [StatusPaneId.State] = m_pluginHost.m_statusBar.addPane (, 80);
	m_statusPaneTable [StatusPaneId.Dtr] = m_pluginHost.m_statusBar.addPane ("DTR", 32);
	m_statusPaneTable [StatusPaneId.Dtr].m_color = gui.StdColor.Red;
	m_statusPaneTable [StatusPaneId.Rts] = m_pluginHost.m_statusBar.addPane ("RTS", 32);
	m_statusPaneTable [StatusPaneId.Rts].m_color = gui.StdColor.Red;
	m_statusPaneTable [StatusPaneId.Dsr] = m_pluginHost.m_statusBar.addPane ("DSR", 32);
	m_statusPaneTable [StatusPaneId.Dsr].m_color = gui.StdColor.Red;
	m_statusPaneTable [StatusPaneId.Cts] = m_pluginHost.m_statusBar.addPane ("CTS", 32);
	m_statusPaneTable [StatusPaneId.Cts].m_color = gui.StdColor.Red;
	m_statusPaneTable [StatusPaneId.Dcd] = m_pluginHost.m_statusBar.addPane ("DCD", 32);
	m_statusPaneTable [StatusPaneId.Dcd].m_color = gui.StdColor.Red;

	// property grid

	gui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Connection",
		"Serial port-specific connection settings"
		);

	m_portProp = m_pluginHost.m_propertyGrid.createStringProperty (
		group,,
		"Port",
		"Specify serial port to use"
		);

	m_baudRateProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		group,,
		"Baud rate",
		"Serial baud rate",
		baudRateTable,
		countof (baudRateTable)
		);

	m_dataBitsProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		group,,
		"Data bits",
		"Serial data bit count (word size)",
		dataBitsTable,
		countof (dataBitsTable)
		);

	m_stopBitsProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		group,,
		"Stop bits",
		"Serial stop bit count",
		stopBitsTable,
		countof (stopBitsTable)
		);

	m_flowControlProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		group,,
		"Flow control",
		"Serial flow control",
		flowControlTable,
		countof (flowControlTable)
		);

	m_parityProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		group,,
		"Parity",
		"Serial parity check",
		parityTable,
		countof (parityTable)
		);

	group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Transmission inter-char delay",
		"Insert delay between transmitted characters"
		);

	m_useInterCharDelayProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Use TX inter-char delay",
		"Insert inter-character delays during transmission"
		);

	m_interCharDelayProp = m_pluginHost.m_propertyGrid.createIntProperty (
		group,,
		"TX inter-char delay (ms)",
		"Transmission inter-character delay (in millisceonds)"
		);

	group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Buffering & compatibility",
		"Advanced options controlling performance, throughtput and compatibility"
		);

	// these settings only make sense on Windows
	// still, we want valid property pointers, so there's less checking

	if (sys.g_systemInfo.m_osKind == sys.OsKind.Windows)
	{
		m_readCheckComstatProp = m_pluginHost.m_propertyGrid.createBoolProperty (
			group,,
			"Read checks COMSTAT",
			"Don't issue read requests unless the driver reports COMSTAT.cbInQue"
			);

		m_readWaitFirstCharProp = m_pluginHost.m_propertyGrid.createBoolProperty (
			group,,
			"Read waits for char",
			"Read request waits until a character is received and then returns immediately"
			);

		m_readIntervalProp = m_pluginHost.m_propertyGrid.createIntProperty (
			group,,
			"Read max inter-char interval (ms)",
			"Maximum inter-character delay during read request (in millisceonds)"
			);

		m_readIntervalProp.m_minValue = 1;
	}
	else
	{
		m_readCheckComstatProp = new gui.BoolProperty (null);
		m_readWaitFirstCharProp = new gui.BoolProperty (null);
		m_readIntervalProp = new gui.IntProperty (null);
	}

	m_bufferPropertySet.createProperties (m_pluginHost.m_propertyGrid, group);

	group = m_pluginHost.m_propertyGrid.createGroupProperty (
		"Log filter",
		"Specify which unwanted log messages to filter out"
		);

	m_settingChangedVisibleProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Show setting changes",
		"Display setting change log records (or filter them out)"
		);

	m_controlLineChangedVisibleProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Show control line changes",
		"Display control line change log records (or filter them out)"
		);

	m_statusLineChangedVisibleProp = m_pluginHost.m_propertyGrid.createBoolProperty (
		group,,
		"Show status line changes",
		"Display status line change log records (or filter them out)"
		);

	m_uiReactor.start ();
}

gui.ComboBox* SerialSession.createSerialSettingComboBox (
	char const* labelText,
	gui.EnumPropertyOption const* optionTable,
	size_t optionCount,
	function* onChanged ()
	)
{
	m_pluginHost.m_toolBar.addLabel (labelText);
	gui.ComboBox* comboBox = m_pluginHost.m_toolBar.addComboBox ();

	for (size_t i = 0; i < optionCount; i++)
		comboBox.addItem (optionTable [i].m_text, optionTable [i].m_value);

	comboBox.adjustSizeToContents ();
	comboBox.m_onChanged += onChanged;

	return comboBox;
}

SerialSession.onRefresh ()
{
	char const* name = m_portCombo.m_currentText;
	m_portCombo.clear ();

	size_t portCount;
	io.SerialPortDesc const* portList = io.createSerialPortDescList (&portCount);
	io.SerialPortDesc const* port = portList;
	for (; port; port = port.m_next)
		m_portCombo.addItem (port.m_deviceName, null);

	m_portCombo.m_editText = name; // preserve current text
	m_portCombo.adjustSizeToContents ();
}

SerialSession.onOpen ()
{
	if (m_state)
		close ();
	else
		try open ();
}

io.SerialOptions SerialSession.getSerialOptions ()
{
	io.SerialOptions options;

	if (m_bufferPropertySet.m_keepReadBlockSizeProp.m_value)
		options |= io.SerialOptions.KeepReadBlockSize;

	if (m_bufferPropertySet.m_keepWriteBlockSizeProp.m_value)
		options |= io.SerialOptions.KeepWriteBlockSize;

	if (m_readCheckComstatProp.m_value)
		options |= io.SerialOptions.WinReadCheckComstat;

	if (m_readWaitFirstCharProp.m_value)
		options |= io.SerialOptions.WinReadWaitFirstChar;

	return options;
}

bool errorcode SerialSession.open ()
{
	close ();
	startSession ();

	char const* name = m_portCombo.m_currentText;

	// configure and open

	m_serial.m_readInterval = getReadInterval ();
	m_serial.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
	m_serial.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
	m_serial.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	m_serial.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
	m_serial.m_options = getSerialOptions ();

	m_serial.m_baudRate = m_baudRateCombo.m_currentData;
	m_serial.m_dataBits = m_dataBitsProp.m_value;
	m_serial.m_stopBits = m_stopBitsProp.m_value;
	m_serial.m_flowControl = m_flowControlProp.m_value;
	m_serial.m_parity = m_parityProp.m_value;
	m_serial.m_dtr = m_actionTable [ActionId.Dtr].m_isChecked;
	m_serial.m_rts = m_actionTable [ActionId.Rts].m_isChecked;

	m_serial.open (name);

	SerialOpenParams params;
	params.m_baudRate = m_serial.m_baudRate;
	params.m_flowControl = m_serial.m_flowControl;
	params.m_dataBits = m_serial.m_dataBits;
	params.m_stopBits = m_serial.m_stopBits;
	params.m_parity = m_serial.m_parity;

	char const* p = $"$(params; B)$name";
	writeLog (SerialLogRecordCode.PortOpened, p, dynamic sizeof (p));

	m_portName = name;
	m_serialDtr = m_serial.m_dtr;
	m_serialRts = m_serial.m_rts;
	m_serialStatusLines = m_serial.m_statusLines;
	m_state = State.Opened;

	waitSerialEvents ();
	return true;

catch:
	m_serial.close ();
	std.Error const* error = std.getLastError ();

	writeLog (
		SerialLogRecordCode.PortOpenError,
		error,
		error.m_size
		);

	return false;
}

SerialSession.close ()
{
	if (m_state != State.Opened)
		return;

	writeLog (
		SerialLogRecordCode.PortClosed,
		m_portName,
		strlen (m_portName) + 1
		);

	m_serial.close ();
	m_delayedWriteTimer.stop ();
	m_delayedWriteList.clear ();
	m_state = State.Closed;
	m_syncId++;
}

void SerialSession.load (doc.Storage* storage)
{
	m_portCombo.m_editText = storage.readString ("portName");
	m_baudRateCombo.m_currentIndex = storage.readInt ("baudRate", 9); // 38400
	m_dataBitsProp.m_currentIndex = storage.readInt ("dataBits", 1);  // 8-bits by default
	m_stopBitsProp.m_currentIndex = storage.readInt ("stopBits");
	m_flowControlProp.m_currentIndex = storage.readInt ("flowControl");
	m_parityProp.m_currentIndex = storage.readInt ("parity");

	m_useInterCharDelayProp.m_value = storage.readBool ("useInterCharDelay", false);
	m_interCharDelayProp.m_value = storage.readInt ("interCharDelay", Defaults.InterCharDelay);

	m_readCheckComstatProp.m_value = storage.readBool ("readCheckComstat", Defaults.ReadCheckComstat);
	m_readWaitFirstCharProp.m_value = storage.readBool ("readWaitFirstChar", Defaults.ReadWaitFirstChar);
	m_readIntervalProp.m_value = storage.readInt ("readInterval", Defaults.ReadInterval);

	m_logFilter.m_isSettingChangedVisible = storage.readBool ("isSettingChangedVisible", true);
	m_logFilter.m_isControlLineChangedVisible = storage.readBool ("isControlLineChangedVisible", true);
	m_logFilter.m_isStatusLineChangedVisible = storage.readBool ("isStatusLineChangedVisible", true);

	m_bufferPropertySet.load (storage);
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);
}

void SerialSession.save (doc.Storage* storage)
{
	storage.writeString ("portName", m_portCombo.m_editText);
	storage.writeInt ("baudRate", m_baudRateCombo.m_currentIndex);
	storage.writeInt ("dataBits", m_dataBitsProp.m_currentIndex);
	storage.writeInt ("stopBits", m_stopBitsProp.m_currentIndex);
	storage.writeInt ("flowControl", m_flowControlProp.m_currentIndex);
	storage.writeInt ("parity", m_parityProp.m_currentIndex);

	storage.writeBool ("useInterCharDelay", m_useInterCharDelayProp.m_value);
	storage.writeInt ("interCharDelay", m_interCharDelayProp.m_value);

	storage.writeInt ("readCheckComstat", m_readCheckComstatProp.m_value);
	storage.writeInt ("readWaitFirstChar", m_readWaitFirstCharProp.m_value);
	storage.writeInt ("readInterval", m_readIntervalProp.m_value);

	storage.writeBool ("isSettingChangedVisible", m_logFilter.m_isSettingChangedVisible);
	storage.writeBool ("isControlLineChangedVisible", m_logFilter.m_isControlLineChangedVisible);
	storage.writeBool ("isStatusLineChangedVisible", m_logFilter.m_isStatusLineChangedVisible);

	m_bufferPropertySet.save (storage);
}

void SerialSession.updateProperties ()
{
	m_portProp.m_value = m_portCombo.m_editText;
	m_baudRateProp.m_currentIndex = m_baudRateCombo.m_currentIndex;
	m_settingChangedVisibleProp.m_value = m_logFilter.m_isSettingChangedVisible;
	m_controlLineChangedVisibleProp.m_value = m_logFilter.m_isControlLineChangedVisible;
	m_statusLineChangedVisibleProp.m_value = m_logFilter.m_isStatusLineChangedVisible;
}

bool errorcode SerialSession.applyProperties ()
{
	m_portCombo.m_editText = m_portProp.m_value;
	m_baudRateCombo.m_currentIndex = m_baudRateProp.m_currentIndex;
	m_logFilter.m_isSettingChangedVisible = m_settingChangedVisibleProp.m_value;
	m_logFilter.m_isControlLineChangedVisible = m_controlLineChangedVisibleProp.m_value;
	m_logFilter.m_isStatusLineChangedVisible = m_statusLineChangedVisibleProp.m_value;
	m_readBuffer.setSize (m_bufferPropertySet.m_readBufferSizeProp.m_value);

	if (m_state != State.Opened)
		return true;

	m_serial.m_readInterval = getReadInterval ();
	m_serial.m_readParallelism = m_bufferPropertySet.m_readParallelismProp.m_value;
	m_serial.m_readBlockSize = m_bufferPropertySet.m_readBlockSizeProp.m_value;
	m_serial.m_readBufferSize = m_bufferPropertySet.m_readBufferSizeProp.m_value;
	m_serial.m_writeBufferSize = m_bufferPropertySet.m_writeBufferSizeProp.m_value;
	m_serial.m_options = getSerialOptions ();

	uint_t dataBitsValue = m_dataBitsProp.m_value;
	if (m_serial.m_dataBits != dataBitsValue)
	{
		m_serial.m_dataBits = dataBitsValue;
		writeLog (SerialLogRecordCode.DataBitsChanged, &dataBitsValue, sizeof (dataBitsValue));
	}

	io.SerialStopBits stopBitsValue = (io.SerialStopBits) m_stopBitsProp.m_value;
	if (m_serial.m_stopBits != stopBitsValue)
	{
		m_serial.m_stopBits = stopBitsValue;
		writeLog (SerialLogRecordCode.StopBitsChanged, &stopBitsValue, sizeof (stopBitsValue));
	}

	io.SerialParity parityValue = (io.SerialParity) m_parityProp.m_value;
	if (m_serial.m_parity != parityValue)
	{
		m_serial.m_parity = parityValue;
		writeLog (SerialLogRecordCode.ParityChanged, &parityValue, sizeof (parityValue));
	}

	if (!m_useInterCharDelayProp.m_value || !m_interCharDelayProp.m_value)
		flushDelayedWrite ();
	else if (m_delayedWriteList.m_count)
		setDelayedWriteTimer ();

	return true;
}

void SerialSession.restoreDefaultProperties ()
{
	m_portProp.m_value = m_portCombo.m_itemText [0];
	m_baudRateProp.m_value = Defaults.BaudRate;
	m_flowControlProp.m_value = Defaults.FlowControl;
	m_dataBitsProp.m_value = Defaults.DataBits;
	m_stopBitsProp.m_value = Defaults.StopBits;
	m_parityProp.m_value = Defaults.Parity;

	m_useInterCharDelayProp.m_value = false;
	m_interCharDelayProp.m_value = Defaults.InterCharDelay;

	m_readCheckComstatProp.m_value = Defaults.ReadCheckComstat;
	m_readWaitFirstCharProp.m_value = Defaults.ReadWaitFirstChar;
	m_readIntervalProp.m_value = Defaults.ReadInterval;

	m_settingChangedVisibleProp.m_value = true;
	m_controlLineChangedVisibleProp.m_value = true;
	m_statusLineChangedVisibleProp.m_value = true;

	m_bufferPropertySet.restoreDefaults ();
}

size_t errorcode SerialSession.transmit (
	void const* p,
	size_t size
	)
{
	if (m_state != State.Opened)
		return -1;

	size_t result = m_useInterCharDelayProp.m_value && m_interCharDelayProp.m_value ?
		addDelayedWrite (p, size) :
		write (p, size);

	if (result < size)
	{
		m_isTransmitEnabled = false;
		waitSerialEvents ();
	}

	return result;
}

size_t SerialSession.write (
	void const* p,
	size_t size
	)
{
	size_t result = m_serial.write (p, size);
	writeLog (log.StdRecordCode.Tx, p, result);
	m_lastWriteTime = sys.getTimestamp ();

	return result;
}

size_t SerialSession.addDelayedWrite (
	void const* p,
	size_t size
	)
{
	void const* data = memdup (p, size);
	m_delayedWriteList.add (data);
	m_totalDelayedWriteSize	+= size;

	if (m_delayedWriteList.m_count == 1)
		setDelayedWriteTimer ();

	return size;
}

SerialSession.flushDelayedWrite ()
{
	while (m_delayedWriteList.m_count)
	{
		void const* p = m_delayedWriteList.removeHead ();
		size_t size = dynamic sizeof (p);
		assert (size);

		write (p, size);
	}

	m_totalDelayedWriteSize = 0;
}

SerialSession.setDelayedWriteTimer ()
{
	uint64_t dueTime = m_lastWriteTime + m_interCharDelayProp.m_value * 10000;

	m_delayedWriteTimer.start (
		onDelayedWriteTimer @ m_pluginHost.m_mainThreadScheduler,
		dueTime,
		m_interCharDelayProp.m_value
		);
}

SerialSession.waitSerialEvents ()
{
	io.SerialEvents eventMask =
		io.SerialEvents.IoError |
		io.SerialEvents.ReceiveBufferFull |
		io.SerialEvents.IncomingData;

	if (!m_isTransmitEnabled)
		eventMask |= io.SerialEvents.TransmitBufferReady;

	eventMask |= (m_serialStatusLines & io.SerialStatusLines.Cts) ?
		io.SerialEvents.CtsOff :
		io.SerialEvents.CtsOn;

	eventMask |= (m_serialStatusLines & io.SerialStatusLines.Dsr) ?
		io.SerialEvents.DsrOff :
		io.SerialEvents.DsrOn;

	eventMask |= (m_serialStatusLines & io.SerialStatusLines.Dcd) ?
		io.SerialEvents.DcdOff :
		io.SerialEvents.DcdOn;

	eventMask |= (m_serialStatusLines & io.SerialStatusLines.Ring) ?
		io.SerialEvents.RingOff :
		io.SerialEvents.RingOn;

	m_serial.wait (eventMask, onSerialEvent ~(++m_syncId) @ m_pluginHost.m_mainThreadScheduler);
}

SerialSession.onSerialEvent (
	uint_t syncId,
	io.SerialEvents triggeredEvents
	)
{
	if (syncId != m_syncId)
		return;

	if (triggeredEvents & io.SerialEvents.IoError)
	{
		writeLog (log.StdRecordCode.Error, m_serial.m_ioError, m_serial.m_ioError.m_size);
		close ();
		return;
	}

	if (triggeredEvents & io.SerialEvents.ReceiveBufferFull)
		writeLog (log.StdRecordCode.RxBufferFullLossy);

	if (triggeredEvents & io.SerialEvents.IncomingData)
	{
		size_t result = m_serial.read (m_readBuffer.m_p, m_readBuffer.m_size);
		if (result)
			writeLog (log.StdRecordCode.Rx, m_readBuffer.m_p, result);
	}

	if (!m_isTransmitEnabled)
		m_isTransmitEnabled = (triggeredEvents & io.SerialEvents.TransmitBufferReady);

	io.SerialStatusLines statusLines = m_serial.m_statusLines;
	io.SerialStatusLines statusLineMask = statusLines ^ m_serialStatusLines;
	if (statusLineMask)
	{
		char const* p = $"$(statusLines; B)$(statusLineMask; B)";
		writeLog (SerialLogRecordCode.StatusLineChanged, p, dynamic sizeof (p));
		m_serialStatusLines = statusLines;
	}

	waitSerialEvents ();
}

SerialSession.onDelayedWriteTimer ()
{
	if (!m_delayedWriteList.m_head)
	{
		m_delayedWriteTimer.stop ();
		return;
	}

	void const* p = m_delayedWriteList.m_head.m_data;
	size_t size = dynamic sizeof (p);
	assert (size);

	write (p, 1);

	// advance one char forward

	m_delayedWriteList.m_head.m_data = ++p;
	m_totalDelayedWriteSize--;

	if (size <= 1)
		m_delayedWriteList.removeHead ();

	if (!m_delayedWriteList.m_head)
		m_delayedWriteTimer.stop ();
}

SerialSession.onBaudRateChanged ()
{
	if (m_state != State.Opened)
		return;

	uint_t value = m_baudRateCombo.m_currentData;
	m_serial.m_baudRate = value;
	writeLog (SerialLogRecordCode.BaudRateChanged, &value, sizeof (value));
}

SerialSession.onDtrChanged ()
{
	if (m_state != State.Opened)
		return;

	bool value = m_actionTable [ActionId.Dtr].m_isChecked;
	m_serial.m_dtr = value;
	m_serialDtr = value;
	writeLog (SerialLogRecordCode.DtrChanged, &value, sizeof (value));
}

SerialSession.onRtsChanged ()
{
	if (m_state != State.Opened)
		return;

	bool value = m_actionTable [ActionId.Rts].m_isChecked;
	m_serial.m_rts = value;
	m_serialRts = value;
	writeLog (SerialLogRecordCode.RtsChanged, &value, sizeof (value));
}

reactor SerialSession.m_uiReactor ()
{
	m_actionTable [ActionId.Open].m_text = m_state ? "Close" : "Open";
	m_actionTable [ActionId.Open].m_icon = m_iconTable [m_state ? IconId.Close : IconId.Open];
	m_title = $"$(m_portCombo.m_currentText) $(m_baudRateCombo.m_currentText)";
	m_isTransmitEnabled = m_state == State.Opened;
	m_interCharDelayProp.m_isEnabled = m_useInterCharDelayProp.m_value;
	m_bufferPropertySet.m_readParallelismProp.m_isEnabled = !m_readCheckComstatProp.m_value;
	m_readIntervalProp.m_isEnabled = !m_readCheckComstatProp.m_value && !m_readWaitFirstCharProp.m_value;
	m_statusPaneTable [StatusPaneId.PortName].m_text = m_portCombo.m_currentText;
	m_statusPaneTable [StatusPaneId.PortName].m_isVisible = m_state != State.Closed;
	m_statusPaneTable [StatusPaneId.State].m_text = m_state ? "Opened" : "Closed";
	m_statusPaneTable [StatusPaneId.Dtr].m_isEnabled = m_state && m_serialDtr;
	m_statusPaneTable [StatusPaneId.Rts].m_isEnabled = m_state && m_serialRts;
	m_statusPaneTable [StatusPaneId.Dsr].m_isEnabled = m_state && (m_serialStatusLines & io.SerialStatusLines.Dsr);
	m_statusPaneTable [StatusPaneId.Cts].m_isEnabled = m_state && (m_serialStatusLines & io.SerialStatusLines.Cts);
	m_statusPaneTable [StatusPaneId.Dcd].m_isEnabled = m_state && (m_serialStatusLines & io.SerialStatusLines.Dcd);
}

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