//..............................................................................
//
// Pipe Monitor session
//

import "doc_Session.jnc"
import "ui_History.jnc"
import "ui_BufferPropertySet.jnc"
import "ui_StdSessionInfoSet.jnc"
import "log_ThroughputCalc.jnc"
import "log_ChecksumCalc.jnc"

using extension ui.ComboBoxHistory;

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

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

	enum State {
		Closed,
		Opened,
	}

	enum ActionId {
		ApplyFilter,
		Capture,
		_Count,
	}

	enum IconId {
		Capture,
		StopCapture,
		_Count,
	}

	enum StatusPaneId {
		State,
		_Count,
	}

protected:
	// two pipe monitors: local NPFS, LANMAN redirector

	State bindable m_state;
	PipeMonitor m_npfsMonitor;
	PipeMonitor m_lanmanMonitor;
	PipeMonLogFilter m_filter;
	char const* m_captureFilter;

	// 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_viewFilterKindCombo;
	ui.ComboBox* m_viewFilterCombo;

	// pipe mon property grid

	ui.StringProperty* m_captureFilterProp;
	ui.EnumProperty* m_viewFilterKindProp;
	ui.StringProperty* m_viewFilterProp;
	ui.BoolProperty* m_useLanmanMonitorProp;
	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();

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

	void onSetViewFilter() {
		applyViewFilter(true);
	}

	void onMonitorError();

	char const* createCaptureFilter() {
		char const* name = m_captureFilterProp.m_value;
		return name && *name ? strdup(name) : "*"; // null wildcard means we also receive notifications from already-opened "nameless" files
	}

	void createUi();
	void applyViewFilter(bool rebuildLog = false);
}

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

PipeMonSession.construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);
	m_npfsMonitor.construct(this);
	m_lanmanMonitor.construct(this);

	createUi();

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

void PipeMonSession.createUi() {
	// toolbar

	static ui.EnumPropertyOption viewFilterKindTable[] = {
		{ "None",         log.MonitorFilterKind.None },
		{ "File name",    log.MonitorFilterKind.FileName },
		{ "File ID",  log.MonitorFilterKind.FileId },
		{ "Process", log.MonitorFilterKind.ProcessName },
		{ "PID",          log.MonitorFilterKind.ProcessId },
	}

	m_viewFilterKindCombo = m_pluginHost.m_toolBar.addComboBox(
		"Filter:",
		viewFilterKindTable,
		countof(viewFilterKindTable)
	);

	m_viewFilterCombo = m_pluginHost.m_toolBar.addComboBox(-1);
	m_viewFilterCombo.m_isEditable = true;
	m_viewFilterCombo.m_onEnter += onSetViewFilter;

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

	m_actionTable[ActionId.ApplyFilter] = createAction("Apply filter", "images/apply.png");
	m_actionTable[ActionId.ApplyFilter].m_onTriggered += onSetViewFilter;

	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(
		"Connection",
		"Named pipe monitor connection settings"
	);

	m_captureFilterProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"Capture filter",
		"Enter a pipe name wildcard...",
		"Specify name wildcard capture filter (supported masks: * and ?; leave empty for no filter)"
	);

	m_viewFilterKindProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		group,,
		"View filter kind",
		"Show only data which pass this filter",
		viewFilterKindTable,
		countof(viewFilterKindTable)
	);

	m_viewFilterProp = m_pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"View filter",
		"Enter a view filter...",
		"Show only data which pass this filter"
	);

	m_useLanmanMonitorProp = m_pluginHost.m_propertyGrid.createBoolProperty(
		group,,
		"Monitor remote connections",
		"Attach and monitor pipe connections redirected over Windows LANMAN"
	);

	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("Pipe 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();

	m_uiReactor.start();
}

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

	m_captureFilter = createCaptureFilter();
	m_npfsMonitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);
	m_npfsMonitor.capture(r"\device\namedpipe", m_captureFilter);

	if (m_useLanmanMonitorProp.m_value) {
		m_lanmanMonitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);
		m_lanmanMonitor.capture(r"\device\lanmanredirector", m_captureFilter);
	}

	m_logWriter.write(DeviceMonLogRecordCode.CaptureStarted_filter, m_captureFilter);
	m_state = State.Opened;
	m_stdSessionInfoSet.startSession(m_pluginHost.m_mainThreadScheduler);
	applyViewFilter();
	return true;

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

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

	m_logWriter.write(DeviceMonLogRecordCode.CaptureStopped);

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

void PipeMonSession.load(doc.Storage* storage) {
	m_viewFilterCombo.loadHistory(storage, "viewFilterHistory");
	m_viewFilterCombo.m_editText = storage.readString("viewFilter");
	m_viewFilterKindCombo.m_currentIndex = storage.readInt("viewFilterKind");
	m_captureFilterProp.m_value = storage.readString("captureFilter");
	m_useLanmanMonitorProp.m_value = storage.readString("useLanmanMonitor");
	m_pendingNotifySizeLimitProp.m_value = storage.readInt("pendingNotifySizeLimit", Defaults.PendingNotifySizeLimit);

	m_bufferPropertySet.load(storage);
	m_npfsMonitor.setReadBufferSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);
	m_lanmanMonitor.setReadBufferSize(m_bufferPropertySet.m_readBufferSizeProp.m_value);
}

void PipeMonSession.save(doc.Storage* storage) {
	m_viewFilterCombo.saveHistory(storage, "viewFilterHistory");
	storage.writeString("captureFilter", m_captureFilterProp.m_value);
	storage.writeString("viewFilter", m_viewFilterCombo.m_currentText);
	storage.writeInt("viewFilterKind", m_viewFilterKindCombo.m_currentIndex);
	storage.writeBool("useLanmanMonitor", m_useLanmanMonitorProp.m_value);
	storage.writeInt("pendingNotifySizeLimit", m_pendingNotifySizeLimitProp.m_value);

	m_bufferPropertySet.save(storage);
}

void PipeMonSession.updateProperties() {
	m_viewFilterKindProp.m_currentIndex = m_viewFilterKindCombo.m_currentIndex;
	m_viewFilterProp.m_value = m_viewFilterCombo.m_editText;
}

bool errorcode PipeMonSession.applyProperties() {
	m_viewFilterKindCombo.m_currentIndex = m_viewFilterKindProp.m_currentIndex;
	m_viewFilterCombo.m_editText = m_viewFilterProp.m_value;

	applyViewFilter();

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

	m_npfsMonitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);
	m_lanmanMonitor.applyProperties(m_bufferPropertySet, m_pendingNotifySizeLimitProp.m_value);

	char const* captureFilter = createCaptureFilter();
	if (strcmp(m_captureFilter, captureFilter)) {
		m_captureFilter = captureFilter;
		m_npfsMonitor.setFileNameFilter(captureFilter);
		m_lanmanMonitor.setFileNameFilter(captureFilter);
		m_logWriter.write(DeviceMonLogRecordCode.SetCaptureFilter, captureFilter);
	}

	return true;
}

void PipeMonSession.restoreDefaultProperties() {
	m_viewFilterKindProp.m_currentIndex = 0;
	m_pendingNotifySizeLimitProp.m_value = Defaults.PendingNotifySizeLimit;
	m_useLanmanMonitorProp.m_value = Defaults.UseLanmanMonitor;
	m_bufferPropertySet.restoreDefaults();
}

void PipeMonSession.applyViewFilter(bool rebuildLog) {
	m_viewFilterCombo.addEditTextToHistory();

	m_pluginHost.m_log.suspend();
	m_filter.setFilter(m_viewFilterKindCombo.m_currentData, m_viewFilterCombo.m_editText);
	m_pluginHost.m_log.resume();

	if (rebuildLog)
		m_pluginHost.m_log.rebuild();
}

reactor PipeMonSession.m_uiReactor {
	m_title = $"NPFS mon $(m_captureFilterProp.m_value)";
	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";
	m_viewFilterProp.m_isEnabled = m_viewFilterKindProp.m_value;
	m_viewFilterCombo.m_isEnabled = m_viewFilterKindCombo.m_currentData;
	m_viewFilterCombo.m_placeholderText = log.getMonitorFilterPlaceholderText(m_viewFilterKindCombo.m_currentData);
	m_viewFilterProp.m_placeholderText = log.getMonitorFilterPlaceholderText(m_viewFilterKindProp.m_value);
}

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