/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2021 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <string.h>
#include <errno.h>

#include "log.h"
#include "program.h"
#include "usb_internal.h"
#include "usb_serial.h"
#include "usb_adapters.h"

static void
usbLogSerialProblem (UsbDevice *device, const char *problem) {
  logMessage(LOG_CATEGORY(SERIAL_IO),
    "%s: Vendor:%04X Product:%04X",
    problem,
    getLittleEndian16(device->descriptor.idVendor),
    getLittleEndian16(device->descriptor.idProduct)
  );
}

int
usbSkipInitialBytes (UsbInputFilterData *data, unsigned int count) {
  if (data->length > count) {
    unsigned char *buffer = data->buffer;
    memmove(buffer, buffer+count, data->length-=count);
  } else {
    data->length = 0;
  }

  return 1;
}

static const UsbSerialAdapter **usbSerialAdapters = NULL;

static int
usbCompareSerialAdapters (const UsbSerialAdapter *adapter1, const UsbSerialAdapter *adapter2) {
  if (adapter1->vendor < adapter2->vendor) return -1;
  if (adapter1->vendor > adapter2->vendor) return 1;

  if (adapter1->product < adapter2->product) return -1;
  if (adapter1->product > adapter2->product) return 1;

  return 0;
}

static int
usbSortSerialAdapters (const void *element1, const void *element2) {
  const UsbSerialAdapter *const *adapter1 = element1;
  const UsbSerialAdapter *const *adapter2 = element2;

  return usbCompareSerialAdapters(*adapter1, *adapter2);
}

static int
usbSearchSerialAdapter (const void *target, const void *element) {
  const UsbSerialAdapter *const *adapter = element;

  return usbCompareSerialAdapters(target, *adapter);
}

static const UsbSerialAdapter *
usbGetSerialAdapter (uint16_t vendor, uint16_t product) {
  const UsbSerialAdapter target = {
    .vendor = vendor,
    .product = product
  };

  const UsbSerialAdapter *const *adapter = bsearch(&target, usbSerialAdapters, usbSerialAdapterCount, sizeof(*usbSerialAdapters), usbSearchSerialAdapter);

  return adapter? *adapter: NULL;
}

const UsbSerialAdapter *
usbFindSerialAdapter (const UsbDeviceDescriptor *descriptor) {
  if (!usbSerialAdapters) {
    const UsbSerialAdapter **adapters;

    if (!(adapters = malloc(usbSerialAdapterCount * sizeof(*adapters)))) {
      logMallocError();
      return NULL;
    }

    {
      const UsbSerialAdapter *source = usbSerialAdapterTable;
      const UsbSerialAdapter *end = source + usbSerialAdapterCount;
      const UsbSerialAdapter **target = adapters;

      while (source < end) *target++ = source++;
      qsort(adapters, usbSerialAdapterCount, sizeof(*adapters), usbSortSerialAdapters);
    }

    usbSerialAdapters = adapters;
    registerProgramMemory("sorted-usb-serial-adapters", &usbSerialAdapters);
  }

  {
    uint16_t vendor = getLittleEndian16(descriptor->idVendor);
    uint16_t product = getLittleEndian16(descriptor->idProduct);
    const UsbSerialAdapter *adapter = usbGetSerialAdapter(vendor, product);

    if (!adapter) adapter = usbGetSerialAdapter(vendor, 0);
    return adapter;
  }
}

int
usbSetSerialOperations (UsbDevice *device) {
  if (!device->serial.operations) {
    const UsbSerialOperations *uso = NULL;

    {
      const UsbSerialAdapter *adapter = usbFindSerialAdapter(&device->descriptor);

      if (adapter) {
        if ((uso = adapter->operations)) {
          UsbInputFilter *filter = uso->inputFilter;
         
          if (filter && !usbAddInputFilter(device, filter)) return 0;
        }
      }
    }

    if (!uso) {
      if (device->descriptor.bDeviceClass == 0X02) {
        uso = &usbSerialOperations_CDC_ACM;
      }
    }

    if (uso) {
      logMessage(LOG_CATEGORY(SERIAL_IO), "USB adapter: %s", uso->name);
      UsbSerialData *usd = NULL;

      if (uso->makeData) {
        if (!uso->makeData(device, &usd)) {
          return 0;
        }
      }

      device->serial.operations = uso;
      device->serial.data = usd;
    }
  }

  return 1;
}

const UsbSerialOperations *
usbGetSerialOperations (UsbDevice *device) {
  return device->serial.operations;
}

UsbSerialData *
usbGetSerialData (UsbDevice *device) {
  return device->serial.data;
}

int
usbSetSerialParameters (UsbDevice *device, const SerialParameters *parameters) {
  int ok = 0;
  const UsbSerialOperations *serial = usbGetSerialOperations(device);

  if (!serial) {
    usbLogSerialProblem(device, "no serial operations");
    errno = ENOSYS;
  } else if (serial->setLineConfiguration) {
    ok = serial->setLineConfiguration(
      device, parameters->baud,
      parameters->dataBits, parameters->stopBits,
      parameters->parity, parameters->flowControl
    );
  } else if (serial->setLineProperties) {
    ok = serial->setLineProperties(
      device, parameters->baud,
      parameters->dataBits, parameters->stopBits,
      parameters->parity
    );
  } else {
    ok = 1;

    if (!serial->setBaud) {
      usbLogSerialProblem(device, "setting baud is not supported");
    } else if (!serial->setBaud(device, parameters->baud)) {
      ok = 0;
    }

    if (!serial->setDataFormat) {
      usbLogSerialProblem(device, "setting data format is not supported");
    } else if (!serial->setDataFormat(device, parameters->dataBits, parameters->stopBits, parameters->parity)) {
      ok = 0;
    }

    if (!serial->setFlowControl) {
      usbLogSerialProblem(device, "setting flow control is not supported");
    } else if (!serial->setFlowControl(device, parameters->flowControl)) {
      ok = 0;
    }
  }

  return ok;
}
