#include <sys/inotify.h>
#include <string.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>

#include "inotify.h"


static struct mask_to_event_t {
  int mask;
  Events event;
} mask_to_event[] = {
  {IN_ACCESS, NOTIFY_IN_ACCESS},
  {IN_ATTRIB, NOTIFY_IN_ATTRIB},
  {IN_CLOSE_WRITE, NOTIFY_IN_CLOSE_WRITE},
  {IN_CLOSE_NOWRITE, NOTIFY_IN_CLOSE_NOWRITE},
  {IN_CREATE, NOTIFY_IN_CREATE},
  {IN_DELETE, NOTIFY_IN_DELETE},
  {IN_DELETE_SELF, NOTIFY_IN_DELETE_SELF},
  {IN_MODIFY, NOTIFY_IN_MODIFY},
  {IN_MOVED_FROM, NOTIFY_IN_MOVED_FROM},
  {IN_MOVED_TO, NOTIFY_IN_MOVED_TO},
  {IN_OPEN, NOTIFY_IN_OPEN},
  {IN_MOVE, NOTIFY_IN_MOVE},
  {-1, -1}
};

unsigned int events_to_mask(Events*, unsigned int);
void mask_to_events(unsigned int mask, Events* events, unsigned int *events_length);

UNIFEX_TERM init(UnifexEnv *env) {
  int fd = inotify_init1(IN_NONBLOCK);
  State *state = NULL;

  if (fd == -1) {
    return init_result_error(env, strerror(errno));
  }

  state = unifex_alloc_state(env);
  if (state == NULL) {
    return init_result_error(env, strerror(errno));
  }

  state->inotify_fd = fd;

  UNIFEX_TERM res = init_result_ok(env, state);
  unifex_release_state(env, state);
  return res;
}

UNIFEX_TERM ex_inotify_add_watch(UnifexEnv* env, char* pathname, Events* events, unsigned int events_length, int opts) {
  State *state = (State *) env->state;
  unsigned int mask = events_to_mask(events, events_length) | opts;

  if (state == NULL)
    return ex_inotify_add_watch_result_error(env, "not initialized");

  int wd = inotify_add_watch(state->inotify_fd, pathname, mask);

  if (wd == -1)
    return ex_inotify_add_watch_result_error(env, strerror(errno));
  else
    return ex_inotify_add_watch_result_ok(env, wd);
}

UNIFEX_TERM ex_inotify_rm_watch(UnifexEnv* env, int wd) {
  State *state = (State *) env->state;
  int ret = inotify_rm_watch(state->inotify_fd, wd);

  if (state == NULL)
    return ex_inotify_add_watch_result_error(env, "not initialized");

  if (ret == -1)
    return ex_inotify_rm_watch_result_error(env, strerror(errno));
  else
    return ex_inotify_rm_watch_result(env);
}

int handle_events(UnifexEnv *env, int fd) {
  char buf[4096];
  const struct inotify_event *event;
  ssize_t len;
  Events myevents[32];
  unsigned int myevents_length = 0;

  if (env->state == NULL)
    return 0;

  // TOMADO DE man inotify
  for (;;) {
    len = read(fd, buf, sizeof(buf));
    if (len == -1 && errno != EAGAIN) {
      return -1;
    }

    if (len <= 0)
      break;

    for (char *ptr = buf; ptr < buf + len;
         ptr += sizeof(struct inotify_event) + event->len) {
      event = (const struct inotify_event *) ptr;

      mask_to_events(event->mask, myevents, &myevents_length);

      send_inotify_event(env, *env->reply_to, 0, event->wd, event->name, myevents, myevents_length);
    }
  }

  return 0;
}

int handle_main(int argc, char **argv) {
  UnifexEnv env;
  int poll_num;
  nfds_t nfds = 2;
  struct pollfd fds[2];
  int done = 0;

  if (unifex_cnode_init(argc, argv, &env)) {
    return 1;
  }

  while (!done) {
    State* state = (State *)env.state;
    if (state == NULL) {
      done = unifex_cnode_receive(&env);
    } else {
      fds[0].fd = state->inotify_fd;
      fds[0].events = POLLIN;

      fds[1].fd = env.ei_socket_fd;
      fds[1].events = POLLIN;

      poll_num = poll(fds, nfds, -1);

      if (poll_num == -1) {
        if (errno == EINTR)
          continue;
        done = 1;
        break;
      }

      if (poll_num > 0) {
        if (fds[0].revents & POLLIN) {
          if (handle_events(&env, fds[0].fd) != 0)
            break;
        }

        if (fds[1].revents & POLLIN) {
          done = unifex_cnode_receive(&env);
        }
      }
    }
  }

  unifex_cnode_destroy(&env);
  return 0;
}

void handle_destroy_state(UnifexEnv *env, State *state) {
  UNIFEX_UNUSED(env);
  close(state->inotify_fd);
}

void mask_to_events(unsigned int mask, Events* events, unsigned int *events_length) {
  int j = 0;

  for(struct mask_to_event_t *map = mask_to_event; map != NULL; map += 1) {
    if (map->mask == -1)
      break;

    if (mask & map->mask) {
      events[j] = map->event;
      j++;
    }
  }

  *events_length = j + 1;
}

unsigned int events_to_mask(Events* events, unsigned int events_length) {
  unsigned int mask = 0;


  for(unsigned  i = 0; i < events_length; i++) {
    for(struct mask_to_event_t *map = mask_to_event; map != NULL; map += 1) {
      if (map->mask == -1)
      break;

      if (events[i] == map->event) {
        mask |= map->mask;
      }
    }
  }

  return mask;
}
