//..............................................................................
//
// Network Sniffer log filter
//

import "doc_Plugin.jnc"
import "log_Filter.jnc"
import "std_HashTable.jnc"
import "ui_ComboBox.jnc"
import "ui_PropertyGrid.jnc"
import "ui_History.jnc"
import "io_pcap.jncx"

using extension ui.ComboBoxHistory;

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

class NetSnifferLogFilter:
	doc.LogFilter,
	log.Filter {
protected:
	log.Log* m_log;
	string_t m_filter;
	bool m_isOptimized;

	std.HashTable m_pcapFilterMap; // link-type to pcap-filter
	io.PcapFilter* m_pcapFilterTable[8]; // for fast lookup
	NetSnifferLogToPcap* m_logToPcap;

	ui.ComboBox* m_filterCombo;
	ui.StringProperty* m_filterProp;

public:
	construct(doc.PluginHost* pluginHost);

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

	override bool filter(
		uint64_t timestamp,
		uint64_t recordCode,
		void const* p,
		size_t size
	);

protected:
	io.PcapFilter* getPcapFilter(io.PcapLinkType linkType);

	bool errorcode setFilter(
		string_t filter,
		bool isOptimized = true
	);

	bool errorcode applyFilter(bool rebuildLog = false);

	void onApply() {
		try applyFilter(true);
	}

	virtual NetSnifferLogToPcap* createLogToPcap() {
		return new NetSnifferLogToPcap(m_log);
	}

	void onExport();
}

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

NetSnifferLogFilter.construct(doc.PluginHost* pluginHost) {
	m_log = pluginHost.m_log;

	pluginHost.m_menu.m_insertActionPos = ui.InsertActionPos.BeforeCurrentHead;
	pluginHost.m_toolBar.m_insertActionPos = ui.InsertActionPos.BeforeCurrentHead;

	pluginHost.m_toolBar.addLabel("Filter:");
	m_filterCombo = pluginHost.m_toolBar.addComboBox(-1);
	m_filterCombo.m_isEditable = true;
	m_filterCombo.m_onReturnPressed += onApply;
	m_filterCombo.m_placeholderText = "Enter a pcap-filter...";
	m_filterCombo.m_toolTip =
		"View only packets which pass this filter\n"
		"For a complete syntax reference, google 'pcap-filter'";

	ui.Action* action = new ui.Action("images/apply.png", "Apply filter");
	action.m_onTriggered += onApply;
	pluginHost.m_menu.addAction(action);
	pluginHost.m_toolBar.addAction(action);

	pluginHost.m_menu.m_insertActionPos = ui.InsertActionPos.Tail;
	pluginHost.m_toolBar.m_insertActionPos = ui.InsertActionPos.Tail;

	action = new ui.Action("images/save-pcap.png", "Export Pcap file (*.pcap)");
	action.m_onTriggered += onExport;
	pluginHost.m_menu.addAction(action);
	pluginHost.m_toolBar.addAction(action);

	ui.GroupProperty* group = pluginHost.m_propertyGrid.createGroupProperty("Pcap filter");

	m_filterProp = pluginHost.m_propertyGrid.createStringProperty(
		group,,
		"View filter",
		"Enter a pcap-filter...",
		"View only packets which pass this filter\n"
		"For a complete syntax reference, google 'pcap-filter'"
	);

	m_log.addFilter(this);
}

void NetSnifferLogFilter.load(doc.Storage* storage) {
	m_filterCombo.loadHistory(storage, "viewFilterHistory");
	m_filterCombo.m_editText = storage.readString("viewFilter");
}

void NetSnifferLogFilter.save(doc.Storage* storage) {
	m_filterCombo.saveHistory(storage, "viewFilterHistory");
	storage.writeString("viewFilter", m_filterCombo.m_editText);
}

void NetSnifferLogFilter.updateProperties() {
	m_filterProp.m_value = m_filterCombo.m_editText;
}

bool errorcode NetSnifferLogFilter.applyProperties() {
	m_filterCombo.m_editText = m_filterProp.m_value;
	applyFilter(false);
	return true;
}

bool errorcode NetSnifferLogFilter.applyFilter(bool rebuildLog) {
	m_log.suspend();
	bool result = try setFilter(m_filterCombo.m_editText);
	m_log.resume();

	if (!result) {
		m_filterCombo.m_backgroundColor = ui.StdColor.PastelRed;
		return false;
	}

	m_filterCombo.addEditTextToHistory();
	m_filterCombo.m_backgroundColor = ui.ColorFlags.Undefined;

	if (rebuildLog)
		m_log.rebuild();

	return true;
}

bool errorcode NetSnifferLogFilter.setFilter(
	string_t filter,
	bool isOptimized
) {
	io.PcapFilter pcapFilter;

	pcapFilter.compile(
		io.PcapLinkType.Ethernet,
		io.Pcap.Defaults.SnapshotSize,
		filter,
		isOptimized
	);

	m_filter = strdup(filter);
	m_isOptimized = isOptimized;
	m_pcapFilterTable = null;
	m_pcapFilterTable[io.PcapLinkType.Ethernet] = pcapFilter;
	m_pcapFilterMap.clear();
	return true;
}

io.PcapFilter* NetSnifferLogFilter.getPcapFilter(io.PcapLinkType linkType) {
	std.MapEntry* entry;
	if ((size_t)linkType < countof(m_pcapFilterTable)) {
		if (m_pcapFilterTable[linkType])
			return m_pcapFilterTable[linkType];
	} else {
		entry = m_pcapFilterMap.visit(linkType);
		io.PcapFilter* pcapFilter = entry.m_value;
		if (pcapFilter)
			return pcapFilter;
	}


	io.PcapFilter* pcapFilter = new io.PcapFilter;

	try pcapFilter.compile(
		linkType,
		io.Pcap.Defaults.SnapshotSize,
		m_filter,
		m_isOptimized
	);

	if ((size_t)linkType < countof(m_pcapFilterTable))
		m_pcapFilterTable[linkType] = pcapFilter;
	else
		entry.m_value = pcapFilter;

	return pcapFilter;
}

bool NetSnifferLogFilter.filter(
	uint64_t timestamp,
	uint64_t recordCode,
	void const* p,
	size_t size
) {
	if (!m_filter)
		return true;

	io.PcapFilter* pcapFilter;

	switch (recordCode) {
	case NetSnifferLogRecordCode.EthernetPacket:
	case NetSnifferLogRecordCode.InjectedEthernetPacket:
		pcapFilter = getPcapFilter(io.PcapLinkType.Ethernet);
		break;

	case NetSnifferLogRecordCode.Packet:
	case NetSnifferLogRecordCode.InjectedPacket:
		pcapFilter = getPcapFilter(*(io.PcapLinkType const*)p);
		p += sizeof(io.PcapLinkType);
		size -= sizeof(io.PcapLinkType);
		break;

	default:
		return true; // everything else is visible
	}

	return pcapFilter ? pcapFilter.match(p, size) : true;
}

void NetSnifferLogFilter.onExport() {
	if (!m_logToPcap)
		m_logToPcap = createLogToPcap();

	m_logToPcap.export();
}

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