//..............................................................................
//
// USB log packet declarations and representer
//

import "log_Representation.jnc"
import "UsbLogRecordCode.jnc"
import "io_usbmon.jncx"
import "io_UsbDescriptors.jnc"
import "io_win_UsbdStatus.jnc"

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

uint_t getTransferIcon(uint8_t endpointId) {
	return (endpointId & 0x80) ? log.StdLogIcon.RxPacket : log.StdLogIcon.TxPacket;
}

string_t getDeviceInfoString(
	string_t prefix,
	UsbDeviceParams const* params,
	uint_t foldFlags
) {
	string_t product = (char const*)(params + 1);
	string_t manufacturer = product.m_p + product.m_length + 1;

	if (!(foldFlags & 1))
		return
			$"[\e^+1\e[34m+\e[m] %1 - \e[34m%2"(prefix, product);

	string_t driver = manufacturer.m_p + manufacturer.m_length + 1;
	string_t string =
		$"[\e^-1\e[34m-\e[m] %1\n"
		$"    \e[90mDescription:\t\e[34m%2\n"
		$"    \e[90mManufacturer:\t\e[34m%3\n"
		$"    \e[90mVendor ID:\t\e[34m0x%(4; 04X)\n"
		$"    \e[90mProduct ID:\t\e[34m0x%(5; 04X)\n"
		$"    \e[90mClass:\t\e[34m%6\n"
		$"    \e[90mSpeed:\t\e[34m%7"(
			prefix,
			product,
			manufacturer,
			params.m_vendorId,
			params.m_productId,
			typeof(io.UsbClass).getValueString(params.m_class),
			typeof(io.UsbSpeed).getValueString(params.m_speed)
		);

	if (!driver)
		return string;

	return $"%1\n    \e[90mDriver:\t\e[34m%2"(string, driver);
}

string_t getInterfaceInfoString(UsbInterfaceParams_0 const* params)  {
	return params.m_altSettingId ?
		$"IF%1.%2"(params.m_interfaceId, params.m_altSettingId) :
		$"IF%1"(params.m_interfaceId);
}

bool representUsbLog(
	log.Representation* representation,
	uint64_t recordCode,
	void const* p,
	size_t size,
	uint_t foldFlags
) {
	switch (recordCode) {
	case UsbLogRecordCode.DeviceOpened_0:
		UsbDeviceParams_0 const* params = (UsbDeviceParams_0 const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Connect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Connect;
		representation.addHyperText(
			$"Device opened: \e[34m%03d:%03d\e[m VID_%04X PID_%04X"(
				params.m_bus,
				params.m_address,
				params.m_vendorId,
				params.m_productId
			)
		);
		break;

	case UsbLogRecordCode.DeviceOpened:
		UsbDeviceParams const* params = (UsbDeviceParams const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Connect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Connect;

		string_t string = getDeviceInfoString(
			$"Device opened at \e[34m%03d:%03d\e[m"(
				params.m_bus,
				params.m_address
			),
			params,
			foldFlags
		);

		representation.addHyperText(string);
		break;

	case UsbLogRecordCode.DeviceOpenError:
		std.Error const* error = (std.Error const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.addHyperText($"Cannot open device: $(error.m_description)");
		break;

	case UsbLogRecordCode.DeviceClosed:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Disconnect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Disconnect;
		representation.addHyperText("Device closed");
		break;

	case UsbLogRecordCode.KernelDriverDetached:
		uint8_t ifaceId = *(uint8_t const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Warning;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Warning;
		representation.addHyperText($"Kernel driver detached from interface ID \e[34m$ifaceId");
		break;

	case UsbLogRecordCode.InterfaceClaimed_0:
		UsbInterfaceParams_0 const* params = (UsbInterfaceParams_0 const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;

		string_t ifaceInfo = getInterfaceInfoString(params);
		representation.addHyperText($"Claimed interface \e[34m%1"(ifaceInfo));
		break;

	case UsbLogRecordCode.InterfaceClaimed:
		UsbInterfaceParams const* params = (UsbInterfaceParams const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;

		string_t ifaceInfo = getInterfaceInfoString(params);
		string_t className = typeof(io.UsbClass).getValueName(params.m_class);
		representation.addHyperText($"Claimed interface \e[34m%1 - %2"(ifaceInfo, className));
		break;

	case UsbLogRecordCode.InterfaceClaimError:
		std.Error const* error = (std.Error const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.addHyperText($"Cannot claim interface: $(error.m_description)");
		break;

	case UsbLogRecordCode.InterfaceReleased:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;
		representation.addHyperText($"Released interface(s)");
		break;

	case UsbLogRecordCode.ReadingStarted_0:
		uint8_t endpointId = *(uint8_t const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;
		representation.addHyperText($"Started reading from endpoint \e[34mEP%1"(endpointId & ~0x80));
		break;

	case UsbLogRecordCode.ReadingStarted:
		UsbEndpointParams const* params = (UsbEndpointParams const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;

		representation.addHyperText(
			$"Started reading from endpoint \e[34mEP%1 - %2"(
				params.m_endpointId & ~0x80,
				typeof(io.UsbTransferType).getValueName(params.m_transferType)
			)
		);
		break;

	case UsbLogRecordCode.ReadingStopped:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Info;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Info;
		representation.addHyperText($"Stopped reading from endpoint(s)");
		break;

	case UsbLogRecordCode.EndpointOpenError:
		std.Error const* error = (std.Error const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.addHyperText($"Cannot open endpoint: $(error.m_description)");
		break;

	case UsbLogRecordCode.CaptureStarted:
		UsbDeviceParams const* params = (UsbDeviceParams const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Connect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Connect;

		string_t string = getDeviceInfoString(
			$"Capture started on \e[34m%1%2:%03d\e[m"(
				params.m_osKind == sys.OsKind.Windows ?
					"USBPcap" :
					"usbmon",
				params.m_captureDeviceId,
				params.m_address
			),
			params,
			foldFlags
		);

		representation.addHyperText(string);
		break;

	case UsbLogRecordCode.CaptureStopped:
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Disconnect;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Disconnect;
		representation.addHyperText("Capture stopped");
		break;

	case UsbLogRecordCode.CaptureError:
		std.Error const* error = (std.Error const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.addHyperText($"Cannot start capturing: $(error.m_description)");
		break;

	case UsbLogRecordCode.ControlTransfer_0:
		UsbControlTransferParams_0 const* params = (UsbControlTransferParams_0 const*)p;
		representation.m_lineAttr.m_iconIdx = (params.m_type & 0x80) ? log.StdLogIcon.RxPacket : log.StdLogIcon.TxPacket;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		representation.addHyperText(
			$"Type: $(params.m_type; 02X) "
			$"Code: $(params.m_code; 02X) "
			$"Value: $(params.m_value; 04X) "
			$"Index: $(params.m_index; 04X)"
		);
		break;

	case UsbLogRecordCode.ControlTransfer:
		io.UsbControlSetup const* setup = (io.UsbControlSetup const*)p;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		representation.m_lineAttr.m_iconIdx = setup.m_requestType.m_direction ?
			log.StdLogIcon.RxPacket :
			log.StdLogIcon.TxPacket;

		string_t string = !(foldFlags & 1) ?
			$"[\e^+1\e[34m+\e[m] \e[1mCONTROL\e[m "
			$"%1 %2 "
			$"Req: \e[34m%3\e[m "
			$"Val: \e[34m0x%(4;04X)\e[m "
			$"Idx: \e[34m0x%(5;04X)"(
				typeof(io.UsbControlRecipient).getValueName(setup.m_requestType.m_recipient),
				typeof(io.UsbControlType).getValueName(setup.m_requestType.m_type),
				typeof(io.UsbControlStdRequest).getValueName(setup.m_request),
				setup.m_value,
				setup.m_index
			) :
			$"[\e^-1\e[34m-\e[m] \e[1mCONTROL\e[m\n"
			$"    \e[90mRequest type:\t\e[34m0x%(1;02X)\n"
			$"    \e[90m    Recipient:\t\e[34m%2\n"
			$"    \e[90m    Type:\t\e[34m%3\n"
			$"    \e[90m    Direction:\t\e[34m%4\n"
			$"    \e[90mRequest:\t\e[34m%5\n"
			$"    \e[90mValue:\t\e[34m0x%(6;04X)\n"
			$"    \e[90mIndex:\t\e[34m0x%(7;04X)\n"
			$"    \e[90mLength:\t\e[34m%8"(
				setup.m_requestType.m_value,
				typeof(io.UsbControlRecipient).getValueString(setup.m_requestType.m_recipient),
				typeof(io.UsbControlType).getValueString(setup.m_requestType.m_type),
				typeof(io.UsbControlDirection).getValueString(setup.m_requestType.m_direction),
				typeof(io.UsbControlStdRequest).getValueString(setup.m_request),
				setup.m_value,
				setup.m_index,
				setup.m_length
			);

		representation.addHyperText(string);
		break;

	case UsbLogRecordCode.BulkTransfer:
		uint8_t endpointId = *(uint8_t const*)p;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		representation.m_lineAttr.m_iconIdx = getTransferIcon(endpointId);
		representation.addHyperText($"\e[1;35mBULK\e[m \e[34mEP$(endpointId & ~0x80)");
		break;

	case UsbLogRecordCode.InterruptTransfer:
		uint8_t endpointId = *(uint8_t const*)p;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		representation.m_lineAttr.m_iconIdx = getTransferIcon(endpointId);
		representation.addHyperText($"\e[1;36mINTERRUPT\e[m \e[34mEP$(endpointId & ~0x80)");
		break;

	case UsbLogRecordCode.IsochronousTransfer:
		uint8_t endpointId = *(uint8_t const*)p;
		representation.m_lineAttr.m_backColor = ui.StdColor.PastelGray;
		representation.m_lineAttr.m_iconIdx = getTransferIcon(endpointId);
		representation.addHyperText($"\e[1;33mISOCHRONOUS\e[m \e[34mEP$(endpointId & ~0x80)");
		break;

	case UsbLogRecordCode.TransferError:
		UsbTransferErrorParams const* params = (UsbTransferErrorParams const*)p;
		string_t description;
		switch (params.m_osKind) {
		case sys.OsKind.Windows:
			description = typeof(io.win.USBD_STATUS).getValueString(params.m_status);
			break;

		case sys.OsKind.Linux:
			std.Error error;
			error.m_size = sizeof(error);
			error.m_guid = std.g_errnoGuid;
			error.m_code = params.m_status;
			description = error.m_description;
			break;
		}

		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.addHyperText($"Transfer error: $description");
		break;

	case UsbLogRecordCode.UsbMonNotDetected:
		sys.OsKind osKind = *(sys.OsKind const*)p;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Error;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Error;

		string_t usbmonService;
		string_t url;
		switch (osKind) {
		case sys.OsKind.Windows:
			usbmonService = "USBPcap";
			url = "http://ioninja.com/doc/kb/usbpcap_windows_installation.html";
			break;

		case sys.OsKind.Linux:
			usbmonService = "usbmon";
			url = "http://ioninja.com/doc/kb/usbmon_linux_installation.html";
			break;

		default:
			representation.addHyperText($"USB capturing service not detected");
			break2;
		}

		representation.addHyperText(
			$"USB capturing service ($usbmonService) not detected\n"
			$"Read this \e^$url\e[4;34mKB article\e[m for more details"
		);

		break;

	case UsbLogRecordCode.TruncatedCapture:
		uint32_t truncated = *(uint32_t const*)p;
		representation.m_lineAttr.m_backColor = log.StdLogColor.Warning;
		representation.m_lineAttr.m_iconIdx = log.StdLogIcon.Warning;
		representation.addHyperText($"Previous transfer not fully captured (%1 bytes truncated)"(truncated));
		break;

	default:
		return false;
	}

	return true;
}

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