//..............................................................................
//
// UART monitor
//

import "io_UartSignalDecoder.jnc"
import "sys_Timer.jnc"

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

class Uart: SerialIface
{
protected:
	bitflag enum Lines: uint16_t
	{
		Tx  = 0x0100,
		Rx  = 0x0200,
		Rts = 0x0400,
		Cts = 0x0800,

		All = -1
	}

	uint16_t m_lastLines;
	io.UartSignalDecoder m_txDecoder;
	io.UartSignalDecoder m_rxDecoder;
	sys.Timer m_timer;

	ui.EnumProperty* m_baudRateProp;
	ui.EnumProperty* m_dataBitsProp;
	ui.EnumProperty* m_stopBitsProp;
	ui.EnumProperty* m_parityProp;

public:
	construct(doc.PluginHost* pluginHost)
	{
		basetype.construct(pluginHost);
		m_name = "UART";
	}

	override createProperties(
		ui.GroupProperty* group = null,
		ui.Property* beforeProp = null
		);

	override loadProperties(doc.Storage* storage);
	override saveProperties(doc.Storage* storage);
	override restoreDefaultProperties();

	override start();

	override stop()
	{
		m_timer.stop();
	}

	override processSignal(
		uint64_t timestamp,
		uint16_t lines
		);

protected:
	finalizeFrame(
		io.UartDecodeResult result,
		uint64_t timestamp,
		uint64_t recordCode,
		uchar_t data
		);

	onTimer();
}

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

Uart.createProperties(
	ui.GroupProperty* group,
	ui.Property* beforeProp
	)
{
	static ui.EnumPropertyOption baudRateTable[] =
	{
		{ "110 bps",    110 },
		{ "300 bps",    300 },
		{ "600 bps",    600 },
		{ "1200 bps",   1200 },
		{ "2400 bps",   2400 },
		{ "4800 bps",   4800 },
		{ "9600 bps",   9600 },
		{ "14400 bps",  14400 },
		{ "19200 bps",  19200 },
		{ "38400 bps",  38400 },
		{ "56000 bps",  56000 },
		{ "57600 bps",  57600 },
		{ "115200 bps", 115200 },
		{ "128000 bps", 128000 },
		{ "153600 bps", 153600 },
		{ "230400 bps", 230400 },
		{ "256000 bps", 256000 },
		{ "460800 bps", 460800 },
		{ "921600 bps", 921600 },
	}

	static ui.EnumPropertyOption dataBitsTable[] =
	{
		{ "7 bits", 7 },
		{ "8 bits", 8 },
	}

	static ui.EnumPropertyOption stopBitsTable[] =
	{
		{ "1 bit",    io.SerialStopBits._1 },
		{ "1.5 bits", io.SerialStopBits._15 },
		{ "2 bits",   io.SerialStopBits._2 },
	}

	static ui.EnumPropertyOption parityTable[] =
	{
		{ "None",  io.SerialParity.None },
		{ "Odd",   io.SerialParity.Odd },
		{ "Even",  io.SerialParity.Even },
		{ "Mark",  io.SerialParity.Mark },
		{ "Space", io.SerialParity.Space },
	}

	m_groupProp = m_pluginHost.m_propertyGrid.createGroupProperty(
		group,
		beforeProp,
		"UART",
		"UART (Universal Asynchronous Receiver/Transmitter)"
		);

	m_baudRateProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		m_groupProp,,
		"Baud rate",
		"Serial baud rate",
		baudRateTable,
		countof(baudRateTable)
		);

	m_dataBitsProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		m_groupProp,,
		"Data bits",
		"Serial data bit count (word size)",
		dataBitsTable,
		countof(dataBitsTable)
		);

	m_stopBitsProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		m_groupProp,,
		"Stop bits",
		"Serial stop bit count",
		stopBitsTable,
		countof(stopBitsTable)
		);

	m_parityProp = m_pluginHost.m_propertyGrid.createEnumProperty(
		m_groupProp,,
		"Parity",
		"Serial parity check",
		parityTable,
		countof(parityTable)
		);
}

Uart.loadProperties(doc.Storage* storage)
{
	m_baudRateProp.m_currentIndex = storage.readInt("baudRate");
	m_dataBitsProp.m_currentIndex = storage.readInt("dataBits");
	m_stopBitsProp.m_currentIndex = storage.readInt("stopBits");
	m_parityProp.m_currentIndex = storage.readInt("parity");
}

Uart.saveProperties(doc.Storage* storage)
{
	storage.writeInt("baudRate", m_baudRateProp.m_currentIndex);
	storage.writeInt("dataBits", m_dataBitsProp.m_currentIndex);
	storage.writeInt("stopBits", m_stopBitsProp.m_currentIndex);
	storage.writeInt("parity", m_parityProp.m_currentIndex);
}

Uart.restoreDefaultProperties()
{
	m_baudRateProp.m_value = 38400;
	m_dataBitsProp.m_value = 8;
	m_stopBitsProp.m_value = io.SerialStopBits._1;
	m_parityProp.m_value = io.SerialParity.None;
}

Uart.start()
{
	m_txDecoder.start(
		m_baudRateProp.m_value,
		m_dataBitsProp.m_value,
		m_stopBitsProp.m_value,
		m_parityProp.m_value
		);

	m_rxDecoder.start(
		m_baudRateProp.m_value,
		m_dataBitsProp.m_value,
		m_stopBitsProp.m_value,
		m_parityProp.m_value
		);

	// resolution of UART timer doesn't have to be bound to baudrate --
	// the main purpose of this timer is to finalize data without stop-bit-start-bit edge

	m_timer.startPeriodicTimer(onTimer @ m_pluginHost.m_mainThreadScheduler, 200);
	m_lastLines = Lines.All;
}

Uart.processSignal(
	uint64_t timestamp,
	uint16_t lines
	)
{
	uint16_t changedLines = m_lastLines ^ lines;
	m_lastLines = lines;

	log.Writer* logWriter = m_pluginHost.m_log.m_writer;

	// logWriter.write (timestamp, log.StdRecordCode.Rx, p, sizeof (Snapshot));

	if (changedLines & Lines.Rts)
	{
		bool state = !(lines & Lines.Rts);
		logWriter.write(timestamp, HwSerialMonRecordCode.UartRtsChanged, &state, sizeof(state));
	}

	if (changedLines & Lines.Cts)
	{
		bool state = !(lines & Lines.Cts);
		logWriter.write(timestamp, HwSerialMonRecordCode.UartCtsChanged, &state, sizeof(state));
	}

	if (changedLines & Lines.Tx)
	{
		io.UartDecodeResult result = m_txDecoder.add(timestamp, lines & Lines.Tx);
		if (result != io.UartDecodeResult.Continue)
			finalizeFrame(
				result,
				timestamp,
				log.StdRecordCode.Tx,
				m_txDecoder.m_char
				);
	}

	if (changedLines & Lines.Rx)
	{
		io.UartDecodeResult result = m_rxDecoder.add(timestamp, lines & Lines.Rx);
		if (result != io.UartDecodeResult.Continue)
			finalizeFrame(
				result,
				timestamp,
				log.StdRecordCode.Rx,
				m_rxDecoder.m_char
				);
	}
}

Uart.finalizeFrame(
	io.UartDecodeResult result,
	uint64_t timestamp,
	uint64_t recordCode,
	uchar_t data
	)
{
	log.Writer* logWriter = m_pluginHost.m_log.m_writer;

	// write data byte even in case of framing/parity errors
	logWriter.write(timestamp, recordCode, &data, 1);

	switch (result)
	{
	case io.UartDecodeResult.FramingError:
		logWriter.write(timestamp, HwSerialMonRecordCode.UartFramingError);
		break;

	case io.UartDecodeResult.ParityError:
		logWriter.write(timestamp, HwSerialMonRecordCode.UartParityError);
		break;
	}
}

Uart.onTimer()
{
	io.UartDecodeResult result;

	result = m_txDecoder.onTimer();
	if (result != io.UartDecodeResult.Continue)
		finalizeFrame(
			result,
			sys.getTimestamp(),
			log.StdRecordCode.Tx,
			m_txDecoder.m_char
			);

	result = m_rxDecoder.onTimer();
	if (result != io.UartDecodeResult.Continue)
		finalizeFrame(
			result,
			sys.getTimestamp(),
			log.StdRecordCode.Rx,
			m_rxDecoder.m_char
			);
}

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