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

import "doc_Session.jnc"
import "ui_SerialUi.jnc"
import "ui_StdSessionInfoSet.jnc"
import "ui_BufferPropertySet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"
import "std_Buffer.jnc"

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

class SerialMonSession: doc.Session {
protected:
	enum Defaults {
		PendingNotifySizeLimit = 1 * 1024 * 1024, // 1MB
	}

	enum State {
		Closed,
		Opened,
	}

	enum ActionId {
		Refresh,
		Capture,
		_Count,
	}

	enum IconId {
		Capture,
		StopCapture,
		_Count,
	}

	enum StatusPaneId {
		State,
		_Count,
	}

protected:
	// serial port

	State bindable m_state;
	bool bindable m_hasDeviceMonitor;
	SerialMonitor m_monitor;
	string_t m_portName;

	// toolbar, status bar & menu

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

	// serial mon property grid

	ui.ComboProperty* m_portProp;
	ui.BufferPropertySet m_bufferPropertySet;
	ui.IntProperty* m_pendingNotifySizeLimitProp;

	// information grid

	ui.StdSessionInfoSet m_stdSessionInfoSet;
	ui.ThroughputInfoSet m_throughputInfoSet;
	ui.ChecksumInfoSet m_checksumInfoSet;

	// ui reactor

	reactor m_uiReactor;

public:
	construct(doc.PluginHost* pluginHost);

	bool errorcode capture();
	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 bool errorcode connectSession() {
		return m_sessionConnectionState ? true : capture();
	}

	override void disconnectSession() {
		close();
	}

protected:
	void onCapture() {
		m_state ? close() : try capture();
	}

	void onPortComboEnter() {
		try capture();
	}

	void createUi();
	void enumeratePorts();
}

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

SerialMonSession.construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);
	m_monitor.construct(this);

	createUi();

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

void SerialMonSession.createUi() {
	// 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_actionTable[ActionId.Refresh] = createAction("Refresh port list", "images/refresh.png");
	m_actionTable[ActionId.Refresh].m_onTriggered += enumeratePorts;

	m_iconTable[IconId.Capture] = new ui.Icon("images/capture.png");
	m_iconTable[IconId.StopCapture] = new ui.Icon("images/pause.png");

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

	// status bar

	m_statusPaneTable[StatusPaneId.State] = m_pluginHost.m_statusBar.addPane(, 80);

	// property grid

	ui.GroupProperty* group = m_pluginHost.m_propertyGrid.createGroupProperty(
		"Device",
		"Serial port monitor target device settings"
	);

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

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

	m_bufferPropertySet.createProperties(
		m_pluginHost.m_propertyGrid,
		group,,
		ui.BufferPropertySet.PropertyId.ReadParallelism |
		ui.BufferPropertySet.PropertyId.ReadBufferSize |
		ui.BufferPropertySet.PropertyId.ReadBlockSize |
		ui.BufferPropertySet.PropertyId.NotifyReadBufferFull
	);

	m_pendingNotifySizeLimitProp = m_pluginHost.m_propertyGrid.createIntProperty(
		group,,
		"Pending notification limit",
		"Set the maximum size of kernel notification queue (exceeding notification will be dropped)"
	);

	m_bufferPropertySet.m_readBlockSizeProp.m_minValue = 1024;
	m_bufferPropertySet.m_readBufferSizeProp.m_minValue = 1024;
	m_pendingNotifySizeLimitProp.m_minValue = 1024;
	m_pendingNotifySizeLimitProp.m_spinBoxStep = 1024; // 1KB

	// information grid

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

	m_stdSessionInfoSet.create(m_pluginHost.m_txRxStats, m_pluginHost.m_informationGrid, infoGroup);
	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();
}

void SerialMonSession.enumeratePorts() {
	m_hasDeviceMonitor = io.detectDeviceMonitor();
	if (!m_hasDeviceMonitor)
		m_portCombo.m_editText = m_portProp.m_value = null; // clear up the port name to show placeholder text
	else
		ui.enumerateSerialPorts(m_portCombo, m_portProp);
}

bool errorcode SerialMonSession.capture() {
	close();
	startSession();

	if (!m_hasDeviceMonitor) {
		m_logWriter.write(
			DeviceMonLogRecordCode.DeviceMonNotDetected,
			&sys.g_systemInfo.m_osKind,
			sizeof(sys.OsKind)
		);

		std.setError("Device monitoring service (tdevmon) not detected");
		return false;
	}

	string_t portName = m_portCombo.m_currentText;

	m_monitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);
	m_monitor.capture(portName);

	io.SerialPortDesc const* portDesc = findSerialPortDesc(portName);
	if (!portDesc)
		m_logWriter.write(DeviceMonLogRecordCode.CaptureStarted_name, portName);
	else {
		std.Buffer buffer;
		packSerialPortDescStrings(buffer, portDesc);

		m_logWriter.write(
			SerialLogRecordCode.CaptureStarted,
			buffer.m_p,
			buffer.m_size
		);
	}

	m_portName = portName;
	m_state = State.Opened;
	m_stdSessionInfoSet.startSession(m_pluginHost.m_mainThreadScheduler);
	return true;

catch:
	m_logWriter.writeLastError(DeviceMonLogRecordCode.CaptureError);
	m_monitor.close();
	return false;
}

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

	m_logWriter.write(DeviceMonLogRecordCode.CaptureStopped);

	m_monitor.close();
	m_state = State.Closed;
	m_stdSessionInfoSet.endSession();
}

void SerialMonSession.load(doc.Storage* storage) {
	m_portCombo.m_editText = m_hasDeviceMonitor ? storage.readString("portName") : null;
	m_pendingNotifySizeLimitProp.m_value = storage.readInt("pendingNotifySizeLimit", Defaults.PendingNotifySizeLimit);

	m_bufferPropertySet.load(storage);
	m_monitor.setReadBufferSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);
}

void SerialMonSession.save(doc.Storage* storage) {
	storage.writeString("portName", m_portCombo.m_editText);
	storage.writeInt("pendingNotifySizeLimit", m_pendingNotifySizeLimitProp.m_value);

	m_bufferPropertySet.save(storage);
}

void SerialMonSession.updateProperties() {
	m_portProp.m_value = m_portCombo.m_editText;
}

bool errorcode SerialMonSession.applyProperties() {
	m_portCombo.m_editText = m_portProp.m_value;

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

	m_monitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);
	return true;
}

void SerialMonSession.restoreDefaultProperties() {
	m_pendingNotifySizeLimitProp.m_value = Defaults.PendingNotifySizeLimit;
	m_bufferPropertySet.restoreDefaults();
}

reactor SerialMonSession.m_uiReactor {
	m_title = m_state ?
		$"Monitor $m_portName" :
		$"Monitor $(m_portCombo.m_currentText)";

	m_sessionConnectionState = m_state ?
		SessionConnectionState.Connected :
		SessionConnectionState.Disconnected;

	m_actionTable[ActionId.Capture].m_text = m_state ? "Stop capture" : "Capture";
	m_actionTable[ActionId.Capture].m_icon = m_iconTable[m_state ? IconId.StopCapture : IconId.Capture];
	m_portCombo.m_placeholderText =
	m_portProp.m_placeholderText = m_hasDeviceMonitor ? "Enter a serial port name..."  : "(tdevmon not detected)";
	m_statusPaneTable[StatusPaneId.State].m_text = m_state ? "Capturing" : "Closed";
}

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