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

double pgnode(double *xx, int nobs, double *new_xx, int new_nobs,
    double *nparams) {

int i = 0;
double xm = 0, res = 0;
long double sd = 0;

  /* compute the mean, */
  for (i = 0; i < nobs; i++)
    xm += xx[i];
  xm /= nobs;

  /* compute the standard deviation. */
  if (nobs == 0)
    sd = NA_REAL;
  else if (nobs == 1)
    sd = 0;
  else {

    for (i = 0; i < nobs; i++)
      sd += (xx[i] - xm) * (xx[i] - xm);

    sd = sqrt(sd / (nobs - 1));

  }/*ELSE*/

  /* compute the log-likelihood (singular models have zero density). */
  if (sd < MACHINE_TOL)
    res = R_NegInf;
  else
    for (i = 0; i < new_nobs; i++)
      res += dnorm(new_xx[i], xm, (double)sd, TRUE);

  /* we may want to store the number of parameters. */
  if (nparams)
    *nparams = 1;

  return res;

}/*PGNODE*/

double cpgnode(double *xx, int nobs, double *new_xx, int new_nobs, double **dd,
    double **dd2, int ncol, double *nparams) {

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

  beta = Calloc1D(ncol + 1, sizeof(double));

  c_ols(dd, xx, nobs, ncol, NULL, NULL, beta, &sd, NULL, FALSE);

  /* this estimate is not unbiased; the denominator is set to match the
   * corresponding denominator in the root nodes to ensure score equivalence. */
  sd = sd * sqrt((double) (nobs - ncol - 1) / (double) (nobs - 1));

  /* compute the log-likelihood (singular models have zero density). */
  if (sd < MACHINE_TOL)
    res = R_NegInf;
  else {

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

      for (j = 1, fitted = beta[0]; j < ncol + 1; j++)
        fitted += beta[j] * dd2[j - 1][i];

      res += dnorm(new_xx[i], fitted, sd, TRUE);

    }/*FOR*/

  }/*ELSE*/

  /* we may want to store the number of parameters. */
  if (nparams)
    *nparams = ncol + 1;

  Free1D(beta);

  return res;

}/*CPGNODE*/

double predictive_loglik_gnode(SEXP target, sparse_amat sam, tabular dt,
    tabular newdt, double *nparams, bool debugging) {

double loglik = 0, **dd = NULL, **dd2 = NULL;
char *t = (char *)CHAR(STRING_ELT(target, 0));
int i = 0, npar = 0, node = 0, new_node = 0, it = 0;

  /* 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 (continuous) column in the training and test data. */
  node = dt.map[tabular_column(dt, t)];
  new_node = newdt.map[tabular_column(newdt, t)];

  /* compute the log-likelihood. */
  if (npar == 0) {

    loglik = pgnode(dt.ccol[node], dt.m.nobs, newdt.ccol[new_node],
               newdt.m.nobs, nparams);

  }/*THEN*/
  else {

    /* gather the parents' columns from both the training and test data. */
    dd = Calloc1D(npar, sizeof(double *));
    dd2 = Calloc1D(npar, sizeof(double *));
    for (i = 0; i < npar; i++) {

      const char *pname = sam.labels[sam.colidx[sam.rowptr[it] + i]];

      dd[i] = dt.ccol[dt.map[tabular_column(dt, pname)]];
      dd2[i] = newdt.ccol[newdt.map[tabular_column(newdt, pname)]];

    }/*FOR*/

    loglik = cpgnode(dt.ccol[node], dt.m.nobs, newdt.ccol[new_node],
               newdt.m.nobs, dd, dd2, npar, nparams);

    Free1D(dd);
    Free1D(dd2);

  }/*ELSE*/

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

  return loglik;

}/*PREDICTIVE_LOGLIK_GNODE*/
