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

import "doc_Session.jnc"
import "io_base.jncx"
import "io_Serial.jnc"
import "io_SerialBuggyDrivers.jnc"
import "ui_SerialSettingUi.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "std_List.jnc"
import "std_Buffer.jnc"
import "sys_Timer.jnc"

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

class SerialSession: doc.Session
{
protected:
	enum Defaults
	{
		ReadMode         = SerialReadMode.WaitFirstChar,
		ReadInterval     = 10,
		Dtr              = true,
		Rts              = true,
		BreakCondition   = false,
		UseReopenTimeout = false,
		ReopenTimeout    = 3000,
	}

	enum State
	{
		Closed,
		Opened,
	}

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

	enum IconId
	{
		Refresh,
		Open,
		Close,
		_Count,
	}

	enum InfoId
	{
		Dsr,
		Cts,
		Dtr,
		Rts,
		BreakCondition,
		_Count,
	}

protected:
	// serial port

	State bindable m_state;
	io.Serial m_serial;
	char const* m_portName;
	sys.Timer m_reopenTimer;
	uint_t bindable m_serialBaudRate;
	io.SerialStatusLines bindable m_serialStatusLines;
	bool bindable m_serialDtr;
	bool bindable m_serialRts;
	bool bindable m_serialBreakCondition;
	std.Buffer m_readBuffer;
	uint_t m_syncId;

	// common serial ui

	ui.SerialSettingUi m_serialSettingUi;

	// toolbar, status bar & menu

	ui.ComboBox* m_portCombo;
	ui.Action* m_actionTable[ActionId._Count];
	ui.Icon* m_iconTable[IconId._Count];
	ui.StatusPane* m_statusPane;

	// serial property grid

	ui.ComboProperty* m_portProp;
	ui.BoolProperty* m_dtrProp;
	ui.BoolProperty* m_rtsProp;
	ui.BoolProperty* m_breakConditionProp;
	ui.BoolProperty* m_useReopenProp;
	ui.IntProperty* m_reopenTimeoutProp;
	ui.EnumProperty* m_readModeProp;
	ui.IntProperty* m_readIntervalProp;
	ui.BufferPropertySet m_bufferPropertySet;

	// information grid

	ui.InformationValue* m_infoTable[InfoId._Count];
	ui.StdSessionInfoSet m_stdSessionInfoSet;
	ui.ThroughputInfoSet m_throughputInfoSet;
	ui.ChecksumInfoSet m_checksumInfoSet;

	// ui reactor

	reactor m_uiReactor;

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:
	onOpen()
	{
		m_state ? close() : try open();
	}

	onPortComboEnter()
	{
		try open();
	}

	onBaudRateChanged();
	onDtrChanged();
	onRtsChanged();
	onBreakConditionChanged();

	onSerialEvent(
		uint_t syncId,
		io.SerialEvents triggeredEvents
		);

	onReopenTimer(uint_t syncId);

	createUi();

	enumeratePorts();
	waitSerialEvents();

	startReopenTimer()
	{
		m_reopenTimer.startSingleShotTimer(
			onReopenTimer ~(++m_syncId) @ m_pluginHost.m_mainThreadScheduler,
			m_reopenTimeoutProp.m_value
			);
	}

	io.SerialOptions getSerialOptions();

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

	updateLineInfoValue(
		InfoId infoId,
		bool line
		);
}

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

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

	m_readBuffer.setSize(ui.BufferPropertySet.Defaults.ReadBufferSize);

	createUi();

	pluginHost.m_log.addSelectionProcessor(log.ThroughputCalcRangeProcessor.factory ~(m_throughputInfoSet));
	pluginHost.m_log.addSelectionProcessor(log.ChecksumCalcRangeProcessor.factory ~(m_checksumInfoSet));
	m_stdSessionInfoSet.addLogListener(pluginHost.m_log);
}

SerialSession.createUi()
{
	static ui.EnumPropertyOption readModeTable[] =
	{
		{ "Check COMSTAT.cbInQue", SerialReadMode.CheckComstat },
		{ "Wait for the 1st char", SerialReadMode.WaitFirstChar },
		{ "Interval-based",        SerialReadMode.IntervalBased },
	}

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

	// toolbar

	m_pluginHost.m_toolBar.addLabel("Port:");
	m_portCombo = m_pluginHost.m_toolBar.addComboBox(-1);
	m_portCombo.m_isEditable = true;
	m_portCombo.m_onEnter += onPortComboEnter;
	m_portCombo.m_placeholderText = "Enter a serial port name...";

	m_actionTable[ActionId.Refresh] = createAction("Re-enumerate ports", m_iconTable [IconId.Refresh]);
	m_actionTable[ActionId.Refresh].m_onTriggered += enumeratePorts;

	m_actionTable[ActionId.Open] = createAction("Open", m_iconTable [IconId.Open]);
	m_actionTable[ActionId.Open].m_onTriggered += onOpen;

	// settings

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

	m_portProp = m_pluginHost.m_propertyGrid.createComboProperty(
		group,,
		"Port",
		"Enter a serial port name...",
		"Specify a serial port name to use"
		);

	m_useReopenProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"Reopen on error",
		"Attempt to re-open port if disconnected on error"
		);

	m_reopenTimeoutProp = m_pluginHost.m_propertyGrid.createIntProperty(
		group,,
		"Reopen timeout",
		"Specify delays to insert between re-open attempts"
		);

	m_serialSettingUi.create(
		m_pluginHost.m_toolBar,
		m_pluginHost.m_propertyGrid,
		group,
		ui.SerialSettingUi.SettingId.BaudRate
		);

	m_serialSettingUi.m_baudRateCombo.m_onEnter += onBaudRateChanged;
	bindingof(m_serialSettingUi.m_baudRateCombo.m_currentIndex) += onBaudRateChanged;

	m_pluginHost.m_menu.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;

	m_actionTable[ActionId.BreakCondition] = createAction("Break", "images/break.png", true, false);
	m_actionTable[ActionId.BreakCondition].m_onTriggered += onBreakConditionChanged;

	// status bar (only one pane)

	m_statusPane = m_pluginHost.m_statusBar.addPane(, 80);

	// property grid

	m_dtrProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"DTR",
		"DTR (Data-Terminal-Ready) modem control line"
		);

	m_rtsProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"RTS",
		"RTS (Request-To-Send) modem control line"
		);

	m_breakConditionProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"BREAK",
		"BREAK condition on the TX line"
		);

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

	if (sys.g_systemInfo.m_osKind == sys.OsKind.Windows)
	{
		// these settings only make sense on Windows

		m_readModeProp = m_pluginHost.m_propertyGrid.createEnumProperty(
			group,,
			"Read mode",
			"Advanced read request-specific options",
			readModeTable,
			countof(readModeTable)
			);

		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
	{
		// still, we want valid property pointers, so there's less checking

		m_readModeProp = new ui.EnumProperty(null);
		m_readIntervalProp = new ui.IntProperty(null);
	}

	m_bufferPropertySet.createProperties(m_pluginHost.m_propertyGrid, group);

	// information grid

	ui.InformationGroup* infoGroup = m_pluginHost.m_informationGrid.createGroup("Serial");

	m_stdSessionInfoSet.createInformationValues(m_pluginHost.m_informationGrid, infoGroup);

	m_infoTable[InfoId.Dsr] = m_pluginHost.m_informationGrid.createValue(infoGroup,,, "DSR");
	m_infoTable[InfoId.Cts] = m_pluginHost.m_informationGrid.createValue(infoGroup,,, "CTS");
	m_infoTable[InfoId.Dtr] = m_pluginHost.m_informationGrid.createValue(infoGroup,,, "DTR");
	m_infoTable[InfoId.Rts] = m_pluginHost.m_informationGrid.createValue(infoGroup,,, "RTS");
	m_infoTable[InfoId.BreakCondition] = m_pluginHost.m_informationGrid.createValue(infoGroup,,, "BREAK");

	m_throughputInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);
	m_checksumInfoSet.createInformationValuesGrouped(m_pluginHost.m_informationGrid);

	m_pluginHost.m_informationGrid.resizeNameColumnToContents();

	enumeratePorts();

	m_uiReactor.start();
}

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_readModeProp.m_value == SerialReadMode.WaitFirstChar)
		options |= io.SerialOptions.WinReadWaitFirstChar;
	else if (m_readModeProp.m_value == SerialReadMode.CheckComstat)
		options |= io.SerialOptions.WinReadCheckComstat;

	return options;
}

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

	char const* portName = 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_serialSettingUi.m_baudRate;
	m_serial.m_dataBits = m_serialSettingUi.m_dataBits;
	m_serial.m_stopBits = m_serialSettingUi.m_stopBits;
	m_serial.m_parity = m_serialSettingUi.m_parity;
	m_serial.m_flowControl = m_serialSettingUi.m_flowControl;
	m_serial.m_dtr = m_actionTable[ActionId.Dtr].m_isChecked;
	m_serial.m_rts = m_actionTable[ActionId.Rts].m_isChecked;

	char const* driverName = io.detectSerialBuggyDriver(portName);
	if (driverName)
	{
		m_logWriter.write(SerialLogRecordCode.BuggyDriverDetected, driverName);
		io.setConservativeSerialSettings(m_serial);
	}

	m_serial.open(portName);

	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;
	params.m_readInterval = m_serial.m_readInterval;
	params.m_dtr = m_serial.m_dtr;
	params.m_rts = m_serial.m_rts;
	params.m_statusLines = m_serial.m_statusLines;
	params.m_options = m_serial.m_options;
	params.m_osKind = sys.g_systemInfo.m_osKind;

	char const* p = $"$(params; B)$portName";
	m_logWriter.write(SerialLogRecordCode.PortOpened, p, dynamic sizeof(p));

	m_portName = portName;
	m_serialBaudRate = m_serial.m_baudRate;
	m_serialDtr = m_serial.m_dtr;
	m_serialRts = m_serial.m_rts;
	m_serialBreakCondition = m_serial.m_breakCondition;
	m_serialStatusLines = m_serial.m_statusLines;
	m_isTransmitEnabled = true;
	m_state = State.Opened;

	waitSerialEvents();
	m_stdSessionInfoSet.startSession(m_pluginHost.m_mainThreadScheduler);
	return true;

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

	m_logWriter.write(
		SerialLogRecordCode.PortOpenError,
		error,
		error.m_size
		);

	m_serial.close();
	return false;
}

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

	m_logWriter.write(SerialLogRecordCode.PortClosed, m_portName);
	m_stdSessionInfoSet.endSession();
	m_serial.close();
	m_isTransmitEnabled = false;
	m_state = State.Closed;
	m_syncId++;
}

void SerialSession.load(doc.Storage* storage)
{
	m_serialSettingUi.load(storage);

	m_portCombo.m_editText = storage.readString("portName");
	m_actionTable[ActionId.Dtr].m_isChecked = storage.readBool("dtr", Defaults.Dtr);
	m_actionTable[ActionId.Rts].m_isChecked = storage.readBool("rts", Defaults.Rts);
	m_useReopenProp.m_value = storage.readBool("useReopenProp", Defaults.UseReopenTimeout);
	m_reopenTimeoutProp.m_value = storage.readInt("reopenTimeout", Defaults.ReopenTimeout);
	m_readModeProp.m_value = storage.readBool("readMode", Defaults.ReadMode);
	m_readIntervalProp.m_value = storage.readInt("readInterval", Defaults.ReadInterval);

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

void SerialSession.save(doc.Storage* storage)
{
	m_serialSettingUi.save(storage);

	storage.writeString("portName", m_portCombo.m_editText);
	storage.writeBool("dtr", m_actionTable [ActionId.Dtr].m_isChecked);
	storage.writeBool("rts", m_actionTable [ActionId.Rts].m_isChecked);
	storage.writeBool("useReopenProp", m_useReopenProp.m_value);
	storage.writeInt("reopenTimeout", m_reopenTimeoutProp.m_value);
	storage.writeInt("readMode", m_readModeProp.m_value);
	storage.writeInt("readInterval", m_readIntervalProp.m_value);

	m_bufferPropertySet.save(storage);
}

void SerialSession.updateProperties()
{
	m_serialSettingUi.updateProperties();

	m_portProp.m_value = m_portCombo.m_editText;
	m_dtrProp.m_value = m_actionTable[ActionId.Dtr].m_isChecked;
	m_rtsProp.m_value = m_actionTable[ActionId.Rts].m_isChecked;
	m_breakConditionProp.m_value = m_actionTable[ActionId.BreakCondition].m_isChecked;
}

bool errorcode SerialSession.applyProperties()
{
	m_serialSettingUi.applyProperties();

	m_portCombo.m_editText = m_portProp.m_value;
	m_actionTable[ActionId.Dtr].m_isChecked = m_dtrProp.m_value;
	m_actionTable[ActionId.Rts].m_isChecked = m_rtsProp.m_value;
	m_actionTable[ActionId.BreakCondition].m_isChecked = m_breakConditionProp.m_value;
	m_readBuffer.setSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);

	m_serialDtr = m_dtrProp.m_value;
	m_serialRts = m_rtsProp.m_value;
	m_serialBreakCondition = m_breakConditionProp.m_value;

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

	// save prev values from serial port

	uint_t prevBaudRate = m_serial.m_baudRate;
	uint_t prevDataBits = m_serial.m_dataBits;
	io.SerialStopBits prevStopBits = m_serial.m_stopBits;
	io.SerialParity prevParity = m_serial.m_parity;
	io.SerialFlowControl prevFlowControl = m_serial.m_flowControl;
	io.SerialOptions prevOptions = m_serial.m_options;
	uint_t prevReadInterval = m_serial.m_readInterval;

	bool prevDtr = m_serial.m_dtr;
	bool prevRts = m_serial.m_rts;
	bool prevBreakCondition = m_serial.m_breakCondition;

	// collect new values from ui

	uint_t baudRate = m_serialSettingUi.m_baudRate;
	uint_t dataBits = m_serialSettingUi.m_dataBits;
	io.SerialStopBits stopBits = m_serialSettingUi.m_stopBits;
	io.SerialParity parity = m_serialSettingUi.m_parity;
	io.SerialFlowControl flowControl = m_serialSettingUi.m_flowControl;

	bool dtr = m_dtrProp.m_value;
	bool rts = m_rtsProp.m_value;
	bool breakCondition = m_breakConditionProp.m_value;

	io.SerialOptions options = getSerialOptions();
	uint_t readInterval = getReadInterval();

	// try to apply

	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 = options;

	m_serial.setupDevice(
		baudRate,
		dataBits,
		stopBits,
		parity,
		flowControl,
		readInterval,
		m_dtrProp.m_value,
		m_rtsProp.m_value
		);

	m_serial.m_breakCondition = m_breakConditionProp.m_value;

	// write changes to the log

	if (baudRate != prevBaudRate)
	{
		m_logWriter.write(SerialLogRecordCode.BaudRateChanged, &baudRate, sizeof(baudRate));
		m_serialBaudRate = baudRate;
	}

	if (dataBits != prevDataBits)
		m_logWriter.write(SerialLogRecordCode.DataBitsChanged, &dataBits, sizeof(dataBits));

	if (stopBits != prevStopBits)
		m_logWriter.write(SerialLogRecordCode.StopBitsChanged, &stopBits, sizeof(stopBits));

	if (parity != prevParity)
		m_logWriter.write(SerialLogRecordCode.ParityChanged, &parity, sizeof(parity));

	if (flowControl != prevFlowControl)
		m_logWriter.write(SerialLogRecordCode.FlowControlChanged, &flowControl, sizeof(flowControl));

	if (dtr != prevDtr)
		m_logWriter.write(SerialLogRecordCode.DtrChanged, &dtr, sizeof(dtr));

	if (rts != prevRts)
		m_logWriter.write(SerialLogRecordCode.RtsChanged, &rts, sizeof(rts));

	if (breakCondition != prevBreakCondition)
		m_logWriter.write(SerialLogRecordCode.RtsChanged, &breakCondition, sizeof(breakCondition));

	io.SerialOptions optionMask = prevOptions ^ options;
	if (optionMask & (io.SerialOptions.WinReadCheckComstat | io.SerialOptions.WinReadWaitFirstChar))
	{
		uint_t mode = m_readModeProp.m_value;
		m_logWriter.write(SerialLogRecordCode.ReadModeChanged, &mode, sizeof(mode));
	}

	if (readInterval != prevReadInterval)
		m_logWriter.write(SerialLogRecordCode.ReadIntervalChanged, &readInterval, sizeof(readInterval));

	return true;

catch:
	m_logWriter.writeLastError(log.StdRecordCode.Error);
	return false;
}

void SerialSession.restoreDefaultProperties()
{
	m_serialSettingUi.restoreDefaultProperties();

	m_dtrProp.m_value = Defaults.Dtr;
	m_rtsProp.m_value = Defaults.Rts;
	m_breakConditionProp.m_value = Defaults.BreakCondition;
	m_useReopenProp.m_value = Defaults.UseReopenTimeout;
	m_reopenTimeoutProp.m_value = Defaults.ReopenTimeout;

	m_readModeProp.m_value = Defaults.ReadMode;
	m_readIntervalProp.m_value = Defaults.ReadInterval;

	m_bufferPropertySet.restoreDefaults();
}

size_t errorcode SerialSession.transmit(
	void const* p,
	size_t size
	)
{
	size_t result = m_serial.write(p, size);

	if (result)
		m_logWriter.write(log.StdRecordCode.Tx, p, result);

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

	return result;
}

SerialSession.enumeratePorts()
{
	char const* editText = m_portCombo.m_editText; // save edit text
	m_portCombo.clear();

	size_t count;
	io.SerialPortDesc const* portList = io.createSerialPortDescList(&count);
	io.SerialPortDesc const* port = portList;

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

	for (; port; port = port.m_next, option++)
	{
		char const* description = $"$(port.m_deviceName) - $(port.m_description)";
		m_portCombo.addItem(description, port.m_deviceName);
		option.m_text = description;
		option.m_value = port.m_deviceName;
	}

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

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

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

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

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

	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)
	{
		m_logWriter.write(log.StdRecordCode.Error, m_serial.m_ioError, m_serial.m_ioError.m_size);
		close();

		if (m_reopenTimeoutProp.m_value)
			startReopenTimer();

		return;
	}

	if ((triggeredEvents & io.SerialEvents.ReadBufferFull) &&
		m_bufferPropertySet.m_notifyReadBufferFullProp.m_value)
		m_logWriter.write(log.StdRecordCode.RxBufferFullLossy);

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

	if (triggeredEvents & io.SerialEvents.LineError)
	{
		io.SerialLineErrors errors = m_serial.clearLineErrors();
		m_logWriter.write(SerialLogRecordCode.LineError, &errors, sizeof(errors));
	}

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

	io.SerialStatusLines statusLines = m_serial.m_statusLines;
	io.SerialStatusLines statusLineMask = statusLines ^ m_serialStatusLines;
	if (statusLineMask)
	{
		SerialStatusLineChangedParams params = { statusLines, statusLineMask }
		m_logWriter.write(SerialLogRecordCode.StatusLineChanged, params, sizeof(params));
		m_serialStatusLines = statusLines;
	}

	waitSerialEvents();
}

SerialSession.onReopenTimer(uint_t syncId)
{
	if (syncId != m_syncId)
		return;

	bool result = try open();
	if (!result && m_useReopenProp.m_value)
		startReopenTimer();
}

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

	uint_t baudRate = m_serialSettingUi.m_baudRate;
	if (baudRate == m_serial.m_baudRate)
		return;

	m_serial.setupDevice(
		baudRate,
		m_serial.m_dataBits,
		m_serial.m_stopBits,
		m_serial.m_parity,
		m_serial.m_flowControl,
		m_serial.m_readInterval,
		m_serial.m_dtr,
		m_serial.m_rts
		);

	m_logWriter.write(SerialLogRecordCode.BaudRateChanged, &baudRate, sizeof(baudRate));
	m_serialBaudRate = baudRate;

catch:
	m_logWriter.writeLastError(log.StdRecordCode.Error);
}

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

	bool value = m_actionTable[ActionId.Dtr].m_isChecked;
	m_serial.m_dtr = value;
	m_serialDtr = value;
	m_logWriter.write(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;
	m_logWriter.write(SerialLogRecordCode.RtsChanged, &value, sizeof(value));
}

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

	bool value = m_actionTable[ActionId.BreakCondition].m_isChecked;
	m_serial.m_breakCondition = value;
	m_serialBreakCondition = value;
	m_logWriter.write(SerialLogRecordCode.BreakConditionChanged, &value, sizeof(value));
}

SerialSession.updateLineInfoValue(
	InfoId infoId,
	bool line
	)
{
	m_infoTable[infoId].m_value = line ? "on" : "off";
	m_infoTable[infoId].m_valueColor = line ? ui.StdColor.LightRed : ui.StdColor.Gray;
}

reactor SerialSession.m_uiReactor
{
	m_title = m_state ?
		$"Serial $m_portName $m_serialBaudRate bps" :
		$"Serial $(m_portCombo.m_currentText) $(m_serialSettingUi.m_baudRateCombo.m_editText)";

	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_actionTable[ActionId.Rts].m_isEnabled = m_serialSettingUi.m_flowControlProp.m_value != io.SerialFlowControl.RtsCts;
	m_rtsProp.m_isEnabled = m_serialSettingUi.m_flowControlProp.m_value != io.SerialFlowControl.RtsCts;
	m_reopenTimeoutProp.m_isEnabled = m_useReopenProp.m_value;
	m_bufferPropertySet.m_readParallelismProp.m_isEnabled = m_readModeProp.m_value != SerialReadMode.CheckComstat;
	m_readIntervalProp.m_isEnabled = m_readModeProp.m_value == SerialReadMode.IntervalBased;
	m_statusPane.m_text = m_state ? m_portName : "Port closed";
	m_statusPane.m_color = m_state ? ui.StdColor.Blue : ui.StdColor.Gray;
	m_infoTable[InfoId.Dsr].m_isEnabled = m_state;
	m_infoTable[InfoId.Cts].m_isEnabled = m_state;
	m_infoTable[InfoId.Dtr].m_isEnabled = m_state;
	m_infoTable[InfoId.Rts].m_isEnabled = m_state;
	m_infoTable[InfoId.BreakCondition].m_isEnabled = m_state;

	updateLineInfoValue(InfoId.Dtr, m_serialDtr);
	updateLineInfoValue(InfoId.Rts, m_serialRts);
	updateLineInfoValue(InfoId.Dsr, m_serialStatusLines & io.SerialStatusLines.Dsr);
	updateLineInfoValue(InfoId.Cts, m_serialStatusLines & io.SerialStatusLines.Cts);

	onevent m_portCombo.m_onChanged()
	{
		m_reopenTimer.stop();
	}
}

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