//---------------------------------------------------------------------------
// Copyright (C) 2004 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//---------------------------------------------------------------------------
//
//  libusblses.c - Acquire and release a Session on the 1-Wire Net using the
//                 USB interface DS2490.  (Requires libusb
//                 http://libusb.sourceforge.net or
//                 http://libusb-win32.sourceforge.net)
//
//
//  Version: 3.00
//
//  History:
//

#include "ownet.h"
#include "libusbds2490.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>

// local functions
static void DS2490Discover(void);

// handles to USB ports
libusb_device_handle *usb_dev_handle_list[MAX_PORTNUM];

// struct usb_dev_handle *usb_dev_handle_list[MAX_PORTNUM];
struct libusb_device *usb_dev_list[MAX_PORTNUM];
int usb_num_devices = -1;

static SMALLINT usbhnd_init = 0;

//---------------------------------------------------------------------------
// Attempt to acquire a 1-Wire net using a USB port and a DS2490 based
// adapter.
//
// 'portnum'    - number 0 to MAX_PORTNUM-1.  This number is provided to
//                indicate the symbolic port number.
// 'port_zstr'  - zero terminated port name.  For this platform
//                use format 'DS2490-X' where X is the port number.
//
// Returns: TRUE - success, USB port opened
//
SMALLINT owAcquire_(int portnum, char *port_zstr) {
  int retportnum = 0;
  retportnum = owAcquireEx(port_zstr);
  if ((retportnum < 0) || (retportnum != portnum)) {
    // Failure from owAcquireEx or from not matching port numbers.
    return FALSE;
  }
  return TRUE;
}

//---------------------------------------------------------------------------
// Attempt to acquire the specified 1-Wire net.
//
// 'port_zstr'  - zero terminated port name.  For this platform
//                use format 'DS2490-X' where X is the port number.
//
// Returns: port number or -1 if not successful in setting up the port.
//
int owAcquireEx_(char *port_zstr) {
  int portnum = -1;

  if (strncasecmp(port_zstr, "DS2490", 6) == 0 ||  strncmp(port_zstr, "USB", 3) == 0) {
    char *q0 = strdup(port_zstr);
    char *q = q0;
    strsep(&q, ":-");
    if (q != NULL && *q != 0) {
      portnum = strtol(q, NULL, 10);
    } else {
      portnum = 1;
    }
    free(q0);
  }

  if (portnum < 1) {
    OWERROR(OWERROR_PORTNUM_ERROR);
    return -1;
  }

  if (!usbhnd_init) { // check to see the USB bus has been setup properly
    // discover all DS2490s on USB bus...
    DS2490Discover();
    usbhnd_init = 1;
  }

  // check to see if the portnumber is valid
  if ((usb_num_devices < portnum) || (portnum == 0)) {
    // error - Attempted to select invalid port number
    OWERROR(OWERROR_LIBUSB_NO_ADAPTER_FOUND);
    return -1;
  }

  // check to see if opening the device is valid
  if (usb_dev_handle_list[portnum] != NULL) {
    // error - USB Device already open
    OWERROR(OWERROR_LIBUSB_DEVICE_ALREADY_OPENED);
    return -1;
  }

  // open the device
  int err = libusb_open(usb_dev_list[portnum], &usb_dev_handle_list[portnum]);
  if (err < 0) {
    // Failed to open usb device
    OWERROR(OWERROR_LIBUSB_OPEN_FAILED);
    return -1;
  }

  libusb_set_auto_detach_kernel_driver(usb_dev_handle_list[portnum], 1);

  //    if (libusb_has_capability(LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER) >= 0 ) {
  //      err = libusb_detach_kernel_driver(usb_dev_handle_list[portnum], 0);
  //}

  // set the configuration
  if (libusb_set_configuration(usb_dev_handle_list[portnum], 1)) {
    // Failed to set configuration
    OWERROR(OWERROR_LIBUSB_SET_CONFIGURATION_ERROR);
    libusb_close(usb_dev_handle_list[portnum]); // close handle
    return -1;
  }

  // claim the interface
  if (libusb_claim_interface(usb_dev_handle_list[portnum], 0)) {
    // Failed to claim interface
    OWERROR(OWERROR_LIBUSB_CLAIM_INTERFACE_ERROR);
    libusb_close(usb_dev_handle_list[portnum]); // close handle
    return -1;
  }

  // set the alt interface
  int rc;
  if ((rc = libusb_set_interface_alt_setting(usb_dev_handle_list[portnum], 0, 3))) {
    // Failed to set altinterface
    OWERROR(OWERROR_LIBUSB_SET_ALTINTERFACE_ERROR);
    libusb_release_interface(usb_dev_handle_list[portnum], 0); // release interface
    libusb_close(usb_dev_handle_list[portnum]);                // close handle
    return -1;
  }

  // clear USB endpoints before doing anything with them.
  libusb_clear_halt(usb_dev_handle_list[portnum], DS2490_EP3);
  libusb_clear_halt(usb_dev_handle_list[portnum], DS2490_EP2);
  libusb_clear_halt(usb_dev_handle_list[portnum], DS2490_EP1);

  // verify adapter is working
  if (!AdapterRecover(portnum)) {
    libusb_release_interface(usb_dev_handle_list[portnum], 0); // release interface
    libusb_close(usb_dev_handle_list[portnum]);                // close handle
    return -1;
  }

  // reset the 1-Wire
  owTouchReset(portnum);
  return portnum;
}

//---------------------------------------------------------------------------
// Release the port previously acquired a 1-Wire net.
//
// 'portnum'    - number 0 to MAX_PORTNUM-1.  This number is provided to
//                indicate the symbolic port number.
//
void owRelease_(int portnum) {
  libusb_release_interface(usb_dev_handle_list[portnum], 0);
  libusb_close(usb_dev_handle_list[portnum]);
  usb_dev_handle_list[portnum] = NULL;
}

static libusb_device **dev_list = NULL;
static libusb_context *ctx;

void __attribute__((destructor)) w1usb_tear_down(void) {
  for (int i = 0; i < usb_num_devices; i++) {
    if (usb_dev_handle_list[i] != NULL) {
      libusb_release_interface(usb_dev_handle_list[i], 0);
      libusb_close(usb_dev_handle_list[i]);
      usb_dev_handle_list[i] = NULL;
    }
  }
  libusb_free_device_list(dev_list, 0);
  libusb_exit(ctx);
}

void owShowVersion_(void) {
  const struct libusb_version *v = libusb_get_version();
  fprintf(stderr, "libusb: %d.%d.%d.%d\n", v->major, v->minor, v->micro, v->nano);
}

//---------------------------------------------------------------------------
// Discover all DS2490s on bus
//
void DS2490Discover(void) {
  // initialize USB subsystem
  libusb_init(&ctx);
  usb_num_devices = 0;
  int n = libusb_get_device_list(ctx, &dev_list);
  for (int i = 0; i < n; i++) {
    libusb_device *dev = dev_list[i];
    struct libusb_device_descriptor desc;
    int err;
    err = libusb_get_device_descriptor(dev, &desc);
    if (err >= 0) {
      if (desc.idVendor == 0x04FA && desc.idProduct == 0x2490) {
        usb_dev_list[++usb_num_devices] = dev;
      }
    }
  }
}
