import "std_Buffer.jnc"

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

class XModemReceiver {
protected:
	enum State {
		Idle,
		Command,
		Buffer,
	}

	State m_state;
	std.Buffer m_buffer;
	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 processXModemBlock();
	void complete();
}

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

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.Command;
	m_offset = 0;
	m_parent.m_file.setSize(0);
	m_buffer.clear();
}

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
) {
	void const* end = p + size;

	while (p < end) {
		switch (m_state) {
		case State.Command:
			char c = *(char const*) p;
			switch (c) {
			case XModemChar.Soh:
				m_state = State.Buffer;
				m_buffer.clear();
				break;

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

			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();
				return;
			}

		case State.Buffer:
			size = end - p;
			if (m_buffer.m_size + size < sizeof(XModemBlock)) {
				m_buffer.append(p, size);
				return;
			}

			size_t appendSize = sizeof(XModemBlock) - m_buffer.m_size;
			m_buffer.append(p, appendSize);
			m_state = State.Command;
			processXModemBlock();
			p += appendSize;
		}
	}
}

void XModemReceiver.processXModemBlock() {
	XModemBlock const* block = (XModemBlock const*)m_buffer.m_p;

	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.getLastErrorDescription();
	complete();
}

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