#include "proc_maps.h"

/* #include "include/eztrace-instrumentation/errors.h" */

#include <assert.h>
#include <eztrace-core/eztrace_attributes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/************************************************************/
/*
static void reset_maps_entry(struct Maps_entry* out) {
  out->fields_count = 0;
  out->stack_end_addr = NULL;
  out->stack_end_addr = NULL;
  out->permissions[0] = '\0';
  out->offset = 0;
  out->device1 = 0;
  out->device2 = 0;
  out->inode = 0;
  out->file[0] = '\0';
}
*/
/************************************************************/

bool maps_entry_filter_all(struct Maps_entry const* map_entry MAYBE_UNUSED) {
  return true;
}

/************************************************************/

bool maps_entry_filter_permissions(struct Maps_entry const* maps_entry, char const* permissions) {
  assert(permissions != NULL);
  assert(maps_entry != NULL);
  assert(maps_entry->fields_count >= 3); // check permission are presents

  for (; *permissions != '\0'; ++permissions) {
    if (strchr(maps_entry->permissions, *permissions) == NULL)
      return false;
  }
  return true;
}

/************************************************************/

static int read_fields_in_maps_line(struct Maps_entry * out, char const* buffer) {
  int fields_count = sscanf(buffer, "%p-%p %s %x %x:%x %d %s",
                            &out->stack_base_addr,
                            &out->stack_end_addr,
                            (char*)&out->permissions,
                            &out->offset,
                            &out->device1,
                            &out->device2,
                            &out->inode,
                            (char*)&out->file);
  out->fields_count = fields_count;
  return fields_count;
}

/************************************************************/

// Warning : this function doesn't allocate, the returned pointer point in maps_entry->file !
static char const* maps_entry_base_name(struct Maps_entry const* maps_entry) {
  char const* res = NULL;
  for (char const* it = maps_entry->file; *it != '\0'; ++it)
    if (*it == '/')
      res = it + 1;
  return res;
}

/************************************************************/

static char const* maps_entry_return_complete_string(struct Maps_entry const* maps_entry) {
  return maps_entry->file;
}

/************************************************************/

typedef char const* (*get_file_name_func_t)(struct Maps_entry const* maps_entry);

static int generic_mapped_names_in_proc_maps(char const* name, char* out, int out_size,
                                             Maps_entry_filter_func filter,
                                             get_file_name_func_t get_file_name_func) {
  assert(out != NULL);
  assert(out_size > 3);
  int res = 0;

  char buffer[4096];
  struct Maps_entry maps_entry;

  sprintf(buffer, "/proc/%s/maps", name);
  FILE * maps_file = fopen(buffer, "r");
  assert(maps_file != NULL);

  // if there is only space for the '\0' char, exit failure
#define WRITE_CHAR(c)                            \
  if (--out_size == 1) { res = -1; goto exit; } \
  else { *out = c; ++out; }

  while (fgets(buffer, sizeof(buffer), maps_file)) {
    int const fields_count = read_fields_in_maps_line(&maps_entry, buffer);
    if (fields_count == 8 && (*filter)(&maps_entry)) {
      char const * file_name = (*get_file_name_func)(&maps_entry);

      if (file_name != NULL) {
        WRITE_CHAR(':')
        for (; *file_name != '\0'; ++file_name)
          WRITE_CHAR(*file_name)
      }
    }
  }
#undef WRITE_CHAR

exit:
  out[0] = ':';
  out[1] = '\0';
  fclose(maps_file);
  return res;
}

/************************************************************/

static int generic_get_entries_in_proc_maps(char const* name,
                                            struct Maps_entry** maps_entries,
                                            Maps_entry_filter_func filter) {
  assert(name != NULL);
  assert(maps_entries != NULL);
  assert(*maps_entries == NULL);
  assert(filter != NULL);

  static const int min_fields_count = 7;

  int line_count = 0;
  char buffer[4096];

  sprintf(buffer, "/proc/%s/maps", name);
  FILE * maps_file = fopen(buffer, "r");
  assert(maps_file != NULL);

  /* count the number of lines */
  while (fgets(buffer, sizeof(buffer), maps_file)) {
    struct Maps_entry maps_entry_buffer;
    int const fields_count = read_fields_in_maps_line(&maps_entry_buffer, buffer);
    if (fields_count >= min_fields_count && (*filter)(&maps_entry_buffer))
      ++line_count;
  }
  /* back to the begining of the file */
  rewind(maps_file);

  /* allocate memory for result */
  *maps_entries = malloc(line_count * sizeof(struct Maps_entry));
  if (*maps_entries == NULL) {
    line_count = -1;
    goto exit;
  }

  /* parse each buffer */
  int current_line_number = 0;
  while (fgets(buffer, sizeof(buffer), maps_file)) {
    int const fields_count = read_fields_in_maps_line(&(*maps_entries)[current_line_number], buffer);
    if (fields_count >= min_fields_count && (*filter)(&((*maps_entries)[current_line_number])))
      ++current_line_number;
  }
  assert(current_line_number == line_count);

exit:
  fclose(maps_file);
  return line_count;
}


/************************************************************/

int get_mapped_file_names_in_proc_pid_maps(pid_t pid, char* out, int out_size,
                                           Maps_entry_filter_func filter) {
  char spid[10];
  sprintf(spid, "%d", pid);
  return generic_mapped_names_in_proc_maps(spid, out, out_size, filter,
                                           &maps_entry_base_name);
}

/************************************************************/

int get_mapped_file_names_in_proc_name_maps(char const* name, char* out, int out_size,
                                            Maps_entry_filter_func filter) {
  return generic_mapped_names_in_proc_maps(name, out, out_size, filter,
                                           &maps_entry_base_name);
}

/************************************************************/

int get_mapped_file_paths_in_proc_pid_maps(pid_t pid, char* out, int out_size,
                                           Maps_entry_filter_func filter) {
  char spid[10];
  sprintf(spid, "%d", pid);
  return generic_mapped_names_in_proc_maps(spid, out, out_size, filter,
                                           &maps_entry_return_complete_string);
}

/************************************************************/

int get_mapped_file_paths_in_proc_name_maps(char const* name, char* out, int out_size,
                                            Maps_entry_filter_func filter) {
  return generic_mapped_names_in_proc_maps(name, out, out_size, filter,
                                           &maps_entry_return_complete_string);
}

/************************************************************/

int get_entries_in_proc_pid_maps(pid_t pid, struct Maps_entry** maps_entries,
                                 Maps_entry_filter_func filter) {
  char spid[10];
  sprintf(spid, "%d", pid);
  return generic_get_entries_in_proc_maps(spid, maps_entries, filter);
}

/************************************************************/

int get_entries_in_proc_name_maps(char const* name, struct Maps_entry** maps_entries,
                                  Maps_entry_filter_func filter) {
  return generic_get_entries_in_proc_maps(name, maps_entries, filter);
}


