/* Raw HID I/O Routines
 * Copyright 2008, PJRC.COM, LLC
 * paul@pjrc.com
 *
 * You may redistribute this program and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 */


// This code will someday be turned into "librawhid", an easy-to-use
// and truly cross platform library for accessing HID reports.  But
// there are many complexities not properly handled by this simple
// code that would be expected from a high quality library.  In
// particular, how report IDs are handled is not uniform on the 3
// platforms.  The mac code uses a single buffer which assumes no
// other functions can cause the "run loop" to process HID callbacks.
// The linux version doesn't extract usage and usage page from the
// report descriptor and just hardcodes a signature for the Teensy
// USB debug example.  Lacking from all platforms are functions to
// manage multiple devices and robust detection of device removal
// and attachment.  There are probably lots of other issues... this
// code has really only been used in 2 projects.  If you use it,
// please report bugs to paul@pjrc.com


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "rawhid.h"

#ifdef OPERATING_SYSTEM
#undef OPERATING_SYSTEM
#endif


/*************************************************************************/
/**                                                                     **/
/**                             Linux                                   **/
/**                                                                     **/
/*************************************************************************/

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/hidraw.h>

struct rawhid_struct {
	int fd;
	int name;
	int isok;
};

rawhid_t * rawhid_open_only1(int vid, int pid, int usage_page, int usage)
{
	struct rawhid_struct *hid;
	struct stat devstat;
	struct hidraw_devinfo info;
	struct hidraw_report_descriptor *desc;
	char buf[512];
	int r, i, fd=-1, len, found=0;

	printf("Searching for device using hidraw....\n");
	for (i=0; i<HIDRAW_MAX_DEVICES; i++) {
		if (fd > 0) close(fd);
		snprintf(buf, sizeof(buf), "/dev/hidraw%d", i);
		r = stat(buf, &devstat);
		if (r < 0) continue;
		printf("device: %s\n", buf);
		fd = open(buf, O_RDWR);
		if (fd < 0) continue;
		printf("  opened\n");
		r = ioctl(fd, HIDIOCGRAWINFO, &info);
		if (r < 0) continue;
		printf("  vid=%04X, pid=%04X\n", info.vendor & 0xFFFF, info.product & 0xFFFF);
		r = ioctl(fd, HIDIOCGRDESCSIZE, &len);
		if (r < 0 || len < 1) continue;
		printf("  len=%u\n", len);
		desc = (struct hidraw_report_descriptor *)buf;
		if (len > sizeof(buf)-sizeof(int)) len = sizeof(buf)-sizeof(int);
		desc->size = len;
		r = ioctl(fd, HIDIOCGRDESC, desc);
		if (r < 0) continue;
		printf("  Match\n");
		found = 1;
		break;
	}
	if (!found) {
		if (fd > 0) close(fd);
		return NULL;
	}
	hid = (struct rawhid_struct *)malloc(sizeof(struct rawhid_struct));
	if (!hid) {
		close(fd);
		return NULL;
	}
	hid->fd = fd;
	return hid;
}

int rawhid_status(rawhid_t *hid)
{
	// TODO: how to check if device is still online?
	return -1;
}

int rawhid_read(rawhid_t *h, void *buf, int bufsize, int timeout_ms)
{
	struct rawhid_struct *hid;
	int num;

	hid = (struct rawhid_struct *)h;
	if (!hid || hid->fd < 0) return -1;

	while (1) {
		num = read(hid->fd, buf, bufsize);
		if (num < 0) {
			if (errno == EINTR || errno == EAGAIN) continue;
			if (errno == EIO) {
				return -1;
				printf("I/O Error\n");
			}
			printf("read error, r=%d, errno=%d\n", num, errno);
			return -1;
		}
		//printf("read %d bytes\n", num);
		return num;
	}
}

void rawhid_close(rawhid_t *h)
{
	struct rawhid_struct *hid;

	hid = (struct rawhid_struct *)h;
	if (!hid || hid->fd < 0) return;
	close(hid->fd);
	hid->fd = -1;
}