#include "inotify.h"
#include <stdio.h>

UnifexState *unifex_alloc_state(UnifexEnv *_env) {
  UNIFEX_UNUSED(_env);
  return (UnifexState *)malloc(sizeof(UnifexState));
}

void unifex_release_state(UnifexEnv *env, UnifexState *state) {
  unifex_cnode_add_to_released_states(env, state);
}

void unifex_cnode_destroy_state(UnifexEnv *env, void *state) {
  handle_destroy_state(env, (UnifexState *)state);
  free(state);
}

UNIFEX_TERM init_result_ok(UnifexEnv *env, UnifexState *state) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_atom(out_buff, "ok");
  {
    UnifexState *unifex_state = state;
    env->state = unifex_state;
  };

  return out_buff;
}

UNIFEX_TERM init_result_error(UnifexEnv *env, char const *error) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_tuple_header(out_buff, 2);
  ei_x_encode_atom(out_buff, "error");
  ei_x_encode_binary(out_buff, error, strlen(error));

  return out_buff;
}

UNIFEX_TERM ex_inotify_add_watch_result_ok(UnifexEnv *env, int wd) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_tuple_header(out_buff, 2);
  ei_x_encode_atom(out_buff, "ok");
  ({
    int tmp_int = wd;
    ei_x_encode_longlong(out_buff, (long long)tmp_int);
  });

  return out_buff;
}

UNIFEX_TERM ex_inotify_add_watch_result_error(UnifexEnv *env,
                                              char const *error) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_tuple_header(out_buff, 2);
  ei_x_encode_atom(out_buff, "error");
  ei_x_encode_binary(out_buff, error, strlen(error));

  return out_buff;
}

UNIFEX_TERM ex_inotify_rm_watch_result(UnifexEnv *env) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_atom(out_buff, "ok");

  return out_buff;
}

UNIFEX_TERM ex_inotify_rm_watch_result_error(UnifexEnv *env,
                                             char const *error) {
  UNIFEX_TERM out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  unifex_cnode_prepare_ei_x_buff(env, out_buff, "result");

  ei_x_encode_tuple_header(out_buff, 2);
  ei_x_encode_atom(out_buff, "error");
  ei_x_encode_binary(out_buff, error, strlen(error));

  return out_buff;
}

UNIFEX_TERM init_caller(UnifexEnv *env, UnifexCNodeInBuff *in_buff) {
  UNIFEX_MAYBE_UNUSED(in_buff);
  UNIFEX_TERM result;

  result = init(env);
  goto exit_init_caller;
exit_init_caller:

  return result;
}

UNIFEX_TERM ex_inotify_add_watch_caller(UnifexEnv *env,
                                        UnifexCNodeInBuff *in_buff) {
  UNIFEX_MAYBE_UNUSED(in_buff);
  UNIFEX_TERM result;
  char *pathname;
  Events *events;
  unsigned int events_length;
  int opts;
  pathname = NULL;
  events = NULL;

  if (({
        int type;
        int size;
        long len;
        ei_get_type(in_buff->buff, in_buff->index, &type, &size);
        size = size + 1; // for NULL byte
        pathname = (char *)malloc(sizeof(char) * size);
        memset(pathname, 0, size);
        ei_decode_binary(in_buff->buff, in_buff->index, pathname, &len);
      })) {
    result = unifex_raise(
        env,
        "Unifex CNode: cannot parse argument 'pathname' of type ':string'");
    goto exit_ex_inotify_add_watch_caller;
  }

  if (({
        int type;
        int size;

        ei_get_type(in_buff->buff, in_buff->index, &type, &size);
        events_length = (unsigned int)size;

        int index = 0;
        UnifexCNodeInBuff unifex_buff;
        UnifexCNodeInBuff *unifex_buff_ptr = &unifex_buff;
        if (type == ERL_STRING_EXT) {
          ei_x_buff buff = unifex_cnode_string_to_list(in_buff, events_length);
          unifex_buff.buff = buff.buff;
          unifex_buff.index = &index;
        } else {
          unifex_buff.buff = in_buff->buff;
          unifex_buff.index = in_buff->index;
        }
        int header_res = ei_decode_list_header(unifex_buff_ptr->buff,
                                               unifex_buff_ptr->index, &size);
        events_length = (unsigned int)size;
        events = (Events *)malloc(sizeof(Events) * events_length);

        for (unsigned int i = 0; i < events_length; i++) {
        }

        for (unsigned int i = 0; i < events_length; i++) {
          if (({
                int res = 1;
                char *enum_as_string = NULL;

                if (!({
                      enum_as_string = (char *)unifex_alloc(MAXATOMLEN);
                      ei_decode_atom(unifex_buff_ptr->buff,
                                     unifex_buff_ptr->index, enum_as_string);
                    })) {
                  if (strcmp(enum_as_string, "notify_in_access") == 0) {
                    events[i] = NOTIFY_IN_ACCESS;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_attrib") == 0) {
                    events[i] = NOTIFY_IN_ATTRIB;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_close_write") ==
                             0) {
                    events[i] = NOTIFY_IN_CLOSE_WRITE;
                    res = 0;
                  } else if (strcmp(enum_as_string,
                                    "notify_in_close_nowrite") == 0) {
                    events[i] = NOTIFY_IN_CLOSE_NOWRITE;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_create") == 0) {
                    events[i] = NOTIFY_IN_CREATE;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_delete") == 0) {
                    events[i] = NOTIFY_IN_DELETE;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_delete_self") ==
                             0) {
                    events[i] = NOTIFY_IN_DELETE_SELF;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_modify") == 0) {
                    events[i] = NOTIFY_IN_MODIFY;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_moved_from") ==
                             0) {
                    events[i] = NOTIFY_IN_MOVED_FROM;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_moved_to") ==
                             0) {
                    events[i] = NOTIFY_IN_MOVED_TO;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_open") == 0) {
                    events[i] = NOTIFY_IN_OPEN;
                    res = 0;
                  } else if (strcmp(enum_as_string, "notify_in_move") == 0) {
                    events[i] = NOTIFY_IN_MOVE;
                    res = 0;
                  }

                  if (enum_as_string != NULL) {
                    unifex_free((void *)enum_as_string);
                  }
                }

                res;
              })) {
            result = unifex_raise(env, "Unifex CNode: cannot parse argument "
                                       "'events' of type '{:list, :events}'");
            goto exit_ex_inotify_add_watch_caller;
          }
        }
        if (events_length) {
          header_res = ei_decode_list_header(unifex_buff_ptr->buff,
                                             unifex_buff_ptr->index, &size);
        }
        header_res;
      })) {
    result = unifex_raise(env, "Unifex CNode: cannot parse argument 'events' "
                               "of type '{:list, :events}'");
    goto exit_ex_inotify_add_watch_caller;
  }

  if (({
        long long tmp_longlong;
        int result =
            ei_decode_longlong(in_buff->buff, in_buff->index, &tmp_longlong);
        opts = (int)tmp_longlong;
        result;
      })) {
    result = unifex_raise(
        env, "Unifex CNode: cannot parse argument 'opts' of type ':int'");
    goto exit_ex_inotify_add_watch_caller;
  }

  result = ex_inotify_add_watch(env, pathname, events, events_length, opts);
  goto exit_ex_inotify_add_watch_caller;
exit_ex_inotify_add_watch_caller:
  unifex_free(pathname);
  if (events != NULL) {
    for (unsigned int i = 0; i < events_length; i++) {
    }
    unifex_free(events);
  }

  return result;
}

UNIFEX_TERM ex_inotify_rm_watch_caller(UnifexEnv *env,
                                       UnifexCNodeInBuff *in_buff) {
  UNIFEX_MAYBE_UNUSED(in_buff);
  UNIFEX_TERM result;
  int wd;

  if (({
        long long tmp_longlong;
        int result =
            ei_decode_longlong(in_buff->buff, in_buff->index, &tmp_longlong);
        wd = (int)tmp_longlong;
        result;
      })) {
    result = unifex_raise(
        env, "Unifex CNode: cannot parse argument 'wd' of type ':int'");
    goto exit_ex_inotify_rm_watch_caller;
  }

  result = ex_inotify_rm_watch(env, wd);
  goto exit_ex_inotify_rm_watch_caller;
exit_ex_inotify_rm_watch_caller:

  return result;
}

int send_inotify_event(UnifexEnv *env, UnifexPid pid, int flags, int wd,
                       char const *name, Events const *events,
                       unsigned int events_length) {
  UNIFEX_UNUSED(flags);
  ei_x_buff *out_buff = (ei_x_buff *)malloc(sizeof(ei_x_buff));
  ei_x_new_with_version(out_buff);

  ei_x_encode_tuple_header(out_buff, 4);
  ei_x_encode_atom(out_buff, "inotify_event");
  ({
    int tmp_int = wd;
    ei_x_encode_longlong(out_buff, (long long)tmp_int);
  });

  ei_x_encode_binary(out_buff, name, strlen(name));
  ({
    ei_x_encode_list_header(out_buff, events_length);
    for (unsigned int i = 0; i < events_length; i++) {
      ({
        if (events[i] == NOTIFY_IN_ACCESS) {
          char *enum_as_string = "notify_in_access";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_ATTRIB) {
          char *enum_as_string = "notify_in_attrib";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_CLOSE_WRITE) {
          char *enum_as_string = "notify_in_close_write";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_CLOSE_NOWRITE) {
          char *enum_as_string = "notify_in_close_nowrite";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_CREATE) {
          char *enum_as_string = "notify_in_create";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_DELETE) {
          char *enum_as_string = "notify_in_delete";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_DELETE_SELF) {
          char *enum_as_string = "notify_in_delete_self";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_MODIFY) {
          char *enum_as_string = "notify_in_modify";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_MOVED_FROM) {
          char *enum_as_string = "notify_in_moved_from";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_MOVED_TO) {
          char *enum_as_string = "notify_in_moved_to";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else if (events[i] == NOTIFY_IN_OPEN) {
          char *enum_as_string = "notify_in_open";
          ei_x_encode_atom(out_buff, enum_as_string);

        } else {
          char *enum_as_string = "notify_in_move";
          ei_x_encode_atom(out_buff, enum_as_string);
        }
      });
    }
    ei_x_encode_empty_list(out_buff);
  });

  unifex_cnode_send_and_free(env, &pid, out_buff);
  return 1;
}

UNIFEX_TERM unifex_cnode_handle_message(UnifexEnv *env, char *fun_name,
                                        UnifexCNodeInBuff *in_buff) {
  if (strcmp(fun_name, "init") == 0) {
    return init_caller(env, in_buff);
  } else if (strcmp(fun_name, "ex_inotify_add_watch") == 0) {
    return ex_inotify_add_watch_caller(env, in_buff);
  } else if (strcmp(fun_name, "ex_inotify_rm_watch") == 0) {
    return ex_inotify_rm_watch_caller(env, in_buff);
  } else {
    return unifex_cnode_undefined_function_error(env, fun_name);
  }
}

int main(int argc, char **argv) { return handle_main(argc, argv); }
