/*
 *   CUPS Backend common code
 *
 *   Copyright (c) 2007-2017 Solomon Peachy <pizza@shaftnet.org>
 *
 *   The latest version of this program can be found at:
 *
 *     http://git.shaftnet.org/cgit/selphy_print.git
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the Free
 *   Software Foundation; either version 2 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 <https://www.gnu.org/licenses/>.
 *
 *          [http://www.gnu.org/licenses/gpl-2.0.html]
 *
 *   SPDX-License-Identifier: GPL-2.0+
 *
 */

#include "backend_common.h"

#define BACKEND_VERSION "0.75G"
#ifndef URI_PREFIX
#error "Must Define URI_PREFIX"
#endif

#define NUM_CLAIM_ATTEMPTS 10

#define URB_XFER_SIZE  (64*1024)
#define XFER_TIMEOUT    15000

#define USB_SUBCLASS_PRINTER 0x1
#define USB_INTERFACE_PROTOCOL_BIDIR 0x2

/* Global Variables */
int dyesub_debug = 0;
int terminate = 0;
int fast_return = 0;
int extra_vid = -1;
int extra_pid = -1;
int extra_type = -1;
int copies = 1;

static int max_xfer_size = URB_XFER_SIZE;
static int xfer_timeout = XFER_TIMEOUT;

/* Support Functions */
static int backend_claim_interface(struct libusb_device_handle *dev, int iface)
{
	int attempts = NUM_CLAIM_ATTEMPTS;
	int ret;
	do {
		ret = libusb_claim_interface(dev, iface);
		if (!ret)
			break;
		if (ret != LIBUSB_ERROR_BUSY)
			break;
		sleep(1);
	} while (--attempts > 0);

	if (ret)
		ERROR("Printer open failure (Could not claim printer interface after %d attempts) (%d)\n", NUM_CLAIM_ATTEMPTS, ret);

	return ret;
}

/* Interface **MUST** already be claimed! */
#define ID_BUF_SIZE 2048
static char *get_device_id(struct libusb_device_handle *dev, int iface)
{
	int length;
	char *buf = malloc(ID_BUF_SIZE + 1);

	if (!buf) {
		ERROR("Memory allocation failure (%d bytes)\n", ID_BUF_SIZE+1);
		return NULL;
	}

	if (libusb_control_transfer(dev,
				    LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_ENDPOINT_IN |
				    LIBUSB_RECIPIENT_INTERFACE,
				    0, 0,
				    (iface << 8),
				    (unsigned char *)buf, ID_BUF_SIZE, 5000) < 0)
	{
		*buf = '\0';
		goto done;
	}

	/* length is the first two bytes, MSB first */
	length = (((unsigned)buf[0] & 255) << 8) |
		((unsigned)buf[1] & 255);

	/* Sanity checks */
	if (length > ID_BUF_SIZE || length < 14)
		length = (((unsigned)buf[1] & 255) << 8) |
			((unsigned)buf[0] & 255);

	if (length > ID_BUF_SIZE)
		length = ID_BUF_SIZE;

	if (length < 14) {
		*buf = '\0';
		goto done;
	}

	/* Move, and terminate */
	memmove(buf, buf + 2, length);
	buf[length] = '\0';

done:
	return buf;
}

/* Used with the IEEE1284 deviceid string parsing */
struct deviceid_dict {
	char *key;
	char *val;
};

#define MAX_DICT 32

static int parse1284_data(const char *device_id, struct deviceid_dict* dict)
{
	char *ptr;
	char key[256];
	char val[256];
	int num = 0;

	if (!device_id)
		return 0;

	//[whitespace]key[whitespace]:[whitespace]value[whitespace];
	while (*device_id && num < MAX_DICT) {
		/* Skip leading spaces */
		if (*device_id == ' ')
			device_id++;
		if (!*device_id)
			break;

		/* Work out key */
		for (ptr = key; *device_id && *device_id != ':'; device_id++)
			*ptr++ = *device_id;
		if (!*device_id)
			break;
		while (ptr > key && *(ptr-1) == ' ')
			ptr--;
		*ptr = 0;
		device_id++;
		if (!*device_id)
			break;

		/* Next up, value */
		for (ptr = val; *device_id && *device_id != ';'; device_id++)
			*ptr++ = *device_id;
		if (!*device_id)
			break;
		while (ptr > val && *(ptr-1) == ' ')
			ptr--;
		*ptr = 0;
		device_id++;

		/* Add it to the dictionary */
		dict[num].key = strdup(key);
		dict[num].val = strdup(val);
		num++;

		if (!*device_id)
			break;
	}
	return num;
}

static char *dict_find(const char *key, int dlen, struct deviceid_dict* dict)
{
	while(dlen) {
		if (!strcmp(key, dict->key))
			return dict->val;
		dlen--;
		dict++;
	}
	return NULL;
}

/* I/O functions */
int read_data(struct libusb_device_handle *dev, uint8_t endp,
	      uint8_t *buf, int buflen, int *readlen)
{
	int ret;

	/* Clear buffer */
	memset(buf, 0, buflen);

	ret = libusb_bulk_transfer(dev, endp,
				   buf,
				   buflen,
				   readlen,
				   xfer_timeout);

	if (ret < 0) {
		ERROR("Failure to receive data from printer (libusb error %d: (%d/%d from 0x%02x))\n", ret, *readlen, buflen, endp);
		goto done;
	}

	if (dyesub_debug) {
		DEBUG("Received %d bytes from printer\n", *readlen);
	}

	if ((dyesub_debug > 1 && buflen < 4096) ||
	    dyesub_debug > 2) {
		int i = *readlen;

		DEBUG("<- ");
		while(i > 0) {
			if ((*readlen-i) != 0 &&
			    (*readlen-i) % 16 == 0) {
				DEBUG2("\n");
				DEBUG("   ");
			}
			DEBUG2("%02x ", buf[*readlen-i]);
			i--;
		}
		DEBUG2("\n");
	}

done:
	return ret;
}

int send_data(struct libusb_device_handle *dev, uint8_t endp,
	      uint8_t *buf, int len)
{
	int num = 0;

	if (dyesub_debug) {
		DEBUG("Sending %d bytes to printer\n", len);
	}

	while (len) {
		int len2 = (len > max_xfer_size) ? max_xfer_size: len;
		int ret = libusb_bulk_transfer(dev, endp,
					       buf, len2,
					       &num, xfer_timeout);

		if ((dyesub_debug > 1 && len < 4096) ||
		    dyesub_debug > 2) {
			int i = num;

			DEBUG("-> ");
			while(i > 0) {
				if ((num-i) != 0 &&
				    (num-i) % 16 == 0) {
					DEBUG2("\n");
					DEBUG("   ");
				}
				DEBUG2("%02x ", buf[num-i]);
				i--;
			}
			DEBUG2("\n");
		}

		if (ret < 0) {
			ERROR("Failure to send data to printer (libusb error %d: (%d/%d to 0x%02x))\n", ret, num, len, endp);
			return ret;
		}
		len -= num;
		buf += num;
	}

	return 0;
}

/* More stuff */
static void sigterm_handler(int signum) {
	UNUSED(signum);

	terminate = 1;
	INFO("Job Cancelled");
}

static char *sanitize_string(char *str) {
	int len = strlen(str);

	while(len && (str[len-1] <= 0x20)) {
		str[len-1] = 0;
		len--;
	}
	return str;
}

/*

   These functions are Public Domain code obtained from:

   http://www.geekhideout.com/urlcode.shtml

*/
#include <ctype.h>  /* for isalnum() */
static char to_hex(char code) {
	static const char hex[] = "0123456789abcdef";
	return hex[code & 15];
}
static char from_hex(char ch) {
	return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
/* Note -- caller must free returned pointer! */
static char *url_encode(char *str) {
	char *pstr = str, *buf = malloc(strlen(str) * 3 + 1), *pbuf = buf;

	if (!buf) {
		ERROR("Memory allocation failure (%d bytes)\n", (int) strlen(str)*3 + 1);
		return NULL;
	}

	while (*pstr) {
		if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~')
			*pbuf++ = *pstr;
		else if (*pstr == ' ')
			*pbuf++ = '+';
		else
			*pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15);
		pstr++;
	}
	*pbuf = '\0';
	return buf;
}
static char *url_decode(char *str) {
	char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;

	if (!buf) {
		ERROR("Memory allocation failure (%d bytes)\n", (int) strlen(str) + 1);
		return NULL;
	}

	while (*pstr) {
		if (*pstr == '%') {
			if (pstr[1] && pstr[2]) {
				*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
				pstr += 2;
			}
		} else if (*pstr == '+') {
			*pbuf++ = ' ';
		} else {
			*pbuf++ = *pstr;
		}
		pstr++;
	}
	*pbuf = '\0';
	return buf;
}

/* And now back to our regularly-scheduled programming */

static int print_scan_output(struct libusb_device *device,
			     struct libusb_device_descriptor *desc,
			     char *prefix, char *manuf_override,
			     int found,
			     int scan_only, char *match_serno,
			     uint8_t *r_iface, uint8_t *r_altset,
			     uint8_t *r_endp_up, uint8_t *r_endp_down,
			     struct dyesub_backend *backend)
{
	struct libusb_device_handle *dev;
	char buf[256];
	char *product = NULL, *serial = NULL, *manuf = NULL, *descr = NULL;
	uint8_t iface, altset;
	struct libusb_config_descriptor *config = NULL;
	int dlen = 0;
	struct deviceid_dict dict[MAX_DICT];
	char *ieee_id = NULL;
	int i;
	uint8_t endp_up, endp_down;

	DEBUG("Probing VID: %04X PID: %04x\n", desc->idVendor, desc->idProduct);

	if (libusb_open(device, &dev)) {
		ERROR("Could not open device %04x:%04x (need to be root?)\n", desc->idVendor, desc->idProduct);
		found = -1;
		goto abort;
	}

	/* XXX FIXME:  Iterate through possible configurations? */
	if (libusb_get_active_config_descriptor(device, &config)) {
		found  = -1;
		goto abort_close;
	}

	/* Loop through all interfaces and altsettings to find candidates */
	for (iface = 0 ; iface < config->bNumInterfaces ; iface ++) {
		for (altset = 0 ; altset < config->interface[iface].num_altsetting ; altset++) {
			/* Skip interfaces that don't have enough endpoints */
			if (config->interface[iface].altsetting[altset].bNumEndpoints < 2) {
				continue;
			}

#if 0
			// Make sure it's a printer class device that supports bidir comms  (XXX Is this always true?)
			if (desc->bDeviceClass == LIBUSB_CLASS_PRINTER ||
			    (desc->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE &&
			     config->interface[iface].altsetting[altset].bInterfaceClass == LIBUSB_CLASS_PRINTER &&
			     config->interface[iface].altsetting[altset].bInterfaceSubClass == USB_SUBCLASS_PRINTER &&
			     config->interface[iface].altsetting[altset].bInterfaceProtocol != USB_INTERFACE_PROTOCOL_BIDIR)) {
				continue;
			}
#endif

			/* Find the first set of endpoints! */
			endp_up = endp_down = 0;
			for (i = 0 ; i < config->interface[iface].altsetting[altset].bNumEndpoints ; i++) {
				if ((config->interface[iface].altsetting[altset].endpoint[i].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) {
					if (config->interface[iface].altsetting[altset].endpoint[i].bEndpointAddress & LIBUSB_ENDPOINT_IN)
						endp_up = config->interface[iface].altsetting[altset].endpoint[i].bEndpointAddress;
					else
						endp_down = config->interface[iface].altsetting[altset].endpoint[i].bEndpointAddress;
				}
				if (endp_up && endp_down)
					goto candidate;
			}
		}
	}

	/* If we got here, we didn't find a match. */
	found = -1;
	goto abort_close;

candidate:

	/* We've now found an interface/altset we need to query in more detail */
	/* Detach the kernel driver */
	if (libusb_kernel_driver_active(dev, iface))
		libusb_detach_kernel_driver(dev, iface);

	/* Claim the interface so we can start querying things! */
	if (backend_claim_interface(dev, iface)) {
		found = -1;
		goto abort_release;
	}

	/* Use the appropriate altesetting! */
	if (altset != 0) {
		if (libusb_set_interface_alt_setting(dev, iface, altset)) {
			found = -1;
			goto abort_release;
		}
	}

	/* Query IEEE1284 info only if it's a PRINTER class */
	if (desc->bDeviceClass == LIBUSB_CLASS_PRINTER ||
	    (desc->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE &&
	     config->interface[iface].altsetting[altset].bInterfaceClass == LIBUSB_CLASS_PRINTER &&
	     config->interface[iface].altsetting[altset].bInterfaceSubClass == USB_SUBCLASS_PRINTER)) {
		ieee_id = get_device_id(dev, iface);
		dlen = parse1284_data(ieee_id, dict);
	}

	/* Look up mfg string. */
	if (manuf_override && strlen(manuf_override)) {
		manuf = url_encode(manuf_override);  /* Backend supplied */
	} else if ((manuf = dict_find("MANUFACTURER", dlen, dict))) {
		manuf = url_encode(manuf);
	} else if ((manuf = dict_find("MFG", dlen, dict))) {
		manuf = url_encode(manuf);
	} else if ((manuf = dict_find("MFR", dlen, dict))) {
		manuf = url_encode(manuf);
	} else if (desc->iManufacturer) { /* Get from USB descriptor */
		buf[0] = 0;
		libusb_get_string_descriptor_ascii(dev, desc->iManufacturer, (unsigned char*)buf, STR_LEN_MAX);
		sanitize_string(buf);
		manuf = url_encode(buf);
	}
	if (!manuf || !strlen(manuf)) {  /* Last-ditch */
		if (manuf) free(manuf);
		manuf = url_encode("Unknown"); // XXX use USB VID?
	}

	/* Look up model number */
	if ((product = dict_find("MODEL", dlen, dict))) {
		product = url_encode(product);
	} else if ((product = dict_find("MDL", dlen, dict))) {
		product = url_encode(product);
	} else if (desc->iProduct) {  /* Get from USB descriptor */
		buf[0] = 0;
		libusb_get_string_descriptor_ascii(dev, desc->iProduct, (unsigned char*)buf, STR_LEN_MAX);
		sanitize_string(buf);
		product = url_encode(buf);
	}

	if (!product || !strlen(product)) { /* Last-ditch */
		if (!product) free(product);
		product = url_encode("Unknown"); // XXX Use USB PID?
	}

	/* Look up description */
	if ((descr = dict_find("DESCRIPTION", dlen, dict))) {
		descr = strdup(descr);
	} else if ((descr = dict_find("DES", dlen, dict))) {
		descr = strdup(descr);
	}
	if (!descr || !strlen(descr)) { /* Last-ditch, generate */
		char *product2 = url_decode(product);
		char *manuf3 = url_decode(manuf);
		descr = malloc(256);
		if (!descr) {
			ERROR("Memory allocation failure (%d bytes)\n", 256);
			if (manuf3)
				free(manuf3);
			if (product2)
				free(product2);
			return -1;
		}

		sprintf(descr, "%s %s", manuf3, product2);
		free(product2);
		free(manuf3);
	}

	/* Look up serial number */
	if ((serial = dict_find("SERIALNUMBER", dlen, dict))) {
		serial = url_encode(serial);
	} else if ((serial = dict_find("SN", dlen, dict))) {
		serial = url_encode(serial);
	} else if ((serial = dict_find("SER", dlen, dict))) {
		serial = url_encode(serial);
	} else if ((serial = dict_find("SERN", dlen, dict))) {
		serial = url_encode(serial);
	} else if (desc->iSerialNumber) {  /* Get from USB descriptor */
		libusb_get_string_descriptor_ascii(dev, desc->iSerialNumber, (unsigned char*)buf, STR_LEN_MAX);
		sanitize_string(buf);
		serial = url_encode(buf);
	} else if (backend->query_serno) { /* Get from backend hook */
		buf[0] = 0;
		/* Ignore result since a failure isn't critical here */
		backend->query_serno(dev, endp_up, endp_down, buf, STR_LEN_MAX);
		serial = url_encode(buf);
	}

	if (!serial || !strlen(serial)) {  /* Last-ditch */
		if (serial) free(serial);
		WARNING("**** THIS PRINTER DOES NOT REPORT A SERIAL NUMBER!\n");
		WARNING("**** If you intend to use multiple printers of this type, you\n");
		WARNING("**** must only plug one in at a time or unexpected behavior will occur!\n");
		serial = strdup("NONE_UNKNOWN");
	}

	if (scan_only) {
		int k = 0;

		/* URLify the manuf and model strings */
		strncpy(buf, manuf, sizeof(buf) - 2);
		k = strlen(buf);
		buf[k++] = '/';
		buf[k] = 0;

		strncpy(buf + k, product, sizeof(buf)-k);

		fprintf(stdout, "direct %s://%s?serial=%s&backend=%s \"%s\" \"%s\" \"%s\" \"\"\n",
			prefix, buf, serial, backend->uri_prefix,
			descr, descr,
			ieee_id? ieee_id : "");
	}

	/* If a serial number was passed down, use it. */
	if (match_serno && strcmp(match_serno, (char*)serial)) {
		found = -1;
	}

	if (dyesub_debug)
		DEBUG("VID: %04X PID: %04X Manuf: '%s' Product: '%s' Serial: '%s' found: %d\n",
		      desc->idVendor, desc->idProduct, manuf, product, serial, found);

	if (found != -1) {
		if (r_iface) *r_iface = iface;
		if (r_altset) *r_altset = altset;
		if (r_endp_up) *r_endp_up = endp_up;
		if (r_endp_up) *r_endp_down = endp_down;
	}

	/* Free things up */
	if(serial) free(serial);
	if(manuf) free(manuf);
	if(product) free(product);
	if(descr) free(descr);
	if(ieee_id) free(ieee_id);

abort_release:

	libusb_release_interface(dev, iface);

abort_close:

	libusb_close(dev);

abort:
	if (config) libusb_free_config_descriptor(config);

	/* Clean up the dictionary */
	while (dlen--) {
		free (dict[dlen].key);
		free (dict[dlen].val);
	}

	return found;
}

extern struct dyesub_backend updr150_backend;
extern struct dyesub_backend kodak6800_backend;
extern struct dyesub_backend kodak605_backend;
extern struct dyesub_backend kodak1400_backend;
extern struct dyesub_backend shinkos1245_backend;
extern struct dyesub_backend shinkos2145_backend;
extern struct dyesub_backend shinkos6145_backend;
extern struct dyesub_backend shinkos6245_backend;
extern struct dyesub_backend canonselphy_backend;
extern struct dyesub_backend canonselphyneo_backend;
extern struct dyesub_backend mitsu70x_backend;
extern struct dyesub_backend mitsu9550_backend;
extern struct dyesub_backend mitsup95d_backend;
extern struct dyesub_backend dnpds40_backend;
extern struct dyesub_backend cw01_backend;

static struct dyesub_backend *backends[] = {
	&canonselphy_backend,
	&canonselphyneo_backend,
	&kodak6800_backend,
	&kodak605_backend,
	&kodak1400_backend,
	&shinkos1245_backend,
	&shinkos2145_backend,
	&shinkos6145_backend,
	&shinkos6245_backend,
	&updr150_backend,
	&mitsu70x_backend,
	&mitsu9550_backend,
	&mitsup95d_backend,
	&dnpds40_backend,
	&cw01_backend,
	NULL,
};

static int find_and_enumerate(struct libusb_context *ctx,
			      struct libusb_device ***list,
			      struct dyesub_backend *backend,
			      char *match_serno,
			      int scan_only,
			      uint8_t *r_iface, uint8_t *r_altset,
			      uint8_t *r_endp_up, uint8_t *r_endp_down)
{
	int num;
	int i, j = 0, k;
	int found = -1;

	/* Enumerate and find suitable device */
	num = libusb_get_device_list(ctx, list);

	for (i = 0 ; i < num ; i++) {
		struct libusb_device_descriptor desc;
		libusb_get_device_descriptor((*list)[i], &desc);

		for (k = 0 ; backends[k] ; k++) {
			if (backend && backend != backends[k])
				continue;
			for (j = 0 ; backends[k]->devices[j].vid ; j++) {
				if (extra_pid != -1 &&
				    extra_vid != -1 &&
				    extra_type != -1) {
					if (backends[k]->devices[j].type == extra_type &&
					    extra_vid == desc.idVendor &&
					    extra_pid == desc.idProduct) {
						found = i;
						goto match;
					}
				}
				if (desc.idVendor == backends[k]->devices[j].vid &&
				    (desc.idProduct == backends[k]->devices[j].pid ||
				     desc.idProduct == 0xffff)) {
					found = i;
					goto match;
				}
			}
		}

		continue;

	match:
		found = print_scan_output((*list)[i], &desc,
					  URI_PREFIX, backends[k]->devices[j].manuf_str,
					  found,
					  scan_only, match_serno,
					  r_iface, r_altset,
					  r_endp_up, r_endp_down,
					  backends[k]);

		if (found != -1 && !scan_only)
			break;
	}

	return found;
}

static struct dyesub_backend *find_backend(char *uri_prefix)
{
	int i;

	if (!uri_prefix)
		return NULL;

	for (i = 0; ; i++) {
		struct dyesub_backend *backend = backends[i];
		if (!backend)
			return NULL;
		if (!strcmp(uri_prefix, backend->uri_prefix))
			return backend;
	}
	return NULL;
}

void print_license_blurb(void)
{
	const char *license = "\n\
Copyright 2007-2017 Solomon Peachy <pizza AT shaftnet DOT org>\n\
\n\
This program is free software; you can redistribute it and/or modify it\n\
under the terms of the GNU General Public License as published by the Free\n\
Software Foundation; either version 3 of the License, or (at your option)\n\
any later version.\n\
\n\
This program is distributed in the hope that it will be useful, but\n\
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n\
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n\
for more details.\n\
\n\
You should have received a copy of the GNU General Public License\n\
along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\
\n          [http://www.gnu.org/licenses/gpl-3.0.html]\n\n";

	fprintf(stderr, "%s", license);
}

void print_help(char *argv0, struct dyesub_backend *backend)
{
	struct libusb_context *ctx = NULL;
	struct libusb_device **list = NULL;
	int i;

	char *ptr = strrchr(argv0, '/');
	if (ptr)
		ptr++;
	else
		ptr = argv0;

	if (!backend)
		backend = find_backend(ptr);

	if (!backend) {
		int i;
		DEBUG("Environment variables:\n");
		DEBUG(" DYESUB_DEBUG EXTRA_PID EXTRA_VID EXTRA_TYPE BACKEND SERIAL\n");
		DEBUG("CUPS Usage:\n");
		DEBUG("\tDEVICE_URI=someuri %s job user title num-copies options [ filename ]\n", URI_PREFIX);
		DEBUG("\n");
		DEBUG("Standalone Usage:\n");
		DEBUG("\t%s\n", URI_PREFIX);
		DEBUG("  [ -D ] [ -G ] [ -f ]\n");
		DEBUG("  [ backend_specific_args ] \n");
		DEBUG("  [ -d copies ] \n");
		DEBUG("  [ - | infile ] \n");
		for (i = 0; ; i++) {
			backend = backends[i];
			if (!backend)
				break;
			DEBUG("  BACKEND=%s\t# %s version %s\n",
			      backend->uri_prefix, backend->name, backend->version);
			if (backend->cmdline_usage)
				backend->cmdline_usage();
		}
	} else {
		DEBUG("Standalone %s backend version %s\n",
		      backend->name, backend->version);
		DEBUG("\t%s\n", backend->uri_prefix);
		DEBUG("\t[ -D ] [ -G ] [ -f ]\n");
		if (backend->cmdline_usage)
			backend->cmdline_usage();
		DEBUG("\t[ -d copies ] [ infile | - ]\n");
	}

	/* Probe for printers */
	i = libusb_init(&ctx);
	if (i) {
		ERROR("Failed to initialize libusb (%d)\n", i);
		exit(CUPS_BACKEND_STOP);
	}
	find_and_enumerate(ctx, &list, backend, NULL, 1, NULL, NULL, NULL, NULL);
	libusb_free_device_list(list, 1);
	libusb_exit(ctx);
}

int main (int argc, char **argv)
{
	struct libusb_context *ctx = NULL;
	struct libusb_device **list = NULL;
	struct libusb_device_handle *dev;
	struct libusb_config_descriptor *config = NULL;

	struct dyesub_backend *backend = NULL;
	void * backend_ctx = NULL;

	uint8_t endp_up, endp_down;
	uint8_t iface, altset;

	int data_fd = fileno(stdin);

	int i;

	int ret = CUPS_BACKEND_OK;

	int found = -1;
	int jobid = 0;
	int current_page = 0;

	char *uri;
	char *fname = NULL;
	char *use_serno = NULL;

	DEBUG("Multi-Call Dye-sublimation CUPS Backend version %s\n",
	      BACKEND_VERSION);
	DEBUG("Copyright 2007-2017 Solomon Peachy\n");
	DEBUG("This free software comes with ABSOLUTELY NO WARRANTY! \n");
	DEBUG("Licensed under the GNU GPL.  Run with '-G' for more details.\n");
	DEBUG("\n");

	/* First pass at cmdline parsing */
	if (getenv("DYESUB_DEBUG"))
		dyesub_debug = atoi(getenv("DYESUB_DEBUG"));
	if (getenv("EXTRA_PID"))
		extra_pid = strtol(getenv("EXTRA_PID"), NULL, 16);
	if (getenv("EXTRA_VID"))
		extra_pid = strtol(getenv("EXTRA_VID"), NULL, 16);
	if (getenv("EXTRA_PID"))
		extra_type = atoi(getenv("EXTRA_TYPE"));
	if (getenv("BACKEND"))
		backend = find_backend(getenv("BACKEND"));
	if (getenv("FAST_RETURN"))
		fast_return++;
	if (getenv("MAX_XFER_SIZE"))
		max_xfer_size = atoi(getenv("MAX_XFER_SIZE"));
	if (getenv("XFER_TIMEOUT"))
		xfer_timeout = atoi(getenv("XFER_TIMEOUT"));
	use_serno = getenv("SERIAL");
	uri = getenv("DEVICE_URI");  /* CUPS backend mode? */

	if (uri) {
		/* CUPS backend mode */
		int base = optind; /* ie 1 */
		if (argc < 6) {
			ERROR("Insufficient arguments\n");
			exit(1);
		}
		if (argv[base])
			jobid = atoi(argv[base]);
		if (argv[base + 3])
			copies = atoi(argv[base + 3]);
		if (argc > 6)
			fname = argv[base + 5];
		else
			fname = "-";

		/* Figure out backend based on URI */
		{
			char *ptr = strstr (uri, "backend="), *ptr2;
			if (!ptr) {
				ERROR("Invalid URI prefix (%s)\n", uri);
				exit(1);
			}
			ptr += 8;
			ptr2 = strchr(ptr, '&');
			if (ptr2)
				*ptr2 = 0;

			backend = find_backend(ptr);
			if (!backend) {
				ERROR("Invalid backend (%s)\n", ptr);
				exit(1);
			}
			if (ptr2)
				*ptr2 = '&';
		}

		use_serno = strchr(uri, '=');
		if (!use_serno || !*(use_serno+1)) {
			ERROR("Invalid URI (%s)\n", uri);
			exit(1);
		}
		use_serno++;
		{
			char *ptr = strchr(use_serno, '&');
			if (ptr)
				*ptr = 0;
		}

		/* Always enable fast return in CUPS mode */
		fast_return++;
	} else {
		/* Standalone mode */

		/* Try to guess backend from executable name */
		if (!backend) {
			char *ptr = strrchr(argv[0], '/');
			if (ptr)
				ptr++;
			else
				ptr = argv[0];
			backend = find_backend(ptr);
		}

		srand(getpid());
		jobid = rand();
	}

#ifndef LIBUSB_PRE_1_0_10
	if (dyesub_debug) {
		const struct libusb_version *ver;
		ver = libusb_get_version();
		DEBUG(" ** running with libusb %d.%d.%d%s (%d)\n",
		      ver->major, ver->minor, ver->micro, (ver->rc? ver->rc : ""), ver->nano );
	}
#endif

	/* Libusb setup */
	ret = libusb_init(&ctx);
	if (ret) {
		ERROR("Failed to initialize libusb (%d)\n", ret);
		ret = CUPS_BACKEND_STOP;
		goto done;
	}

	/* If we don't have a valid backend, print help and terminate */
	if (!backend) {
		print_help(argv[0], NULL); // probes all devices
		exit(1);
	}

	/* If we're in standalone mode, print help only if no args */
	if (!uri) {
		if (argc < 2) {
			print_help(argv[0], backend); // probes all devices
			exit(1);
		}
	}

	/* Enumerate devices */
	found = find_and_enumerate(ctx, &list, backend, use_serno, 0, &iface, &altset, &endp_up, &endp_down);

	if (found == -1) {
		ERROR("Printer open failure (No matching printers found!)\n");
		ret = CUPS_BACKEND_HOLD;
		goto done;
	}

	/* Open an appropriate device */
	ret = libusb_open(list[found], &dev);
	if (ret) {
		ERROR("Printer open failure (Need to be root?) (%d)\n", ret);
		ret = CUPS_BACKEND_STOP;
		goto done;
	}

	/* Detach the kernel driver */
	if (libusb_kernel_driver_active(dev, iface)) {
		ret = libusb_detach_kernel_driver(dev, iface);
		if (ret) {
			ERROR("Printer open failure (Could not detach printer from kernel) (%d)\n", ret);
			ret = CUPS_BACKEND_STOP;
			goto done_close;
		}
	}

	/* Claim the interface so we can start using this! */
	ret = backend_claim_interface(dev, iface);
	if (ret) {
		ret = CUPS_BACKEND_STOP;
		goto done_close;
	}

	/* Use the appropriate altesetting! */
	if (altset != 0) {
		if (libusb_set_interface_alt_setting(dev, iface, altset)) {
			found = -1;
			goto done_close;
		}
	}

	if (config)
		libusb_free_config_descriptor(config);

	/* Initialize backend */
	DEBUG("Initializing '%s' backend (version %s)\n",
	      backend->name, backend->version);
	backend_ctx = backend->init();

	/* Attach backend to device */
	backend->attach(backend_ctx, dev, endp_up, endp_down, jobid);

	if (!uri) {
		if (backend->cmdline_arg(backend_ctx, argc, argv) < 0)
			goto done_claimed;

		/* Grab the filename */
		fname = argv[optind]; // XXX do this a smarter way?
	}

	if (!fname) {
		if (uri)
			fprintf(stderr, "ERROR: No input file specified\n");
		goto done_claimed;
	}

	/* Open file if not STDIN */
	if (strcmp("-", fname)) {
		data_fd = open(fname, O_RDONLY);
		if (data_fd < 0) {
			perror("ERROR:Can't open input file");
			exit(1);
		}
	}

	/* Ensure we're using BLOCKING I/O */
	i = fcntl(data_fd, F_GETFL, 0);
	if (i < 0) {
		perror("ERROR:Can't open input");
		exit(1);
	}
	i &= ~O_NONBLOCK;
	i = fcntl(data_fd, F_SETFL, i);
	if (i < 0) {
		perror("ERROR:Can't open input");
		exit(1);
	}

	/* Ignore SIGPIPE */
	signal(SIGPIPE, SIG_IGN);
	signal(SIGTERM, sigterm_handler);

	/* Time for the main processing loop */
	INFO("Printing started (%d copies)\n", copies);

newpage:

	/* Read in data */
	if ((ret = backend->read_parse(backend_ctx, data_fd))) {
		if (current_page)
			goto done_multiple;
		else
			goto done_claimed;
	}

	INFO("Printing page %d\n", ++current_page);

	ret = backend->main_loop(backend_ctx, copies);
	if (ret)
		goto done_claimed;

	/* Log the completed page */
	if (!uri)
		PAGE("%d %d\n", current_page, copies);

	/* Since we have no way of telling if there's more data remaining
	   to be read (without actually trying to read it), always assume
	   multiple print jobs. */
	goto newpage;

done_multiple:
	close(data_fd);

	/* Done printing, log the total number of pages */
	if (!uri)
		PAGE("total %d\n", current_page * copies);
	ret = CUPS_BACKEND_OK;

done_claimed:
	libusb_release_interface(dev, iface);

done_close:
	libusb_close(dev);
done:

	if (backend && backend_ctx)
		backend->teardown(backend_ctx);

	if (list)
		libusb_free_device_list(list, 1);
	if (ctx)
		libusb_exit(ctx);

	return ret;
}

int lookup_printer_type(struct dyesub_backend *backend, uint16_t idVendor, uint16_t idProduct)
{
	int i;
	int type = -1;

	for (i = 0 ; backend->devices[i].vid ; i++) {
		if (extra_pid != -1 &&
		    extra_vid != -1 &&
		    extra_type != -1) {
			if (backend->devices[i].type == extra_type &&
			    extra_vid == idVendor &&
			    extra_pid == idProduct) {
				return extra_type;
			}
		}
		if (idVendor == backend->devices[i].vid &&
		    idProduct == backend->devices[i].pid) {
			return backend->devices[i].type;
		}
	}

	return type;
}

uint16_t uint16_to_packed_bcd(uint16_t val)
{
        uint16_t bcd;
        uint16_t i;

        /* Handle from 0-9999 */
        i = val % 10;
        bcd = i;
        val /= 10;
        i = val % 10;
        bcd |= (i << 4);
        val /= 10;
        i = val % 10;
        bcd |= (i << 8);
        val /= 10;
        i = val % 10;
        bcd |= (i << 12);

        return bcd;
}

uint32_t packed_bcd_to_uint32(char *in, int len)
{
	uint32_t out = 0;

	while (len--) {
		out *= 10;
		out += (*in >> 4);
		out *= 10;
		out += (*in & 0xf);
		in++;
	}
        return out;
}
