#include "rlang.h"
#include "decl/cnd-decl.h"


#define BUFSIZE 8192

#define INTERP(BUF, FMT, DOTS)                  \
  {                                             \
    va_list dots;                               \
    va_start(dots, FMT);                        \
    vsnprintf(BUF, BUFSIZE, FMT, dots);         \
    va_end(dots);                               \
                                                \
    BUF[BUFSIZE - 1] = '\0';                    \
  }

static r_obj* msg_call = NULL;
void r_inform(const char* fmt, ...) {
  char buf[BUFSIZE];
  INTERP(buf, fmt, ...);

  r_eval_with_x(msg_call, KEEP(r_chr(buf)), r_envs.ns);

  FREE(1);
}

static r_obj* wng_call = NULL;
void r_warn(const char* fmt, ...) {
  char buf[BUFSIZE];
  INTERP(buf, fmt, ...);

  r_eval_with_x(wng_call, KEEP(r_chr(buf)), r_envs.ns);

  FREE(1);
}

static r_obj* err_call = NULL;
void r_abort(const char* fmt, ...) {
  char buf[BUFSIZE];
  INTERP(buf, fmt, ...);
  r_obj* message = KEEP(r_chr(buf));

  // Evaluate in a mask but forward error call to the current frame
  r_obj* frame = KEEP(r_peek_frame());
  r_obj* mask = KEEP(r_alloc_environment(2, frame));
  r_env_bind(mask, r_syms.error_call_flag, frame);

  struct r_pair args[] = {
    { r_syms.message, message }
  };

  r_exec_n(r_null, r_syms.abort, args, R_ARR_SIZEOF(args), mask);

  while (1); // No return
}

r_no_return
void r_abort_n(const struct r_pair* args, int n) {
  r_exec_mask_n(r_null, r_syms.abort, args, n, r_peek_frame());
  r_stop_unreachable();
}

r_no_return
void r_abort_call(r_obj* call, const char* fmt, ...) {
  char buf[BUFSIZE];
  INTERP(buf, fmt, ...);
  r_obj* message = KEEP(r_chr(buf));

  struct r_pair args[] = {
    { r_syms.message, message },
    { r_syms.call, call }
  };

  r_obj* frame = KEEP(r_peek_frame());
  r_exec_mask_n(r_null, r_syms.abort, args, R_ARR_SIZEOF(args), frame);

  r_stop_unreachable();
}

void r_cnd_signal(r_obj* cnd) {
  r_eval_with_x(cnd_signal_call, cnd, r_envs.base);
}

// For `R_interrupts_suspended`
#include <R_ext/GraphicsEngine.h>
#include <R_ext/GraphicsDevice.h>

#ifdef _WIN32
#include <Rembedded.h>
void r_interrupt(void) {
  UserBreak = 1;
  R_CheckUserInterrupt();
}
#else
#include <Rinterface.h>
void r_interrupt(void) {
  Rf_onintr();
}
#endif

enum r_cnd_type r_cnd_type(r_obj* cnd) {
  r_obj* classes = r_class(cnd);
  if (r_typeof(cnd) != R_TYPE_list ||
      r_typeof(classes) != R_TYPE_character) {
    goto error;
  }

  r_obj* const * v_classes = r_chr_cbegin(classes);
  r_ssize n_classes = r_length(classes);

  for (r_ssize i = n_classes - 2; i >= 0; --i) {
    r_obj* class_str = v_classes[i];

    if (class_str == r_strs.error) {
      return R_CND_TYPE_error;
    }
    if (class_str == r_strs.warning) {
      return R_CND_TYPE_warning;
    }
    if (class_str == r_strs.message) {
      return R_CND_TYPE_message;
    }
    if (class_str == r_strs.interrupt) {
      return R_CND_TYPE_interrupt;
    }
  }

  if (r_inherits(cnd, "condition")) {
    return R_CND_TYPE_condition;
  }

 error:
  r_abort("`cnd` is not a condition object.");
}


void r_init_library_cnd(void) {
  msg_call = r_parse("message(x)");
  r_preserve(msg_call);

  wng_call = r_parse("warning(x, call. = FALSE)");
  r_preserve(wng_call);

  err_call = r_parse("rlang::abort(x)");
  r_preserve(err_call);

  cnd_signal_call = r_parse("rlang::cnd_signal(x)");
  r_preserve(cnd_signal_call);

  // Silence "'noreturn' attribute does not apply to types warning".
  // It seems like GCC doesn't handle attributes in casts so we need
  // to cast through a typedef.
  // https://stackoverflow.com/questions/9441262/function-pointer-to-attribute-const-function
  typedef r_no_return void (*r_stop_internal_t)(const char*,
                                                int,
                                                r_obj*,
                                                const char* fmt,
                                                ...);
  r_stop_internal = (r_stop_internal_t) R_GetCCallable("rlang", "rlang_stop_internal2");

  r_format_error_arg = (const char* (*)(r_obj*)) r_peek_c_callable("rlang", "rlang_format_error_arg");
  r_obj_type_friendly_full = (const char* (*)(r_obj*, bool, bool)) r_peek_c_callable("rlang", "rlang_obj_type_friendly_full");
}

r_no_return
void (*r_stop_internal)(const char* file,
                        int line,
                        r_obj* call,
                        const char* fmt,
                        ...) = NULL;

static
r_obj* cnd_signal_call = NULL;

const char* (*r_format_error_arg)(r_obj* arg) = NULL;
const char* (*r_obj_type_friendly_full)(r_obj* x, bool value, bool length) = NULL;

const char* r_format_lazy_error_arg(struct r_lazy arg) {
  r_obj* ffi_arg = KEEP(r_lazy_eval(arg));
  const char* out = r_format_error_arg(ffi_arg);
  FREE(1);
  return out;
}
