/*
 * Main API entry point
 *
 * Copyright (c) 2000-2003 Johannes Erdfelt <johannes@erdfelt.com>
 *
 * This library is covered by the LGPL, read LICENSE for details.
 */

#include <stdlib.h>	/* getenv */
#include <stdio.h>	/* stderr */
#include <errno.h>

#include "usbi.h"

int usb_debug = 0;
struct usb_bus *usb_busses = NULL;

int usb_find_busses(void)
{
  struct usb_bus *busses, *bus;
  int ret, changes = 0;

  ret = usb_os_find_busses(&busses);
  if (ret < 0)
    return ret;

  /*
   * Now walk through all of the busses we know about and compare against
   * this new list. Any duplicates will be removed from the new list.
   * If we don't find it in the new list, the bus was removed. Any
   * busses still in the new list, are new to us.
   */
  bus = usb_busses;
  while (bus) {
    int found = 0;
    struct usb_bus *nbus, *tbus = bus->next;

    nbus = busses;
    while (nbus) {
      struct usb_bus *tnbus = nbus->next;

      if (!strcmp(bus->dirname, nbus->dirname)) {
        /* Remove it from the new devices list */
        LIST_DEL(busses, nbus);

        usb_free_bus(nbus);
        found = 1;
        break;
      }

      nbus = tnbus;
    }

    if (!found) {
      /* The device was removed from the system */
      LIST_DEL(usb_busses, bus);
      changes++;
    }

    bus = tbus;
  }

  /* Anything on the *busses list is new. So add them to usb_busses */
  /*  and process them like the new bus they are */
  bus = busses;
  while (bus) {
    struct usb_bus *tbus = bus->next;

    /* Remove it from the temporary list first and add it to the real */
    /*  usb_busses list */
    LIST_DEL(busses, bus);

    LIST_ADD(usb_busses, bus);

    changes++;

    bus = tbus;
  }

  return changes;
}

int usb_find_devices(void)
{
  struct usb_bus *bus;
  int ret, changes = 0;

  for (bus = usb_busses; bus; bus = bus->next) {
    struct usb_device *devices, *dev;

    /* Find all of the devices and put them into a temporary list */
    ret = usb_os_find_devices(bus, &devices);
    if (ret < 0)
      return ret;

    /*
     * Now walk through all of the devices we know about and compare
     * against this new list. Any duplicates will be removed from the new
     * list. If we don't find it in the new list, the device was removed.
     * Any devices still in the new list, are new to us.
     */
    dev = bus->devices;
    while (dev) {
      int found = 0;
      struct usb_device *ndev, *tdev = dev->next;

      ndev = devices;
      while (ndev) {
        struct usb_device *tndev = ndev->next;

        if (!strcmp(dev->filename, ndev->filename)) {
          /* Remove it from the new devices list */
          LIST_DEL(devices, ndev);

          usb_free_dev(ndev);
          found = 1;
          break;
        }

        ndev = tndev;
      }

      if (!found) {
        /* The device was removed from the system */
        LIST_DEL(bus->devices, dev);
        changes++;
      }

      dev = tdev;
    }

    /* Anything on the *devices list is new. So add them to bus->devices */
    /*  and process them like the new device they are */
    dev = devices;
    while (dev) {
      struct usb_device *tdev = dev->next;

      /* Remove it from the temporary list first and add it to the real */
      /*  bus->devices list */
      LIST_DEL(devices, dev);

      LIST_ADD(bus->devices, dev);

      /* Some ports fetch the descriptors on scanning (like Linux) so we */
      /*  don't need to fetch them again */
      if (!dev->config) {
        usb_dev_handle *udev;

        udev = usb_open(dev);
        if (udev) {
          usb_fetch_and_parse_descriptors(udev);

          usb_close(udev);
        }
      }

      changes++;

      dev = tdev;
    }
  }

  return changes;
}

/* void usb_set_debug(int level) */
/* { */
/*   if (usb_debug || level) */
/*     fprintf(stderr, "usb_set_debug: Setting debugging level to %d (%s)\n", */
/* 	level, level ? "on" : "off"); */

/*   usb_debug = level; */
/* } */

void usb_init(void)
{
  if (getenv("USB_DEBUG"))
    usb_set_debug(atoi(getenv("USB_DEBUG")));

  usb_os_init();
}

usb_dev_handle *usb_open(struct usb_device *dev)
{
  usb_dev_handle *udev;

  udev = malloc(sizeof(*udev));
  if (!udev)
    return NULL;

  udev->fd = -1;
  udev->device = dev;
  udev->bus = dev->bus;
  udev->config = udev->interface = udev->altsetting = -1;

  if (usb_os_open(udev) < 0) {
    free(udev);
    return NULL;
  }

  return udev;
}

int usb_get_string(usb_dev_handle *dev, int index, int langid, char *buf,
	size_t buflen)
{
  /*
   * We can't use usb_get_descriptor() because it's lacking the index
   * parameter. This will be fixed in libusb 1.0
   */
  return usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
			(USB_DT_STRING << 8) + index, langid, buf, buflen, 1000);
}

int usb_get_string_simple(usb_dev_handle *dev, int index, char *buf, size_t buflen)
{
  char tbuf[255];
  int ret, langid, si, di;

  /*
   * Asking for the zero'th index is special - it returns a string
   * descriptor that contains all the language IDs supported by the
   * device. Typically there aren't many - often only one. The
   * language IDs are 16 bit numbers, and they start at the third byte
   * in the descriptor. See USB 2.0 specification, section 9.6.7, for
   * more information on this. */
  ret = usb_get_string(dev, 0, 0, tbuf, sizeof(tbuf));
  if (ret < 0)
    return ret;

  if (ret < 4)
    return -EIO;

  langid = tbuf[2] | (tbuf[3] << 8);

  ret = usb_get_string(dev, index, langid, tbuf, sizeof(tbuf));
  if (ret < 0)
    return ret;

  if (tbuf[1] != USB_DT_STRING)
    return -EIO;

  if (tbuf[0] > ret)
    return -EFBIG;

  for (di = 0, si = 2; si < tbuf[0]; si += 2) {
    if (di >= (buflen - 1))
      break;

    if (tbuf[si + 1])	/* high byte */
      buf[di++] = '?';
    else
      buf[di++] = tbuf[si];
  }

  buf[di] = 0;

  return di;
}

int usb_close(usb_dev_handle *dev)
{
  int ret;

  ret = usb_os_close(dev);
  free(dev);

  return ret;
}

struct usb_device *usb_device(usb_dev_handle *dev)
{
  return dev->device;
}

void usb_free_dev(struct usb_device *dev)
{
  usb_destroy_configuration(dev);
  free(dev);
}

struct usb_bus *usb_get_busses(void)
{
  return usb_busses;
}

void usb_free_bus(struct usb_bus *bus)
{
  free(bus);
}
