#include "../../include/rcore.h"
#include "../../core/allocations.h"
#include "../../include/globals.h"
#include "../../include/graph.h"
#include "../../include/learning.h"
#include "../../math/linear.algebra.h"
#include "../../math/sparse.amat.h"
#include "../../minimal/common.h"
#include "../../minimal/strings.h"
#include "../../scores/scores.h"
#include "search.state.h"

SEXP score_cache_fill(SEXP nodes, SEXP data, SEXP score,
    SEXP extra, SEXP reference, SEXP equivalence, SEXP decomposability,
    SEXP updated, SEXP state, SEXP blacklist, SEXP debug) {

int nnodes = length(nodes), lupd = length(updated);
int *upd = NULL, *b = NULL, nblack = length(blacklist);
int i = 0, j = 0, k = 0;
double *cache_value = NULL, *ref = NULL;
bool debugging = isTRUE(debug);
bool decomp = isTRUE(decomposability), equiv = isTRUE(equivalence);
search_state *st = search_state_from_SEXP(state);
sparse_amat base = { 0 }, cand = { 0 };
SEXP arc, cur;

  /* save a pointer to the blacklist (a sorted set of flat CMC() arc
   * coordinates) and the updated nodes. */
  b = INTEGER(blacklist);
  upd = INTEGER(updated);

  /* if there are no nodes to update, return. */
  if (lupd == 0) return R_NilValue;

  /* build the base parents (transposed) CSR snapshot of the current network from
   * the coordinate set held in the state: candidates differ from it by one arc
   * and are scored off spliced copies. */
  base = sparse_amat_from_coords(st->coords, st->narcs, nodes, TRUE);

  /* reusable candidate CSR: capacity narcs + 1 (a single arc addition), row
   * offsets of length nnodes + 1, and the labels shared with the base. */
  cand.rowptr = Calloc1D(nnodes + 1, sizeof(int));
  cand.colidx = Calloc1D(base.narcs + 1, sizeof(int));

  cache_value = st->cache;
  ref = REAL(reference);

  /* the target for the fast path; the arc for the fallback. */
  PROTECT(cur = allocVector(STRSXP, 1));
  PROTECT(arc = allocVector(STRSXP, 2));

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

    for (j = 0; j < nnodes; j++) {

       /* incident nodes must be different from each other. */
       if (i == j) continue;

       /* if only one or two nodes' caches need updating, skip the rest. */
       for (k = 0; k < lupd; k++)
         if (upd[k] == j)
           goto there;

       continue;

there:

       /* no need to compute the score delta for blacklisted arcs. */
       if (coord_set_member(b, nblack, CMC(i, j, nnodes)))
         continue;

       /* use score equivalence if possible to check only one orientation. */
       if (equiv) {

         /* the parent counts come straight from the transposed CSR row lengths.
          * if the following conditions are met, look up the score delta of the
          * reverse of the current arc:
          *   1) that score delta has already been computed.
          *   2) both incident nodes have no parent, so the arc is really
          *      score equivalent (no v-structures).
          *   3) the reversed arc has not been blacklisted, as the score delta
          *      is not computed in this case. */
         int npi = base.rowptr[i + 1] - base.rowptr[i];
         int npj = base.rowptr[j + 1] - base.rowptr[j];

         if ((i > j) && (npi + npj == 0) &&
             !coord_set_member(b, nblack, CMC(j, i, nnodes))) {

           cache_value[CMC(i, j, nnodes)] = cache_value[CMC(j, i, nnodes)];
           continue;

         }/*THEN*/

       }/*THEN*/

       /* arc i -> j is added if absent (set) or removed if present (drop); in
        * the transposed matrix i is a parent of j, so look up row j. */
       bool present = has_arc(base, j, i);

       if (decomp) {

         /* C-native decomposable delta: score the arc's head under the
          * candidate structure and compare it against the reference. */
         double newval = 0;

         SET_STRING_ELT(cur, 0, STRING_ELT(nodes, j));
         sparse_amat_candidate(base, &cand, j, i, !present);
         c_per_node_score(cand, data, score, cur, extra, FALSE, &newval);
         cache_value[CMC(i, j, nnodes)] = robust_score_difference(ref[j], 0,
                                            newval, 0);
         test_counter++;

       }/*THEN*/
       else {

         /* non-decomposable (Castelo-Siebes/marginal prior) path: the score
          * delta is not separable per node, so compute it off the base parents
          * CSR with the coord-native score-delta core. */
         double newval[2];

         SET_STRING_ELT(arc, 0, STRING_ELT(nodes, i));
         SET_STRING_ELT(arc, 1, STRING_ELT(nodes, j));
         cache_value[CMC(i, j, nnodes)] = score_delta_cs_core(arc, i, j, base,
           data, score, reference, present ? "drop" : "set", extra, newval);

       }/*ELSE*/

       if (debugging)
         Rprintf("* caching score delta for arc %s -> %s (%lf).\n",
           CHAR(STRING_ELT(nodes, i)), CHAR(STRING_ELT(nodes, j)),
            cache_value[CMC(i, j, nnodes)]);

    }/*FOR*/

  }/*FOR*/

  UNPROTECT(2);

  Free1D(cand.rowptr);
  Free1D(cand.colidx);
  FreeSparseAMAT(base);

  return R_NilValue;

}/*SCORE_CACHE_FILL*/

void bestop_update(SEXP bestop, char *op, const char *from, const char *to) {

  SET_VECTOR_ELT(bestop, 0, mkString(op));
  SET_VECTOR_ELT(bestop, 1, mkString(from));
  SET_VECTOR_ELT(bestop, 2, mkString(to));

}/*BESTOP_UPDATE*/

SEXP hc_to_be_added(SEXP arcs, SEXP blacklist, SEXP whitelist, SEXP nparents,
    SEXP maxp, SEXP nodes) {

int i = 0, j = 0, k = 0, p = 0, nnodes = length(nodes), count = 0;
int nbl = 0, nwl = 0;
int *bl = NULL, *wl = NULL, *cand = NULL;
double *mp = REAL(maxp), *np = NULL;
sparse_amat sam = { 0 }, listsam = { 0 };
SEXP result;

  /* the current network as a (children) CSR. */
  sam = sparse_amat_from_arcs(arcs, nodes);

  /* the blacklist and the whitelist as sorted arc-coordinate sets. */
  if (!isNull(blacklist)) {

    listsam = sparse_amat_from_arcs(blacklist, nodes);
    nbl = listsam.narcs;
    if (nbl > 0) {
      bl = Calloc1D(nbl, sizeof(int));
      sparse_amat_coords(listsam, bl);
    }
    FreeSparseAMAT(listsam);

  }/*THEN*/

  if (!isNull(whitelist)) {

    listsam = sparse_amat_from_arcs(whitelist, nodes);
    nwl = listsam.narcs;
    if (nwl > 0) {
      wl = Calloc1D(nwl, sizeof(int));
      sparse_amat_coords(listsam, wl);
    }
    FreeSparseAMAT(listsam);

  }/*THEN*/

  /* the parent count of each node: provided, or the in-degree read off the CSR. */
  if (nparents == R_NilValue) {

    np = Calloc1D(nnodes, sizeof(double));
    for (i = 0; i < nnodes; i++)
      for (p = sam.rowptr[i]; p < sam.rowptr[i + 1]; p++)
        np[sam.colidx[p]]++;

  }/*THEN*/
  else {

    np = REAL(nparents);

  }/*ELSE*/

  /* collect the addable arcs (plus the whitelisted ones, which are forced in) in
   * the from-major order amat2arcs() would produce. */
  cand = Calloc1D((size_t) nnodes * (nnodes - 1), sizeof(int));
  for (i = 0; i < nnodes; i++)
    for (j = 0; j < nnodes; j++)
      if (is_addable(sam, i, j, np, mp, bl, nbl, nnodes) ||
          ((i != j) && coord_set_member(wl, nwl, CMC(i, j, nnodes))))
        cand[count++] = CMC(i, j, nnodes);

  /* build the from/to arc set. */
  PROTECT(result = allocMatrix(STRSXP, count, 2));
  setDimNames(result, R_NilValue, mkStringVec(2, "from", "to"));
  for (k = 0; k < count; k++) {

    SET_STRING_ELT(result, k, STRING_ELT(nodes, cand[k] % nnodes));
    SET_STRING_ELT(result, k + count, STRING_ELT(nodes, cand[k] / nnodes));

  }/*FOR*/

  if (nparents == R_NilValue)
    Free1D(np);
  Free1D(bl);
  Free1D(wl);
  Free1D(cand);
  FreeSparseAMAT(sam);

  UNPROTECT(1);

  return result;

}/*HC_TO_BE_ADDED*/

