//..............................................................................
//
// $(pluginName) layer
//

import "doc_Layer.jnc"
import "std_Buffer.jnc"

//
// This plugin scans the RX stream for in-band commands in the form:
//     ... STX <command> CR ...
//
// The (easily extensible) list of supported commands:
//     about
//     version
//     help

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

class $(layerClass): doc.Layer {
protected:
	enum {
		MaxCommandSize = 256 // don't let buffer grow uncontrollably
	}

	enum ScannerState {
		Default,
		Stx,
	}

protected:
	ScannerState m_scannerState;
	std.Buffer m_command;

public:
	construct(doc.PluginHost* pluginHost);

protected:
	void onLogRecord(
		uint64_t timestamp,
		uint64_t recordCode,
		void const* p,
		size_t size
	);

	void scan(
		char const* p,
		size_t length
	);

	void processCommand(string_t command);

	size_t errorcode transmit(string_t text) {
		return transmitNext(text);
	}
}

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

$(layerClass).construct(doc.PluginHost* pluginHost) {
	basetype.construct(pluginHost);
	pluginHost.m_log.addObserver(onLogRecord @ pluginHost.m_mainThreadScheduler);
}

void $(layerClass).onLogRecord(
	uint64_t timestamp,
	uint64_t recordCode,
	void const* p,
	size_t size
) {
	if (recordCode == log.StdRecordCode.Rx)
		scan(p, size);
	else
		m_scannerState = ScannerState.Default; // reset state
}

// our commands

void $(layerClass).scan(
	char const* p,
	size_t length
) {
	char const* end = p + length;
	while (p < end) {
		switch (m_scannerState) {
		case ScannerState.Default:
			char const* stx = memchr(p, '\x02', end - p); // search for STX
			if (!stx)
				break2;

			m_scannerState = ScannerState.Stx;
			m_command.clear();
			p = stx + 1;
			break;

		case ScannerState.Stx:
			char const* cr = memchr(p, '\r', end - p); // search for CR
			if (!cr) {
				m_command.append(p, end - p);
				if (m_command.m_size > MaxCommandSize) {
					transmit("Command too long\r\n");
					m_scannerState = ScannerState.Default;
				}

				break2;
			}

			m_command.append(p, cr - p);
			m_command.append(0); // make it null-terminated
			processCommand(m_command.m_p);
			m_scannerState = ScannerState.Default;
			p = cr + 1;
			break;
		}
	}
}

void $(layerClass).processCommand(string_t command) {
	switch (command) {
	case "about":
		transmit("IO Ninja Answering Machine\r\n");
		break;

	case "version":
		transmit("Version 1.0.0\r\n");
		break;

	case "help":
		transmit("This plugin is a starting point for writing your test utility\r\n");
		break;

	default:
		transmit($"Unknown command '%s'\r\n"(command));
		break;
	}
}

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