//..............................................................................
//
// HID report UI (for sending output & feature reports)
//

import "io_hid.jncx"
import "io_HidRd.jnc"
import "ui_GroupBox.jnc"
import "ui_ToolBar.jnc"
import "std_HashTable.jnc"
import "std_Buffer.jnc"

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

class HidReportUi {
protected:
	log.Writer* m_logWriter;
	log.TxRxStats* m_txRxStats;
	io.HidDevice* m_device;
	io.HidRd const* m_rd;
	HidReportDecodeInfoDb* m_reportDecodeInfoDb;
	std.Buffer m_buffer;
	ui.VBoxLayout m_container;
	ui.Icon* m_sendIcon;
	ui.Icon* m_refreshIcon;

public:
	construct(
		doc.PluginHost* pluginHost,
		HidReportDecodeInfoDb* reportDecodeInfoDb,
		io.HidDevice* device,
		io.HidRd const* rd
	);

	void createReportForms();

	void clear() {
		m_container.clear();
	}

protected:
	void createReportForm(io.HidReport const* report);

	HidReportFieldEncoder* createFieldEncoder(
		ui.FormLayout* layout,
		char const* text,
		size_t reportSize
	);

	void onRefreshReport();

	void onSendReport(
		io.HidReport const* report,
		HidReportFieldEncoder** encoderArray
	);
}

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

HidReportUi.construct(
	doc.PluginHost* pluginHost,
	HidReportDecodeInfoDb* reportDecodeInfoDb,
	io.HidDevice* device,
	io.HidRd const* rd
) {
	m_sendIcon = new ui.Icon("images/send.png");
	m_refreshIcon = new ui.Icon("images/refresh.png");
	m_logWriter = pluginHost.m_log.m_writer;
	m_txRxStats = pluginHost.m_txRxStats;
	m_reportDecodeInfoDb = reportDecodeInfoDb;
	m_device = device;
	m_rd = rd;

	pluginHost.createForm(m_container);
}

void HidReportUi.createReportForms() {
	m_container.clear();

	size_t reportCount = m_rd.m_reportCount[io.HidReportKind.Output];
	for (size_t i = 0; i < reportCount; i++)
		createReportForm(m_rd.m_reportArray[io.HidReportKind.Output][i]);

	reportCount = m_rd.m_reportCount[io.HidReportKind.Feature];
	for (size_t i = 0; i < reportCount; i++)
		createReportForm(m_rd.m_reportArray[io.HidReportKind.Feature][i]);
}

void HidReportUi.createReportForm(io.HidReport const* report) {
	char const* text = typeof(io.HidReportKind).getValueString(report.m_reportKind);
	if (report.m_reportId)
		text = $"%1 #%2"(text, report.m_reportId);

	// calculate the number of encoders

	size_t encoderCount = 0;
	for (size_t i = 0; i < report.m_fieldCount; i++) {
		io.HidReportField const* field = report.m_fieldArray[i];
		if (!field.m_isPadding)
			encoderCount += field[io.HidRdItemId.ReportCount];
	}

	HidReportFieldEncoder** encoderArray = new HidReportFieldEncoder*[encoderCount];

	ui.FormLayout layout;
	layout.m_spacing = 1; // make it dense

	ui.GroupBox groupBox(text, layout);
	io.HidUsagePage const* usagePage;
	size_t encoderIdx = 0;
	for (size_t i = 0; i < report.m_fieldCount; i++) {
		io.HidReportField const* field = report.m_fieldArray[i];
		if (field.m_isPadding)
			continue;

		if (usagePage != field.m_usagePage) {
			usagePage = field.m_usagePage;

			ui.Label label(usagePage.m_name);
			label.m_styleSheet = "font-weight: bold";
			layout.addRow(label);
		}

		size_t reportSize = field[io.HidRdItemId.ReportSize];
		size_t reportCount = field[io.HidRdItemId.ReportCount];

		for (size_t j = 0; j < reportCount; j++) {
			text = $"[$j]";

			if (field.m_valueFlags & io.HidRdValueFlags.Variable)
				text = $"%s %s"(text, usagePage.getUsageName(field.m_usageArray[j]));

			if (reportSize > 1)
				text = $"%s:"(text);

			encoderArray[encoderIdx++] = createFieldEncoder(layout, text, reportSize);
		}

		ui.Action action(m_sendIcon, "Send", onSendReport~(report, encoderArray));
		ui.ToolBar controlToolBar;
		controlToolBar.addStretch();
		controlToolBar.setIconSize(73, 27); // images/send.png
		controlToolBar.addAction(action);
		layout.addRow(controlToolBar);
	}

	m_container.addWidget(groupBox);
}

HidReportFieldEncoder* HidReportUi.createFieldEncoder(
	ui.FormLayout* layout,
	char const* text,
	size_t reportSize
) {
	if (reportSize == 1) {
		ui.CheckBox checkBox(text);
		layout.addRow(checkBox);
		return new HidReportBitEncoder(checkBox);
	} else {
		ui.Label label(text);
		ui.LineEdit edit;
		edit.m_text = "0";
		layout.addRow(label, edit);
		return new HidReportIntEncoder(edit, reportSize);
	}
}

void HidReportUi.onSendReport(
	io.HidReport const* report,
	HidReportFieldEncoder** encoderArray
) {
	m_buffer.setSize(report.m_size + sizeof(uint64_t));
	memset(m_buffer.m_p, 0, m_buffer.m_size);
	m_buffer[0] = report.m_reportId; // report ID in the first byte
	void* p = m_buffer.m_p + 1;

	size_t bitOffset = 0;
	size_t encoderIdx = 0;
	for (size_t i = 0; i < report.m_fieldCount; i++) {
		io.HidReportField const* field = report.m_fieldArray[i];
		if (field.m_isPadding) {
			bitOffset += field.m_bitCount;
			continue;
		}

		size_t reportSize = field[io.HidRdItemId.ReportSize];
		size_t reportCount = field[io.HidRdItemId.ReportCount];
		for (size_t j = 0; j < reportCount; j++) {
			encoderArray[encoderIdx++].encode(p, bitOffset);
			bitOffset += reportSize;
		}
	}

	uint64_t recordCode;

	if (report.m_reportKind == io.HidReportKind.Feature) {
		m_device.sendFeatureReport(m_buffer.m_p, report.m_size + 1);
		recordCode = HidLogRecordCode.SendFeatureReport;
	} else {
		m_device.write(m_buffer.m_p, report.m_size + 1);
		recordCode = HidLogRecordCode.OutputReport;
	}

	std.Buffer const* decodeInfo = m_reportDecodeInfoDb.getReportDecodeInfo(report);

	m_logWriter.write(
		recordCode,
		decodeInfo.m_p,
		decodeInfo.m_size,
		p,
		report.m_size
	);

	m_txRxStats.addTx(report.m_size);

catch:
	m_logWriter.write(log.StdRecordCode.Error, std.getLastError());
}

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

class HidReportFieldEncoder {
public:
	virtual void encode(
		void* p,
		size_t bitOffset
	) {}
}

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

class HidReportBitEncoder: HidReportFieldEncoder {
protected:
	ui.CheckBox* m_checkBox;

public:
	construct(ui.CheckBox* checkBox) {
		m_checkBox = checkBox;
	}

	override void encode(
		void* p,
		size_t bitOffset
	) {
		if (m_checkBox.m_isChecked) {
			*(uint8_t*)(p + bitOffset / 8) |= 1 << (bitOffset & 7);
		}
	}
}

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

class HidReportIntEncoder: HidReportFieldEncoder {
protected:
	ui.LineEdit* m_lineEdit;
	size_t m_bitCount;

public:
	construct(
		ui.LineEdit* lineEdit,
		size_t bitCount
	) {
		m_lineEdit = lineEdit;
		m_bitCount = bitCount;
	}

	override void encode(
		void* p0,
		size_t bitOffset
	) {
		uint64_t value = strtol(m_lineEdit.m_text);
		uint_t shift = bitOffset & 7;
		uint_t mask = (1 << m_bitCount) - 1;

		int64_t* p = (int64_t*)(p0 + bitOffset / 8);
		*p &= ~(mask << shift);
		*p |= value << shift;
	}
}

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