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

import "io_Modbus.jnc"
import "doc_Layer.jnc"
import "std_Buffer.jnc"
import "crc16.jnc"

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

class ModbusLayer:
	doc.Layer,
	log.Converter
{
	enum Def
	{
		Protocol          = io.ModbusProtocol.Rtu,
		StreamRole        = ModbusParserMode.Master,
		HalfDuplexTimeout = 2000, // reset to master after 2 seconds
	}

	static const std.Guid m_logGuid = "53603C98-34BC-46EF-BA08-D3274F1057B4";

protected:
	ModbusParserBase* m_txParser;
	ModbusParserBase* m_rxParser;

	gui.ComboBox* m_streamRoleCombo;

	gui.EnumProperty* m_protocolProp;
	gui.EnumProperty* m_streamRoleProp;
	gui.IntProperty* m_halfDuplexTimeoutProp;

	reactor m_uiReactor;

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 void restoreDefaultProperties ();

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

protected:
	createUi ();

	onStreamRoleComboChanged ()
	{
		setStreamRoles (m_streamRoleCombo.m_currentData);
	}

	setStreamRoles (ModbusParserMode txMode);
}

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

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

	m_txParser = new ModbusRtuParser;
	m_rxParser = new ModbusRtuParser;

	m_txParser.m_mode = ModbusParserMode.Master;
	m_rxParser.m_mode = ModbusParserMode.Slave;

	pluginHost.m_log.addConverter (this, &m_logGuid);

	createUi ();
}

ModbusLayer.createUi ()
{
	static gui.EnumPropertyOption protocolTable [] =
	{
		{ "Modbus RTU",   io.ModbusProtocol.Rtu },
		{ "Modbus ASCII", io.ModbusProtocol.Ascii },
		{ "Modbus TCP",   io.ModbusProtocol.Tcp },
	}

	static gui.EnumPropertyOption streamRoleTable [] =
	{
		{ "TX - Master; RX - Slave", ModbusParserMode.Master },
		{ "TX - Slave; RX - Master", ModbusParserMode.Slave },
		{ "Half-duplex",             ModbusParserMode.HalfDuplex },
	}

	m_streamRoleCombo = m_pluginHost.m_toolBar.addComboBox (
		"Modbus roles:",
		streamRoleTable,
		countof (streamRoleTable)
		);

	m_streamRoleCombo.m_onChanged += onStreamRoleComboChanged;

	m_protocolProp = m_pluginHost.m_propertyGrid.createEnumProperty (
		"Protocol",
		"Specify the flavor of Modbus protocol",
		protocolTable,
		countof (protocolTable)
		);

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

	m_halfDuplexTimeoutProp = m_pluginHost.m_propertyGrid.createIntProperty (
		"Half-duplex timeout (ms)",
		"Reset the current Modbus role to 'master' after this delay"
		);

	m_uiReactor.start ();
}

reactor ModbusLayer.m_uiReactor
{
	m_halfDuplexTimeoutProp.m_isEnabled = m_streamRoleProp.m_value == ModbusParserMode.HalfDuplex;
}

void ModbusLayer.load (doc.Storage* storage)
{
	m_streamRoleCombo.m_currentData = storage.readInt ("streamRole", Def.StreamRole);
	m_protocolProp.m_value = storage.readInt ("protocol", Def.Protocol);
	m_halfDuplexTimeoutProp.m_value = storage.readInt ("halfDuplexTimeout", Def.HalfDuplexTimeout);

	applyProperties ();
}

void ModbusLayer.save (doc.Storage* storage)
{
	storage.writeInt ("streamRole", m_streamRoleCombo.m_currentData);
	storage.writeInt ("protocol", m_protocolProp.m_value);
	storage.writeInt ("halfDuplexTimeout", m_halfDuplexTimeoutProp.m_value);
}

void ModbusLayer.updateProperties ()
{
	m_streamRoleProp.m_value = m_streamRoleCombo.m_currentData;
}

bool errorcode ModbusLayer.applyProperties ()
{
	m_pluginHost.m_log.suspend ();

	if (m_protocolProp.m_value != m_txParser.m_protocol)
	{
		switch (m_protocolProp.m_value)
		{
		case io.ModbusProtocol.Rtu:
			m_txParser = new ModbusRtuParser;
			m_rxParser = new ModbusRtuParser;
			break;

		case io.ModbusProtocol.Ascii:
			m_txParser = new ModbusAsciiParser;
			m_rxParser = new ModbusAsciiParser;
			break;

		case io.ModbusProtocol.Tcp:
			m_txParser = new ModbusTcpParser;
			m_rxParser = new ModbusTcpParser;
			break;
		}
	}

	ModbusParserMode mode = m_streamRoleProp.m_value;
	m_streamRoleCombo.m_currentData = mode;
	setStreamRoles (mode);

	m_pluginHost.m_log.resume ();
	return true;
}

void ModbusLayer.restoreDefaultProperties ()
{
	m_protocolProp.m_value = Def.Protocol;
	m_streamRoleProp.m_value = Def.StreamRole;
	m_halfDuplexTimeoutProp.m_value = Def.HalfDuplexTimeout;
}

bool ModbusLayer.convert (
	log.Writer* writer,
	uint64_t timestamp,
	uint64_t recordCode,
	void const* p,
	size_t size
	)
{
	ModbusParserBase* parser;

	switch (recordCode)
	{
	case log.StdRecordCode.Tx:
		parser = m_txParser;
		break;

	case log.StdRecordCode.Rx:
		parser = m_rxParser;
		break;

	default:
		return false;
	}

	parser.parse (writer, timestamp, recordCode, p, size);
	return true;
}

void ModbusLayer.setStreamRoles (ModbusParserMode mode)
{
	m_txParser.m_mode = mode;

	if (mode != ModbusParserMode.HalfDuplex)
	{
		m_rxParser.m_mode = (ModbusParserMode) !mode;
	}
	else
	{
		m_rxParser.m_mode = ModbusParserMode.HalfDuplex;

		uint64_t timeout = (uint64_t) m_halfDuplexTimeoutProp.m_value * 10000;
		m_txParser.m_halfDuplexTimeout = timeout;
		m_rxParser.m_halfDuplexTimeout = timeout;
	}
}

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