/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* vusb.c
 *
 * Copyright (c) 2015,2016 Marcus Meissner <marcus@jet.franken.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */
#define _DARWIN_C_SOURCE
#include "config.h"
#include <gphoto2/gphoto2-port-library.h>

#include "vcamera.h"

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <string.h>

#include <gphoto2/gphoto2-port.h>
#include <gphoto2/gphoto2-port-result.h>
#include <gphoto2/gphoto2-port-log.h>

#include "libgphoto2_port/i18n.h"


#define CHECK(result) {int r=(result); if (r<0) return (r);}

struct _GPPortPrivateLibrary {
	int	isopen;
	vcamera	*vcamera;
};

GPPortType
gp_port_library_type (void)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	return GP_PORT_USB;
}

int
gp_port_library_list (GPPortInfoList *list)
{
	GPPortInfo info;

	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");

	CHECK (gp_port_info_new (&info));
	gp_port_info_set_type (info, GP_PORT_USB);
	gp_port_info_set_name (info, "");
	gp_port_info_set_path (info, "^usb:");
	gp_port_info_list_append (list, info); /* do not check, might be -1 */

	CHECK (gp_port_info_new (&info));
	gp_port_info_set_type (info, GP_PORT_USB);
	gp_port_info_set_name (info, "");
	gp_port_info_set_path (info, "^vusb:");
	gp_port_info_list_append (list, info); /* do not check, might be -1 */

	gp_port_info_new (&info);
	gp_port_info_set_type (info, GP_PORT_USB);
	gp_port_info_set_name (info, "Universal Serial Bus");
	gp_port_info_set_path (info, "usb:001,001");
	CHECK (gp_port_info_list_append (list, info));

	gp_port_info_new (&info);
	gp_port_info_set_type (info, GP_PORT_USB_SCSI);
	gp_port_info_set_name (info, "");
	gp_port_info_set_path (info, "^usbscsi:");
	CHECK (gp_port_info_list_append (list, info));

	gp_port_info_new (&info);
	gp_port_info_set_type (info, GP_PORT_USB_DISK_DIRECT);
	gp_port_info_set_name (info, "");
	gp_port_info_set_path (info, "^usbdiskdirect:");
	CHECK (gp_port_info_list_append (list, info));
	return GP_OK;
}

static int gp_port_vusb_init (GPPort *dev)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	C_MEM (dev->pl = calloc (1, sizeof (GPPortPrivateLibrary)));

	dev->pl->vcamera = vcamera_new(NIKON_D750);
	dev->pl->vcamera->init(dev->pl->vcamera);

	return GP_OK;
}

static int
gp_port_vusb_exit (GPPort *port)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	port->pl->vcamera->exit(port->pl->vcamera);
	free (port->pl->vcamera);
	port->pl->vcamera = NULL;
	free (port->pl);
	port->pl = NULL;


	return GP_OK;
}

static int
gp_port_vusb_open (GPPort *port)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(%s)", port->settings.usb.port);
	if (port->pl->isopen)
		return GP_ERROR;
	port->pl->vcamera->open(port->pl->vcamera, port->settings.usb.port);
	port->pl->isopen = 1;
	return GP_OK;
}

static int
gp_port_vusb_close (GPPort *port)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	if (!port->pl->isopen)
		return GP_ERROR;
	port->pl->vcamera->close(port->pl->vcamera);
	port->pl->isopen = 0;
	return GP_OK;
}

static int
gp_port_vusb_write (GPPort *port, const char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(%d)", size);

	C_PARAMS (port && port->pl && port->pl->vcamera);
	return port->pl->vcamera->write(port->pl->vcamera, 0x02, (unsigned char*)bytes, size);
}

static int
gp_port_vusb_read(GPPort *port, char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(%d)", size);
	return port->pl->vcamera->read(port->pl->vcamera, 0x81, (unsigned char*)bytes, size);
}

static int
gp_port_vusb_send_scsi_cmd (GPPort *port, int to_dev, char *cmd,
	int cmd_size, char *sense, int sense_size, char *data, int data_size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(dev=%d, cmdsize %d, sense_size %d, data_size %d)", to_dev, cmd_size, sense_size, data_size);
	return port->pl->vcamera->read(port->pl->vcamera, 0x81, (unsigned char*)data, data_size);
}

static int
gp_port_vusb_seek (GPPort *port, int offset, int whence)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(offset %d, whence %d)", offset, whence);
	return GP_OK;
}

static int
gp_port_vusb_reset(GPPort *port)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	C_PARAMS (port && port->pl);

	return GP_OK;
}

static int
gp_port_vusb_check_int (GPPort *port, char *bytes, int size, int timeout)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(bytes=%d, timeout=%d)", size, timeout);
	C_PARAMS (port && port->pl && timeout >= 0);

	return port->pl->vcamera->readint(port->pl->vcamera, (unsigned char*)bytes, size, timeout);
}

static int
gp_port_vusb_update (GPPort *port)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	return GP_OK;
}

static int
gp_port_vusb_clear_halt_lib(GPPort *port, int ep)
{
	unsigned char internal_ep;

	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(ep=%d)", ep);
	C_PARAMS (port && port->pl);

	switch (ep) {
	case GP_PORT_USB_ENDPOINT_IN :
		internal_ep = port->settings.usb.inep;
		break;
	case GP_PORT_USB_ENDPOINT_OUT :
		internal_ep = port->settings.usb.outep;
		break;
	case GP_PORT_USB_ENDPOINT_INT :
		internal_ep = port->settings.usb.intep;
		break;
	default:
		gp_port_set_error (port, "Bad EndPoint argument 0x%x", ep);
		return GP_ERROR_BAD_PARAMETERS;
	}
	gp_log(GP_LOG_DEBUG,"gp_port_vusb_clear_halt_lib","clearing halt on ep 0x%x", internal_ep);
	/* now clear halt */
	return GP_OK;
}

/* The next two functions support the nonstandard request types 0x41 (write)
 * and 0xc1 (read), which are occasionally needed.
 */
static int
gp_port_vusb_msg_interface_write_lib(GPPort *port, int request,
	int value, int index, char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(request=%d, value=%d, index=%d, size=%d)", request, value, index, size);
	return GP_OK;
}


static int
gp_port_vusb_msg_interface_read_lib(GPPort *port, int request,
	int value, int index, char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(req=%x, value=%x, index=%d, size=%d)", request, value, index, size);
	return port->pl->vcamera->read(port->pl->vcamera, 0x81, (unsigned char*)bytes, size);
}


/* The next two functions support the nonstandard request types 0x21 (write)
 * and 0xa1 (read), which are occasionally needed.
 */
static int
gp_port_vusb_msg_class_write_lib(GPPort *port, int request,
	int value, int index, char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(request=%d, value=%d, index=%d, size=%d)", request, value, index, size);
	return GP_OK;
}



static int
gp_port_vusb_msg_class_read_lib(GPPort *port, int request,
	int value, int index, char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(request=%d, value=%d, index=%d, size=%d)", request, value, index, size);
	return GP_OK;	/* or bytes */
}


static int
gp_port_vusb_msg_write_lib(GPPort *port, int request, int value, int index,
	char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(request=%d, value=%d, index=%d, size=%d)", request, value, index, size);
	return GP_OK;
}

static int
gp_port_vusb_msg_read_lib(GPPort *port, int request, int value, int index,
	char *bytes, int size)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(req=%x, value=%x, index=%x, size=%d)", request, value, index, size);
	return port->pl->vcamera->read(port->pl->vcamera, index, (unsigned char*)bytes, size);
}

static int
gp_port_vusb_find_device_lib(GPPort *port, int idvendor, int idproduct)
{
#ifdef FUZZ_PTP
	if ((idvendor == 0x04b0) && (idproduct == 0x0437)) { /* Nikon D750 */
#else
	GPPortInfo info;
	char	*path, *s;
	static unsigned short vendor = 0x04b0, product = 0x0437; /* defaults are the Nikon D750 */
	int	fd;

	static char *lastpath = NULL;

	gp_port_get_info (port, &info);
	gp_port_info_get_path (info, &path);

	if (!lastpath || strcmp(path, lastpath)) {
		gp_log(GP_LOG_DEBUG,__FUNCTION__,"(path=%s)", path);
		if (lastpath) {
			free(lastpath);
		}
		lastpath = strdup(path);

		s = strchr(path, ':')+1;
		fd = open(s, O_RDONLY);
		/*vendor = product = 0; */
		if (fd != -1) {
			if (-1 == read( fd, &vendor, 2))
				gp_log(GP_LOG_DEBUG,__FUNCTION__,"could not read vendor");
			if (-1 == read( fd, &product, 2))
				gp_log(GP_LOG_DEBUG,__FUNCTION__,"could not read product");
			close(fd);
		}
	}
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(using vendor 0x%04x,0x%04x)", vendor, product);

	if ((idvendor == vendor) && (idproduct == product)) {
#endif
		port->settings.usb.config	= 1;
		port->settings.usb.interface	= 1;
		port->settings.usb.altsetting	= 1;

		port->settings.usb.inep  = 0x81;
		port->settings.usb.outep = 0x02;
		port->settings.usb.intep = 0x83;
		port->settings.usb.maxpacketsize = 512;
		return GP_OK;
	}
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(0x%04x,0x%04x)", idvendor, idproduct);
	return GP_ERROR_IO_USB_FIND;
}

static int
gp_port_vusb_find_device_by_class_lib(GPPort *port, int class, int subclass, int protocol)
{
	gp_log(GP_LOG_DEBUG,__FUNCTION__,"(0x%02x,0x%02x,0x%02x)", class, subclass, protocol);

#ifdef FUZZ_PTP
	if ((class == 6) && (subclass == 1) && (protocol == 1)) {
		port->settings.usb.config	= 1;
		port->settings.usb.interface	= 1;
		port->settings.usb.altsetting	= 1;

		port->settings.usb.inep  = 0x81;
		port->settings.usb.outep = 0x02;
		port->settings.usb.intep = 0x83;
		port->settings.usb.maxpacketsize = 512;
		return GP_OK;
	}
#endif
	return GP_ERROR_IO_USB_FIND;
}



GPPortOperations *
gp_port_library_operations (void)
{
	GPPortOperations *ops;

	gp_log(GP_LOG_DEBUG,__FUNCTION__,"()");
	ops = calloc (1, sizeof (GPPortOperations));
	if (!ops)
		return NULL;

	ops->init	= gp_port_vusb_init;
	ops->exit	= gp_port_vusb_exit;
	ops->open	= gp_port_vusb_open;
	ops->close	= gp_port_vusb_close;
	ops->read	= gp_port_vusb_read;
	ops->write	= gp_port_vusb_write;
	ops->reset	= gp_port_vusb_reset;

	ops->check_int 	= gp_port_vusb_check_int;
	ops->update 	= gp_port_vusb_update;
	ops->clear_halt = gp_port_vusb_clear_halt_lib;
	ops->msg_write  = gp_port_vusb_msg_write_lib;
	ops->msg_read   = gp_port_vusb_msg_read_lib;

	ops->msg_interface_write	= gp_port_vusb_msg_interface_write_lib;
	ops->msg_interface_read		= gp_port_vusb_msg_interface_read_lib;
	ops->msg_class_write  		= gp_port_vusb_msg_class_write_lib;
	ops->msg_class_read   		= gp_port_vusb_msg_class_read_lib;
	ops->send_scsi_cmd   		= gp_port_vusb_send_scsi_cmd;
	ops->seek   			= gp_port_vusb_seek;

	ops->find_device 		= gp_port_vusb_find_device_lib;
	ops->find_device_by_class	= gp_port_vusb_find_device_by_class_lib;
	return ops;
}
