/***************************************************************************
 * Copyright (C) 2005 Lorenz Mösenlechner & Matthias Kranz                 *
 *                    <icd2linux@hcilab.org>                               *
 * Copyright (C) 2003-2005 Orion Sky Lawlor <olawlor@acm.org>              *
 * Copyright (C) 2005-2007 Nicolas Hadacek <hadacek@kde.org>               *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/
#include "usb_port.h"

#ifdef HAVE_USB
# include <usb.h>
#endif
#include <tqdatetime.h>

#include "common/common/version_data.h"
#include "common/common/number.h"

//-----------------------------------------------------------------------------
class Port::USB::Private
{
public:
#ifdef HAVE_USB
  Private() : _interface(0) {}
  const usb_interface_descriptor *_interface;
#endif
};

bool Port::USB::_initialized = false;

void Port::USB::initialize()
{
  if (_initialized) return;
  _initialized = true;
#ifdef HAVE_USB
  usb_init();
  VersionData vd = VersionData::fromString(LIBUSB_VERSION);
  TQString s = TQString("libusb %1").arg(vd.pretty());
  if ( vd<VersionData(0, 1, 8) ) tqWarning("%s: may be too old (you need at least version 0.1.8)", s.latin1());
#endif
}

bool Port::USB::isAvailable()
{
#ifdef HAVE_USB
  return true;
#else
  return false;
#endif
}

usb_bus *Port::USB::getBusses()
{
  initialize();
#ifdef HAVE_USB
  // refresh libusb structures
  usb_find_busses();
  usb_find_devices();
  return usb_get_busses();
#else
  return 0;
#endif
}

bool Port::USB::findBulk(const struct usb_device *dev)
{
  initialize();
#ifdef HAVE_USB
  int configuration = -1, interface = -1, altsetting = -1, bulk_endpoint = -1;
  // walk through the possible configs, etc.
  tqDebug("This device has %d possible configuration(s).", dev->descriptor.bNumConfigurations);
  for (int c=0; c<dev->descriptor.bNumConfigurations; c++) {
    tqDebug("Looking at configuration %d...This configuration has %d interfaces.", c, dev->config[c].bNumInterfaces);
    for(int i=0; i<dev->config[c].bNumInterfaces; i++) {
      tqDebug("  Looking at interface %d...This interface has %d altsettings.", i, dev->config[c].interface[i].num_altsetting);
      for (int a=0; a < dev->config[c].interface[i].num_altsetting; a++) {
        tqDebug("    Looking at altsetting %d...This altsetting has %d endpoints.", a, dev->config[c].interface[i].altsetting[a].bNumEndpoints);
        for (int e=0; e < dev->config[c].interface[i].altsetting[a].bNumEndpoints; e++) {
          TQString s;
          s.sprintf("      Endpoint %d: Address %02xh, attributes %02xh ", e, dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress, dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes);
          uchar attribs = (dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes & 3);
          switch (attribs) {
          case 0: s += "(Control) "; break;
          case 1: s += "(Isochronous) "; break;
          case 2: s += "(Bulk) ";
            /* Found the correct configuration, interface etc... it has bulk endpoints! */
            configuration=c;
            interface=i;
            altsetting=a;
            break;
          case 3: s += "(Interrupt) "; break;
          default: s += "ERROR! Got an illegal value in endpoint bmAttributes";
          }
          if ( attribs!=0 ) {
            uchar dir = (dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress & 0x80);
            switch (dir) {
            case 0x00: s += "(Out)";
              // Do nothing in this case
              break;
            case 0x80: s += "(In)";
              if ((dev->config[c].interface[i].altsetting[a].endpoint[e].bmAttributes & 0x03) == 2) /* Make sure it's a *bulk* endpoint */ {
                // Found the correct endpoint to use for bulk transfer; use its ADDRESS
                bulk_endpoint=dev->config[c].interface[i].altsetting[a].endpoint[e].bEndpointAddress;
              }
              break;
            default: s += "ERROR! Got an illegal value in endpoint bEndpointAddress";
            }
          }
          tqDebug("%s", s.latin1());
        }
      }
    }
  }
  if (bulk_endpoint<0) {
    tqDebug("No valid interface found!");
    return false;
  }
#endif
  return true;
}

TQStringList Port::USB::probedDeviceList()
{
  initialize();
  TQStringList list;
#ifdef HAVE_USB
  usb_init(); // needed ?
  for (usb_bus *bus=getBusses(); bus; bus=bus->next) {
    for (struct usb_device *dev=bus->devices; dev; dev=dev->next) {
      if ( dev->descriptor.idVendor==0 ) continue; // controller
      list.append(TQString("Vendor Id: %1 - Product Id: %2")
                  .arg(toLabel(NumberBase::Hex, dev->descriptor.idVendor, 4)).arg(toLabel(NumberBase::Hex, dev->descriptor.idProduct, 4)));
    }
  }
#endif
  return list;
}

struct usb_device *Port::USB::findDevice(uint vendorId, uint productId)
{
  initialize();
#ifdef HAVE_USB
  for (usb_bus *bus=getBusses(); bus; bus=bus->next) {
    for (struct usb_device *dev=bus->devices; dev; dev=dev->next) {
      if ( dev->descriptor.idVendor==vendorId && dev->descriptor.idProduct==productId )
        return dev;
    }
  }
#else
  Q_UNUSED(vendorId); Q_UNUSED(productId);
  tqDebug("USB support disabled");
#endif
  return 0;
}

//-----------------------------------------------------------------------------
const char * const Port::USB::ENDPOINT_MODE_NAMES[Nb_EndpointModes] = {
  "bulk", "interrupt", "control", "isochronous"
};

Port::USB::USB(Log::Base &base, uint vendorId, uint productId, uint config, uint interface)
  : Base(base), _vendorId(vendorId), _productId(productId),
    _config(config), _interface(interface), _handle(0), _device(0)
{
  Q_ASSERT( config>=1 );
  _private = new Private;
  initialize();
}

Port::USB::~USB()
{
  close();
  delete _private;
}

void Port::USB::setSystemError(const TQString &message)
{
#ifdef HAVE_USB
  log(Log::LineType::Error, message + TQString(" (err=%1).").arg(usb_strerror()));
#else
  log(Log::LineType::Error, message);
#endif
}

void Port::USB::tryToDetachDriver()
{
  // try to detach an already existing driver... (linux only)
#if defined(LIBUSB_HAS_GET_DRIVER_NP) && LIBUSB_HAS_GET_DRIVER_NP
  log(Log::DebugLevel::Extra, "find if there is already an installed driver");
  char dname[256] = "";
  if ( usb_get_driver_np(_handle, _interface, dname, 255)<0 ) return;
  log(Log::DebugLevel::Normal, TQString("  a driver \"%1\" is already installed...").arg(dname));
#  if defined(LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP) && LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
  usb_detach_kernel_driver_np(_handle, _interface);
  log(Log::DebugLevel::Normal, "  try to detach it...");
  if ( usb_get_driver_np(_handle, _interface, dname, 255)<0 ) return;
  log(Log::DebugLevel::Normal, "  failed to detach it");
#  endif
#endif
}

bool Port::USB::internalOpen()
{
#ifdef HAVE_USB
  _device = findDevice(_vendorId, _productId);
  if ( _device==0 ) {
    log(Log::LineType::Error, i18n("Could not find USB device (vendor=%1 product=%2).")
             .arg(toLabel(NumberBase::Hex, _vendorId, 4)).arg(toLabel(NumberBase::Hex, _productId, 4)));
    return false;
  }
  log(Log::DebugLevel::Extra, TQString("found USB device as \"%1\" on bus \"%2\"").arg(_device->filename).arg(_device->bus->dirname));
  _handle = usb_open(_device);
  if ( _handle==0 ) {
    setSystemError(i18n("Error opening USB device."));
    return false;
  }
// windows: usb_reset takes about 7-10 seconds to re-enumerate the device...
// BSD : not implemented in libusb...
#  if !defined(Q_OS_BSD4) && !defined(Q_OS_WIN)
  if ( usb_reset(_handle)<0 ) {
    setSystemError(i18n("Error resetting USB device."));
    return false;
  }
#  endif
  usb_close(_handle);
  _handle = usb_open(_device);
  if ( _handle==0 ) {
    setSystemError(i18n("Error opening USB device."));
    return false;
  }
  tryToDetachDriver();
  uint i = 0;
  for (; i<_device->descriptor.bNumConfigurations; i++)
    if ( _config==_device->config[i].bConfigurationValue ) break;
  if ( i==_device->descriptor.bNumConfigurations ) {
    uint old = _config;
    i = 0;
    _config = _device->config[i].bConfigurationValue;
    log(Log::LineType::Warning, i18n("Configuration %1 not present: using %2").arg(old).arg(_config));
  }
  const usb_config_descriptor &configd = _device->config[i];
  if ( usb_set_configuration(_handle, _config)<0 ) {
    setSystemError(i18n("Error setting USB configuration %1.").arg(_config));
    return false;
  }
  for (i=0; i<configd.bNumInterfaces; i++)
    if ( _interface==configd.interface[i].altsetting[0].bInterfaceNumber ) break;
  if ( i==configd.bNumInterfaces ) {
    uint old = _interface;
    i = 0;
    _interface = configd.interface[i].altsetting[0].bInterfaceNumber;
    log(Log::LineType::Warning, i18n("Interface %1 not present: using %2").arg(old).arg(_interface));
  }
  _private->_interface = &(configd.interface[i].altsetting[0]);
  if ( usb_claim_interface(_handle, _interface)<0 ) {
    setSystemError(i18n("Could not claim USB interface %1").arg(_interface));
    return false;
  }
  log(Log::DebugLevel::Max, TQString("alternate setting is %1").arg(_private->_interface->bAlternateSetting));
  log(Log::DebugLevel::Max, TQString("USB bcdDevice: %1").arg(toHexLabel(_device->descriptor.bcdDevice, 4)));
  return true;
#else
  log(Log::LineType::Error, i18n("USB support disabled"));
  return false;
#endif
}

void Port::USB::internalClose()
{
  if ( _handle==0 ) return;
#ifdef HAVE_USB
  usb_release_interface(_handle, _interface);
  usb_close(_handle);
  _private->_interface = 0;
#endif
  _device = 0;
  _handle = 0;
}

bool Port::USB::sendControlMessage(const ControlMessageData &data)
{
  if ( hasError() ) return false;
#ifdef HAVE_USB
  TQString s = data.bytes;
  uint length = strlen(data.bytes) / 2;
  TQByteArray ba(length);
  for (uint i=0; i<length; i++)
    ba[i] = fromString(NumberBase::Hex, s.mid(2*i, 2), 0);
  int res = usb_control_msg(_handle, data.type, data.request, data.value, 0, ba.data(), length, 1000); // 1s
  if ( res<0 ) {
    setSystemError(i18n("Error sending control message to USB port."));
    return false;
  }
#endif
  return true;
}

uint timeout(uint size)
{
  return qMax(size*5, uint(1000)); // 5ms per byte or 1s
}


Port::USB::EndpointMode Port::USB::endpointMode(uint ep) const
{
#ifdef HAVE_USB
  uint index = ep & USB_ENDPOINT_ADDRESS_MASK;
  Q_ASSERT(_private->_interface);
  const usb_endpoint_descriptor *ued = _private->_interface->endpoint + index;
  Q_ASSERT(ued);
  switch (ued->bmAttributes & USB_ENDPOINT_TYPE_MASK) {
    case USB_ENDPOINT_TYPE_BULK: return Bulk;
    case USB_ENDPOINT_TYPE_INTERRUPT: return Interrupt;
    case USB_ENDPOINT_TYPE_ISOCHRONOUS: return Isochronous;
    case USB_ENDPOINT_TYPE_CONTROL: return Control;
    default: break;
  }
#endif
  Q_ASSERT(false);
  return Nb_EndpointModes;
}

Port::IODir Port::USB::endpointDir(uint ep) const
{
#ifdef HAVE_USB
  switch (ep & USB_ENDPOINT_DIR_MASK) {
    case USB_ENDPOINT_IN: return In;
    case USB_ENDPOINT_OUT: return Out;
    default: break;
  }
#endif
  Q_ASSERT(false);
  return NoIO;
}

bool Port::USB::write(uint ep, const char *data, uint size)
{
  if ( hasError() ) return false;
#ifdef HAVE_USB
  IODir dir = endpointDir(ep);
  EndpointMode mode = endpointMode(ep);
  log(Log::DebugLevel::LowLevel, TQString("write to endpoint %1 (%2 - %3) %4 chars: \"%5\"")
                          .arg(toHexLabel(ep, 2)).arg(ENDPOINT_MODE_NAMES[mode]).arg(IO_DIR_NAMES[dir]).arg(size).arg(toPrintable(data, size, PrintEscapeAll)));
  Q_ASSERT( dir==Out );
  TQTime time;
  time.start();
  int todo = size;
  for (;;) {
    int res = 0;
    //tqDebug("write ep=%i todo=%i/%i", ep, todo, size);
    if ( mode==Interrupt ) res = usb_interrupt_write(_handle, ep, (char *)data + size - todo, todo, timeout(todo));
    else res = usb_bulk_write(_handle, ep, (char *)data + size - todo, todo, timeout(todo));
    //tqDebug("res: %i", res);
    if ( res==todo ) break;
    if ( uint(time.elapsed())>3000 ) { // 3 s
      if ( res<0 ) setSystemError(i18n("Error sending data (ep=%1 res=%2)").arg(toHexLabel(ep, 2)).arg(res));
      else log(Log::LineType::Error, i18n("Timeout: only some data sent (%1/%2 bytes).").arg(size-todo).arg(size));
      return false;
    }
    if ( res==0 ) log(Log::DebugLevel::Normal, i18n("Nothing sent: retrying..."));
    if ( res>0 ) todo -= res;
    msleep(100);
  }
#else
  Q_UNUSED(ep); Q_UNUSED(data); Q_UNUSED(size);
#endif
  return true;
}

bool Port::USB::read(uint ep, char *data, uint size, bool *poll)
{
  if ( hasError() ) return false;
#ifdef HAVE_USB
  IODir dir = endpointDir(ep);
  EndpointMode mode = endpointMode(ep);
  log(Log::DebugLevel::LowLevel, TQString("read from endpoint %1 (%2 - %3) %4 chars")
                          .arg(toHexLabel(ep, 2)).arg(ENDPOINT_MODE_NAMES[mode]).arg(IO_DIR_NAMES[dir]).arg(size));
  Q_ASSERT( dir==In );
  TQTime time;
  time.start();
  int todo = size;
  for (;;) {
    int res = 0;
    //tqDebug("read ep=%i size=%i", ep, todo);
    if ( mode==Interrupt ) res = usb_interrupt_read(_handle, ep, data + size - todo, todo, timeout(todo));
    else res = usb_bulk_read(_handle, ep, data + size - todo, todo, timeout(todo));
    //tqDebug("res: %i", res);
    if ( res==todo ) break;
    if ( uint(time.elapsed())>3000 ) { // 3 s: seems to help icd2 in some case (?)
      if ( res<0 ) setSystemError(i18n("Error receiving data (ep=%1 res=%2)").arg(toHexLabel(ep, 2)).arg(res));
      else log(Log::LineType::Error, i18n("Timeout: only some data received (%1/%2 bytes).").arg(size-todo).arg(size));
      return false;
    }
    if ( res==0 ) {
      if (poll) {
        *poll = false;
        return true;
      } else log(Log::DebugLevel::Normal, i18n("Nothing received: retrying..."));
    }
    if ( res>0 ) todo -= res;
    msleep(100);
  }
  if (poll) *poll = true;
#else
  Q_UNUSED(ep); Q_UNUSED(data); Q_UNUSED(size);
#endif
  return true;
}