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

class YModemTransmitter
{	
protected:
	enum State
	{
		Idle,
		WaitReceiver,
		FileName,
		Data,
		Eot,
		EotWaitReceiver,
		EotNullFileName,
	}
	
	State m_state;
	uint64_t m_offset;

	SitaraLayer* m_parent;
	
public:
	construct (SitaraLayer* parent);
	
	start  ();	
	cancel ();	

	onRx (
		void const* p,
		size_t size
		);
		
protected:
	complete ();
	sendFileNameBlock ();
	sendNextDataBlock ();
}

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

YModemTransmitter.construct (SitaraLayer* parent)
{		
	m_state = State.Idle;
	m_parent = parent;
}

YModemTransmitter.start  ()
{
	cancel ();

	m_parent.m_pluginHost.m_progressDlg.m_status = "Waiting for the peer to accept YModem transfer...";
	m_state = State.WaitReceiver;
	m_offset = 0;
}		

YModemTransmitter.cancel ()
{
	if (m_state == State.Idle)
		return;

	m_parent.m_pluginHost.m_progressDlg.m_status = "Cancelling YModem transfer...";

	static char c = YModemChar.Can;
	for (size_t i = 0; i < YModemCanCount; i++)
		try m_parent.transmit (&c, sizeof (c));

	complete ();
}

YModemTransmitter.complete ()
{
	m_state = State.Idle;
	m_offset = 0;
	m_parent.m_pluginHost.m_progressDlg.m_buttons = gui.StdDlgButtons.Ok;
	m_parent.m_file2.close ();
}

YModemTransmitter.sendFileNameBlock ()
{
	YModemBlock block;
	block.m_blockNumber = 0;
	block.m_code = YModemChar.Soh;
	block.m_complement = (uchar_t) (block.m_blockNumber ^ 0xff);
	
	if (m_state == State.FileName)
	{
		char const* sizeString = $"$(m_parent.m_file2.m_size)";	
		memcpy (block.m_data, "u-boot.img", 10);
		memcpy (&block.m_data [11], sizeString, strlen (sizeString));
	}
	
	block.m_crc16 = crc16 (block.m_data, sizeof (block.m_data));
	m_parent.transmit (&block, sizeof (block));

catch:
	m_parent.m_pluginHost.m_progressDlg.m_status = std.getLastError ().m_description;
	complete ();
}

YModemTransmitter.sendNextDataBlock ()
{
	if (m_offset > m_parent.m_file2.m_size)
		return;

	size_t size = (size_t) (m_parent.m_file2.m_size - m_offset);
	if (size > YModemBlockSize)
		size = YModemBlockSize;
	
	void const* p = m_parent.m_file2.view (m_offset, size);

	YModemBlock block;
	block.m_blockNumber = (uchar_t) (m_offset / YModemBlockSize + 1);
	block.m_code = YModemChar.Soh;
	block.m_complement = (uchar_t) (block.m_blockNumber ^ 0xff);

	memcpy (block.m_data, p, size);
	block.m_crc16 = crc16 (block.m_data, sizeof (block.m_data));
	
	m_parent.transmit (&block, sizeof (block));

	m_parent.m_pluginHost.m_progressDlg.m_progress = (int) ((m_offset + size / 2) * 100 / m_parent.m_file2.m_size);
	m_offset += size;	

catch:
	m_parent.m_pluginHost.m_progressDlg.m_status = std.getLastError ().m_description;
	complete ();
}

YModemTransmitter.onRx (
	void const* p,
	size_t size
	)
{
	char c = *(char const*) p;
	
	switch (m_state)
	{
	case State.WaitReceiver:
		switch (c)
		{
		case YModemChar.Nak:
		case YModemChar.C:
			m_parent.m_pluginHost.m_progressDlg.m_status = "Transferring YModem file name...";
			m_state = State.FileName;
			sendFileNameBlock ();
			break;
		}
		
		break;

	case State.FileName:
		switch (c)
		{
		case YModemChar.Ack:
			m_parent.m_pluginHost.m_progressDlg.m_status = "Transferring YModem data blocks...";
			m_state = State.Data;
			sendNextDataBlock ();
			break;

		case YModemChar.Nak:
			sendFileNameBlock (); // retransmit file name
			break; 
		}

		break;

	case State.Data:
		switch (c)
		{
		case YModemChar.Ack:
			m_parent.m_pluginHost.m_progressDlg.m_progress = (int) (m_offset * 100 / m_parent.m_file2.m_size);

			if (m_offset < m_parent.m_file2.m_size)
			{
				sendNextDataBlock ();
				break;
			}

			m_parent.m_pluginHost.m_progressDlg.m_status = "Ending YModem transfer...";
			m_state = State.Eot;

			static char c = YModemChar.Eot;
			try m_parent.transmit (&c, sizeof (c));
			break;

		case YModemChar.Nak:
			m_parent.m_pluginHost.m_progressDlg.m_status = "Remote node reported out-of-sequence YModem block";
			complete ();
			break;

		case YModemChar.Can:
			m_parent.m_pluginHost.m_progressDlg.m_status = "YModem transfer cancelled by remote node";
			complete ();
			break;
		}

		break;

	case State.Eot:
		switch (c)
		{
		case YModemChar.Ack:
			m_parent.m_pluginHost.m_progressDlg.m_status = "Finishing YModem transfer...";
			m_state = State.EotWaitReceiver;
			break; 

		case YModemChar.Nak:
			static char c = YModemChar.Eot; // retransmit eot
			try m_parent.transmit (&c, sizeof (c));
			break;
		}

		break;

	case State.EotWaitReceiver:
		switch (c)
		{
		case YModemChar.Nak:
		case YModemChar.C:
			m_parent.m_pluginHost.m_progressDlg.m_status = "Transferring YModem NULL file name...";
			m_state = State.EotNullFileName;
			sendFileNameBlock ();
			break;
		}
		
		break;

	case State.EotNullFileName:
		switch (c)
		{
		case YModemChar.Ack:
			m_parent.m_pluginHost.m_progressDlg.m_status = "YModem transfer finished.";
			complete ();
			break;

		case YModemChar.Nak:
			sendFileNameBlock (); // retransmit file name
			break; 
		}
		
		break;
	}
}		

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