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

import "doc_Session.jnc"
import "io_base.jncx"
import "io_Serial.jnc"
import "io_SerialBuggyDrivers.jnc"
import "ui_SerialUi.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "ui_GroupBox.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "std_Buffer.jnc"
import "std_String.jnc"
import "sys_Timer.jnc"

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

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

	enum State {
		Closed,
		Opened,
	}

	enum ActionId {
		Refresh,
		Open,
		_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;
	string_t m_portName;
	sys.Timer m_reopenTimer;

	uint_t bindable m_baudRate;
	bool bindable m_dtr;
	bool bindable m_rts;
	bool bindable m_breakCondition;
	io.SerialStatusLines bindable m_statusLines;

	std.Buffer m_readBuffer;
	uint_t m_syncId;

	// common serial ui

	ui.SerialUi m_serialUi;

	// 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_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();
	void 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
	);

	override bool errorcode connectSession() {
		return m_sessionConnectionState ? true : open();
	}

	override void disconnectSession() {
		close();
	}

	override bool errorcode dispatch(
		uint_t code,
		variant_t in,
		variant_t* out
	);

protected:
	void onOpen() {
		m_state ? close() : try open();
	}

	void onPortComboEnter() {
		try open();
	}

	void onBaudRateChanged();
	void onDataBitsChanged();
	void onParityChanged();
	void onStopBitsChanged();
	void onFlowControlChanged();
	void onDtrChanged();
	void onRtsChanged();
	void onBreakConditionChanged();

	void onSerialEvent(
		uint_t syncId,
		io.SerialEvents triggeredEvents
	);

	void onReopenTimer(uint_t syncId);

	void createUi();

	void enumeratePorts() {
		ui.enumerateSerialPorts(m_portCombo, m_portProp);
	}

	void waitSerialEvents();

	void 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;
	}

	void 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));
}

void 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_onReturnPressed += 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* groupProp = m_pluginHost.m_propertyGrid.createGroupProperty(
		"Serial port",
		"Serial port-specific connection settings"
	);

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

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

	m_useReopenProp.m_isGroupEnabler = true;

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

	ui.FormLayout layout;
	m_serialUi.createForm(layout);
	m_serialUi.createProperties(m_pluginHost.m_propertyGrid, groupProp);

	ui.GroupBox groupBox("Serial settings", layout);
	m_pluginHost.createForm(groupBox);

	m_serialUi.m_baudRateCombo.m_onReturnPressed += onBaudRateChanged;
	bindingof(m_serialUi.m_baudRateCombo.m_currentIndex) += onBaudRateChanged;
	bindingof(m_serialUi.m_dataBitsCombo.m_currentIndex) += onDataBitsChanged;
	bindingof(m_serialUi.m_parityCombo.m_currentIndex) += onParityChanged;
	bindingof(m_serialUi.m_stopBitsCombo.m_currentIndex) += onStopBitsChanged;
	bindingof(m_serialUi.m_flowControlCombo.m_currentIndex) += onFlowControlChanged;
	m_serialUi.m_dtrAction.m_onTriggered += onDtrChanged;
	m_serialUi.m_rtsAction.m_onTriggered += onRtsChanged;
	m_serialUi.m_breakConditionAction.m_onTriggered += onBreakConditionChanged;

	// status bar (only one pane)

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

	// property grid

	groupProp = 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(
			groupProp,,
			"Read mode",
			"Advanced read request-specific options",
			readModeTable,
			countof(readModeTable)
		);

		m_readIntervalProp = m_pluginHost.m_propertyGrid.createIntProperty(
			groupProp,,
			"Read 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, groupProp);

	// information grid

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

	m_stdSessionInfoSet.create(m_pluginHost.m_txRxStats, 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_propertyGrid.resizeNameColumnToContents();
	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();

	string_t 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_serialUi.m_baudRate;
	m_serial.m_dataBits = m_serialUi.m_dataBits;
	m_serial.m_stopBits = m_serialUi.m_stopBits;
	m_serial.m_parity = m_serialUi.m_parity;
	m_serial.m_flowControl = m_serialUi.m_flowControl;
	m_serial.m_dtr = m_serialUi.m_dtr;
	m_serial.m_rts = m_serialUi.m_rts;

	string_t 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;

	m_logWriter.write(
		SerialLogRecordCode.PortOpened,
		&params,
		sizeof(params),
		portName.m_p,
		portName.m_length
	);

	m_portName = portName;
	m_baudRate = m_serial.m_baudRate;
	m_dtr = m_serial.m_dtr;
	m_rts = m_serial.m_rts;
	m_breakCondition = m_serial.m_breakCondition;
	m_statusLines = m_serial.m_statusLines;
	m_isTransmitEnabled = true;
	m_state = State.Opened;
	m_sessionConnectionState = SessionConnectionState.Connected;

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

catch:
	m_logWriter.writeLastError(SerialLogRecordCode.PortOpenError);
	m_serial.close();
	return false;
}

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

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

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

	m_portCombo.m_editText = storage.readString("portName");
	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_serialUi.save(storage);

	storage.writeString("portName", m_portCombo.m_editText);
	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_serialUi.updateProperties();
	m_portProp.m_value = m_portCombo.m_editText;
}

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

	m_portCombo.m_editText = m_portProp.m_value;
	m_readBuffer.setSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);

	m_dtr = m_serialUi.m_dtrProp.m_value;
	m_rts = m_serialUi.m_rtsProp.m_value;
	m_breakCondition = m_serialUi.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_serialUi.m_baudRate;
	uint_t dataBits = m_serialUi.m_dataBits;
	io.SerialStopBits stopBits = m_serialUi.m_stopBits;
	io.SerialParity parity = m_serialUi.m_parity;
	io.SerialFlowControl flowControl = m_serialUi.m_flowControl;

	bool dtr = m_serialUi.m_dtrProp.m_value;
	bool rts = m_serialUi.m_rtsProp.m_value;
	bool breakCondition = m_serialUi.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,
		dtr,
		rts
	);

	m_serial.m_breakCondition = breakCondition;

	// write changes to the log

	if (baudRate != prevBaudRate) {
		m_logWriter.write(SerialLogRecordCode.BaudRateChanged, &baudRate, sizeof(baudRate));
		m_baudRate = 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.BreakConditionChanged, &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_serialUi.restoreDefaultProperties();
	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
) {
	if (m_state != State.Opened) {
		std.setError("serial port not opened");
		return -1;
	}

	size_t result = m_serial.write(p, size);

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

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

	return result;
}

bool errorcode SerialSession.dispatch(
	uint_t code,
	variant_t in,
	variant_t* out
) {
	switch (code) {
	case SerialDispatchCode.GetPortName:
		*out = m_portCombo.m_currentText;
		break;

	case SerialDispatchCode.SetPortName:
		m_portCombo.m_editText = in;
		break;

	case SerialDispatchCode.GetBaudRate:
		*out = m_serialUi.m_baudRate;
		break;

	case SerialDispatchCode.SetBaudRate:
		m_serialUi.m_baudRate = in;
		onBaudRateChanged();
		break;

	case SerialDispatchCode.GetDataBits:
		*out = m_serialUi.m_dataBits;
		break;

	case SerialDispatchCode.SetDataBits:
		uint_t dataBits = in;
		m_serialUi.m_dataBits = dataBits;
		onDataBitsChanged();
		break;

	case SerialDispatchCode.GetParity:
		*out = m_serialUi.m_parity;
		break;

	case SerialDispatchCode.SetParity:
		io.SerialParity parity = in;
		m_serialUi.m_parity = parity;
		onParityChanged();
		break;

	case SerialDispatchCode.GetStopBits:
		*out = m_serialUi.m_stopBits;
		break;

	case SerialDispatchCode.SetStopBits:
		io.SerialStopBits stopBits = in;
		m_serialUi.m_stopBits = stopBits;
		onStopBitsChanged();
		break;

	case SerialDispatchCode.GetFlowControl:
		*out = m_serialUi.m_flowControl;
		break;

	case SerialDispatchCode.SetFlowControl:
		io.SerialFlowControl flowControl = in;
		m_serialUi.m_flowControl = flowControl;
		onFlowControlChanged();
		break;

	case SerialDispatchCode.GetStatusLines:
		*out = m_statusLines;
		break;

	case SerialDispatchCode.GetDtr:
		*out = m_dtr;
		break;

	case SerialDispatchCode.SetDtr:
		m_serialUi.m_dtr = in;
		onDtrChanged();
		break;

	case SerialDispatchCode.GetRts:
		*out = m_rts;
		break;

	case SerialDispatchCode.SetRts:
		m_serialUi.m_rts = in;
		onRtsChanged();
		break;

	case SerialDispatchCode.GetBreakCondition:
		*out = m_breakCondition;
		break;

	case SerialDispatchCode.SetBreakCondition:
		m_serialUi.m_breakCondition = in;
		onBreakConditionChanged();
		break;

	case SerialDispatchCode.Open:
		if (in)
			m_portCombo.m_editText = in;

		return open();

	case SerialDispatchCode.Close:
		close();
		break;

	default:
		std.setError("unsupported dispatch code");
		return false;
	}

	return true;
}

void 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_statusLines & io.SerialStatusLines.Cts) ?
		io.SerialEvents.CtsOff :
		io.SerialEvents.CtsOn;

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

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

void 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);
		close();

		if (m_useReopenProp.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_statusLines;
	if (statusLineMask) {
		SerialStatusLineChangedParams params = { statusLines, statusLineMask }
		m_logWriter.write(SerialLogRecordCode.StatusLineChanged, params, sizeof(params));
		m_statusLines = statusLines;
	}

	waitSerialEvents();
}

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

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

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

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

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

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

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

	uint_t dataBits = m_serialUi.m_dataBits;
	if (dataBits == m_serial.m_dataBits)
		return;

	m_serial.m_dataBits = dataBits;
	m_logWriter.write(SerialLogRecordCode.DataBitsChanged, &dataBits, sizeof(dataBits));

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

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

	io.SerialParity parity = m_serialUi.m_parity;
	if (parity == m_serial.m_parity)
		return;

	m_serial.m_parity = parity;
	m_logWriter.write(SerialLogRecordCode.ParityChanged, &parity, sizeof(parity));

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

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

	io.SerialStopBits stopBits = m_serialUi.m_stopBits;
	if (stopBits == m_serial.m_stopBits)
		return;

	m_serial.m_stopBits = stopBits;
	m_logWriter.write(SerialLogRecordCode.StopBitsChanged, &stopBits, sizeof(stopBits));

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

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

	io.SerialFlowControl flowControl = m_serialUi.m_flowControl;
	if (flowControl == m_serial.m_flowControl)
		return;

	m_serial.m_flowControl = flowControl;
	m_logWriter.write(SerialLogRecordCode.FlowControlChanged, &flowControl, sizeof(flowControl));

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

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

	bool value = m_serialUi.m_dtr;
	m_serial.m_dtr = value;
	m_dtr = value;
	m_logWriter.write(SerialLogRecordCode.DtrChanged, &value, sizeof(value));
}

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

	bool value = m_serialUi.m_rts;
	m_serial.m_rts = value;
	m_rts = value;
	m_logWriter.write(SerialLogRecordCode.RtsChanged, &value, sizeof(value));
}

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

	bool value = m_serialUi.m_breakCondition;
	m_serial.m_breakCondition = value;
	m_breakCondition = value;
	m_logWriter.write(SerialLogRecordCode.BreakConditionChanged, &value, sizeof(value));
}

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

reactor SerialSession.m_uiReactor {
	m_title = m_state ?
		$"Serial $m_portName $m_baudRate bps" :
		$"Serial $(m_portCombo.m_currentText) $(m_serialUi.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_serialUi.m_rtsAction.m_isEnabled = m_serialUi.m_flowControlCombo.m_currentData != io.SerialFlowControl.RtsCts;
	m_serialUi.m_rtsProp.m_isEnabled = m_serialUi.m_flowControlProp.m_value != io.SerialFlowControl.RtsCts;
	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.BrightBlack;
	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_dtr);
	updateLineInfoValue(InfoId.Rts, m_rts);
	updateLineInfoValue(InfoId.Dsr, m_statusLines & io.SerialStatusLines.Dsr);
	updateLineInfoValue(InfoId.Cts, m_statusLines & io.SerialStatusLines.Cts);
	updateLineInfoValue(InfoId.BreakCondition, m_breakCondition);

	onevent bindingof(m_portCombo.m_editText)() {
		m_reopenTimer.stop();
	}
}

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