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

class XModemReceiver
{
protected:
	enum State
	{
		Idle,
		Data,
		Eot,
	}

	State m_state;
	uint64_t m_offset;

	XModemLayer* m_parent;

public:
	construct(XModemLayer* parent);

	void start();
	void cancel();

	void onRx(
		void const* p,
		size_t size
		);

protected:
	void onRx_Data(XModemBlock const* block);

	void complete();
	void sendNextDataBlock();
}

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

XModemReceiver.construct(XModemLayer* parent)
{
	m_state = State.Idle;
	m_parent = parent;
}

void XModemReceiver.start()
{
	cancel();

	static char c = XModemChar.Nak;
	try m_parent.transmit(&c, sizeof(c));

	m_parent.m_progressDlg.m_status = "Receiving XModem data blocks...";
	m_state = State.Data;
	m_offset = 0;
	m_parent.m_file.setSize(0);
}

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

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

	m_parent.m_progressDlg.m_status = "XModem transfer cancelled by user";
	complete();
}

void XModemReceiver.complete()
{
	m_parent.m_progressDlg.m_buttons = ui.StdDlgButtons.Ok;
	m_parent.m_file.unmapAllViews();
	m_parent.m_file.setSize(m_offset);
	m_parent.m_file.close();
	m_state = State.Idle;
	m_offset = 0;
}

void XModemReceiver.onRx(
	void const* p,
	size_t size
	)
{
	char c = *(char const*) p;

	switch (c)
	{
	case XModemChar.Soh:
		if (m_state == State.Data && size == sizeof(XModemBlock))
			onRx_Data((XModemBlock const*) p);

		break;

	case XModemChar.Can:
		m_parent.m_progressDlg.m_status = "XModem transfer cancelled by remote node";
		complete();
		break;

	case XModemChar.Eot:
		static char c = XModemChar.Ack;
		try m_parent.transmit(&c, sizeof(c));

		m_parent.m_progressDlg.m_status = "XModem transfer completed successfully";
		complete();
		break;
	}
}

void XModemReceiver.onRx_Data(XModemBlock const* block)
{
	static char ack = XModemChar.Ack;
	static char nak = XModemChar.Nak;

	uchar_t blockNumber = (uchar_t)(m_offset / XModemBlockSize + 1);
	if (block.m_blockNumber != blockNumber)
	{
		m_parent.transmit(&nak, sizeof(nak));
		m_parent.m_progressDlg.m_status = "Out-of-sequence XModem block";
		return; // and ignore it
	}

	uchar_t checksum = calcXModemChecksum(block.m_data);
	if (block.m_checksum != checksum)
	{
		m_parent.transmit(&nak, sizeof(nak));
		m_parent.m_progressDlg.m_status = "Bad checksum of XModem block";
		return; // and ignore it
	}

	m_parent.transmit(&ack, sizeof(ack));

	void* p = m_parent.m_file.view(m_offset, XModemBlockSize);
	memcpy(p, block.m_data, XModemBlockSize);
	m_offset += XModemBlockSize;

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

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