#include "../../include/rcore.h"
#include "../../core/allocations.h"
#include "../../math/sparse.amat.h"
#include "search.state.h"

/* mix a single arc coordinate into a 64-bit value (the splitmix64 finaliser): a
 * bijection, so distinct in-range coordinates get distinct nonzero values. */
uint64_t coord_hash(int coord) {

uint64_t z = (uint64_t) coord + 0x9E3779B97F4A7C15ULL;

  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
  z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;

  return z ^ (z >> 31);

}/*COORD_HASH*/

/* the fingerprint of a network is the XOR of its arcs' coordinate hashes; the
 * XOR makes it incremental, since candidates differ by one or two arcs. */
static uint64_t network_hash(int *coords, int narcs) {

uint64_t h = 0;

  for (int k = 0; k < narcs; k++)
    h ^= coord_hash(coords[k]);

  return h;

}/*NETWORK_HASH*/

/* free the C-side search state when its external pointer is garbage collected. */
static void search_state_finalizer(SEXP state) {

search_state *st = (search_state *) R_ExternalPtrAddr(state);

  if (!st)
    return;

  Free1D(st->tabu_hashes);
  Free1D(st->tabu_len);
  Free1D(st->cache);
  Free1D(st->coords);
  Free1D(st);

  R_ClearExternalPtr(state);

}/*SEARCH_STATE_FINALIZER*/

/* allocate the search state and wrap it in an external pointer with a
 * finalizer, so the tabu ring lives in C across the search iterations. */
SEXP search_state_create(SEXP nodes, SEXP tabu) {

search_state *st = NULL;
SEXP state;

  st = Calloc1D(1, sizeof(search_state));
  st->nnodes = length(nodes);
  st->cache = Calloc1D((size_t) st->nnodes * st->nnodes, sizeof(double));
  /* the current network, at most nnodes * (nnodes - 1) directed arcs. */
  st->coords = Calloc1D((size_t) st->nnodes * (st->nnodes - 1), sizeof(int));
  st->narcs = 0;
  st->tabu_size = asInteger(tabu);
  st->tabu_hashes = Calloc1D(st->tabu_size, sizeof(uint64_t));
  st->tabu_len = Calloc1D(st->tabu_size, sizeof(int));

  /* mark all the ring slots as empty (never written). */
  for (int i = 0; i < st->tabu_size; i++)
    st->tabu_len[i] = -1;

  PROTECT(state = R_MakeExternalPtr(st, R_NilValue, R_NilValue));
  R_RegisterCFinalizerEx(state, search_state_finalizer, TRUE);
  UNPROTECT(1);

  return state;

}/*SEARCH_STATE_CREATE*/

search_state *search_state_from_SEXP(SEXP state) {

  return (search_state *) R_ExternalPtrAddr(state);

}/*SEARCH_STATE_FROM_SEXP*/

/* sync the current network stored in the state (as a sorted arc-coordinate set)
 * from an R arc set. */
SEXP search_set_network(SEXP state, SEXP arcs, SEXP nodes) {

search_state *st = search_state_from_SEXP(state);
sparse_amat sam = { 0 };

  sam = sparse_amat_from_arcs(arcs, nodes);
  st->narcs = sam.narcs;
  sparse_amat_coords(sam, st->coords);
  st->current_hash = network_hash(st->coords, st->narcs);
  FreeSparseAMAT(sam);

  return R_NilValue;

}/*SEARCH_SET_NETWORK*/

/* store the fingerprint of the current network into the tabu ring slot
 * `current`, replacing whatever was there. The current network (and its hash)
 * must already have been synced into the state by search_set_network(). */
SEXP search_hash(SEXP state, SEXP current) {

search_state *st = search_state_from_SEXP(state);
int cur = asInteger(current);

  st->tabu_hashes[cur] = st->current_hash;
  st->tabu_len[cur] = st->narcs;

  return R_NilValue;

}/*SEARCH_HASH*/

/* look up a candidate network (by its fingerprint) in the tabu ring; return its
 * 1-based slot index, or 0 if it is not there. */
int tabu_match(search_state *st, int cur, uint64_t hash) {

int i = 0, ntabu = st->tabu_size, slot = 0;

  for (i = 0; i < ntabu; i++) {

    slot = (cur + i) % ntabu;

    /* an empty slot (never written) cannot match. */
    if (st->tabu_len[slot] < 0)
      continue;

    if (st->tabu_hashes[slot] == hash)
      return slot + 1;

  }/*FOR*/

  return 0;

}/*TABU_MATCH*/
