//..............................................................................
//
// Modbus RTU Protocol Analyzer layer
//

import "io_Modbus.jnc"
import "doc_Plugin.jnc"
import "std_Buffer.jnc"
import "crc16.jnc"

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

class ModbusRtuLayer:
	doc.Layer,
	log.Converter
{
	// checking for correct modbus delays (1.5/3.5 chars) is not possible
	// due to insufficient precision of timestamps	(we get timestamp of read
	// completion and no timestamps of individual bytes)

	enum: uint64_t
	{
		MinInterFrameDelay = 500 * 10000, // 500 ms
	}

protected:
	uint_t m_lastRecordCode;
	uint64_t m_lastRecordTimestamp;
	std.Buffer m_dataBuffer;
	gui.EnumProperty* m_streamRoleProp;
	bool volatile m_isInvertedStreamRole; // we need a volatile duplicate (main thread vs log thread)

public:
	construct (doc.PluginHost* pluginHost);

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

	override bool convert (
		log.Writer* writer,
		uint64_t timestamp,
		uint_t recordCode,
		void const* p,
		size_t size
		);
}

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

ModbusRtuLayer.construct (doc.PluginHost* pluginHost)
{
	basetype1.construct (pluginHost);

	m_lastRecordCode = 0;

	static gui.EnumPropertyOption streamRoleTable [] =
	{
		{ "TX - Master; RX - Slave", false },
		{ "RX - Master; TX - Slave", true },
	}

	m_streamRoleProp =  m_pluginHost.m_propertyGrid.createEnumProperty (
		"Stream roles",
		"Specify the roles of IO streams",
		streamRoleTable,
		countof (streamRoleTable)
		);

	pluginHost.m_log.attachConverter (
		this,
		&pluginHost.m_pluginGuid,
		representModbusRtuLog
		);
}

void ModbusRtuLayer.load (doc.Storage* storage)
{
	m_streamRoleProp.m_value = storage.readBool ("streamRole");
	m_isInvertedStreamRole = m_streamRoleProp.m_value;
}

void ModbusRtuLayer.save (doc.Storage* storage)
{
	storage.writeBool ("streamRole", m_streamRoleProp.m_value);
}

bool ModbusRtuLayer.applyProperties ()
{
	m_isInvertedStreamRole = m_streamRoleProp.m_value;
	return true;
}

void ModbusRtuLayer.restoreDefaultProperties ()
{
	m_streamRoleProp.m_value = false;
	m_isInvertedStreamRole = false;
}

bool ModbusRtuLayer.convert (
	log.Writer* writer,
	uint64_t timestamp,
	uint_t recordCode,
	void const* p,
	size_t size
	)
{
	bool result;
	io.ModbusRole role;

	switch (recordCode)
	{
	case log.StdRecordCode.Tx:
		role = m_isInvertedStreamRole ? io.ModbusRole.Slave : io.ModbusRole.Master;
		break;

	case log.StdRecordCode.Rx:
		role = m_isInvertedStreamRole ? io.ModbusRole.Master : io.ModbusRole.Slave;
		break;

	default:
		return false;
	}

	ModbusRtuLogRecordCode packetRecordCode = role == io.ModbusRole.Master ?
		ModbusRtuLogRecordCode.MasterPacket :
		ModbusRtuLogRecordCode.SlavePacket;

	bool isNewFrame = false;

	if (recordCode != m_lastRecordCode)
	{
		isNewFrame = true;
	}
	else if (timestamp - m_lastRecordTimestamp >= MinInterFrameDelay)
	{
		isNewFrame = true;
		writer.write (log.StdRecordCode.Break);
	}

	if (isNewFrame) // drop the previous data (if any)
		m_dataBuffer.clear ();

	m_lastRecordCode = 0;
	m_dataBuffer.append (p, size);

	io.ModbusRtuAduHdr const* aduHdr = (io.ModbusRtuAduHdr const*) m_dataBuffer.m_p;
	size_t aduSize = io.getModbusRtuAduSize (role, aduHdr, m_dataBuffer.m_size);
	if (aduSize == -1)
	{
		writer.write (timestamp, recordCode, p, size); // actual data first
		writer.write (timestamp, ModbusRtuLogRecordCode.InvalidFunction, aduHdr + 1, sizeof (uint8_t));
		m_dataBuffer.clear ();
		return true;
	}

	if (m_dataBuffer.m_size < aduSize) // wait for the next chunk of data
	{
		m_lastRecordCode = recordCode;
		m_lastRecordTimestamp = timestamp;
		return false;
	}

	writer.write (timestamp, recordCode, p, aduSize); // actual data first
	writer.write (timestamp, packetRecordCode, aduHdr, aduSize);

	if (m_dataBuffer.m_size > aduSize)
		writer.write (
			timestamp,
			recordCode,
			m_dataBuffer.m_p + aduSize,
			m_dataBuffer.m_size - aduSize
			);

	m_dataBuffer.clear ();
	return true;
}

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