#include "LIBUSBport.h"
#include "QDebug"

LIBUSBPort::LIBUSBPort() {
  this->dev_handle = nullptr;
  this->device = nullptr;
  this->opened = false;
  this->bufferLenAvailable = BUFFER_LEN;
  this->toEmit = QByteArray();
  this->eventHandler = new LIBUSBEventHandler();
  this->outQueu = QList<QByteArray>();
  this->controlQueu = QList<QByteArray>();
  connect(this, SIGNAL(close(void)), this, SLOT(closeAll(void)));
}

LIBUSBPort::~LIBUSBPort() { delete (this->eventHandler); }

void LIBUSBPort::requestOpen(libusb_device *dev) {
  this->bufferLenAvailable = BUFFER_LEN;
  this->outQueu.clear();
  this->controlQueu.clear();
  this->sendDone = true;

  int status;
  this->device = dev;

  // open device
  status = libusb_open(this->device, &this->dev_handle);
  if (status != 0) {
    emit openStatus(false);
    qDebug() << "Open device error: " << status;
    return;
  }
  qDebug() << "Device opened";

  // detach kernel drivers
  status = libusb_kernel_driver_active(this->dev_handle, INTERF);
  if (status == 1) {
    status = libusb_detach_kernel_driver(this->dev_handle, INTERF);
    if (status != 0) {
      emit openStatus(false);
      this->dev_handle = nullptr;
      this->device = nullptr;
      qDebug() << "Detach kernel driver error: " << status;
      return;
    }
  }

  // claim interface
  status = libusb_claim_interface(this->dev_handle, INTERF);
  if (status == 0) {
    qDebug() << "Interface claimed";

    // allocate transfers
    this->readTransfer = libusb_alloc_transfer(0);
    this->writeTransfer = libusb_alloc_transfer(0);
    qDebug() << "Transfers allocated";

    // start event handling libusb thread
    this->eventHandler->running = true;
    this->eventHandler->start();
    qDebug() << "Event thread started";

    // set open status
    this->opened = true;
    // emit open status
    emit openStatus(true);
    qDebug() << "Emit open port";

    // start reading
    this->setRead();
    qDebug() << "Reading started";

    // Imitate CDC drivers to behave same for FW
    // set SET_CONTROL_LINE_STATE data
    unsigned char *SET_CONTROL_LINE_STATE =
        new unsigned char[LIBUSB_CONTROL_SETUP_SIZE];
    libusb_fill_control_setup(SET_CONTROL_LINE_STATE, 0x21, 0x22, 0x1 | 0x2, 0,
                              0);
    QByteArray setControlLineState =
        QByteArray(reinterpret_cast<char *>(SET_CONTROL_LINE_STATE),
                   LIBUSB_CONTROL_SETUP_SIZE);
    free(SET_CONTROL_LINE_STATE);

    // set SET_LINE_CODING data
    unsigned char *SET_LINE_CODING =
        new unsigned char[LIBUSB_CONTROL_SETUP_SIZE + 7];
    libusb_fill_control_setup(SET_LINE_CODING, 0x21, 0x20, 0, 0, 7);
    unsigned char data[] = {0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08};
    for (uint32_t i = 0; i < 7; i++) {
      SET_LINE_CODING[LIBUSB_CONTROL_SETUP_SIZE + i] = data[i];
    }
    QByteArray setLineCoding = QByteArray(
        reinterpret_cast<char *>(SET_LINE_CODING), LIBUSB_CONTROL_SETUP_SIZE);
    setLineCoding.append(reinterpret_cast<char *>(data), 7);
    free(SET_CONTROL_LINE_STATE);

    this->controlQueu.append(setControlLineState);
    this->controlQueu.append(setLineCoding);
    this->sendControlQueu();

  } else {
    emit openStatus(false);
    this->dev_handle = nullptr;
    this->device = nullptr;
    qDebug() << "Claiming interface error: " << status;
  }
  return;
}

void LIBUSBPort::setRead() {
  if (this->opened) {
    // fill transfer
    libusb_fill_bulk_transfer(
        this->readTransfer, this->dev_handle, READ_ENDPOINT,
        &this->inBuff[BUFFER_LEN - this->bufferLenAvailable],
        this->bufferLenAvailable, readDone, this, READ_TIMEOUT);
    int status;
    // submit transfer
    status = libusb_submit_transfer(this->readTransfer);
    if (status == 0) {
      // qDebug() << "Read transfer submited";
    } else {
      qDebug() << "Read transfer submit error: " << status;
    }
  }
}

void LIBUSBPort::requestToStop() {
  this->opened = false;
  // Imitate CDC drivers to behave same for FW

  // set SET_CONTROL_LINE_STATE data
  unsigned char *SET_CONTROL_LINE_STATE =
      new unsigned char[LIBUSB_CONTROL_SETUP_SIZE];
  libusb_fill_control_setup(SET_CONTROL_LINE_STATE, 0x21, 0x22, 0x1 | 0x2, 0,
                            0);
  QByteArray setControlLineState =
      QByteArray(reinterpret_cast<char *>(SET_CONTROL_LINE_STATE),
                 LIBUSB_CONTROL_SETUP_SIZE);
  free(SET_CONTROL_LINE_STATE);

  // set SET_LINE_CODING data
  unsigned char *SET_LINE_CODING =
      new unsigned char[LIBUSB_CONTROL_SETUP_SIZE + 7];
  libusb_fill_control_setup(SET_LINE_CODING, 0x21, 0x20, 0, 0, 7);
  unsigned char data[] = {0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08};
  for (uint32_t i = 0; i < 7; i++) {
    SET_LINE_CODING[LIBUSB_CONTROL_SETUP_SIZE + i] = data[i];
  }
  QByteArray setLineCoding = QByteArray(
      reinterpret_cast<char *>(SET_LINE_CODING), LIBUSB_CONTROL_SETUP_SIZE);
  setLineCoding.append(reinterpret_cast<char *>(data), 7);
  free(SET_CONTROL_LINE_STATE);

  this->controlQueu.append(setLineCoding);
  this->controlQueu.append(setControlLineState);
  this->sendControlQueu();
}

void LIBUSB_CALL LIBUSBPort::readDone(struct libusb_transfer *transfer) {
  if (reinterpret_cast<LIBUSBPort *>(transfer->user_data)->opened) {

    // data left from previous due to align
    int fromPreviousTransfer =
        BUFFER_LEN -
        reinterpret_cast<LIBUSBPort *>(transfer->user_data)->bufferLenAvailable;
    int received = transfer->actual_length + fromPreviousTransfer;

    if (received > 0) {
      int receivedAlign = 8 * (received / 8);
      // clear emit array
      reinterpret_cast<LIBUSBPort *>(transfer->user_data)->toEmit.clear();

      // append from readBuffer to emit array
      reinterpret_cast<LIBUSBPort *>(transfer->user_data)
          ->toEmit.append(
              reinterpret_cast<const char *>(
                  reinterpret_cast<LIBUSBPort *>(transfer->user_data)->inBuff),
              receivedAlign);
      // emit new data
      if (reinterpret_cast<LIBUSBPort *>(transfer->user_data)->toEmit.length() >
          0) {
        emit reinterpret_cast<LIBUSBPort *>(transfer->user_data)
            ->newData(
                reinterpret_cast<LIBUSBPort *>(transfer->user_data)->toEmit);
      }
      // set available buffer len
      reinterpret_cast<LIBUSBPort *>(transfer->user_data)->bufferLenAvailable =
          BUFFER_LEN - (received - receivedAlign);

      // move not emitted data to begin of receive buffer
      for (int i = 0; i < received - receivedAlign; i++) {
        reinterpret_cast<LIBUSBPort *>(transfer->user_data)->inBuff[i] =
            reinterpret_cast<LIBUSBPort *>(transfer->user_data)
                ->inBuff[receivedAlign + i];
      }
    }

    // set new transfer
    reinterpret_cast<LIBUSBPort *>(transfer->user_data)->setRead();
  }
}

void LIBUSB_CALL LIBUSBPort::writeDone(struct libusb_transfer *transfer) {
  free(transfer->buffer);
  reinterpret_cast<LIBUSBPort *>(transfer->user_data)->sendDone = true;
  if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
    qDebug() << "Write error: " << transfer->status;
  } else {
    // qDebug() << "Data sent";
  }
  if (reinterpret_cast<LIBUSBPort *>(transfer->user_data)->outQueu.length() >
      0) {
    QByteArray toSend =
        reinterpret_cast<LIBUSBPort *>(transfer->user_data)->outQueu.at(0);
    reinterpret_cast<LIBUSBPort *>(transfer->user_data)->outQueu.removeAt(0);
    reinterpret_cast<LIBUSBPort *>(transfer->user_data)->requestSend(toSend);
  }
}

void LIBUSB_CALL LIBUSBPort::conrolWriteDone(struct libusb_transfer *transfer) {
  free(transfer->buffer);
  if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
    qDebug() << "Control write error: " << transfer->status;
  } else {
    qDebug() << "Control write done";
  }

  if (reinterpret_cast<LIBUSBPort *>(transfer->user_data)
          ->controlQueu.length() > 0) {
    reinterpret_cast<LIBUSBPort *>(transfer->user_data)->sendControlQueu();
  } else if (!reinterpret_cast<LIBUSBPort *>(transfer->user_data)->opened) {
    // close all after control transfers
    emit reinterpret_cast<LIBUSBPort *>(transfer->user_data)->close();
  }
}

void LIBUSBPort::requestSend(QByteArray data) {
  if (this->opened) {
    if (this->sendDone) {
      // fill output buffer
      this->outBuff = new unsigned char[data.length()];
      for (int i = 0; i < data.length(); i++) {
        this->outBuff[i] = static_cast<unsigned char>(data.at(i));
      }
      // fill transfer
      libusb_fill_bulk_transfer(this->writeTransfer, this->dev_handle,
                                WRITE_ENDPOINT, this->outBuff, data.length(),
                                writeDone, this, 10);
      // submit transfer
      libusb_submit_transfer(this->writeTransfer);
      this->sendDone = false;
    }
  } else {
    this->outQueu.append(data);
  }
}

void LIBUSBPort::sendControlQueu() {
  QByteArray data = this->controlQueu.at(0);
  this->controlQueu.removeAt(0);
  this->controlBuff = new unsigned char[data.length()];
  for (int i = 0; i < data.length(); i++) {
    this->controlBuff[i] = static_cast<unsigned char>(data.at(i));
  }
  // fill transfer
  libusb_fill_control_transfer(this->writeTransfer, this->dev_handle,
                               this->controlBuff, conrolWriteDone, this, 10);
  // submit transfer
  libusb_submit_transfer(this->writeTransfer);
}

void LIBUSBPort::closeAll() {

  // stop event handling thread
  this->eventHandler->running = false;
  qDebug() << "Stop event thread";

  // atach kernel driver
  libusb_release_interface(this->dev_handle, INTERF);
  libusb_attach_kernel_driver(this->dev_handle, INTERF);
  qDebug() << "Attach kernel driver";

  // close device
  libusb_close(this->dev_handle);
  qDebug() << "Port closed";

  // port closed  - free
  libusb_free_transfer(this->readTransfer);
  libusb_free_transfer(this->writeTransfer);
  qDebug() << "Free transfers";

  // emit close status
  emit openStatus(false);
}
