#include "../include/rcore.h"
#include "../core/allocations.h"
#include "../core/sets.h"
#include "../include/globals.h"
#include "../math/linear.algebra.h"
#include "../minimal/common.h"
#include "../minimal/data.frame.h"
#include "scores.h"

double c_fast_ccgnal(double *xx, double **gp, int ngp, int nobs,
    int *config, int nconfig, double k) {

int i = 0, j = 0, nc = 0;
double res = 0, *fitted = NULL, *sd = NULL;

  /* allocate the fitted values and the standard error. */
  fitted = Calloc1D(nobs, sizeof(double));
  sd = Calloc1D((!config) ? 1 : nconfig, sizeof(double));

  /* if the regression is conditional on config, iterate over its values,
   * otherwise fit using the whole sample. */
  if (!config) {

    c_ols(gp, xx, nobs, ngp, fitted, NULL, NULL, sd, &nc, TRUE);

    /* compute the log-likelihood. */
    if ((*sd < MACHINE_TOL) || (nc == 0)) {

      /* singular distributions and those with no locally-complete observations
       * are scored to prevent their selection in structure learning. */
      res = R_NegInf;

    }/*THEN*/
    else {

      for (j = 0; j < nobs; j++)
        if (!ISNAN(xx[j]) && !ISNAN(fitted[j]) && !ISNAN(*sd))
          res += dnorm(xx[j], fitted[j], *sd, TRUE);

    }/*ELSE*/

  }/*THEN*/
  else {

    c_cls(gp, xx, config, nobs, ngp, nconfig, fitted, NULL, NULL, sd, &nc,
      TRUE);

    /* if any standard error is zero, the model is singular; if it is undefined,
     * there are no locally-complete observations. */
    for (i = 0; i < nconfig; i++) {

      if ((sd[i] < MACHINE_TOL) || ISNAN(sd[i])) {

        res = R_NegInf;
        goto end;

      }/*FOR*/

    }/*FOR*/

    /* if the model is not singular compute the log-likelihood. */
    for (j = 0; j < nobs; j++)
      if (!ISNAN(xx[j]) && !ISNAN(fitted[j]) && !ISNAN(sd[config[j] - 1]))
        res += dnorm(xx[j], fitted[j], sd[config[j] - 1], TRUE);

  }/*ELSE*/

  /* average over the locally-complete data... */
  res /= nc;
  /* and add the penalty term, scaled by the original sample size. */
  res -= k / nobs * (ngp + 2) * ((!config) ? 1 : nconfig);

end:

  Free1D(fitted);
  Free1D(sd);

  return res;

}/*C_FAST_CCGNAL*/

double ccgnal(double *xx, int nobs, int **dp, int *nlevels, int ndp,
    double **gp, int ngp, double k) {

int nconfig = 0;
int *config = NULL;
double res = 0;

  /* build the configurations of the discrete parents. */
  config = Calloc1D(nobs, sizeof(int));
  c_fast_config(dp, nobs, ndp, nlevels, config, &nconfig, 1);

  res = c_fast_ccgnal(xx, gp, ngp, nobs, config, nconfig, k);

  Free1D(config);

  return res;

}/*CCGNAL*/

double nal_cgnode(SEXP target, sparse_amat sam, tabular dt, double k,
    bool debugging) {

double nal = 0, **gp = NULL;
int i = 0, npar = 0, node_col = 0, node = 0, ndp = 0, ngp = 0, nconfig = 0;
int it = 0;
int **dp = NULL, *nlevels = NULL, *config = NULL;
char *t = (char *)CHAR(STRING_ELT(target, 0));
bool node_discrete = FALSE;

  /* get the parents of the node from the sparse adjacency matrix. */
  it = sparse_amat_node(sam, t);
  npar = sam.rowptr[it + 1] - sam.rowptr[it];

  /* locate the node's column in the tabular data. */
  node_col = tabular_column(dt, t);
  node = dt.map[node_col];
  node_discrete = dt.m.flag[node_col].discrete;

  if (npar == 0) {

    /* no parents, reuse the marginal likelihoods. */
    if (node_discrete)
      nal = nal_dnode_root(dt.dcol[node], dt.nlvl[node], dt.m.nobs, k);
    else
      nal = glik_incomplete(dt.ccol[node], dt.m.nobs, k);

  }/*THEN*/
  else {

    /* gather the discrete and continuous parents' columns separately. */
    dp = Calloc1D(npar, sizeof(int *));
    nlevels = Calloc1D(npar, sizeof(int));
    gp = Calloc1D(npar, sizeof(double *));

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

      int pc = tabular_column(dt, sam.labels[sam.colidx[sam.rowptr[it] + i]]);
      int slot = dt.map[pc];

      if (dt.m.flag[pc].discrete) {

        dp[ndp] = dt.dcol[slot];
        nlevels[ndp++] = dt.nlvl[slot];

      }/*THEN*/
      else {

        gp[ngp++] = dt.ccol[slot];

      }/*ELSE*/

    }/*FOR*/

    if (node_discrete) {

      /* discrete nodes are not allowed to have continuous parents; then reuse
       * the conditional discrete likelihood. */
      if (ngp != 0) {

        nal = R_NegInf;

      }/*THEN*/
      else {

        config = Calloc1D(dt.m.nobs, sizeof(int));
        c_fast_config(dp, dt.m.nobs, ndp, nlevels, config, &nconfig, 1);
        nal = nal_dnode_parents(dt.dcol[node], dt.nlvl[node], config, nconfig,
                dt.m.nobs, k);
        Free1D(config);

      }/*ELSE*/

    }/*THEN*/
    else {

      if (ndp == 0)
        nal = cglik_incomplete(dt.ccol[node], dt.m.nobs, gp, ngp, k);
      else
        nal = ccgnal(dt.ccol[node], dt.m.nobs, dp, nlevels, ndp, gp, ngp, k);

    }/*ELSE*/

    Free1D(dp);
    Free1D(nlevels);
    Free1D(gp);

  }/*ELSE*/

  if (debugging)
    Rprintf("  > log-likelihood is %lf.\n", nal);

  return nal;

}/*NAL_CGNODE*/

