#define HID_INTERNAL

#include <hid.h>
#include <hid_helpers.h>
#include <os.h>
#include <errno.h>
#include <constants.h>
#include <compiler.h>

#include <debug.h>
#include <assert.h>

/*!@brief Send a control message to retrieve an entire input report
 *
 * To use an interrupt endpoint instead of EP0, use hid_interrupt_read().
 *
 * @param[in] hidif Which interface to query
 * @param[in] path  Path to input item (to find Report ID)
 * @param[in] depth See hid_find_object()
 * @param[out] buffer Result is stored here
 * @param[in] size  How many bytes to fetch
 */
hid_return hid_get_input_report(HIDInterface* const hidif, int const path[],
    unsigned int const depth, char* const buffer, unsigned int const size)
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(buffer);

  if (!buffer) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("looking up report ID...");
  hidif->hid_data->Type = ITEM_INPUT;
  hidif->hid_data->ReportID = 0;

  hid_find_object(hidif, path, depth);

  TRACE("retrieving report ID 0x%02x (length: %d) from USB device %s...", 
        hidif->hid_data->ReportID, size, hidif->id);

  int len = usb_control_msg(hidif->dev_handle,
      USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_REPORT_GET,
      hidif->hid_data->ReportID + (HID_RT_INPUT << 8),
      hidif->interface,
      buffer, size, USB_TIMEOUT);

  if (len < 0) {
    WARNING("failed to retrieve report from USB device %s:%s.", hidif->id, usb_strerror());
    return HID_RET_FAIL_GET_REPORT;
  }

  if (len != (signed)size) {
    WARNING("failed to retrieve complete report from USB device %s; "
        "requested: %d bytes, got: %d bytes.", hidif->id, size, len);
    return HID_RET_FAIL_GET_REPORT;
  }

  NOTICE("successfully retrieved report from USB device %s.", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Send an entire output report to the device
 *
 * This routine uses a control message to send the report. To use an interrupt
 * endpoint, use hid_interrupt_write().
 *
 * @param[in] hidif Which interface to send to
 * @param[in] path  Path to an output item (to find Report ID)
 * @param[in] depth See hid_find_object()
 * @param[in] buffer Output Report
 * @param[in] size  How many bytes to send
 */
hid_return hid_set_output_report(HIDInterface* const hidif, int const path[],
    unsigned int const depth, char const* const buffer, unsigned int const size)
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(buffer);

  if (!buffer) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("looking up report ID...");
  hidif->hid_data->Type = ITEM_OUTPUT;
  hidif->hid_data->ReportID = 0;

  hid_find_object(hidif, path, depth);

  TRACE("sending report ID 0x%02x (length: %d) to USB device %s...", 
        hidif->hid_data->ReportID, size, hidif->id);

  int len = usb_control_msg(hidif->dev_handle,
      USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_REPORT_SET,
      hidif->hid_data->ReportID + (HID_RT_OUTPUT << 8),
      hidif->interface,
      (char*)buffer, size, USB_TIMEOUT);

  if (len < 0) {
    WARNING("failed to send report to USB device %s:%s.", hidif->id,usb_strerror());
    return HID_RET_FAIL_SET_REPORT;
  }

  if (len != (signed)size) {
    WARNING("failed to send complete report to USB device %s; "
        "requested: %d bytes, sent: %d bytes.", hidif->id, 
        size, len);
    return HID_RET_FAIL_SET_REPORT;
  }

  NOTICE("successfully sent report to USB device %s.", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Send a control message to retrieve an entire feature report
 *
 * To use an interrupt endpoint instead of EP0, use hid_interrupt_read().
 *
 * @param[in] hidif Which interface to query
 * @param[in] path  Path to input item (to find Report ID)
 * @param[in] depth See hid_find_object()
 * @param[out] buffer Result is stored here
 * @param[in] size  How many bytes to fetch
 */
hid_return hid_get_feature_report(HIDInterface* const hidif, int const path[],
    unsigned int const depth, char* const buffer, unsigned int const size)
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(buffer);

  if (!buffer) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("looking up report ID...");
  hidif->hid_data->Type = ITEM_FEATURE;
  hidif->hid_data->ReportID = 0;

  hid_find_object(hidif, path, depth);

  TRACE("retrieving report ID 0x%02x (length: %d) from USB device %s...", 
        hidif->hid_data->ReportID, size, hidif->id);

  int len = usb_control_msg(hidif->dev_handle,
      USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_REPORT_GET,
      hidif->hid_data->ReportID + (HID_RT_FEATURE << 8),
      hidif->interface,
      buffer, size, USB_TIMEOUT);

  if (len < 0) {
    WARNING("failed to retrieve report from USB device %s:%s.", hidif->id, usb_strerror());
    return HID_RET_FAIL_GET_REPORT;
  }

  if (len != (signed)size) {
    WARNING("failed to retrieve complete report from USB device %s; "
        "requested: %d bytes, got: %d bytes.", hidif->id, size, len);
    return HID_RET_FAIL_GET_REPORT;
  }

  NOTICE("successfully retrieved report from USB device %s.", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Send an entire feature report to the device
 *
 * This routine uses a control message to send the report. To use an interrupt
 * endpoint, use hid_interrupt_write().
 *
 * @param[in] hidif Which interface to send to
 * @param[in] path  Path to an output item (to find Report ID)
 * @param[in] depth See hid_find_object()
 * @param[in] buffer Output Report
 * @param[in] size  How many bytes to send
 */
hid_return hid_set_feature_report(HIDInterface* const hidif, int const path[],
    unsigned int const depth, char const* const buffer, unsigned int const size)
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(buffer);

  if (!buffer) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("looking up report ID...");
  hidif->hid_data->Type = ITEM_FEATURE;
  hidif->hid_data->ReportID = 0;

  hid_find_object(hidif, path, depth);

  TRACE("sending report ID 0x%02x (length: %d) to USB device %s...", 
        hidif->hid_data->ReportID, size, hidif->id);

  int len = usb_control_msg(hidif->dev_handle,
      USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_REPORT_SET,
      hidif->hid_data->ReportID + (HID_RT_FEATURE << 8),
      hidif->interface,
      (char*)buffer, size, USB_TIMEOUT);

  if (len < 0) {
    WARNING("failed to send report to USB device %s:%s.", hidif->id,usb_strerror());
    return HID_RET_FAIL_SET_REPORT;
  }

  if (len != (signed)size) {
    WARNING("failed to send complete report to USB device %s; "
        "requested: %d bytes, sent: %d bytes.", hidif->id, 
        size, len);
    return HID_RET_FAIL_SET_REPORT;
  }

  NOTICE("successfully sent report to USB device %s.", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Retrieve a numeric input item
 *
 * @param[in] hidif Which interface to send to
 * @param[in] path  Path to input item
 * @param[in] depth See hid_find_object()
 * @param[out] value Result from hid_extract_value()
 *
 * @todo Handle exponent and unit conversion (separate library?)
 */
hid_return hid_get_item_value(HIDInterface* const hidif, int const path[],
    unsigned int const depth, double *const value)
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));

  unsigned int size;
  unsigned char buffer[32]; /*! @todo Dynamically allocate the item buffer */

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("retrieving report from USB device %s...", hidif->id);
  hidif->hid_data->Type = ITEM_FEATURE;
  hidif->hid_data->ReportID = 0;

  /* TODO: i think this and the buffer stuff should be passed in */
  hid_find_object(hidif, path, depth);
  hid_get_report_size(hidif, hidif->hid_data->ReportID,
      hidif->hid_data->Type, &size);

  ASSERT(size <= 32); /* remove when buffer situation is fixed. */

  int len = usb_control_msg(hidif->dev_handle,
      USB_ENDPOINT_IN + USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_REPORT_GET,
      hidif->hid_data->ReportID + (HID_RT_FEATURE << 8),
      hidif->interface,
      (char*)buffer, size, USB_TIMEOUT);

  if (len < 0) {
    WARNING("failed to retrieve report from USB device %s:%s.", hidif->id,usb_strerror());
    return HID_RET_FAIL_GET_REPORT;
  }

  if ((unsigned)len != size) {
    WARNING("failed to retrieve complete report from USB device %s; "
        "requested: %d bytes, got: %d bytes.", hidif->id, 
        size, len);
    return HID_RET_FAIL_GET_REPORT;
  }

  if (hid_extract_value(hidif, buffer, value) != HID_RET_SUCCESS) {
    return HID_RET_FAIL_GET_REPORT;
  }

  NOTICE("successfully retrieved report from USB device %s.", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Currently unimplemented */
hid_return hid_get_item_string(HIDInterface* const hidif UNUSED,
    int const path[] UNUSED, unsigned int const depth UNUSED,
    char *const value UNUSED, unsigned int const maxlen UNUSED)
{
  bool const not_yet_implemented = false;
  ASSERT(not_yet_implemented);
  return HID_RET_SUCCESS;
}


/*!@brief Currently unimplemented */
hid_return hid_set_item_value(HIDInterface* const hidif UNUSED,
    int const path[] UNUSED, unsigned int const depth UNUSED,
    double const value UNUSED)
{
  bool const not_yet_implemented = false;
  ASSERT(not_yet_implemented);
  return HID_RET_SUCCESS;
}

/*!@brief Read from the interrupt endpoint
 *
 * @param[in]  hidif  Which interface to send to
 * @param[in]  ep     Which endpoint to read
 * @param[out] bytes  Buffer to store results
 * @param[in]  size   How many bytes to read
 * @param[in] timeout How long to wait, in milliseconds
 *
 * Upon successful completion, the @a bytes array will contain an input report
 * from the device. It is up to the programmer to determine which input
 * endpoint to read, and to ensure that the endpoint is bitwise-ORed with 0x80
 * (USB_ENDPOINT_IN).
 */
hid_return hid_interrupt_read(HIDInterface * const hidif,
     unsigned int const ep, char* const bytes, unsigned int const size, 
     unsigned int const timeout) 
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(bytes);

  if (!bytes) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("retrieving interrupt report from device %s ...", hidif->id);

  int len = usb_interrupt_read(hidif->dev_handle,
                               ep,
                               bytes,
                               size,
                               timeout);

  if (len == -ETIMEDOUT) {
    WARNING("timeout on interrupt read from device %s", hidif->id);
    return HID_RET_TIMEOUT;
  }

  if (len < 0) {
    WARNING("failed to get interrupt read from device %s: %s", hidif->id, usb_strerror());
    return HID_RET_FAIL_INT_READ;
  }

  if (len != (signed)size) {
    WARNING("failed to get all of interrupt report from device %s; "
      "requested: %d bytes, sent: %d bytes.", hidif->id,
      size, len);
    return HID_RET_FAIL_INT_READ;
  }

  NOTICE("successfully got interrupt report from device %s", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Write to the interrupt endpoint
 *
 * @param[in]  hidif  Which interface to send to
 * @param[in]  ep     Which endpoint to write
 * @param[out] bytes  Buffer to send
 * @param[in]  size   How many bytes to write
 * @param[in] timeout How long to wait, in milliseconds
 *
 * It is up to the programmer to determine which input endpoint to write.
 */
hid_return hid_interrupt_write(HIDInterface * const hidif,
     unsigned int const ep, const char* const bytes, unsigned int const size, 
     unsigned int const timeout) 
{
  ASSERT(hid_is_initialised());
  ASSERT(hid_is_opened(hidif));
  ASSERT(bytes);

  if (!bytes) return HID_RET_INVALID_PARAMETER;

  if (!hid_is_opened(hidif)) {
    WARNING("the device has not been opened.");
    return HID_RET_DEVICE_NOT_OPENED;
  }

  TRACE("writing interrupt report to device %s ...", hidif->id);

  int len = usb_interrupt_write(hidif->dev_handle,
                               ep,
                               (char *)bytes,
                               size,
                               timeout);

  if (len == -ETIMEDOUT) {
    WARNING("timeout on interrupt write to device %s", hidif->id);
    return HID_RET_TIMEOUT;
  }

  if (len < 0) {
    WARNING("failed to perform interrupt write to device %s: %s", hidif->id, usb_strerror());
    return HID_RET_FAIL_INT_READ;
  }

  if (len != (signed)size) {
    WARNING("failed to write all of interrupt report to device %s; "
      "requested: %d bytes, sent: %d bytes.", hidif->id,
      size, len);
    return HID_RET_FAIL_INT_READ;
  }

  NOTICE("successfully sent interrupt report to device %s", hidif->id);
  return HID_RET_SUCCESS;
}

/*!@brief Execute a Set_Idle request on an Interrupt In pipe
 *
 * This is used to tell a device not to send reports unless something has
 * changed (duration = 0), or unless a minimum time interval has passed.
 *
 * @param[in] hidif      Which interface to send to
 * @param[in] duration   0 for indefinite, otherwise in increments of 4 ms (to 1020 ms)
 * @param[in] report_id  0 for all reports, otherwise a Report ID
 */
hid_return hid_set_idle(HIDInterface * const hidif,
		unsigned duration, unsigned report_id) 
{
  if(duration > 255) {
    WARNING("duration must be in the range [0,255]");
    return HID_RET_INVALID_PARAMETER;
  }

  if(report_id > 255) {
    WARNING("Report ID must be in the range [0,255]");
    return HID_RET_INVALID_PARAMETER;
  }

  int len = usb_control_msg(hidif->dev_handle,
      USB_TYPE_CLASS + USB_RECIP_INTERFACE,
      HID_SET_IDLE,
      report_id + ((duration & 0xff) << 8),
      hidif->interface,
      NULL, 0, USB_TIMEOUT);

  if (len != 0) {
    WARNING("failed to Set_Idle for USB device %s:%s.", hidif->id, usb_strerror());
    return HID_RET_FAIL_GET_REPORT;
  }

  return HID_RET_SUCCESS;
}

/* COPYRIGHT --
 *
 * This file is part of libhid, a user-space HID access library.
 * libhid is (c) 2003-2005
 *   Martin F. Krafft <libhid@pobox.madduck.net>
 *   Charles Lepple <clepple@ghz.cc>
 *   Arnaud Quette <arnaud.quette@free.fr> && <arnaud.quette@mgeups.com>
 * and distributed under the terms of the GNU General Public License.
 * See the file ./COPYING in the source distribution for more information.
 *
 * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES
 * OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */


syntax highlighted by Code2HTML, v. 0.9.1