#include <rlang.h>

#include "decl/tests-decl.h"


struct r_test {
  const char* desc;
  bool (*fn_ptr)(void);
};

bool test_that_true_is_true(void) {
  if (true) return r_true; else return r_false;
}
bool test_that_false_is_false(void) {
  if (false) return r_false; else return r_true;
}

enum tests_df {
  TESTS_DF_desc = 0,
  TESTS_DF_fn_ptr,
  TESTS_DF_SIZE
};
static
const char* tests_df_names_c_strings[TESTS_DF_SIZE] = {
  [TESTS_DF_desc] = "desc",
  [TESTS_DF_fn_ptr] = "fn_ptr"
};
static
const enum r_type tests_df_types[TESTS_DF_SIZE] = {
  [TESTS_DF_desc] = R_TYPE_character,
  [TESTS_DF_fn_ptr] = R_TYPE_list
};


extern const struct r_test tests[];

r_obj* ffi_c_tests(void) {
  int n_rows = 0;
  while (tests[n_rows].desc) {
    ++n_rows;
  }

  r_obj* df = KEEP(r_alloc_df_list(n_rows,
                                   tests_df_names,
                                   tests_df_types,
                                   TESTS_DF_SIZE));
  r_init_tibble(df, n_rows);

  r_obj* desc_col = r_list_get(df, TESTS_DF_desc);
  r_obj* fn_ptr_col = r_list_get(df, TESTS_DF_fn_ptr);

  for (int i = 0; i < n_rows; ++i) {
    struct r_test test = tests[i];

    r_chr_poke(desc_col, i, r_str(test.desc));
    r_list_poke(fn_ptr_col, i, r_new_fn_ptr((r_void_fn) test.fn_ptr));
  }

  FREE(1);
  return df;
}

r_obj* ffi_run_c_test(r_obj* fn_ptr) {
  if (r_typeof(fn_ptr) != R_TYPE_pointer) {
    r_stop_unexpected_type(r_typeof(fn_ptr));
  }

  bool (*p)(void) = (bool (*)(void)) r_fn_ptr_addr(fn_ptr);
  return r_lgl(p());
}


// ------------------------------------------------------------------------

r_obj* ffi_r_string(r_obj* str) {
  return r_chr_get(str, 0);
}


// cnd.c

r_obj* ffi_test_r_warn(r_obj* x) {
  r_warn(r_chr_get_c_string(x, 0));
  return r_null;
}

r_obj* ffi_test_Rf_warning(r_obj* msg) {
  Rf_warning("%s", r_chr_get_c_string(msg, 0));
  return r_null;
}
r_obj* ffi_test_Rf_error(r_obj* msg) {
  Rf_error("%s", r_chr_get_c_string(msg, 0));
  return r_null;
}

r_obj* ffi_test_Rf_warningcall(r_obj* call, r_obj* msg) {
  Rf_warningcall(call, "%s", r_chr_get_c_string(msg, 0));
  return r_null;
}
r_obj* ffi_test_Rf_errorcall(r_obj* call, r_obj* msg) {
  Rf_errorcall(call, "%s", r_chr_get_c_string(msg, 0));
  return r_null;
}


// env.c

r_obj* ffi_test_base_ns_get(r_obj* name) {
  return r_base_ns_get(r_chr_get_c_string(name, 0));
}

r_obj* ffi_test_r_env_get(r_obj* env, r_obj* name) {
  return r_env_get(env, r_str_as_symbol(r_chr_get(name, 0)));
}


// formula.c

extern r_obj* r_new_formula(r_obj*, r_obj*, r_obj*);


// parse.c

r_obj* ffi_test_parse(r_obj* str) {
  return r_parse(r_chr_get_c_string(str, 0));
}
r_obj* ffi_test_parse_eval(r_obj* str, r_obj* env) {
  return r_parse_eval(r_chr_get_c_string(str, 0), env);
}


// squash.c

bool rlang_is_clevel_spliceable(r_obj* x) {
  return Rf_inherits(x, "foo");
}


// stack.c

r_obj* ffi_test_sys_call(r_obj* n) {
  return r_sys_call(r_int_get(n, 0), NULL);
}
r_obj* ffi_test_sys_frame(r_obj* n) {
  return r_sys_frame(r_int_get(n, 0), NULL);
}


// vec-lgl.c

r_obj* ffi_test_lgl_sum(r_obj* x, r_obj* na_true) {
  return r_int(r_lgl_sum(x, r_lgl_get(na_true, 0)));
}
r_obj* ffi_test_lgl_which(r_obj* x, r_obj* na_true) {
  return r_lgl_which(x, r_lgl_get(na_true, 0));
}


// vec-chr.c

extern r_obj* chr_prepend(r_obj*, r_obj*);
extern r_obj* chr_append(r_obj*, r_obj*);


// internals/utils.c

r_obj* nms_are_duplicated(r_obj* nms, bool from_last);

r_obj* ffi_test_nms_are_duplicated(r_obj* nms, r_obj* from_last) {
  return nms_are_duplicated(nms, r_lgl_get(from_last, 0));
}


void rlang_init_tests(void) {
  tests_df_names = r_chr_n(tests_df_names_c_strings, TESTS_DF_SIZE);
  r_preserve_global(tests_df_names);
}

static
r_obj* tests_df_names = NULL;
