//..............................................................................
//
// HID log representer
//

import "io_hid.jncx"
import "io_HidRd.jnc"
import "io_HidEnumerator.jnc"
import "io_AutoHidDb.jnc"
import "log_Representation.jnc"
import "std_String.jnc"

using extension io.HidUsagePageEnumStrings;

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

bool representHidLog(
	log.Representation* representation,
	uint64_t recordCode,
	void const* p,
	size_t size,
	uint_t foldFlags
) {
	switch (recordCode) {
	case HidLogRecordCode.Open:
		representDeviceInfo(
			representation,
			log.StdLogIcon.Connect,
			log.StdLogColor.Connect,
			"Opened HID device",
			(HidLogDeviceInfoParams const*)p,
			foldFlags
		);
		break;

	case HidLogRecordCode.DeviceInfo:
		representDeviceInfo(
			representation,
			log.StdLogIcon.Info,
			ui.StdColor.PastelGray,
			"HID device info",
			(HidLogDeviceInfoParams const*)p,
			foldFlags
		);
		break;

	case HidLogRecordCode.OpenError:
		std.Error const* error = (std.Error const*)p;
		string_t path = (char const*)p + error.m_size;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.addHyperText($"Cannot open HID device \e[34m$path\e[m: $(error.m_description)");
		break;

	case HidLogRecordCode.Close:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Disconnect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Disconnect;
		representation.addHyperText($"HID device closed");
		break;

	case HidLogRecordCode.ReportDescriptor:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		if (!(foldFlags & 1)) {
			representation.addHyperText($"[\e^+1\e[34m+\e[m] HID report descriptor");
			break;
		}

		representation.addHyperText($"[\e^-1\e[34m-\e[m] HID report descriptor:");

		io.HidRd rd;
		rd.parse(io.g_hidDb, p, size);

		std.StringBuilder indent;
		indent.copy(' ',  4);

		for (size_t i = 0; i < rd.m_rootCollection.m_collectionCount; i++)
			representCollection(representation, indent, rd.m_rootCollection.m_collectionArray[i]);

		representFieldArray(representation, indent, rd.m_rootCollection);

		if (!(foldFlags & 2)) {
			representation.addHyperText($"[\e^+2\e[34m+\e[m] Raw data");
			break;
		}

		representation.addHyperText($"[\e^-2\e[34m-\e[m] Raw data:");
		representation.m_lineAttr.m_backColor = ui.ColorFlags.Undefined;
		representation.addBin(p, size);
		break;

	case HidLogRecordCode.InputReport:
	case HidLogRecordCode.InputReportNoId:
		representReport(
			representation,
			log.StdLogIcon.RxPacket,
			log.StdLogColor.Rx,
			"INPUT",
			recordCode == HidLogRecordCode.InputReport,
			p,
			size
		);
		break;

	case HidLogRecordCode.OutputReport:
	case HidLogRecordCode.OutputReportNoId:
		representReport(
			representation,
			log.StdLogIcon.TxPacket,
			log.StdLogColor.Tx,
			"OUTPUT",
			recordCode == HidLogRecordCode.OutputReport,
			p,
			size
		);
		break;

	case HidLogRecordCode.GetFeatureReport:
	case HidLogRecordCode.GetFeatureReportNoId:
		representReport(
			representation,
			log.StdLogIcon.RxPacket,
			log.StdLogColor.Rx,
			"GET FEATURE",
			recordCode == HidLogRecordCode.GetFeatureReport,
			p,
			size
		);
		break;

	case HidLogRecordCode.SendFeatureReport:
	case HidLogRecordCode.SendFeatureReportNoId:
		representReport(
			representation,
			log.StdLogIcon.TxPacket,
			log.StdLogColor.Tx,
			"SET FEATURE",
			recordCode == HidLogRecordCode.SendFeatureReport,
			p,
			size
		);
		break;

	default:
		return false;
	}

	return true;
}

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

void representDeviceInfo(
	log.Representation* representation,
	uint_t iconIdx,
	uint_t backColor,
	string_t text,
	HidLogDeviceInfoParams const* params,
	uint_t foldFlags
) {
	string_t path = (char const*)(params + 1);
	string_t manufacturer = path.m_p + path.m_length + 1;
	string_t product = manufacturer.m_p + manufacturer.m_length + 1;
	string_t serialNumber = product.m_p + product.m_length + 1;

	representation.m_lineAttr.m_iconIdx = iconIdx;
	representation.m_lineAttr.m_backColor = backColor;

	if (!(foldFlags & 1)) {
		representation.addHyperText(
			$"[\e^+1\e[34m+\e[m] %1: \e[34m%2 IF%3\e[m: \e[34m%4"(
				text,
				product,
				params.m_interfaceId,
				path
			)
		);
		return;
	}

	io.HidUsagePage const* page = io.g_hidDb.getUsagePage(params.m_usagePage);

	representation.addHyperText(
		$"[\e^-1\e[34m-\e[m] %1:\n"
		$"    \e[90mPath:\t\e[34m%2\n"
		$"    \e[90mDescription:\t\e[34m%3\n"
		$"    \e[90mManufacturer:\t\e[34m%4\n"
		$"    \e[90mVendor ID:\t\e[34m0x%(5; 04X)\n"
		$"    \e[90mProduct ID:\t\e[34m0x%(6; 04X)\n"
		$"    \e[90mBus:\t\e[34m%7\n"
		$"    \e[90mInterface:\t\e[34m%8\n"
		$"    \e[90mUsage page:\t\e[34m0x%9\n"
		$"    \e[90mUsage:\t\e[34m%10"(
			text,
			path,
			product,
			manufacturer,
			params.m_vendorId,
			params.m_productId,
			typeof(io.HidBusType).getValueString(params.m_busType),
			params.m_interfaceId,
			page.m_enumString,
			page.getUsageEnumString(params.m_usage)
		)
	);

	if (serialNumber)
		representation.addHyperText($"    \e[90mSerial number:\t\e[34m%1"(serialNumber));
}

void representCollection(
	log.Representation* representation,
	std.StringBuilder* indent,
	io.HidRdCollection const* collection
) {
	representation.addHyperText(
		$"%1\e[90mCollection:\t\e[34m%2\n"
		$"%1    \e[90mUsagePage:\t\e[34m%3\n"
		$"%1    \e[90mUsage:\t\e[34m%4"(
			indent.m_p,
			typeof(io.HidRdCollectionKind).getValueString(collection.m_collectionKind),
			collection.m_usagePage.m_enumString,
			collection.m_usagePage.getUsageEnumString(collection.m_usage)
		)
	);

	indent.append(' ', 4);

	for (size_t i = 0; i < collection.m_collectionCount; i++)
		representCollection(representation, indent, collection.m_collectionArray[i]);

	representFieldArray(representation, indent, collection);
	indent.chop(4);
}

void representFieldArray(
	log.Representation* representation,
	std.StringBuilder* indent,
	io.HidRdCollection const* collection
) {
	size_t count = collection.m_fieldCount;
	for (size_t i = 0; i < count; i++) {
		io.HidReportField const* field = collection.m_fieldArray[i];
		if (field.m_isPadding) {
			representation.addHyperText($"%s\e[90mPadding:\t\e[34m%d-bit"(indent.m_p, field.m_bitCount));
			continue;
		}

		string_t kind = typeof(io.HidReportType).getValueName(field.m_report.m_reportType);
		representation.addHyperText($"%s\e[90m%s:\t\e[34m%d-bit"(indent.m_p, kind, field.m_bitCount));

		indent.append(' ', 4);

		if (field.m_valueFlags) {
			string_t text = typeof(io.HidRdValueFlags).getValueString(field.m_valueFlags);
			representation.addHyperText($"%s\e[90mFlags:\t\e[34m%s"(indent.m_p, text));
		}

		representation.addHyperText($"%s\e[90mUsagePage:\t\e[34m%s"(indent.m_p, field.m_usagePage.m_enumString));

		if (field.isSet(io.HidRdItemId.Usage)) {
			for (size_t i = 0; i < field.m_usageCount; i++) {
				string_t text = field.m_usagePage.getUsageEnumString(field.m_usageArray[i]);
				representation.addHyperText($"%s\e[90mUsage:\t\e[34m%s"(indent.m_p, text));
			}
		}

		if (field.isSet(io.HidRdItemId.UsageMinimum)) {
			string_t text = field.m_usagePage.getUsageEnumString(field[io.HidRdItemId.UsageMinimum]);
			representation.addHyperText($"%s\e[90mUsageMinimum:\t\e[34m%s"(indent.m_p, text));
		}

		if (field.isSet(io.HidRdItemId.UsageMaximum)) {
			string_t text = field.m_usagePage.getUsageEnumString(field[io.HidRdItemId.UsageMaximum]);
			representation.addHyperText($"%s\e[90mUsageMaximum:\t\e[34m%s"(indent.m_p, text));
		}

		representItem(representation, indent, field, io.HidRdItemId.LogicalMinimum);
		representItem(representation, indent, field, io.HidRdItemId.LogicalMaximum);
		representItem(representation, indent, field, io.HidRdItemId.PhysicalMinimum);
		representItem(representation, indent, field, io.HidRdItemId.PhysicalMaximum);
		representItem(representation, indent, field, io.HidRdItemId.ReportId);
		representItem(representation, indent, field, io.HidRdItemId.ReportSize);
		representItem(representation, indent, field, io.HidRdItemId.ReportCount);

		indent.chop(4);
	}
}

void representItem(
	log.Representation* representation,
	std.StringBuilder const* indent,
	io.HidReportField const* field,
	io.HidRdItemId id
) {
	if (field.isSet(id))
		representation.addHyperText(
			$"%s\e[90m%s:\t\e[34m%d"(
				indent.m_p,
				typeof(io.HidRdItemId).getValueName(id),
				field[id]
			)
		);
}

int extractValue(
	void const* p,
	size_t size,
	size_t bitOffset,
	size_t bitCount,
	bool isUnsigned
) {
	size_t i = bitOffset / 8;
	size_t j = (bitOffset + bitCount - 1) / 8;
	size_t l = j - i;
	int shift = bitOffset & 7;
	int mask = (1 << bitCount) - 1;

	p += i;

	int value =
		l == 0 ? *(uint8_t const*)p >> shift :
		l == 1 ? *(uint16_t const*)p >> shift :
		l <= 3 ? *(uint32_t const*)p >> shift :
		*(uint64_t const*)p >> shift;

	return bitCount > 1 && !isUnsigned && (value & (1 << (bitCount - 1))) ? // negative
		(value & mask) | ((int)-1 & ~mask) :
		value & mask;
}

void representReport(
	log.Representation* representation,
	uint_t iconIdx,
	uint_t binTextColor,
	string_t text,
	bool isReportIdIncluded,
	void const* p,
	size_t size
) {
	io.HidStandaloneReport* report;
	uint_t reportId;
	string_t suffix;

	if (!(uint16_t const*)p) {
		HidLogUnknownReportParams const* params = (HidLogUnknownReportParams const*)p;
		reportId = params.m_reportId;
		suffix = " \e[31mUNKNOWN";
		p = params + 1;
		size -= sizeof(HidLogUnknownReportParams);
	} else {
		report = new io.HidStandaloneReport;
		size_t decodeInfoSize = report.loadDecodeInfo(io.g_hidDb, p, size);
		reportId = report.m_reportId;
		suffix = "";
		p += decodeInfoSize;
		size -= decodeInfoSize;
	}

	representation.m_lineAttr.m_iconIdx = iconIdx;
	representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
	representation.addHyperText(
		reportId ?
			$"\e[1m%1\e[m \e[34m#%2%3"(text, reportId, suffix) :
			$"\e[1m%1\e[m%2"(text, suffix)
	);

	if (report)
		if (isReportIdIncluded)
			representReportFields(representation, report, p + 1, size - 1);
		else
			representReportFields(representation, report, p, size);

	representation.m_lineAttr.m_backColor = ui.ColorFlags.Undefined;
	representation.m_lineAttr.m_textColor = binTextColor;
	representation.addBin(p, size);
}

void representReportFields(
	log.Representation* representation,
	io.HidStandaloneReport* report,
	void const* p,
	size_t size
) {
	size_t bitOffset = 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];
		uint_t usageMin = field[io.HidRdItemId.UsageMinimum];
		size_t usageCount = field.m_usageCount;
		size_t baseUsageIdx = usageCount > reportCount ? usageCount - reportCount : 0;
		bool isUnsigned = field.m_isUnsigned;

		bool hasIndex =
			reportCount > 1 &&
			(field.m_valueFlags & io.HidRdValueFlags.Variable) &&
			field.m_usageArray[0] == field.m_usageArray[1];

		for (size_t i = 0; i < reportCount; i++, bitOffset += reportSize) {
			int value = extractValue(p, size, bitOffset, reportSize, isUnsigned);
			if (value)
				if (!(field.m_valueFlags & io.HidRdValueFlags.Variable)) {
					string_t usageName = field.m_usagePage.getUsageEnumString(usageMin + value);
					representation.addHyperText($"[%d]:\t\e[34m%s"(i, usageName));
				} else {
					uint_t usage = field.m_usageArray[baseUsageIdx + i];
					string_t usageName = field.m_usagePage.getUsageString(usage);
					representation.addHyperText(
						hasIndex ?
							$"%s [%d]:\t\e[34m%d"(usageName, i,value) :
							$"%s:\t\e[34m%d"(usageName, value)
					);
				}
		}

		representation.addBreak();
	}
}

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