#include "config.h"

//#ifdef LTRD_USB_ENABLED
#if 1

#include "libusb.h"
#include "ltrsrv_log.h"
#include "ltrsrv_crates.h"
#include "ltrsrv_wait.h"
#include "ltrsrv_usb_p.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <poll.h>


typedef struct {
    libusb_device_handle *handle; /**< Описатель открытого USB-устройства */
    uint8_t ep_wr;
    uint8_t ep_rd;
} t_usb_libusb_data;



static int f_usb_init = 0;

static void f_ctl_cb(struct libusb_transfer *transfer);
static void f_cb_rd(struct libusb_transfer *transfer);
static void f_cb_wr(struct libusb_transfer *transfer);
static int f_start_ioctl(t_usb_data* usb_data, uint8_t cmd, uint32_t param,
                         uint16_t reply_len, uint16_t data_len, const uint8_t* tx_data);
static int f_start_bulk(t_usb_data* usb_data, t_bulk_transf_ctl* bulk_ctl, unsigned timeout);
static void f_check_devlist(void);


static int f_open(t_usb_data *intf);
static int  f_start_ioctl(t_usb_data* intf, uint8_t cmd, uint32_t param,
                          uint16_t reply_len, uint16_t data_len,
                          const uint8_t* tx_data);
static int f_start_bulk(t_usb_data* intf, t_bulk_transf_ctl* bulk_ctl, unsigned timeout);
static void f_transf_cancel(t_usb_data *intf, t_transf_params *transf);
static void f_cleanup(t_usb_data *intf);

static int f_devid_cmp(const void *dev1, const void *dev2);
static void f_devid_free(void *devid);

static void f_check_devlist(void);

static const t_usb_port_intf f_port_intf = {    
    f_open,
    f_start_ioctl,
    f_start_bulk,
    f_transf_cancel,
    f_cleanup,
    fopen,

    f_devid_cmp,
    f_devid_free
};




#ifndef HAVE_LIBUSB_ERROR_NAME
    /* В верии libusb-1.0.8 функции libusb_error_name нет => реализуем ее вручную
     * в таком случае */
    const char* libusb_error_name(int error_code) {
        switch (error_code) {
            case LIBUSB_SUCCESS:
                return "OK";
            case LIBUSB_ERROR_IO:
                return "LIBUSB_ERROR_IO";
            case LIBUSB_ERROR_INVALID_PARAM:
                return "LIBUSB_ERROR_INVALID_PARAM";
            case LIBUSB_ERROR_ACCESS:
                return "LIBUSB_ERROR_ACCESS";
            case LIBUSB_ERROR_NO_DEVICE:
                return "LIBUSB_ERROR_NO_DEVICE";
            case LIBUSB_ERROR_NOT_FOUND:
                return "LIBUSB_ERROR_NOT_FOUND";
            case LIBUSB_ERROR_BUSY:
                return "LIBUSB_ERROR_BUSY";
            case LIBUSB_ERROR_TIMEOUT:
                return "LIBUSB_ERROR_TIMEOUT";
            case LIBUSB_ERROR_INTERRUPTED:
                return "LIBUSB_ERROR_INTERRUPTED";
            case LIBUSB_ERROR_OVERFLOW:
                return "LIBUSB_ERROR_OVERFLOW";
            case LIBUSB_ERROR_PIPE:
                return "LIBUSB_ERROR_PIPE";
            case LIBUSB_ERROR_NO_MEM:
                return "LIBUSB_ERROR_NO_MEM";
            case LIBUSB_ERROR_NOT_SUPPORTED:
                return "LIBUSB_ERROR_NOT_SUPPORTED";
            case LIBUSB_ERROR_OTHER:
                return "LIBUSB_ERROR_OTHER";

            default:
                return "LIBUSB_UNKNOWN_ERROR";
        }
    }
#endif

/* Так как не во всех версиях libusb libusb_error_name() подходит
   и для перевода статусов в строку, то это делаем вручную */
const char* libusb_status_name(int status) {
    switch (status) {
        case LIBUSB_TRANSFER_COMPLETED:
            return "TRANSFER_COMPLETED";
        case LIBUSB_TRANSFER_ERROR:
            return "TRANSFER_ERROR";
        case LIBUSB_TRANSFER_TIMED_OUT:
            return "TRANSFER_TIMED_OUT";
        case LIBUSB_TRANSFER_CANCELLED:
            return "TRANSFER_CANCELLED";
        case LIBUSB_TRANSFER_STALL:
            return "TRANSFER_STALL";
        case LIBUSB_TRANSFER_NO_DEVICE:
            return "TRANSFER_NO_DEVICE";
        case LIBUSB_TRANSFER_OVERFLOW:
            return "TRANSFER_OVERFLOW";
        default:
            return "UNKNOWN_STATUS";
    }
}


static int f_devid_cmp(const void *dev1, const void *dev2) {
    return dev1 == dev2;
}

static void f_devid_free(void *devid) {
    libusb_unref_device((libusb_device*)devid);
}


static void f_transf_cancel(t_usb_data *intf, t_transf_params *transf) {
    libusb_cancel_transfer((struct libusb_transfer*)transf->port_transfer);
}

/* Функция полной очистки памяти с информацией о соединении по USB.
 * Выполняется по таймеру после завершения последнего запроса по USB
 * при запросе на закрытие устройства. Нельзя выполнять в callback-функции
 * о завершении прерывания, т.к. это может вызывать зависания на
 * libusb_close() */
static void f_clean_cb(t_socket sock, int event, void* data) {
    unsigned i;
    t_usb_data* intf = (t_usb_data*)data;
    t_usb_libusb_data *privdata = (t_usb_libusb_data *)intf->privdata;

    /* очищаем используемую память */
   if (intf->ctl.port_transfer!=NULL) {
       libusb_free_transfer((struct libusb_transfer*)intf->ctl.port_transfer);
       intf->ctl.port_transfer = NULL;
   }

   for (i=0; i < LTRSRV_USB_BULK_MAX_TRANSF_CNT; i++) {
       if (intf->bulk_rd.transfs[i].port_transfer!=NULL) {
           libusb_free_transfer((struct libusb_transfer*)intf->bulk_rd.transfs[i].port_transfer);
           intf->bulk_rd.transfs[i].port_transfer = NULL;
       }

       if (intf->bulk_wr.transfs[i].port_transfer!=NULL) {
           libusb_free_transfer((struct libusb_transfer*)intf->bulk_wr.transfs[i].port_transfer);
           intf->bulk_wr.transfs[i].port_transfer = NULL;
       }
   }

   if ((privdata!=NULL) && (privdata->handle!=NULL)) {
       libusb_release_interface(privdata->handle,0);
       libusb_close(privdata->handle);
       privdata->handle = NULL;
   }

   p_usb_cleanup_finish(intf);
}




/* Проверка, все ли трансферы остановлены, и если это так - то
 * полная очистка памяти и закрытие хендла устройства.
 * Для неостановленных трансферов вызывается отмена трансфера, и
 * данная функция должна вызываеться из callback'а на завершение
 * трансфера, чтобы полная очистка произошла только когда все трансферы
 * выполнятся или будут отменены */
static void f_cleanup(t_usb_data *intf) {
    ltrsrv_wait_add_tmr(intf, LTRSRV_WAITFLG_AUTODEL, 10, f_clean_cb, intf);
}



static int f_open(t_usb_data *intf) {
    libusb_device_handle* handle;
    libusb_device *device = (libusb_device *)intf->devid;
    int err = LTRSRV_ERR_SUCCESS;
    int usb_err;

    sprintf(intf->crate->par.location, "addr: %d",
            libusb_get_device_address((libusb_device *)intf->devid));

    usb_err = libusb_open(device, &handle);
    if (!usb_err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_DBG_HIGH, LTRSRV_LOGSRC_SERVER, 0,
                       "open usb device: device %p, handle %p", device, handle);
        t_usb_libusb_data *privdata = (t_usb_libusb_data*)calloc
                (1, sizeof(t_usb_libusb_data));
        if (privdata) {
            struct libusb_config_descriptor* cfg;

            privdata->handle = handle;
            libusb_claim_interface(handle,0);

            /* Чтобы определить номера используемых конечных точек, получаем
             * конфигурацию устройства и находим по одной конечной точки типа
             * bulk на каждое направление (in/out) */
            usb_err = libusb_get_config_descriptor(device, 0, &cfg);
            if (!usb_err) {
                int intf_idx, s, fnd_in=0, fnd_out=0;
                for (intf_idx = 0; intf_idx < cfg->bNumInterfaces; intf_idx++) {
                    for (s=0; s < cfg->interface[intf_idx].num_altsetting; s++) {
                        int e;
                        for (e=0; e < cfg->interface[intf_idx].altsetting[s].bNumEndpoints; e++) {
                            const struct libusb_endpoint_descriptor *ep_descr =
                                    &cfg->interface[intf_idx].altsetting[s].endpoint[e];
                            if ((ep_descr->bmAttributes & 0x3)==
                                    LIBUSB_TRANSFER_TYPE_BULK ) {
                                if ((ep_descr->bEndpointAddress & 0x80)
                                        == LIBUSB_ENDPOINT_IN) {
                                    if (!fnd_in) {
                                         privdata->ep_rd= ep_descr->bEndpointAddress;
                                         fnd_in = 1;
                                    }
                                } else {
                                    if (!fnd_out) {
                                         privdata->ep_wr = ep_descr->bEndpointAddress;
                                         fnd_out = 1;
                                    }
                                }
                            }
                        }
                    }
                }
                libusb_free_config_descriptor(cfg);
                if (!fnd_in || !fnd_out)
                    err = LTRSRV_ERR_USB_INVALID_CONFIGURATION;
            } else {
                err = LTRSRV_ERR_USB_INVALID_CONFIGURATION;
            }

            intf->privdata = privdata;
        } else {
            err = LTRSRV_ERR_MEMALLOC;
        }
    } else if (usb_err == LIBUSB_ERROR_ACCESS) {
        err = LTRSRV_ERR_CRATE_INSUF_PERMISSON;
    } else {
        err = LTRSRV_ERR_USB_OPEN_DEVICE;
    }
    return err;
}


static int f_get_transf_err(struct libusb_transfer *transfer) {
    if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
        return LTRSRV_ERR_USB_TRANSF_TOUT;
    if (transfer->status != LIBUSB_TRANSFER_COMPLETED)
        return LTRSRV_ERR_USB_TRANSFER_FAILED;
    return 0;
}

/* если обмен завершился не по очереди, то находим соответствующий элемент в поставленных
 * на передачу запросах и завершаем его через p_usb_process_bulk_out_of_order */
static void f_proc_out_of_order_transf(t_bulk_transf_ctl *ctl, struct libusb_transfer *transfer) {
    int pos = ctl->get_pos;
    int out = 0;
    t_usb_data* intf = (t_usb_data*)transfer->user_data;

    while (!out) {
        t_transf_params *transf_par = &intf->bulk_rd.transfs[pos];
        if (transf_par->state != USB_TRANSF_STATE_FREE) {
            if (transf_par->port_transfer == transfer) {
                int err = f_get_transf_err(transfer);
                int syserr = (err == LTRSRV_ERR_USB_TRANSFER_FAILED) ? LTRSRV_LAST_SYSERR : 0;
                p_usb_process_bulk_out_of_order(intf, transf_par, err, syserr);
                out = 1;
            } else {
                if (++pos==LTRSRV_USB_BULK_MAX_TRANSF_CNT)
                    pos = 0;
                if (pos == ctl->put_pos)
                    out = 1;
            }
        }
    }
}


/* Callback на завершения приема данных */
static void f_cb_rd(struct libusb_transfer *transfer) {
    t_usb_data* intf = (t_usb_data*)transfer->user_data;

    if (intf->bulk_rd.transfs[intf->bulk_rd.get_pos].port_transfer != transfer) {
        f_proc_out_of_order_transf(&intf->bulk_rd, transfer);
    } else {
        int err = f_get_transf_err(transfer);
        int syserr = (err == LTRSRV_ERR_USB_TRANSFER_FAILED) ? LTRSRV_LAST_SYSERR : 0;
        p_usb_process_bulk_rd(intf, err, syserr, transfer->buffer, (unsigned)transfer->actual_length);
    }
}



/* Callback на завершения прередачи данных данных */
static void f_cb_wr(struct libusb_transfer *transfer) {
    t_usb_data* intf = (t_usb_data*)transfer->user_data;

    if (intf->bulk_wr.transfs[intf->bulk_wr.get_pos].port_transfer != transfer) {
        f_proc_out_of_order_transf(&intf->bulk_wr, transfer);
    } else {
        int err = f_get_transf_err(transfer);
        int syserr = (err == LTRSRV_ERR_USB_TRANSFER_FAILED) ? LTRSRV_LAST_SYSERR : 0;
        p_usb_process_bulk_wr(intf, err, syserr, transfer->buffer, (unsigned)transfer->actual_length);
    }
}


static void f_ctl_cb(struct libusb_transfer *transfer) {
    t_usb_data* intf = (t_usb_data*)transfer->user_data;    
    uint8_t* trans_data = libusb_control_transfer_get_data(transfer);

    p_usb_process_ioctl(intf, f_get_transf_err(transfer), trans_data, transfer->actual_length);
}


static void f_update_usb_devs_cb(t_socket sock, int event, void* data) {
    if (event==LTRSRV_EVENT_TOUT)
        f_check_devlist();
}


static int f_start_bulk(t_usb_data* intf, t_bulk_transf_ctl* bulk_ctl, unsigned timeout) {
    int err = bulk_ctl->transfs[bulk_ctl->put_pos].state == USB_TRANSF_STATE_FREE ?
               0 : LTRSRV_ERR_USB_TRANSF_BUSY;
    t_transf_params* trans;
    struct libusb_transfer* transfer;

    if (!err) {
        trans = &bulk_ctl->transfs[bulk_ctl->put_pos];
        transfer = (struct libusb_transfer*)trans->port_transfer;

        if ((transfer==NULL) || (transfer->timeout!=timeout)) {
            if (transfer)
                libusb_free_transfer(transfer);

            trans->port_transfer = transfer = libusb_alloc_transfer(0);
            if (transfer==NULL)
                err = LTRSRV_ERR_MEMALLOC;
        }
    }

    if (!err && (trans->buf==NULL)) {
        trans->buf = (uint8_t*)malloc(LTRSRV_USB_BULK_TRANSF_SIZE);
        if (trans->buf==NULL) {
            err = LTRSRV_ERR_MEMALLOC;
        } else {
            trans->len = LTRSRV_USB_BULK_TRANSF_SIZE;
        }
    }

    if (!err) {
        t_usb_libusb_data *privdata = (t_usb_libusb_data*)intf->privdata;
        libusb_fill_bulk_transfer(transfer, privdata->handle,
                                  bulk_ctl->dir == USB_BULK_DIR_READ ? privdata->ep_rd : privdata->ep_wr,
                                  trans->buf, trans->len,
                                  bulk_ctl->dir == USB_BULK_DIR_READ ? f_cb_rd : f_cb_wr,
                                  intf, timeout);
        err = libusb_submit_transfer(transfer);
        if (err) {

            ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, intf->crate->log_str, LTRSRV_ERR_USB_START_TRANSFER,
                           "libusb submit transfer err: %s", libusb_error_name(err));
            err = LTRSRV_ERR_USB_START_TRANSFER;
        }

        if (!err) {
            if (++bulk_ctl->put_pos==LTRSRV_USB_BULK_MAX_TRANSF_CNT)
                bulk_ctl->put_pos = 0;
            trans->state = USB_TRANSF_STATE_PROGRESS;
        }
    }
    return err;
}




/* Запуск управляющего запроса. По завершению выполняется функция usb_data->ctl_cb */
static int f_start_ioctl(t_usb_data* intf, uint8_t cmd, uint32_t param,
                         uint16_t reply_len, uint16_t data_len, const uint8_t* tx_data) {
    uint8_t req_type = LIBUSB_REQUEST_TYPE_VENDOR;
    uint16_t len;
    int err = 0;
    if (reply_len) {
        req_type |= LIBUSB_ENDPOINT_IN;
        len = reply_len;
    } else {
        req_type |= LIBUSB_ENDPOINT_OUT;
        len = data_len;
    }

    /* если выделенный буфер меньше заданного размера - выделяем новый */
    if (intf->ctl.len < len) {
        free(intf->ctl.buf);
        intf->ctl.buf = (uint8_t*)malloc(LIBUSB_CONTROL_SETUP_SIZE+len);
        if (intf->ctl.buf!=NULL) {
            intf->ctl.len = len;
        } else {
            intf->ctl.len = 0;
            err = LTRSRV_ERR_MEMALLOC;
        }
    }

    /* если не выделен трансфер - то выделяем */
    if (!intf->ctl.port_transfer) {
        intf->ctl.port_transfer = libusb_alloc_transfer(0);
        if (intf->ctl.port_transfer==NULL)
            err = LTRSRV_ERR_MEMALLOC;        
    }

    if (!err) {
        struct libusb_transfer* transfer = (struct libusb_transfer*)intf->ctl.port_transfer;
        t_usb_libusb_data *privdata = (t_usb_libusb_data*)intf->privdata;
        /* подготавливаем буфер для управляющей передачи */
        libusb_fill_control_setup(intf->ctl.buf, req_type, cmd, param & 0xFFFF,
                              (param>>16)&0xFFFF, len);
        libusb_fill_control_transfer(transfer, privdata->handle,
                                     intf->ctl.buf, f_ctl_cb, intf,
                                     LTRSRV_USB_CTL_TOUT);
        /* если есть данные на передачу - передаем */
        if (data_len)
            memcpy(libusb_control_transfer_get_data(transfer), tx_data, data_len);

        err = libusb_submit_transfer(transfer);

        if (!err) {
             intf->ctl.state = USB_TRANSF_STATE_PROGRESS;             
        }
    }    

    return err;
}


static void f_check_devlist(void) {
    libusb_device **list;
    ssize_t cnt = libusb_get_device_list(NULL, &list);
    ssize_t i;

    p_usb_check_devlist_init();

    for (i=0; i < cnt; i++) {
        int err = 0;
        libusb_device *device = list[i];
        struct libusb_device_descriptor devdescr;
        err = libusb_get_device_descriptor(device, &devdescr);
        if (!err) {
            unsigned fnd;
            const t_usb_dev_id *id_entry;

            for (id_entry = &crate_usb_dev_ids[0], fnd=0; !fnd &&
                 (id_entry->type!=LTR_CRATE_TYPE_UNKNOWN); id_entry++) {
                if ((devdescr.idVendor==id_entry->vid) &&
                        (devdescr.idProduct==id_entry->pid)) {
                    fnd = 1;
                }
            }

            if (fnd) {
                if (p_usb_dev_is_new(device, &f_port_intf)) {
                    libusb_ref_device(device);
                    p_usb_init_new_dev(device, &f_port_intf);
                }
            }
        }
    }
    libusb_free_device_list(list, 1);

    p_usb_check_devlist_finish();
}

static void f_evt_cb(t_socket sock, int event, void* data) {
    if (event!=LTRSRV_EVENT_SRV_CLOSE) {
        struct timeval zero_tv;
        memset(&zero_tv, 0, sizeof(zero_tv));
        libusb_handle_events_timeout(NULL, &zero_tv);
    }
}

/* Функция, вызываемая при добавлении дескриптора, за состоянием которого нужно следить */
static void f_pollfd_added_cb(int fd, short events, void *user_data) {
    int flags = 0;
    if (events & POLLIN)
        flags |= LTRSRV_WAITFLG_RECV;
    if (events & POLLOUT)
        flags |= LTRSRV_WAITFLG_SEND;

    ltrsrv_wait_add_sock(fd, flags, 0, f_evt_cb, NULL);
}

/* Функция, вызываемая при удалении дескриптора, за состоянием которого нужно следить */
static void  f_pollfd_removed_cb(int fd, void *user_data) {
    ltrsrv_wait_remove_sock(fd);
}

int ltrsrv_usb_init(void) {
    int err = libusb_init(NULL);
    if (!err) {
        /* получаем списко всех дескрипторов, за которыми нужно следить.
         * (так как они могли быть добавлены в init() */
        const struct libusb_pollfd **fds;
        fds = libusb_get_pollfds(NULL);
        if (fds) {
            const struct libusb_pollfd **cur_fd;
            for (cur_fd = fds; *cur_fd!=NULL; cur_fd++) {
                f_pollfd_added_cb((*cur_fd)->fd, (*cur_fd)->events, NULL);
            }
            free(fds);
        }

        f_usb_init = 1;

        /* регистрируем callback на изменения дескрипторов */
        libusb_set_pollfd_notifiers(NULL, f_pollfd_added_cb, f_pollfd_removed_cb, NULL);

        /* проверяем наличие устройств */
        f_check_devlist();

        ltrsrv_wait_add_tmr(&f_port_intf, 0, LTRSRV_USB_DEVCHECK_TOUT,
                            f_update_usb_devs_cb, NULL);
    }

    if (err) {
        ltrsrv_log_rec(LTRSRV_LOGLVL_ERR, LTRSRV_LOGSRC_SERVER, LTRSRV_ERR_USB_INIT,
                       "libusb initialization error (%d)", err);
    }
    return err;
}

void ltrsrv_usb_close(void) {
    p_usb_close();

    ltrsrv_wait_remove_tmr(&f_port_intf);

    while (f_usb_crates_cnt)
        ltrsrv_wait_event(1000);


    if (f_usb_init)
        libusb_exit(NULL);
}
#endif

