/*
 * Copyright (c) 2011 Vojtech Horky
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup usbvirthid
 * @{
 */
/**
 * @file
 * Virtual USB HID device.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <str_error.h>
#include <getopt.h>

#include <usb/usb.h>
#include <usb/descriptor.h>
#include <usb/debug.h>
#include <usb/classes/classes.h>
#include <usb/hid/hid.h>
#include <usbvirt/device.h>

#include "virthid.h"
#include "ifaces.h"
#include "stdreq.h"

#define DEFAULT_CONTROLLER   "/virt/usbhc/virtual"

static usbvirt_control_request_handler_t endpoint_zero_handlers[] = {
	{
		STD_REQ_IN(USB_REQUEST_RECIPIENT_INTERFACE, USB_DEVREQ_GET_DESCRIPTOR),
		.name = "Get_Descriptor",
		.callback = req_get_descriptor
	},
	{
		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_SET_PROTOCOL),
		.name = "Set_Protocol",
		.callback = req_set_protocol
	},
	{
		CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_INTERFACE, USB_HIDREQ_SET_REPORT),
		.name = "Set_Report",
		.callback = req_set_report
	},
	{
		.callback = NULL
	}
};

/** Keyboard callbacks.
 * We abuse the fact that static variables are zero-filled.
 */
static usbvirt_device_ops_t hid_ops = {
	.control = endpoint_zero_handlers,
};

/** Standard configuration descriptor. */
static usb_standard_configuration_descriptor_t std_configuration_descriptor = {
	.length = sizeof(usb_standard_configuration_descriptor_t),
	.descriptor_type = USB_DESCTYPE_CONFIGURATION,
	/* Will be patched at runtime. */
	.total_length = sizeof(usb_standard_configuration_descriptor_t),
	.interface_count = 0,
	.configuration_number = 1,
	.str_configuration = 0,
	.attributes = 128, /* denotes bus-powered device */
	.max_power = 50
};

/** Standard device descriptor. */
static usb_standard_device_descriptor_t std_device_descriptor = {
	.length = sizeof(usb_standard_device_descriptor_t),
	.descriptor_type = USB_DESCTYPE_DEVICE,
	.usb_spec_version = 0x110,
	.device_class = USB_CLASS_USE_INTERFACE,
	.device_subclass = 0,
	.device_protocol = 0,
	.max_packet_size = 64,
	.configuration_count = 1
};

/** HID configuration. */
usbvirt_device_configuration_t configuration = {
	.descriptor = &std_configuration_descriptor,
	.extra = NULL,
	.extra_count = 0
};

/** HID standard descriptors. */
usbvirt_descriptors_t descriptors = {
	.device = &std_device_descriptor,
	.configuration = &configuration,
	.configuration_count = 1,
};

static vuhid_data_t vuhid_data = {
	.in_endpoints_mapping = { NULL },
	.in_endpoint_first_free = 1,
	.out_endpoints_mapping = { NULL },
	.out_endpoint_first_free = 1,

	.iface_count = 0,
	.iface_died_count = 0
	// mutex and CV must be initialized elsewhere
};

/** Keyboard device.
 * Rest of the items will be initialized later.
 */
static usbvirt_device_t hid_dev = {
	.ops = &hid_ops,
	.descriptors = &descriptors,
	.name = "HID",
	.device_data = &vuhid_data
};

static struct option long_options[] = {
	{ "help", optional_argument, NULL, 'h' },
	{ "controller", required_argument, NULL, 'c' },
	{ "list", no_argument, NULL, 'l' },
	{ 0, 0, NULL, 0 }
};
static const char *short_options = "hc:l";

static void print_help(const char *name, const char *module)
{
	if (module == NULL) {
		/* Default help */
		printf("Usage: %s [options] device.\n", name);
		printf("\t-h, --help [device]\n");
		printf("\t\to With no argument print this help and exit.\n");
		printf("\t\to With argument print device specific help and exit.\n");
		printf("\t-l, --list \n\t\tPrint list of available devices.\n");
		printf("\t-c, --controller \n\t\t"
		    "Use provided virtual hc instead of default (%s)\n",
		    DEFAULT_CONTROLLER);
		return;
	}
	printf("HELP for module %s\n", module);
}

static void print_list(void)
{
	printf("Available devices:\n");
	for (vuhid_interface_t **i = available_hid_interfaces; *i != NULL; ++i) {
		printf("\t`%s'\t%s\n", (*i)->id, (*i)->name);
	}

}

static const char *controller = DEFAULT_CONTROLLER;

int main(int argc, char *argv[])
{

	if (argc == 1) {
		print_help(*argv, NULL);
		return 0;
	}

	int opt = 0;
	while ((opt = getopt_long(argc, argv, short_options, long_options, NULL)) > 0) {
		switch (opt) {
		case 'h':
			print_help(*argv, optarg);
			return 0;
		case 'c':
			controller = optarg;
			break;
		case 'l':
			print_list();
			return 0;
		case -1:
		default:
			break;
		}
	}

	log_init("vuhid");

	fibril_mutex_initialize(&vuhid_data.iface_count_mutex);
	fibril_condvar_initialize(&vuhid_data.iface_count_cv);

	/* Determine which interfaces to initialize. */
	for (int i = optind; i < argc; i++) {
		errno_t rc = add_interface_by_id(available_hid_interfaces, argv[i],
		    &hid_dev);
		if (rc != EOK) {
			fprintf(stderr, "Failed to add device `%s': %s.\n",
			    argv[i], str_error(rc));
		} else {
			printf("Added device `%s'.\n", argv[i]);
		}
	}

	for (int i = 0; i < (int) hid_dev.descriptors->configuration->extra_count; i++) {
		usb_log_debug("Found extra descriptor: %s.",
		    usb_debug_str_buffer(
		    hid_dev.descriptors->configuration->extra[i].data,
		    hid_dev.descriptors->configuration->extra[i].length,
		    0));
	}

	const errno_t rc = usbvirt_device_plug(&hid_dev, controller);
	if (rc != EOK) {
		printf("Unable to start communication with VHCD `%s': %s.\n",
		    controller, str_error(rc));
		return rc;
	}

	printf("Connected to VHCD `%s'...\n", controller);

	wait_for_interfaces_death(&hid_dev);

	printf("Terminating...\n");

	usbvirt_device_unplug(&hid_dev);

	return 0;
}

/** @}
 */
