#include "libusb_wrapper.h"

void usbinit(libusb_context **context_ptr) {
  // libusb_context *context;
  int statusInit = libusb_init(context_ptr);
  if (statusInit != LIBUSB_SUCCESS) {
    perror("libusb_init");
    exit(-1);
  }
}

void usbclose(libusb_context *context) { libusb_exit(context); }

void interfaceclaim(libusb_device_handle *handle,
                    struct libusb_interface *interface) {
  const struct libusb_interface_descriptor *interface_desc =
      &interface->altsetting[0]; // TODO enlever cette ligne, refactor

  if (interface_desc->bInterfaceClass ==
      LIBUSB_CLASS_VENDOR_SPEC) { // ON PEUT VERIFIER LA CLASS
    printf("> vendor specific class\n");

    int status =
        libusb_claim_interface(handle, interface_desc->bInterfaceNumber);
    if (status != 0) {
      perror("libusb_claim_interface");
      exit(-1);
    }
  }
}

void interfaceclose(libusb_device_handle *handle,
                    struct libusb_interface *interface) {
  const struct libusb_interface_descriptor *interface_desc =
      &interface->altsetting[0];
  int status =
      libusb_release_interface(handle, interface_desc->bInterfaceNumber);
  if (status != 0) {
    perror("libusb_release_interface");
    exit(-1);
  }
}

// Si le méchant noyau est passé avant vous :
void getFromKernel(libusb_device_handle *handle,
                   int interface) { // private method for now
  if (libusb_kernel_driver_active(handle, interface)) {
    int statusKDriver = libusb_detach_kernel_driver(handle, interface);
    if (statusKDriver != LIBUSB_SUCCESS) {
      perror("libusb_detach_kernel_driver");
      exit(-1);
    }
  }
}

ssize_t getListDevices(libusb_context *context, libusb_device ***list_ptr) {
  ssize_t count = libusb_get_device_list(context, list_ptr);
  if (count < 0) {
    perror("libusb_get_device_list");
    exit(-1);
  }
  return count;
}

void *_enumerateDevices(libusb_context *context,
                        void (*func)(libusb_device *device)) {
  libusb_device **list;
  ssize_t count = getListDevices(context, &list);

  ssize_t i = 0;
  for (i = 0; i < count; i++) {
    libusb_device *device = list[i];

    func(device);
  }

  libusb_free_device_list(list, 1);

  return NULL; // DBGONLY TEMP
}

// print readable string, not asked for tutoring
void _printConfig(libusb_device *device) { // private method
  // Ouverture du périphérique
  libusb_device_handle *handle;
  int statusDevice = libusb_open(device, &handle);
  if (statusDevice != LIBUSB_SUCCESS) {
    perror("libusb_open");
    // return; //exit(-1);
  }

  int MAXLEN_DESCRIPTOR_STRING = 200;
  uint8_t desc_idx = 2;
  // uint16_t langid = 16;
  unsigned char data[200];

  // TEST 16
  for (desc_idx = 0; desc_idx < 16; desc_idx++) {
    int statusAscii = libusb_get_string_descriptor_ascii(
        handle, desc_idx, data, MAXLEN_DESCRIPTOR_STRING);
    if (statusAscii == -9) // TEST seems to be LIBUSB_ERROR
      break;
    printf(" - Descriptor string : %s ; %i\n", data, statusAscii);
  }

  libusb_close(handle);
}

void _displayOneDevice(libusb_device *device) {
  struct libusb_device_descriptor desc;

  int status = libusb_get_device_descriptor(device, &desc);
  if (status != LIBUSB_SUCCESS) {
    printf("Cannot get device desc : %s\n",
           libusb_error_name(status)); // DBGONLY
    perror("libusb_open");
    return;
  }

  uint8_t bus = libusb_get_bus_number(device);
  uint8_t address = libusb_get_device_address(device);

  printf("Device Found @ (Bus:Address) %d:%d\n", bus, address);
  printf("Vendor ID 0x0%x\n", desc.idVendor);
  printf("Product ID 0x0%x\n", desc.idProduct);

  // displayDeviceEndpoints(device); //Not really work on Axel@Alptop
}

void displayDevices(libusb_context *context) {
  _enumerateDevices(context, _displayOneDevice);
}

void _displayOneDeviceMore(libusb_device *device) {
  _displayOneDevice(device);
  _printConfig(device);
}

void displayDevicesMore(libusb_context *context) {
  _enumerateDevices(context, _displayOneDeviceMore);
}

// get device from iteration (and not from not recommanded function :
// ugly using global vars ?? Cannot communicate between getFirstDeviceFromID and
// _getFirstDeviceFromID
int g_vid;
int g_pid;
libusb_device *g_device = NULL;
void _getFirstDeviceFromID(libusb_device *device) {
  struct libusb_device_descriptor desc;

  int status = libusb_get_device_descriptor(device, &desc);
  if (status != LIBUSB_SUCCESS) {
    printf("Cannot get device desc : %s\n",
           libusb_error_name(status)); // DBGONLY
    perror("libusb_open");
    return;
  }

  if (desc.idVendor == g_vid && desc.idProduct == g_pid) {
    g_device = device;
  }
}

void getFirstDeviceFromID(libusb_context *context, int vid, int pid,
                          libusb_device **device) {
  g_vid = vid; // pass parameters for enumeration
  g_pid = pid; // idem
  _enumerateDevices(context, _getFirstDeviceFromID);
  *device = g_device; // get return from enumeration
}

// void getDevicesFromID(vid, pid) //return array

void displayDeviceEndpoints(libusb_device_handle *handle) {
  // recover pointer from handle
  libusb_device *device;
  device = libusb_get_device(handle);

  // lectures des configs => on prend la première configuration pour l'instant
  /*int configuration = 0; // valueof("bConfigurationValue");
  int statusConfig = libusb_set_configuration(handle, configuration);
  if (statusConfig != LIBUSB_SUCCESS) {
      perror("libusb_set_configuration");
      return;
  }*/

  // 4.2 configuration du périph usb
  struct libusb_config_descriptor *config;
  int statusFetchConfig = libusb_get_active_config_descriptor(device, &config);
  if (statusFetchConfig != LIBUSB_SUCCESS) {
    perror("libusb_get_active_config_descriptor");
    return;
  }

  // caractéristiques globales
  printf("Config.bConfigurationValue : %d\n", config->bConfigurationValue);
  printf("Config/ bLength:%d\nbDescriptorType:%d\nbNumInterfaces:%d\n",
         config->bLength, config->bDescriptorType, config->bNumInterfaces);

  // itération des interfaces
  printf("Itération de l'interface\n");
  for (int indexInterface = 0; indexInterface < config->bNumInterfaces;
       indexInterface++) {
    printf("-indexInterface=%d\n", indexInterface);
    printf("-Altsetting=%d\n",
           config->interface[indexInterface].num_altsetting);

    const struct libusb_interface *interface =
        &config->interface[indexInterface];

    // if 1 setting (or more)
    if (interface->num_altsetting != 0) {
      const struct libusb_interface_descriptor *interface_desc =
          &interface->altsetting[0];

      printf("--bNumEndpoints=%d\n", interface_desc->bNumEndpoints);
      printf("--bDescriptorType=%d\n", interface_desc->bDescriptorType);

      for (int indexEndpoints = 0;
           indexEndpoints < interface_desc->bNumEndpoints; indexEndpoints++) {
        const struct libusb_endpoint_descriptor *endpoint_desc =
            &interface_desc->endpoint[indexEndpoints];
        printf("---bDescriptorType=%d\n", endpoint_desc->bDescriptorType);
        printf("---bEndpointAddress=%d\n", endpoint_desc->bEndpointAddress);

        if (endpoint_desc->extra != NULL)
          printf("---extra: %s\n", endpoint_desc->extra);
        if (endpoint_desc->bmAttributes ==
            LIBUSB_TRANSFER_TYPE_INTERRUPT) // TODO AJOUT MASQUE ? (voir doc)
          printf("---is of type INTERRUPT\n");
      }
    }

    // Affichez l’indice et le numéro de chaque interface détectée et réclamée.
    // Affichez aussi les points d’accès trouvés en précisant ceux sauvés.

    /*
            //int interface=valueof("bInterfaceNumber");
            int
       status=libusb_claim_interface(handle,interface->bInterfaceNumber);
            if(status!=0){ perror("libusb_claim_interface"); exit(-1); }

            status=libusb_release_interface(handle,interface->bInterfaceNumber);
            if(status!=0){ perror("libusb_release_interface"); exit(-1); }

            //claim_interface(.., config interface altsetting bInterfaceNumber
            //config interface altsetting endpoint bmAttributes ==
       LIBUSB_TRANSFER_TYPE_INTERRUPT
            //add des enpoint unint8_t*/

    // DESCRIPTOR.C /.H :décommenter

    // IL FAUT RELEASE UNIQUEMENT LES INTERFACES QUI NE SONT PAS DES HID
  }

  printf("\n");

  libusb_close(handle);
}

void getOurInterfaces(libusb_device *device,
                      struct libusb_interface **int_hidjoy,
                      struct libusb_interface **int_leds,
                      struct libusb_interface **int_vibrators) {
  struct libusb_config_descriptor *config;
  int statusFetchConfig = libusb_get_active_config_descriptor(device, &config);
  if (statusFetchConfig != LIBUSB_SUCCESS) {
    perror("libusb_get_active_config_descriptor");
    return;
  }

  // itération des interfaces
  for (int indexInterface = 0; indexInterface < config->bNumInterfaces;
       indexInterface++) {

    const struct libusb_interface *interface =
        &config->interface[indexInterface];

    // todo use le descriptor pour avoir bInterfaceNumber?
    if (indexInterface == 0) {
      *int_hidjoy = (struct libusb_interface *)interface;
    } else if (indexInterface == 1) {
      *int_leds = (struct libusb_interface *)interface;
    } else if (indexInterface == 2) {
      *int_vibrators = (struct libusb_interface *)interface;
    }

    //#define pour les indices correspondants aux interfaces
    // variables globales
  }
}

char getOnlyEndpoint(struct libusb_interface *interface) {

  const struct libusb_interface_descriptor *interface_desc =
      &interface->altsetting[0];

  const struct libusb_endpoint_descriptor *endpoint_desc =
      &interface_desc->endpoint[0];

  if (endpoint_desc->bmAttributes !=
      LIBUSB_TRANSFER_TYPE_INTERRUPT) // ON PEUT VERIFIER LE TRANSFER TYPE
    printf("> is NOT of type INTERRUPT\n");

  return (char)endpoint_desc->bEndpointAddress;
}
