#include <sys/cdefs.h>
#include <sys/endian.h>
#include <sys/ioctl.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <dev/iicbus/iic.h>

struct i2c_hid_desc {
	uint16_t wHIDDescLength;
	uint16_t bcdVersion;
	uint16_t wReportDescLength;
	uint16_t wReportDescRegister;
	uint16_t wInputRegister;
	uint16_t wMaxInputLength;
	uint16_t wOutputRegister;
	uint16_t wMaxOutputLength;
	uint16_t wCommandRegister;
	uint16_t wDataRegister;
	uint16_t wVendorID;
	uint16_t wProductID;
	uint16_t wVersionID;
	uint32_t reserved;
} __attribute__((packed));

void
dump_hid(struct i2c_hid_desc *hiddesc)
{

	printf("HID descriptor:\n\n");
	printf("wHIDDescLength:      %hu\n",    le16toh(hiddesc->wHIDDescLength));
	printf("bcdVersion:          0x%04x\n",    le16toh(hiddesc->bcdVersion));
	printf("wReportDescLength:   %hu\n",    le16toh(hiddesc->wReportDescLength));
	printf("wReportDescRegister: 0x%04x\n", le16toh(hiddesc->wReportDescRegister));
	printf("wInputRegister:      0x%04x\n", le16toh(hiddesc->wInputRegister));
	printf("wMaxInputLength:     %hu\n",    le16toh(hiddesc->wMaxInputLength));
	printf("wOutputRegister:     0x%04x\n", le16toh(hiddesc->wOutputRegister));
	printf("wMaxOutputLength:    %hu\n",    le16toh(hiddesc->wMaxOutputLength));
	printf("wCommandRegister:    0x%04x\n", le16toh(hiddesc->wCommandRegister));
	printf("wDataRegister:       0x%04x\n", le16toh(hiddesc->wDataRegister));
	printf("wVendorID:           0x%04x\n", le16toh(hiddesc->wVendorID));
	printf("wProductID:          0x%04x\n", le16toh(hiddesc->wProductID));
	printf("wVersionID:          0x%04x\n",    le16toh(hiddesc->wVersionID));
	printf("\n");
}

void
dump_hex(const void* data, size_t size)
{
	size_t i;

	printf("Report descriptor:\n\n");
	for (i = 0; i < size; ++i) {
		printf("%02X ", ((unsigned char*)data)[i]);
		if ((i+1) % 8 == 0)
			printf(" ");
		if ((i+1) % 16 == 0 || i+1 == size)
			printf("\n");
	}
	printf("\n");
}

int
main(int argc, char** argv)
{
	int fd;
	uint8_t i2caddr;
	uint16_t hidaddr;
	struct i2c_hid_desc hiddesc;
	char *ptr, *dev;
	uint8_t *reportbuf;
	struct iic_msg msg[2];
	struct iic_rdwr_data rdwr;

	if (argc < 4) {
		fprintf(stderr, "usage: %s device I2Caddr HIDaddr\n", getprogname());
		return (-1);
	}

	dev = argv[1];
	i2caddr = strtol(argv[2], &ptr, 0);
	hidaddr = htole16((uint16_t)strtol(argv[3], &ptr, 0));

	if ((fd = open(dev, O_RDWR)) < 0 ) {
		perror("open() failed:");
		exit(-1);
	}

	msg[0].slave = i2caddr << 1;
	msg[0].flags = IIC_M_WR | IIC_M_NOSTOP;
	msg[0].len = sizeof(hidaddr);
	msg[0].buf = (uint8_t *) &hidaddr;

	msg[1].slave = i2caddr << 1;
	msg[1].flags = IIC_M_RD;
	msg[1].len = sizeof(hiddesc);
	msg[1].buf = (uint8_t *) &hiddesc;

	rdwr.nmsgs = 2;
	rdwr.msgs = msg;

	if (ioctl(fd, I2CRDWR, &rdwr) < 0) {
		fprintf(stderr, "ioctl() failed: %d\n", errno);
		close(fd);
		exit(-1);
	}

	dump_hid(&hiddesc);

	if ((reportbuf = calloc(1, le16toh(hiddesc.wReportDescLength))) == NULL) {
		perror("calloc() failed:");
		close(fd);
		exit(-1);
	}

	msg[0].len = sizeof(hiddesc.wReportDescRegister);
	msg[0].buf = (uint8_t *) &hiddesc.wReportDescRegister;
	msg[1].len = le16toh(hiddesc.wReportDescLength);
	msg[1].buf = reportbuf;

	if (ioctl(fd, I2CRDWR, &rdwr) < 0) {
		fprintf(stderr, "ioctl() failed: %d\n", errno);
		free(reportbuf);
		close(fd);
		exit(-1);
	}

	dump_hex(reportbuf, le16toh(hiddesc.wReportDescLength));

	free(reportbuf);
	close(fd);
	exit(0);
}
