#include <rlang.h>
#include "internal.h"

#include "decl/cnd-handlers-decl.h"
#include "vec-chr.h"

r_obj* ffi_try_fetch(r_obj* try_fetch_args) {
  r_obj* env = r_node_cadr(try_fetch_args);

  // TODO: `rlang_env_dots_list()` will always clone `handlers`
  // on the way out, and we could probably avoid that by switching
  // to or creating another variant that doesn't clone.
  r_obj* handlers = KEEP(rlang_env_dots_list(env));
  r_env_bind(env, rlang_syms.handlers, handlers);

  if (!r_length(handlers)) {
    FREE(1);
    return r_eval(r_syms.expr, env);
  }

  r_obj* classes = r_names(handlers);
  if (classes == r_null) {
    const char* arg = r_format_error_arg(r_syms.dots);
    r_abort("%s must be named with condition classes.", arg);
  }

  int n = r_length(handlers);
  r_obj* const * v_classes = r_chr_cbegin(classes);

  // Build handlers arguments with updated index into the `handlers` list.
  // See `handler_call` at R level for the template.
  r_obj* args = r_null;
  r_keep_loc shelter; KEEP_HERE(args, &shelter);

  r_obj* exiting_args = r_null;
  r_keep_loc exiting_shelter; KEEP_HERE(exiting_args, &exiting_shelter);

  for (int i = n - 1; i >= 0; --i) {
    r_obj* cls = v_classes[i];

    if (cls == r_strs.error) {
      r_obj* exiting_hnd = KEEP(r_call3(r_syms.brackets2,
                                        rlang_syms.handlers,
                                        r_int(i + 1)));
      exiting_args = r_new_node(exiting_hnd, exiting_args);
      KEEP_AT(exiting_args, exiting_shelter);

      r_node_poke_tag(exiting_args, r_syms.stack_overflow_error);
      FREE(1);
    }

    r_obj* hnd = KEEP(r_copy(hnd_call));

    // Picks up `I`
    r_obj* subscript_node = r_node_cddr(r_node_caar(r_node_cddr(r_node_cadr(r_node_cadr(r_node_cdar(r_node_cdar(r_node_cddr(r_node_cadr(r_node_cdar(r_node_cddr(hnd)))))))))));
    r_node_poke_car(subscript_node, r_int(i + 1));

    // Picks up `CLASS`
    r_obj* class_node = r_node_cdr(r_node_cdar(r_node_cdar(r_node_cdar(r_node_cddr(r_node_cadr(r_node_cdar(r_node_cddr(hnd))))))));
    r_node_poke_car(class_node, r_str_as_character(cls));

    args = r_new_node3(hnd, args, r_syms.condition);

    KEEP_AT(args, shelter);
    FREE(1);
  }

  args = r_new_node(r_syms.expr, args);
  KEEP_AT(args, shelter);

  r_obj* call = r_new_call(rlang_syms.withCallingHandlers, args);
  KEEP_AT(call, shelter);

  // Wrap in a `tryCatch(stackOverflowError = )` call if there are any
  // `error` handlers
  if (exiting_args != r_null) {
    exiting_args = r_new_node(call, exiting_args);
    KEEP_AT(exiting_args, exiting_shelter);

    call = r_new_call(rlang_syms.tryCatch, exiting_args);
    KEEP_AT(call, shelter);
  }

  r_obj* out = r_eval(call, env);

  FREE(3);
  return out;
}


void rlang_init_cnd_handlers(r_obj* ns) {
  hnd_call = r_eval(r_sym("handler_call"), ns);
  r_preserve_global(hnd_call);
}
