#include "rlang.h"

#define PRECIOUS_DICT_INIT_SIZE 256

static
struct r_dict* p_precious_dict = NULL;

#include "decl/obj-decl.h"


r_obj* r_vec_clone(r_obj* x) {
  r_obj* out = KEEP(r_clone(x));

  r_obj* names = r_names(x);
  if (names != r_null) {
    r_attrib_poke_names(out, r_clone(names));
  }

  FREE(1);
  return out;
}

r_obj* r_vec_clone_shared(r_obj* x) {
  if (r_is_shared(x)) {
    return r_vec_clone(x);
  }

  r_obj* names = r_names(x);
  if (names != r_null && r_is_shared(names)) {
    r_attrib_poke_names(x, r_clone(names));
    return x;
  }

  return x;
}

void (_r_preserve)(r_obj* x) {
  if (!_r_use_local_precious_list) {
    return;
  }

  r_obj* stack = r_dict_get0(p_precious_dict, x);
  if (!stack) {
    stack = KEEP(new_precious_stack(x));
    r_dict_put(p_precious_dict, x, stack);
    FREE(1);
  }

  push_precious(stack);
}

void (_r_unpreserve)(r_obj* x) {
  if (!_r_use_local_precious_list) {
    return;
  }

  r_obj* stack = r_dict_get0(p_precious_dict, x);
  if (!stack) {
    r_abort("Can't unpreserve `x` because it was not being preserved.");
  }

  int n = pop_precious(stack);

  if (n < 0) {
    r_stop_internal("`n` unexpectedly < 0.");
  }
  if (n == 0) {
    r_dict_del(p_precious_dict, x);
  }
}

static
r_obj* new_precious_stack(r_obj* x) {
  r_obj* stack = KEEP(r_alloc_list(2));

  // Store (0) protection count and (1) element to protect
  r_list_poke(stack, 0, r_int(0));
  r_list_poke(stack, 1, x);

  FREE(1);
  return stack;
}

static
int push_precious(r_obj* stack) {
  r_obj* n = r_list_get(stack, 0);
  int* p_n = r_int_begin(n);
  return ++(*p_n);
}

static
int pop_precious(r_obj* stack) {
  r_obj* n = r_list_get(stack, 0);
  int* p_n = r_int_begin(n);
  return --(*p_n);
}

// For unit tests
struct r_dict* rlang__precious_dict(void) {
  return p_precious_dict;
}


enum r_type r_chr_as_r_type(r_obj* type) {
  if (!r_is_string(type)) {
    r_abort("`type` must be a character string.");
  }
  return r_c_str_as_r_type(r_chr_get_c_string(type, 0));
}

const char* obj_address_formatter = "%p";

r_obj* r_obj_address(r_obj* x) {
  static char buf[1000];
  snprintf(buf, 1000, obj_address_formatter, (void*) x);
  return Rf_mkChar(buf);
}

r_obj* (*r_obj_encode_utf8)(r_obj* x) = NULL;


r_obj* r_as_label(r_obj* x) {
  return r_eval_with_x(as_label_call, x, r_ns_env("rlang"));
}


void r_init_library_obj(r_obj* ns) {
  p_precious_dict = r_new_dict(PRECIOUS_DICT_INIT_SIZE);
  KEEP(p_precious_dict->shelter);
  r_env_bind(ns,
             r_sym(".__rlang_lib_precious_dict__."),
             p_precious_dict->shelter);
  FREE(1);

  // The Microsoft C library doesn't implement the hexadecimal
  // formatter correctly
  const char* null_addr = r_str_c_string(r_obj_address(r_null));
  if (null_addr[0] != '0' || null_addr[1] != 'x') {
    obj_address_formatter = "0x%p";
  }

  r_obj_encode_utf8 = (r_obj* (*)(r_obj*)) r_peek_c_callable("rlang", "rlang_obj_encode_utf8");

  as_label_call = r_parse("as_label(x)");
  r_preserve_global(as_label_call);
}

static r_obj* as_label_call = NULL;

