#include "rlang.h"

/**
 * - If `sentinel` is found in the first node: `parent_out` is `r_null`
 * - If `sentinel` is not found: both return value and `parent_out`
 *   are `r_null`
 * - If `sentinel` is `r_null`, this is like a full shallow duplication
 *   but returns tail node
 */
r_obj* r_pairlist_clone_until(r_obj* node, r_obj* sentinel, r_obj** parent_out) {
  r_obj* parent = r_null;
  r_obj* cur = node;
  int n_kept = 0;

  while (true) {
    if (cur == sentinel) {
      FREE(n_kept);
      *parent_out = parent;
      return node;
    }
    // Return NULL if sentinel is not found
    if (cur == r_null) {
      FREE(n_kept);
      *parent_out = r_null;
      return r_null;
    }

    r_obj* tag = r_node_tag(cur);
    cur = r_new_node(r_node_car(cur), r_node_cdr(cur));
    r_node_poke_tag(cur, tag);

    if (parent == r_null) {
      KEEP_N(cur, &n_kept);
      node = cur;
    } else {
      r_node_poke_cdr(parent, cur);
    }

    parent = cur;
    cur = r_node_cdr(cur);
  }

  r_stop_unreachable();
}


r_obj* r_attrs_set_at(r_obj* attrs, r_obj* node, r_obj* value) {
  r_obj* sentinel = r_node_cdr(node);
  r_obj* new_node = r_null;

  attrs = KEEP(r_pairlist_clone_until(attrs, sentinel, &new_node));
  r_node_poke_car(new_node, value);

  FREE(1);
  return attrs;
}
r_obj* r_attrs_zap_at(r_obj* attrs, r_obj* node, r_obj* value) {
  r_obj* sentinel = node;
  r_obj* new_node = r_null;

  attrs = KEEP(r_pairlist_clone_until(attrs, sentinel, &new_node));

  if (new_node == r_null) {
    // `node` is the first node of `attrs`
    attrs = r_node_cdr(attrs);
  } else {
    r_node_poke_cdr(new_node, r_node_cdr(node));
  }

  FREE(1);
  return attrs;
}
r_obj* r_clone2(r_obj* x) {
  r_obj* attrs = KEEP(r_attrib(x));

  // Prevent attributes from being cloned
  r_poke_attrib(x, r_null);
  r_obj* out = r_clone(x);
  r_poke_attrib(x, attrs);
  r_poke_attrib(out, attrs);

  FREE(1);
  return out;
}

r_obj* r_attrib_set(r_obj* x, r_obj* tag, r_obj* value) {
  r_obj* attrs = r_attrib(x);
  r_obj* out = KEEP(r_clone2(x));

  r_obj* node = attrs;
  while (node != r_null) {
    if (r_node_tag(node) == tag) {
      if (value == r_null) {
        attrs = r_attrs_zap_at(attrs, node, value);
      } else {
        attrs = r_attrs_set_at(attrs, node, value);
      }
      r_poke_attrib(out, attrs);

      FREE(1);
      return out;
    }

    node = r_node_cdr(node);
  }

  if (value != r_null) {
    // Just add to the front if attribute does not exist yet
    attrs = KEEP(r_new_node(out, attrs));
    r_node_poke_tag(attrs, tag);
    r_node_poke_car(attrs, value);
    r_poke_attrib(out, attrs);
    FREE(1);
  }

  FREE(1);
  return out;
}

bool r_is_named(r_obj* x) {
  r_obj* nms = r_names(x);

  if (r_typeof(nms) != R_TYPE_character) {
    return false;
  }

  if (r_chr_has(nms, "")) {
    return false;
  }

  return true;
}

void r_attrib_poke_classes(r_obj* x, const char** classes, r_ssize n) {
  r_obj* classes_chr = KEEP(r_chr_n(classes, n));
  r_attrib_poke(x, r_syms.class_, classes_chr);
  FREE(1);
}
