#define _GNU_SOURCE

#include <sys/stat.h>
#include <dirent.h>

#include "udev.h"
#include "global.h"
#include "pci.h"

#ifndef PATH_MAX
  #define PATH_MAX 1024
#endif

#define PCI_PATH "/sys/bus/pci/devices/"
#define MAX_LENGTH_PCI_DIR_NAME 1024

// Return a list of PCI devices containing only
// the sysfs path
struct pci_devices * get_pci_paths(void) {
  DIR *dirp;

  if ((dirp = opendir(PCI_PATH)) == NULL) {
    perror("opendir");
    return NULL;
  }

  struct dirent *dp;
  int numDirs = 0;  
  errno = 0;

  while ((dp = readdir(dirp)) != NULL) {
    if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
      numDirs++;
  }  
  if (errno != 0) {
    perror("readdir");
    return NULL;
  }

  rewinddir(dirp);

  struct pci_devices * pci = emalloc(sizeof(struct pci_devices));
  pci->num_devices = numDirs;
  pci->devices = emalloc(sizeof(struct pci_device) * pci->num_devices);
  char * full_path = emalloc(PATH_MAX * sizeof(char));
  struct stat stbuf;
  int i = 0;

  while ((dp = readdir(dirp)) != NULL) {
    if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
      continue;

    if (strlen(dp->d_name) > MAX_LENGTH_PCI_DIR_NAME) {
      printErr("Directory name is too long: %s", dp->d_name);
      return NULL;
    }

    memset(full_path, 0, PATH_MAX * sizeof(char));
    snprintf(full_path, min(strlen(PCI_PATH) + strlen(dp->d_name) + 1, PATH_MAX), "%s%s", PCI_PATH, dp->d_name);    

    if (stat(full_path, &stbuf) == -1) {
      perror("stat");
      return NULL;
    }

    if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
      int strLen = min(MAX_LENGTH_PCI_DIR_NAME, strlen(dp->d_name)) + 1;
      pci->devices[i] = emalloc(sizeof(struct pci_device));
      pci->devices[i]->path = ecalloc(strLen, sizeof(char));
      strncpy(pci->devices[i]->path, dp->d_name, strLen);
      i++;
    }        
  }
  if (errno != 0) {
    perror("readdir");
    return NULL;
  }

  return pci;
}

// For each PCI device in the list pci, fetch its vendor and 
// device id using sysfs (e.g., /sys/bus/pci/devices/XXX/{vendor/device})
void populate_pci_devices(struct pci_devices * pci) {
  int filelen;
  char* buf;

  for (int i=0; i < pci->num_devices; i++) {
    struct pci_device* dev = pci->devices[i];
    int path_size = strlen(PCI_PATH) + strlen(dev->path) + 2;

    // Read vendor_id
    char *vendor_id_path = emalloc(sizeof(char) * (path_size + strlen("vendor") + 1));
    sprintf(vendor_id_path, "%s/%s/%s", PCI_PATH, dev->path, "vendor");

    if ((buf = read_file(vendor_id_path, &filelen)) == NULL) {
      printWarn("read_file: %s: %s\n", vendor_id_path, strerror(errno));
      dev->vendor_id = 0;
    }
    else {
      dev->vendor_id = strtol(buf, NULL, 16);
    }

    // Read device_id
    char *device_id_path = emalloc(sizeof(char) * (path_size + strlen("device") + 1));
    sprintf(device_id_path, "%s/%s/%s", PCI_PATH, dev->path, "device");

    if ((buf = read_file(device_id_path, &filelen)) == NULL) {
      printWarn("read_file: %s: %s\n", device_id_path, strerror(errno));
      dev->device_id = 0;
    }
    else {
      dev->device_id = strtol(buf, NULL, 16);
    }

    free(vendor_id_path);
    free(device_id_path);
  }
}

// Return a list of PCI devices that could be used to infer the SoC.
// The criteria to determine which devices are suitable for this task
// is decided in filter_pci_devices.
struct pci_devices * get_pci_devices(void) {
  struct pci_devices * pci = get_pci_paths();

  if (pci == NULL)
    return NULL;

  populate_pci_devices(pci);  

  return pci;
}
