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

import "io_SerialUi.jnc"
import "io_BufferUi.jnc"
import "std_Buffer.jnc"
import "doc_Plugin.jnc"
import "gui_StdSessionInfo.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.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;
	SerialMonitor m_monitor;
	SerialLogFilter m_logFilter;
	char const* m_portName;

	// common serial ui

	io.SerialFilterUi m_serialFilterUi;

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

	// serial mon property grid

	gui.ComboProperty* m_portProp;
	io.BufferPropertySet m_bufferPropertySet;
	gui.IntProperty* m_pendingNotifySizeLimitProp;

	// information grid

	gui.StdSessionInfoSet m_stdSessionInfoSet;
	log.ThroughputInfoSet m_throughputInfoSet;
	log.ChecksumInfoSet m_checksumInfoSet;

public:
	construct (doc.PluginHost* pluginHost);

	bool errorcode capture ();
	close ();

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

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

	onPortComboEnter ()
	{
		try capture ();
	}

	createUi ();
	enumeratePorts ();

	reactor m_uiReactor ();
	updateTitle ();
}

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

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

	createUi ();

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

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_onEnter += onPortComboEnter;

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

	m_iconTable [IconId.Capture] = new gui.Icon ("images/capture.png");
	m_iconTable [IconId.StopCapture] = new gui.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

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

	m_portProp = m_pluginHost.m_propertyGrid.createComboProperty (
		group,,
		"Port",
		"Specify serial port 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,,
		io.BufferPropertySet.PropertyId.ReadParallelism |
		io.BufferPropertySet.PropertyId.ReadBufferSize |
		io.BufferPropertySet.PropertyId.ReadBlockSize |
		io.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

	// filter

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

	m_serialFilterUi.create (m_pluginHost.m_propertyGrid, group);

	// information grid

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

	m_stdSessionInfoSet.createInformationValues (m_pluginHost.m_informationGrid, infoGroup);
	m_throughputInfoSet.createInformationValuesGrouped (m_pluginHost.m_informationGrid);
	m_checksumInfoSet.createInformationValuesGrouped (m_pluginHost.m_informationGrid);

	m_pluginHost.m_informationGrid.resizeNameColumnToContents ();

	enumeratePorts ();
	updateTitle ();
	m_uiReactor.start ();
}

SerialMonSession.enumeratePorts ()
{
	char const* name = m_portCombo.m_currentText;
	m_portCombo.clear ();

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

	gui.EnumPropertyOption* optionArray = new gui.EnumPropertyOption [count];
	gui.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 = name; // preserve current text
	m_portProp.setOptions (optionArray, count);
}

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

	char const* name = m_portCombo.m_currentText;

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

	writeLog (SerialMonLogRecordCode.CaptureStarted, name, strlen (name));

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

catch:
	m_monitor.close ();

	std.Error const* error = std.getLastError ();
	writeLog (
		SerialMonLogRecordCode.CaptureError,
		error,
		error.m_size
		);
	return false;
}

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

	writeLog (SerialMonLogRecordCode.CaptureStopped);

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

void SerialMonSession.load (doc.Storage* storage)
{
	m_serialFilterUi.load (storage);

	m_logFilter.m_isSettingChangedVisible = m_serialFilterUi.m_settingChangedVisibleProp.m_value;
	m_logFilter.m_isControlLineChangedVisible = m_serialFilterUi.m_controlLineChangedVisibleProp.m_value;
	m_logFilter.m_isStatusLineChangedVisible = m_serialFilterUi.m_statusLineChangedVisibleProp.m_value;

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

	m_serialFilterUi.m_settingChangedVisibleProp.m_value = m_logFilter.m_isSettingChangedVisible;
	m_serialFilterUi.m_controlLineChangedVisibleProp.m_value = m_logFilter.m_isControlLineChangedVisible;
	m_serialFilterUi.m_statusLineChangedVisibleProp.m_value = m_logFilter.m_isStatusLineChangedVisible;
}

bool errorcode SerialMonSession.applyProperties ()
{
	m_portCombo.m_editText = m_portProp.m_value;
	m_logFilter.m_isSettingChangedVisible = m_serialFilterUi.m_settingChangedVisibleProp.m_value;
	m_logFilter.m_isControlLineChangedVisible = m_serialFilterUi.m_controlLineChangedVisibleProp.m_value;
	m_logFilter.m_isStatusLineChangedVisible = m_serialFilterUi.m_statusLineChangedVisibleProp.m_value;

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

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

void SerialMonSession.restoreDefaultProperties ()
{
	m_serialFilterUi.restoreDefaultProperties ();
	m_pendingNotifySizeLimitProp.m_value = Defaults.PendingNotifySizeLimit;

	m_bufferPropertySet.restoreDefaults ();
}

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

reactor SerialMonSession.m_uiReactor ()
{
	onevent (
		bindingof (m_state),
		m_portCombo.m_onChanged
		) ()
	{
		updateTitle ();
	}

	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_statusPaneTable [StatusPaneId.State].m_text = m_state ? "Capturing" : "Closed";
}

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