//
// Copyright 2010 Ettus Research LLC
//
// 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 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see .
//
#include "libusb1_base.hpp"
#include 
#include 
#include 
#include 
#include 
#include 
using namespace uhd::transport;
const int libusb_debug_level = 3;
const int libusb_timeout = 0;
/***********************************************************************
 * Helper functions
 *
 * Print to stdout the values of a libusb_transfer struct
 ***********************************************************************/
void pp_transfer(libusb_transfer *lut)
{
    std::cout << "Libusb transfer"       << std::endl;
    std::cout << "    flags:         0x" << std::hex << (unsigned int) lut->flags << std::endl;
    std::cout << "    endpoint:      0x" << std::hex << (unsigned int) lut->endpoint << std::endl;
    std::cout << "    type:          0x" << std::hex << (unsigned int) lut->type << std::endl;
    std::cout << "    timeout:       "   << std::dec << lut->timeout << std::endl;
    std::cout << "    status:        0x" << std::hex << lut->status << std::endl;
    std::cout << "    length:        "   << std::dec << lut->length << std::endl;
    std::cout << "    actual_length: "   << std::dec << lut->actual_length << std::endl;
}
/***********************************************************************
 * USB asynchronous phony zero_copy endpoint
 *   This endpoint implementation provides asynchronous I/O to libusb-1.0
 *   devices. Each endpoint is directional and two can be combined to
 *   create a bidirectional interface. It is a zero copy implementation
 *   with respect to libusb, however, each send and recv requires a copy
 *   operation from kernel to userspace; this is due to the usbfs
 *   interface provided by the kernel. 
 **********************************************************************/
class usb_endpoint {
private:
    libusb_device_handle *_dev_handle;
    libusb_context *_ctx;
    int  _endpoint;
    bool _input;
    size_t _transfer_size;
    size_t _num_transfers;
    /*
     * Transfer state lists (free, pending, or completed)
     */
    std::list  _free_list;
    std::list  _pending_list;
    std::list  _completed_list;
    /*
     * Calls for processing asynchronous I/O 
     */
    libusb_transfer *allocate_transfer(int buff_len);
    bool cancel(libusb_transfer *lut);
    bool cancel_all();
    bool reap_pending_list();
    bool reap_pending_list_timeout();
    bool reap_completed_list();
    /*
     * Transfer state manipulators 
     */
    void free_list_add(libusb_transfer *lut);
    void pending_list_add(libusb_transfer *lut);
    void completed_list_add(libusb_transfer *lut);
    libusb_transfer *free_list_get();
    libusb_transfer *completed_list_get();
    bool pending_list_remove(libusb_transfer *lut);
    /*
     * Misc
     */
    void print_transfer_status(libusb_transfer *lut);
public:
    usb_endpoint(libusb_device_handle *dev_handle,
                 libusb_context *ctx, int endpoint, bool input,
                 size_t transfer_size, size_t num_transfers);
    ~usb_endpoint();
    /*
     * Accessors 
     */
    int get_endpoint() const { return _endpoint; }
    bool get_direction() const { return _input; }
    libusb_device_handle *get_dev_handle() const { return _dev_handle; }
    libusb_context *get_ctx() const { return _ctx; }
    /*
     * Exposed interface for submitting / retrieving transfer buffers
     * used in zero-copy interface
     */
    bool submit(libusb_transfer *lut);
    libusb_transfer *get_completed_transfer();
    libusb_transfer *get_free_transfer();
    /*
     * Callback use only
     */
    void callback_handle_transfer(libusb_transfer *lut);
};
/*
 * Callback function called when submitted transfers complete.
 * The endpoint upon which the transfer is part of is recovered
 * and the transfer moved from pending to completed state.
 */
static void callback(libusb_transfer *lut)
{
    usb_endpoint *endpoint = (usb_endpoint *) lut->user_data; 
    endpoint->callback_handle_transfer(lut);
}
/*
 * Accessor call to allow list access from callback space
 */
void usb_endpoint::callback_handle_transfer(libusb_transfer *lut)
{
    if (!pending_list_remove(lut)) {
        std::cerr << "USB: pending remove failed" << std::endl;
        return;
    }
    completed_list_add(lut);    
}
/*
 * Constructor
 *
 * Allocate libusb transfers. For IN endpoints, submit the transfers
 * so that they're ready to return when data is available. 
 */
usb_endpoint::usb_endpoint(libusb_device_handle *dev_handle,
                          libusb_context *ctx, int endpoint, bool input,
                          size_t transfer_size, size_t num_transfers)
    : _dev_handle(dev_handle),
      _ctx(ctx), _endpoint(endpoint), _input(input),
      _transfer_size(transfer_size), _num_transfers(num_transfers)
{
    unsigned int i;
    for (i = 0; i < _num_transfers; i++) {
        free_list_add(allocate_transfer(_transfer_size));
        if (_input)
            submit(free_list_get());
    }
}
/*
 * Destructor
 */
usb_endpoint::~usb_endpoint()
{
    cancel_all();
    while (!_pending_list.empty()) {
        if (!reap_pending_list())
            std::cerr << "error: destructor failed to reap" << std::endl;
    }
    while (!_completed_list.empty()) {
        if (!reap_completed_list())
            std::cerr << "error: destructor failed to reap" << std::endl;
    }
    while (!_free_list.empty()) {
        libusb_free_transfer(free_list_get());
    }
}
/*
 * Allocate a libusb transfer 
 *
 * The allocated transfer is continuously reused and should be freed at
 * shutdown.
 */
libusb_transfer *usb_endpoint::allocate_transfer(int buff_len)
{
    libusb_transfer *lut = libusb_alloc_transfer(0);
    unsigned char *buff = new unsigned char[buff_len];
    unsigned int endpoint = ((_endpoint & 0x7f) | (_input ? 0x80 : 0));
    libusb_fill_bulk_transfer(lut,                // transfer
                              _dev_handle,        // dev_handle
                              endpoint,           // endpoint
                              buff,               // buffer
                              buff_len,           // length
                              callback,           // callback
                              this,               // user_data
                              0);                 // timeout
    return lut;
}
/*
 * Asynchonous transfer submission
 *
 * Submit and mark transfer as pending.
 */
bool usb_endpoint::submit(libusb_transfer *lut)
{
    int retval;
    if ((retval = libusb_submit_transfer(lut)) < 0) {
        std::cerr << "error: libusb_submit_transfer: " << retval << std::endl;
        return false;
    }
    pending_list_add(lut);
    return true;
}
/*
 * Cancel a pending transfer 
 *
 * Search the pending list for the transfer and cancel if found.
 * Returns true on success. False otherwise or on error. 
 *
 * Note: success only indicates submission of cancelation request.
 * Sucessful cancelation is not known until the callback occurs.
 */
bool usb_endpoint::cancel(libusb_transfer *lut)
{
    std::list::iterator iter;
    for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
        if (*iter == lut) { 
            libusb_cancel_transfer(lut); 
            return true;
        }
    }
    return false;
}
/*
 * Cancel all pending transfers 
 *
 * Note: success only indicates submission of cancelation request.
 * Sucessful cancelation is not known until the callback occurs.
 */
bool usb_endpoint::cancel_all()
{
    std::list::iterator iter;
    for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
        if (libusb_cancel_transfer(*iter) < 0) {
            std::cerr << "error: libusb_cancal_transfer() failed" << std::endl;
            return false;
        }
    }
    return true;
}
/*
 * Reap completed transfers
 * 
 * return true if at least one transfer was reaped, false otherwise. 
 *
 * Check completed transfers for errors and mark as free. This is a
 * blocking call. 
 */
bool usb_endpoint::reap_completed_list()
{
    libusb_transfer *lut;
    if (_completed_list.empty()) {
        if (!reap_pending_list_timeout())
            return false;
    }
    while (!_completed_list.empty()) {
        lut = completed_list_get();
        print_transfer_status(lut);
        free_list_add(lut);
    }
    return true;
}
/*
 * Print completed transfer status error(s) 
 * 
 * return true if at least one transfer was reaped, false otherwise. 
 *
 * Check completed transfers for errors and mark as free. This is a
 * blocking call. 
 */
void usb_endpoint::print_transfer_status(libusb_transfer *lut)
{
    switch (lut->status) {
    case LIBUSB_TRANSFER_COMPLETED:
        if (lut->actual_length < lut->length) {
            std::cerr << "USB: transfer completed with short write,"
                      << " length = " << lut->length
                      << " actual = " << lut->actual_length << std::endl;
        }
        if ((lut->actual_length < 0) || (lut->length < 0)) {
            std::cerr << "USB: transfer completed with invalid response"
                      << std::endl;
        }
        break;
    case LIBUSB_TRANSFER_CANCELLED:
        break;
    case LIBUSB_TRANSFER_NO_DEVICE:
        std::cerr << "USB: device was disconnected" << std::endl;
        break;
    case LIBUSB_TRANSFER_OVERFLOW:
        std::cerr << "USB: device sent more data than requested" << std::endl;
        break;
    case LIBUSB_TRANSFER_TIMED_OUT:
        std::cerr << "USB: transfer timed out" << std::endl;
        break;
    case LIBUSB_TRANSFER_STALL:
        std::cerr << "USB: halt condition detected (stalled)" << std::endl;
        break;
    case LIBUSB_TRANSFER_ERROR:
        std::cerr << "USB: transfer failed" << std::endl;
        break;
    default:
        std::cerr << "USB: received unknown transfer status" << std::endl;
    }
}
/*
 * Reap pending transfers 
 *
 * Return true if at least one transfer was reaped, false otherwise. This is
 * a blocking call.
 *
 * Reaping submitted transfers is handled by libusb and the assigned callback
 * function. Block until at least one transfer is reaped.
 */
bool usb_endpoint::reap_pending_list()
{
    int retval;
    if ((retval = libusb_handle_events(_ctx)) < 0) {
        std::cerr << "error: libusb_handle_events: " << retval << std::endl;
        return false;
    }
    return true;
}
/*
 * Reap pending transfers with timeout 
 *
 * Return true if at least one transfer was reaped, false otherwise. This call
 * blocks until a transfer is reaped or timeout.
 *
 * Reaping submitted transfers is handled by libusb and the assigned callback
 * function. Block until at least one transfer is reaped or timeout occurs.
 */
bool usb_endpoint::reap_pending_list_timeout()
{
    int retval;
    timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 100000; //100ms
    size_t pending_list_size = _pending_list.size();
    if ((retval = libusb_handle_events_timeout(_ctx, &tv)) < 0) {
        std::cerr << "error: libusb_handle_events: " << retval << std::endl;
        return false;
    }
    if (_pending_list.size() < pending_list_size) {
        return true;
    }
    else {
        return false;
    }
}
/*
 * Returns a free transfer with empty data bufer for OUT requests 
 */
libusb_transfer *usb_endpoint::get_free_transfer()
{
    if (_free_list.empty()) {
        if (!reap_completed_list())
            return NULL; 
    }
    return free_list_get();
}
/*
 * Returns a transfer containing data for IN requests
 */
libusb_transfer *usb_endpoint::get_completed_transfer()
{
    if (_completed_list.empty()) {
        if (!reap_pending_list_timeout())
            return NULL; 
    }
    return completed_list_get();
}
/*
 * List operations 
 */
void usb_endpoint::free_list_add(libusb_transfer *lut)
{
    _free_list.push_back(lut);
}
void usb_endpoint::pending_list_add(libusb_transfer *lut)
{
    _pending_list.push_back(lut);
}
void usb_endpoint::completed_list_add(libusb_transfer *lut)
{
    _completed_list.push_back(lut);
}
/*
 * Free and completed lists don't have ordered content 
 *
 * Pop transfers from the front as needed
 */
libusb_transfer *usb_endpoint::free_list_get()
{
    libusb_transfer *lut;
    if (_free_list.size() == 0) {
        return NULL; 
    }
    else { 
        lut = _free_list.front();
        _free_list.pop_front();
        return lut;
    }
}
/*
 * Free and completed lists don't have ordered content 
 *
 * Pop transfers from the front as needed
 */
libusb_transfer *usb_endpoint::completed_list_get()
{
    libusb_transfer *lut;
    if (_completed_list.empty()) {
        return NULL;
    }
    else { 
        lut = _completed_list.front();
        _completed_list.pop_front();
        return lut;
    }
}
/*
 * Search and remove transfer from pending list
 *
 * Assuming that the callbacks occur in order, the front element
 * should yield the correct transfer. If not, then something else
 * is going on. If no transfers match, then something went wrong.
 */
bool usb_endpoint::pending_list_remove(libusb_transfer *lut)
{
    std::list::iterator iter;
    for (iter = _pending_list.begin(); iter != _pending_list.end(); iter++) {
        if (*iter == lut) { 
            _pending_list.erase(iter);
            return true;
        }
    }
    return false;
}
/***********************************************************************
 * Managed buffers 
 **********************************************************************/
class libusb_managed_recv_buffer_impl : public managed_recv_buffer {
public:
    libusb_managed_recv_buffer_impl(libusb_transfer *lut,
                                    usb_endpoint *endpoint)
        : _buff(lut->buffer, lut->length)
    {
        _lut = lut;
        _endpoint = endpoint;
    }
    ~libusb_managed_recv_buffer_impl()
    {
       if (!_endpoint->submit(_lut))
           std::cerr << "USB: failed to submit IN transfer" << std::endl;
    }
private:
    const boost::asio::const_buffer &get() const
    {
        return _buff; 
    }
    libusb_transfer *_lut;
    usb_endpoint *_endpoint;
    const boost::asio::const_buffer _buff;
};
class libusb_managed_send_buffer_impl : public managed_send_buffer {
public:
    libusb_managed_send_buffer_impl(libusb_transfer *lut,
                                    usb_endpoint *endpoint,
                                    size_t buff_size)
        : _buff(lut->buffer, buff_size)
    {
        _lut = lut;
        _endpoint = endpoint;
    }
    ~libusb_managed_send_buffer_impl()
    {
        /* NOP */
    }
    ssize_t commit(size_t num_bytes)
    {
        _lut->length = num_bytes;
        _lut->actual_length = 0;
        if (_endpoint->submit(_lut))
            return num_bytes;
        else
            return 0;
    }
private:
    const boost::asio::mutable_buffer &get() const
    {
        return _buff; 
    }
    libusb_transfer *_lut;
    usb_endpoint *_endpoint;
    const boost::asio::mutable_buffer _buff;
};
/***********************************************************************
 * USB zero_copy device class
 **********************************************************************/
class libusb_zero_copy_impl : public usb_zero_copy
{
private:
    usb_endpoint          *_rx_ep;
    usb_endpoint          *_tx_ep;
    /*
     * Libusb handles
     */
    libusb_context       *_rx_ctx;
    libusb_context       *_tx_ctx;
    libusb_device_handle *_rx_dev_handle;
    libusb_device_handle *_tx_dev_handle;
    size_t _recv_buff_size;
    size_t _send_buff_size;
    size_t _num_frames;
public:
    typedef boost::shared_ptr sptr;
    /*
     * Structors
     */
    libusb_zero_copy_impl(usb_device_handle::sptr handle,
                          unsigned int rx_endpoint,
                          unsigned int tx_endpoint,
                          size_t recv_buff_size,
                          size_t send_buff_size);
    
    ~libusb_zero_copy_impl();
    managed_recv_buffer::sptr get_recv_buff(void);
    managed_send_buffer::sptr get_send_buff(void);
    size_t get_num_recv_frames(void) const { return _num_frames; }
    size_t get_num_send_frames(void) const { return _num_frames; }
};
libusb_zero_copy_impl::libusb_zero_copy_impl(usb_device_handle::sptr handle,
                                             unsigned int rx_endpoint,
                                             unsigned int tx_endpoint,
                                             size_t buff_size,
                                             size_t block_size)
 : _rx_ctx(NULL), _tx_ctx(NULL), _rx_dev_handle(NULL), _tx_dev_handle(NULL),
   _recv_buff_size(block_size), _send_buff_size(block_size),
   _num_frames(buff_size / block_size)
{
    libusb::init(&_rx_ctx, libusb_debug_level);
    libusb::init(&_tx_ctx, libusb_debug_level);
    UHD_ASSERT_THROW((_rx_ctx != NULL) && (_tx_ctx != NULL));
    _rx_dev_handle = libusb::open_device(_rx_ctx, handle);
    _tx_dev_handle = libusb::open_device(_tx_ctx, handle);
    libusb::open_interface(_rx_dev_handle, 2);
    libusb::open_interface(_tx_dev_handle, 1);
    _rx_ep = new usb_endpoint(_rx_dev_handle,
                              _rx_ctx,
                              rx_endpoint,
                              true,
                              _recv_buff_size,
                              _num_frames); 
    _tx_ep = new usb_endpoint(_tx_dev_handle,
                              _tx_ctx,
                              tx_endpoint,
                              false,
                              _send_buff_size,
                              _num_frames);
}
libusb_zero_copy_impl::~libusb_zero_copy_impl()
{
    delete _rx_ep;
    delete _tx_ep; 
    libusb_close(_rx_dev_handle);
    libusb_close(_tx_dev_handle);
    libusb_exit(_rx_ctx);
    libusb_exit(_tx_ctx);
}
managed_recv_buffer::sptr libusb_zero_copy_impl::get_recv_buff()
{
    libusb_transfer *lut = _rx_ep->get_completed_transfer();
    if (lut == NULL) {
        return managed_recv_buffer::sptr();
    }
    else {
        return managed_recv_buffer::sptr(
            new libusb_managed_recv_buffer_impl(lut,
                                                _rx_ep));
    }
}
managed_send_buffer::sptr libusb_zero_copy_impl::get_send_buff()
{
    libusb_transfer *lut = _tx_ep->get_free_transfer();
    if (lut == NULL) {
        return managed_send_buffer::sptr();
    }
    else {
        return managed_send_buffer::sptr(
            new libusb_managed_send_buffer_impl(lut,
                                                _tx_ep,
                                                _send_buff_size));
    }
}
/***********************************************************************
 * USB zero_copy make functions
 **********************************************************************/
usb_zero_copy::sptr usb_zero_copy::make(usb_device_handle::sptr handle,
                                        unsigned int rx_endpoint,
                                        unsigned int tx_endpoint,
                                        size_t buff_size,
                                        size_t block_size)
{
    return sptr(new libusb_zero_copy_impl(handle,
                                          rx_endpoint,
                                          tx_endpoint,
                                          buff_size, 
                                          block_size));
}