/* Copyright (c) 2020 Stephen Wasilewski, HSLU and EPFL
 * =======================================================================
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *=======================================================================
 *
 * The Following code is copied from, adapts, includes, and/or links parts of
 * the Radiance source code which is licensed by the following:
 *
 * The Radiance Software License, Version 1.0
 *
 * Copyright (c) 1990 - 2018 The Regents of the University of California,
 * through Lawrence Berkeley National Laboratory.   All rights reserved.
 *
 * If a copy of the full text of the License was not distributed with this file
 * (in ./ray/License.txt) the License is available at
 * https://www.radiance-online.org/download-install/license
 */

/*
 *  rcontribparts.c - partial modified rcontrib.c  (includes at top),
 *  splits call to rcontrib (as rcontrib_call)
 *  to return without exiting and to read input from a file instead of stdin.
 */

#ifdef __cplusplus
extern "C" {
#endif



/*
* Accumulate ray contributions for a set of materials
* Initialization and calculation routines
*/

#include "copyright.h"

#include "rcontrib.h"
#include "otypes.h"
#include "source.h"
#include "rcinit.h"

char	*shm_boundary = NULL;		/* boundary of shared memory */

int current_vec_cnt = 0;
long putcount = 0;

CUBE	thescene;			/* our scene */
OBJECT	nsceneobjs;			/* number of objects in our scene */

int	dimlist[MAXDIM];		/* sampling dimensions */
int	ndims = 0;			/* number of sampling dimensions */
int	samplendx = 0;			/* index for this sample */

void	(*trace)() = NULL;		/* trace call (NULL before rcinit) */

int	do_irrad = 0;			/* compute irradiance? */

int	rand_samp = 1;			/* pure Monte Carlo sampling? */

double	dstrsrc = 0.9;			/* square source distribution */
double	shadthresh = 0.;		/* shadow threshold */
double	shadcert = .75;			/* shadow certainty */
int	directrelay = 3;		/* number of source relays */
int	vspretest = 512;		/* virtual source pretest density */
int	directvis = 1;			/* sources visible? */
double	srcsizerat = .2;		/* maximum ratio source size/dist. */

COLOR	cextinction = BLKCOLOR;		/* global extinction coefficient */
COLOR	salbedo = BLKCOLOR;		/* global scattering albedo */
double	seccg = 0.;			/* global scattering eccentricity */
double	ssampdist = 0.;			/* scatter sampling distance */

double	specthresh = .02;		/* specular sampling threshold */
double	specjitter = 1.;		/* specular sampling jitter */

int	backvis = 1;			/* back face visibility */

int	maxdepth = -10;			/* maximum recursion depth */
double	minweight = 2e-3;		/* minimum ray weight */

char	*ambfile = NULL;		/* ambient file name */
COLOR	ambval = BLKCOLOR;		/* ambient value */
int	ambvwt = 0;			/* initial weight for ambient value */
double	ambacc = 0.;			/* ambient accuracy */
int	ambres = 256;			/* ambient resolution */
int	ambdiv = 350;			/* ambient divisions */
int	ambssamp = 0;			/* ambient super-samples */
int	ambounce = 1;			/* ambient bounces */
char	*amblist[AMBLLEN+1];		/* ambient include/exclude list */
int	ambincl = -1;			/* include == 1, exclude == 0 */

int	account;			/* current accumulation count */
RNUMBER	raysleft;			/* number of rays left to trace */
long	waitflush;			/* how long until next flush */

RNUMBER	lastray = 0;			/* last ray number sent */
RNUMBER	lastdone = 0;			/* last ray output */

static void	trace_contrib(RAY *r);	/* our trace callback */

static void mcfree(void *p) { epfree((*(MODCONT *)p).binv); free(p); }

LUTAB	modconttab = LU_SINIT(NULL,mcfree);	/* modifier lookup table */

/************************** INITIALIZATION ROUTINES ***********************/
int feed_repeat = 0;
char *
formstr(				/* return format identifier */
        int  f
)
{
  switch (f) {
    case 'a': return("ascii");
    case 'f': return("float");
    case 'd': return("double");
    case 'c': return(COLRFMT);
  }
  return("unknown");
}


/* Add modifier to our list to track */
MODCONT *
addmodifier(char *modn, char *outf, char *prms, char *binv, int bincnt)
{
  LUENT	*lep = lu_find(&modconttab,modn);
  MODCONT	*mp;
  EPNODE	*ebinv;
  int	i;
  return_value_count += bincnt;
  if (lep->data != NULL) {
    sprintf(errmsg, "duplicate modifier '%s'", modn);
    error(USER, errmsg);
  }
  if (!strcmp(modn, VOIDID)) {
    sprintf(errmsg, "cannot track '%s' modifier", VOIDID);
    error(USER, errmsg);
  }
  if (nmods >= modasiz) {		/* need bigger modifier array */
    modasiz += modasiz/2 + 64;
    if (modname == NULL)
      modname = (char **)malloc(modasiz*sizeof(char *));
    else
      modname = (char **)realloc(modname, modasiz*sizeof(char *));
    if (modname == NULL)
      error(SYSTEM, "Out of memory in addmodifier()");
  }
  modname[nmods++] = modn;	/* XXX assumes static string */
  lep->key = modn;		/* XXX assumes static string */
  if (binv == NULL)
    binv = "0";		/* use single bin if unspecified */
  ebinv = eparse(binv);
  if (ebinv->type == NUM) {	/* check value if constant */
    bincnt = (int)(evalue(ebinv) + 1.5);
    if (bincnt != 1) {
      sprintf(errmsg, "illegal non-zero constant for bin (%s)",
              binv);
      error(USER, errmsg);
    }
  } else if (bincnt <= 0) {
    sprintf(errmsg,
            "unspecified or illegal bin count for modifier '%s'",
            modn);
    error(USER, errmsg);
  }
  /* initialize results holder */
  mp = (MODCONT *)malloc(sizeof(MODCONT)+sizeof(DCOLOR)*(bincnt-1));
  if (mp == NULL)
    error(SYSTEM, "out of memory in addmodifier");
  mp->outspec = outf;		/* XXX assumes static string */
  mp->modname = modn;		/* XXX assumes static string */
  mp->params = prms;		/* XXX assumes static string */
  mp->binv = ebinv;
  mp->bin0 = 0;
  mp->nbins = bincnt;
  memset(mp->cbin, 0, sizeof(DCOLOR)*bincnt);
  lep->data = (char *)mp;
  return(mp);
}

/* Add modifiers from a file list */
void
addmodfile(char *fname, char *outf, char *prms, char *binv, int bincnt)
{
	char	*path = getpath(fname, getrlibpath(), R_OK);
	char	mod[MAXSTR];
	FILE	*fp;

	if (path == NULL || (fp = fopen(path, "r")) == NULL) {
		if (path == NULL)
			sprintf(errmsg, "cannot find modifier file '%s'", fname);
		else
			sprintf(errmsg, "cannot load modifier file '%s'", path);
		error(SYSTEM, errmsg);
	}
	while (fgetword(mod, sizeof(mod), fp) != NULL)
		addmodifier(savqstr(mod), outf, prms, binv, bincnt);
	fclose(fp);
}


/* Check if we have any more rays left (and report progress) */
int
morays(void)
{
  static RNUMBER	total_rays;
  static time_t	tstart, last_report;
  time_t		tnow;

  if (!raysleft)
    return(1);	/* unknown total, so nothing to do or say */

  if (report_intvl > 0 && (tnow = time(0)) >= last_report+report_intvl) {
    if (!total_rays) {
      total_rays = raysleft;
      tstart = tnow;
    } else {
      sprintf(errmsg, "%.2f%% done after %.3f hours\n",
              100.-100.*raysleft/total_rays,
              (1./3600.)*(tnow - tstart));
      eputs(errmsg);
    }
    last_report = tnow;
  }
  return(--raysleft);
}


/************************** MAIN CALCULATION PROCESS ***********************/

/* Our trace call to sum contributions */
static void
trace_contrib(RAY *r)
{
  MODCONT	*mp;
  double	bval;
  int	bn;
  RREAL	contr[3];

  if (r->ro == NULL || r->ro->omod == OVOID)
    return;
  /* shadow ray not on source? */
  if (r->rsrc >= 0 && source[r->rsrc].so != r->ro)
    return;

  mp = (MODCONT *)lu_find(&modconttab,objptr(r->ro->omod)->oname)->data;

  if (mp == NULL)				/* not in our list? */
    return;

  worldfunc(RCCONTEXT, r);		/* else set context */
  set_eparams((char *)mp->params);
  if ((bval = evalue(mp->binv)) <= -.5)	/* and get bin number */
    return;				/* silently ignore negatives */
  if ((bn = (int)(bval + .5)) >= mp->nbins) {
    sprintf(errmsg, "bad bin number (%d ignored)", bn);
    error(WARNING, errmsg);
    return;
  }
  /*TODO: AB 1 OUTPUT!!! to add output after 1 ab, add a second array (contr[3]) then in raycontrib, check
   * r->rlvl and r->rtype to filter out deeper runs (set to zero?). this will also require
   * a second table, or a 6 value table. Alternatively, could use one of the color channels and then
   * only modify raycontrib, which might be best for a proof of concept. This may not work if the ambient rays are
   * self contained...
   * then edits would need to be upstream in doambient/samp_hemi? this would start at line 342 in normal.c where
   * the call to multambient would need to split the result stored in ctmp and added to the reesult... probably
   * not possible?*/
  raycontrib(contr, r, PRIMARY);		/* compute coefficient */
  if (contrib)
    multcolor(contr, r->rcol);	/* -> contribution */
  addcolor(mp->cbin[bn], contr);
}


/* Evaluate irradiance contributions */
static void
eval_irrad(FVECT org, FVECT dir)
{
  RAY	thisray;

  VSUM(thisray.rorg, org, dir, 1.1e-4);
  thisray.rdir[0] = -dir[0];
  thisray.rdir[1] = -dir[1];
  thisray.rdir[2] = -dir[2];
  thisray.rmax = 0.0;
  rayorigin(&thisray, PRIMARY, NULL, NULL);
  /* pretend we hit surface */
  thisray.rxt = thisray.rot = 1e-5;
  thisray.rod = 1.0;
  VCOPY(thisray.ron, dir);
  VSUM(thisray.rop, org, dir, 1e-4);
  samplendx++;			/* compute result */
  (*ofun[Lamb.otype].funp)(&Lamb, &thisray);
}


/* Evaluate radiance contributions */
static void
eval_rad(FVECT org, FVECT dir, double dmax)
{
  RAY	thisray;
  /* set up ray */
  VCOPY(thisray.rorg, org);
  VCOPY(thisray.rdir, dir);
  thisray.rmax = dmax;
  rayorigin(&thisray, PRIMARY, NULL, NULL);
  samplendx++;			/* call ray evaluation */
  rayvalue(&thisray);
}


/* Accumulate and/or output ray contributions (child or only process) */
static void
done_contrib(void)
{
  MODCONT	*mp;
  int	i;
//  fprintf(stderr, "done_contrib %d\n", account);
  if (account <= 0 || --account)
    return;			/* not time yet */

  for (i = 0; i < nmods; i++) {	/* output records & clear */
    mp = (MODCONT *)lu_find(&modconttab,modname[i])->data;
    mod_output(mp);
    memset(mp->cbin, 0, sizeof(DCOLOR)*mp->nbins);
  }
  end_record();			/* end lines & flush if time */
  account = accumulate;		/* reset accumulation counter */
}

/*
 * Modified so that accumulate repeats rays (like vwrays), instead of averaging (classic behaviour)
 */
FVECT vec_0;
FVECT vec_1;

int
getvecfa(FVECT vec, const double *vecs, int raycount)
{

  int	i;
  feed_repeat = feed_repeat % (accumulate * 2);
  if (feed_repeat < 2) {
    if (current_vec_cnt >= raycount * 6)
      return -1;
    if (feed_repeat == 0) {
      for (i = 0; i < 3; i++) {
        vec_0[i] = vecs[current_vec_cnt + i];
      }
    } else {
      for (i = 0; i < 3; i++) {
        vec_1[i] = vecs[current_vec_cnt + i];
      }
    }
    current_vec_cnt += 3;
  }
  if (feed_repeat % 2 == 0)
    VCOPY(vec, vec_0);
  else
    VCOPY(vec, vec_1);
  feed_repeat++;
  return 0;
}

int
getvecb(FVECT vec)
{
  double	vd[3];
  if (getbinary((char *)vd, sizeof(double), 3, stdin) != 3)
    return(-1);
  VCOPY(vec, vd);
  return(0);
}

/************************** MODIFIED PARTS ***********************/

int
rcinit2(const double *vecs, int raycount)
{
  int	i;

  if (nproc > MAXPROCESS)
    sprintf(errmsg, "too many processes requested -- reducing to %d",
            nproc = MAXPROCESS);
  if (nproc > 1) {
    preload_objs();		/* preload auxiliary data */
    /* set shared memory boundary */
    shm_boundary = strcpy((char *)malloc(16), "SHM_BOUNDARY");
  }
  trace = trace_contrib;		/* set up trace call-back */
  for (i = 0; i < nsources; i++)	/* tracing to sources as well */
    source[i].sflags |= SFOLLOW;
  if (yres > 0) {			/* set up flushing & ray counts */
    if (xres > 0)
      raysleft = (RNUMBER)xres*yres;
    else
      raysleft = yres;
  } else
    raysleft = 0;
  if ((account = accumulate) > 1)
    raysleft *= accumulate;
  waitflush = (yres > 0) & (xres > 1) ? 0 : xres;
  if (nproc > 1 && in_rchild2())	/* forked child? */{
    return 0;      /* return to main processing loop */
  }
  if (nproc == 1)	{ /* single process? */
    return 1;
  }
  /* else run appropriate controller */
  parental_loop2(vecs, raycount);
  return 2;
}

/* Quit program */
void
quitrc(
        int  code
)
{
  if (nchild > 0)		/* close children if any */
    end_children(code != 0);
  else if (nchild < 0)
    _exit(code);	/* avoid flush() in child */
  exit(code);
}

void rcontrib_call(const double *vecs, int rows){
  current_vec_cnt = 0;
  putcount = 0;
  static int	ignore_warning_given = 0;
  FVECT		orig, direc;
  double		d;
  int control;
  output_values = (RREAL *)malloc(sizeof(RREAL) * rows * return_value_count);
  control = rcinit2(vecs, rows);
  if (control == 2){
    return;
  }
  if (control == 1){
    while (getvecfa(orig, vecs, rows) == 0 && getvecfa(direc, vecs, rows) == 0) {
      d = normalize(direc);
      if (nchild != -1 && (d == 0.0) & (accumulate == 0)) {
        if (!ignore_warning_given++)
          error(WARNING,
                "dummy ray(s) ignored during accumulation\n");
        continue;
      }
      if (lastray + 1 < lastray)
        lastray = lastdone = 0;
      ++lastray;
      if (d == 0.0) {        /* zero ==> flush */
        if ((yres <= 0) | (xres <= 1))
          waitflush = 1;    /* flush after */
        if (nchild == -1)
          account = 1;
      } else if (imm_irrad) {      /* else compute */
        eval_irrad(orig, direc);
      } else {
        eval_rad(orig, direc, lim_dist ? d : 0.0);
      }
      done_contrib();    /* accumulate/output */
      ++lastdone;
//      fprintf(stderr, "last done %lu\n", lastdone);
      if (raysleft && !--raysleft)
        break;    /* preemptive EOI */
    }
  } else {
    while (getvecb(orig) == 0 && getvecb(direc) == 0) {
      d = normalize(direc);
      if (nchild != -1 && (d == 0.0) & (accumulate == 0)) {
        if (!ignore_warning_given++)
          error(WARNING,
                "dummy ray(s) ignored during accumulation\n");
        continue;
      }
      if (lastray + 1 < lastray)
        lastray = lastdone = 0;
      ++lastray;
      if (d == 0.0) {        /* zero ==> flush */
        if ((yres <= 0) | (xres <= 1))
          waitflush = 1;    /* flush after */
        if (nchild == -1)
          account = 1;
      } else if (imm_irrad) {      /* else compute */
        eval_irrad(orig, direc);
      } else {
        eval_rad(orig, direc, lim_dist ? d : 0.0);
      }
      done_contrib();    /* accumulate/output */
      ++lastdone;
      if (raysleft && !--raysleft)
        break;    /* preemptive EOI */
    }
  }
  if (nchild != -1 && (accumulate <= 0) | (account < accumulate)) {
    if (account < accumulate) {
      error(WARNING, "partial accumulation in final record");
      accumulate -= account;
    }
    account = 1;		/* output accumulated totals */
    done_contrib();
  }
  if (raysleft)
    error(USER, "unexpected EOF on input");
  if (control == 0) quitrc(0);
}

void rcontrib_clear(){
//  lu_doall(&modconttab, &lu_delete, NULL);
  for (int i = 0; i < nmods; i++)
    lu_delete(&modconttab, modname[i]);
  nmods = 0;
}



#ifdef __cplusplus
}
#endif
