/* Copyright (c) 2020 Stephen Wasilewski
 * =======================================================================
 * 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
 */

/*
 *  rtraceparts.c - partial modified rtrace.c (includes at top), splits call to rtrace (as rtrace_call)
 *  to return without exiting.
 */

#ifdef __cplusplus
extern "C" {
#endif

/*
 *  rtrace.c - program and variables for individual ray tracing.
 */

#include "copyright.h"

/*
 *  Input is in the form:
 *
 *	xorg	yorg	zorg	xdir	ydir	zdir
 *
 *  The direction need not be normalized.  Output is flexible.
 *  If the direction vector is (0,0,0), then the output is flushed.
 *  All values default to ascii representation of real
 *  numbers.  Binary representations can be selected
 *  with '-ff' for float or '-fd' for double.  By default,
 *  radiance is computed.  The '-i' or '-I' options indicate that
 *  irradiance values are desired.
 */

#include  <time.h>

#include  "platform.h"
#include  "ray.h"
#include  "ambient.h"
#include  "source.h"
#include  "otypes.h"
#include  "otspecial.h"
#include  "resolu.h"
#include  "random.h"

extern int  inform;			/* input format */
extern int  outform;			/* output format */
extern char  *outvals;			/* output values */

extern int  imm_irrad;			/* compute immediate irradiance? */
extern int  lim_dist;			/* limit distance? */

extern char  *tralist[];		/* list of modifers to trace (or no) */
extern int  traincl;			/* include == 1, exclude == 0 */

extern int  hresolu;			/* horizontal resolution */
extern int  vresolu;			/* vertical resolution */

int  castonly = 0;			/* only doing ray-casting? */

#ifndef  MAXTSET
#define	 MAXTSET	8191		/* maximum number in trace set */
#endif
OBJECT	traset[MAXTSET+1]={0};		/* trace include/exclude set */

static RAY  thisray;			/* for our convenience */

static FILE  *inpfp = NULL;		/* input stream pointer */

static FVECT	*inp_queue = NULL;	/* ray input queue if flushing */
static int	inp_qpos = 0;		/* next ray to return */
static int	inp_qend = 0;		/* number of rays in this work group */

typedef void putf_t(RREAL *v, int n);
static putf_t puta, putd, putf, putrgbe;

typedef void oputf_t(RAY *r);
static oputf_t  oputo, oputd, oputv, oputV, oputl, oputL, oputc, oputp,
        oputr, oputR, oputx, oputX, oputn, oputN, oputs,
        oputw, oputW, oputm, oputM, oputtilde;

extern void tranotify(OBJECT obj);
static int is_fifo(FILE *fp);
static void bogusray(void);
static void raycast(RAY *r);
static void rayirrad(RAY *r);
static void rtcompute(FVECT org, FVECT dir, double dmax);
static int printvals(RAY *r);
static int getvec(FVECT vec, int fmt, FILE *fp);
static int iszerovec(const FVECT vec);
static double nextray(FVECT org, FVECT dir);
static void tabin(RAY *r);
static void ourtrace(RAY *r);

static oputf_t *ray_out[32], *every_out[32];
static putf_t *putreal;

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");
}


static void
trace_sources(void)			/* trace rays to light sources, also */
{
  int	sn;

  for (sn = 0; sn < nsources; sn++)
    source[sn].sflags |= SFOLLOW;
}


static void
bogusray(void)			/* print out empty record */
{
  rayorigin(&thisray, PRIMARY, NULL, NULL);
  printvals(&thisray);
}


static void
raycast(			/* compute first ray intersection only */
        RAY *r
)
{
  if (!localhit(r, &thescene)) {
    if (r->ro == &Aftplane) {	/* clipped */
      r->ro = NULL;
      r->rot = FHUGE;
    } else
      sourcehit(r);
  }
}


static void
rayirrad(			/* compute irradiance rather than radiance */
        RAY *r
)
{
  void	(*old_revf)(RAY *) = r->revf;
  /* pretend we hit surface */
  r->rxt = r->rot = 1e-5;
  VSUM(r->rop, r->rorg, r->rdir, r->rot);
  r->ron[0] = -r->rdir[0];
  r->ron[1] = -r->rdir[1];
  r->ron[2] = -r->rdir[2];
  r->rod = 1.0;
  /* compute result */
  r->revf = raytrace;
  (*ofun[Lamb.otype].funp)(&Lamb, r);
  r->revf = old_revf;
}


static void
rtcompute(			/* compute and print ray value(s) */
        FVECT  org,
        FVECT  dir,
        double	dmax
)
{
  /* set up ray */
  rayorigin(&thisray, PRIMARY, NULL, NULL);
  if (imm_irrad) {
    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;
    thisray.revf = rayirrad;
  } else {
    VCOPY(thisray.rorg, org);
    VCOPY(thisray.rdir, dir);
    thisray.rmax = dmax;
    if (castonly)
      thisray.revf = raycast;
  }
  if (ray_pnprocs > 1) {		/* multiprocessing FIFO? */
    if (ray_fifo_in(&thisray) < 0)
      error(USER, "lost children");
    return;
  }
  samplendx++;			/* else do it ourselves */
  rayvalue(&thisray);
  printvals(&thisray);
}


static int
printvals(			/* print requested ray values */
        RAY  *r
)
{
  oputf_t **tp;

  if (ray_out[0] == NULL)
    return(0);
  for (tp = ray_out; *tp != NULL; tp++)
    (**tp)(r);
  if (outform == 'a')
    putchar('\n');
  return(1);
}


static int
is_fifo(		/* check if file pointer connected to pipe */
        FILE *fp
)
{
#ifdef S_ISFIFO
  struct stat  sbuf;

	if (fstat(fileno(fp), &sbuf) < 0)
		error(SYSTEM, "fstat() failed on input stream");
	return(S_ISFIFO(sbuf.st_mode));
#else
  return (fp == stdin);		/* just a guess, really */
#endif
}


static int
getvec(			/* get a vector from fp */
        FVECT  vec,
        int  fmt,
        FILE  *fp
)
{
  static float  vf[3];
  static double  vd[3];
  char  buf[32];
  int  i;

  switch (fmt) {
    case 'a':					/* ascii */
      for (i = 0; i < 3; i++) {
        if (fgetword(buf, sizeof(buf), fp) == NULL ||
            !isflt(buf))
          return(-1);
        vec[i] = atof(buf);
      }
      break;
    case 'f':					/* binary float */
      if (getbinary(vf, sizeof(float), 3, fp) != 3)
        return(-1);
      VCOPY(vec, vf);
      break;
    case 'd':					/* binary double */
      if (getbinary(vd, sizeof(double), 3, fp) != 3)
        return(-1);
      VCOPY(vec, vd);
      break;
    default:
      error(CONSISTENCY, "botched input format");
  }
  return(0);
}


static int
iszerovec(const FVECT vec)
{
  return (vec[0] == 0.0) & (vec[1] == 0.0) & (vec[2] == 0.0);
}


static double
nextray(		/* return next ray in work group (-1.0 if EOF) */
        FVECT org,
        FVECT dir
)
{
  const size_t	qlength = !vresolu * hresolu;

  if ((org == NULL) | (dir == NULL)) {
    if (inp_queue != NULL)	/* asking to free queue */
      free(inp_queue);
    inp_queue = NULL;
    inp_qpos = inp_qend = 0;
    return(-1.);
  }
  if (!inp_qend) {		/* initialize FIFO queue */
    int	rsiz = 6*20;	/* conservative ascii ray size */
    if (inform == 'f') rsiz = 6*sizeof(float);
    else if (inform == 'd') rsiz = 6*sizeof(double);
    /* check against pipe limit */
    if (qlength*rsiz > 512 && is_fifo(inpfp))
      inp_queue = (FVECT *)malloc(sizeof(FVECT)*2*qlength);
    inp_qend = -(inp_queue == NULL);	/* flag for no queue */
  }
  if (inp_qend < 0) {		/* not queuing? */
    if (getvec(org, inform, inpfp) < 0 ||
        getvec(dir, inform, inpfp) < 0)
      return(-1.);
    return normalize(dir);
  }
  if (inp_qpos >= inp_qend) {	/* need to refill input queue? */
    for (inp_qend = 0; inp_qend < qlength; inp_qend++) {
      if (getvec(inp_queue[2*inp_qend], inform, inpfp) < 0
          || getvec(inp_queue[2*inp_qend+1],
                    inform, inpfp) < 0)
        break;		/* hit EOF */
      if (iszerovec(inp_queue[2*inp_qend+1])) {
        ++inp_qend;	/* flush request */
        break;
      }
    }
    inp_qpos = 0;
  }
  if (inp_qpos >= inp_qend)	/* unexpected EOF? */
    return(-1.);
  VCOPY(org, inp_queue[2*inp_qpos]);
  VCOPY(dir, inp_queue[2*inp_qpos+1]);
  ++inp_qpos;
  return normalize(dir);
}


void
tranotify(			/* record new modifier */
        OBJECT	obj
)
{
  static int  hitlimit = 0;
  OBJREC	 *o = objptr(obj);
  char  **tralp;

  if (obj == OVOID) {		/* starting over */
    traset[0] = 0;
    hitlimit = 0;
    return;
  }
  if (hitlimit || !ismodifier(o->otype))
    return;
  for (tralp = tralist; *tralp != NULL; tralp++)
    if (!strcmp(o->oname, *tralp)) {
      if (traset[0] >= MAXTSET) {
        error(WARNING, "too many modifiers in trace list");
        hitlimit++;
        return;		/* should this be fatal? */
      }
      insertelem(traset, obj);
      return;
    }
}


static void
ourtrace(				/* print ray values */
        RAY  *r
)
{
  oputf_t **tp;

  if (every_out[0] == NULL)
    return;
  if (r->ro == NULL) {
    if (traincl == 1)
      return;
  } else if (traincl != -1 && traincl != inset(traset, r->ro->omod))
    return;
  tabin(r);
  for (tp = every_out; *tp != NULL; tp++)
    (**tp)(r);
  if (outform == 'a')
    putchar('\n');
}


static void
tabin(				/* tab in appropriate amount */
        RAY  *r
)
{
  const RAY  *rp;

  for (rp = r->parent; rp != NULL; rp = rp->parent)
    putchar('\t');
}


static void
oputo(				/* print origin */
        RAY  *r
)
{
  (*putreal)(r->rorg, 3);
}


static void
oputd(				/* print direction */
        RAY  *r
)
{
  (*putreal)(r->rdir, 3);
}


static void
oputr(				/* print mirrored contribution */
        RAY  *r
)
{
  RREAL	cval[3];

  cval[0] = colval(r->mcol,RED);
  cval[1] = colval(r->mcol,GRN);
  cval[2] = colval(r->mcol,BLU);
  (*putreal)(cval, 3);
}



static void
oputR(				/* print mirrored distance */
        RAY  *r
)
{
  (*putreal)(&r->rmt, 1);
}


static void
oputx(				/* print unmirrored contribution */
        RAY  *r
)
{
  RREAL	cval[3];

  cval[0] = colval(r->rcol,RED) - colval(r->mcol,RED);
  cval[1] = colval(r->rcol,GRN) - colval(r->mcol,GRN);
  cval[2] = colval(r->rcol,BLU) - colval(r->mcol,BLU);
  (*putreal)(cval, 3);
}


static void
oputX(				/* print unmirrored distance */
        RAY  *r
)
{
  (*putreal)(&r->rxt, 1);
}


static void
oputv(				/* print value */
        RAY  *r
)
{
  RREAL	cval[3];

  cval[0] = colval(r->rcol,RED);
  cval[1] = colval(r->rcol,GRN);
  cval[2] = colval(r->rcol,BLU);
  (*putreal)(cval, 3);
}


static void
oputV(				/* print value contribution */
        RAY *r
)
{
  RREAL	contr[3];

  raycontrib(contr, r, PRIMARY);
  multcolor(contr, r->rcol);
  (*putreal)(contr, 3);
}


static void
oputl(				/* print effective distance */
        RAY  *r
)
{
  RREAL	d = raydistance(r);

  (*putreal)(&d, 1);
}


static void
oputL(				/* print single ray length */
        RAY  *r
)
{
  (*putreal)(&r->rot, 1);
}


static void
oputc(				/* print local coordinates */
        RAY  *r
)
{
  (*putreal)(r->uv, 2);
}


static RREAL	vdummy[3] = {0.0, 0.0, 0.0};


static void
oputp(				/* print point */
        RAY  *r
)
{
  if (r->rot < FHUGE*.99)
    (*putreal)(r->rop, 3);
  else
    (*putreal)(vdummy, 3);
}


static void
oputN(				/* print unperturbed normal */
        RAY  *r
)
{
  if (r->rot < FHUGE*.99) {
    if (r->rflips & 1) {	/* undo any flippin' flips */
      FVECT	unrm;
      unrm[0] = -r->ron[0];
      unrm[1] = -r->ron[1];
      unrm[2] = -r->ron[2];
      (*putreal)(unrm, 3);
    } else
      (*putreal)(r->ron, 3);
  } else
    (*putreal)(vdummy, 3);
}


static void
oputn(				/* print perturbed normal */
        RAY  *r
)
{
  FVECT  pnorm;

  if (r->rot >= FHUGE*.99) {
    (*putreal)(vdummy, 3);
    return;
  }
  raynormal(pnorm, r);
  (*putreal)(pnorm, 3);
}


static void
oputs(				/* print name */
        RAY  *r
)
{
  if (r->ro != NULL)
    fputs(r->ro->oname, stdout);
  else
    putchar('*');
  putchar('\t');
}


static void
oputw(				/* print weight */
        RAY  *r
)
{
  RREAL	rwt = r->rweight;

  (*putreal)(&rwt, 1);
}


static void
oputW(				/* print coefficient */
        RAY  *r
)
{
  RREAL	contr[3];
  /* shadow ray not on source? */
  if (r->rsrc >= 0 && source[r->rsrc].so != r->ro)
    setcolor(contr, 0.0, 0.0, 0.0);
  else
    raycontrib(contr, r, PRIMARY);

  (*putreal)(contr, 3);
}


static void
oputm(				/* print modifier */
        RAY  *r
)
{
  if (r->ro != NULL)
    if (r->ro->omod != OVOID)
      fputs(objptr(r->ro->omod)->oname, stdout);
    else
      fputs(VOIDID, stdout);
  else
    putchar('*');
  putchar('\t');
}


static void
oputM(				/* print material */
        RAY  *r
)
{
  OBJREC	*mat;

  if (r->ro != NULL) {
    if ((mat = findmaterial(r->ro)) != NULL)
      fputs(mat->oname, stdout);
    else
      fputs(VOIDID, stdout);
  } else
    putchar('*');
  putchar('\t');
}


static void
oputtilde(			/* output tilde (spacer) */
        RAY  *r
)
{
  fputs("~\t", stdout);
}


static void
puta(				/* print ascii value(s) */
        RREAL *v, int n
)
{
  if (n == 3) {
    printf("%e\t%e\t%e\t", v[0], v[1], v[2]);
    return;
  }
  while (n--)
    printf("%e\t", *v++);
}


static void
putd(RREAL *v, int n)		/* output binary double(s) */
{
#ifdef	SMLFLT
  double	da[3];
	int	i;

	if (n > 3)
		error(INTERNAL, "code error in putd()");
	for (i = n; i--; )
		da[i] = v[i];
	putbinary(da, sizeof(double), n, stdout);
#else
  putbinary(v, sizeof(RREAL), n, stdout);
#endif
}


static void
putf(RREAL *v, int n)		/* output binary float(s) */
{
#ifndef	SMLFLT
  float	fa[3];
  int	i;

  if (n > 3)
    error(INTERNAL, "code error in putf()");
  for (i = n; i--; )
    fa[i] = v[i];
  putbinary(fa, sizeof(float), n, stdout);
#else
  putbinary(v, sizeof(RREAL), n, stdout);
#endif
}


static void
putrgbe(RREAL *v, int n)	/* output RGBE color */
{
  COLR  cout;

  if (n != 3)
    error(INTERNAL, "putrgbe() not called with 3 components");
  setcolr(cout, v[0], v[1], v[2]);
  putbinary(cout, sizeof(cout), 1, stdout);
}



extern char	*shm_boundary;		/* boundary of shared memory */

extern void
rtrace_setup( /* initialize processes */
	int  nproc
)
{
  setambient();
  long  nextflush = (!vresolu | (hresolu <= 1)) * hresolu;
  if (castonly || every_out[0] != NULL)
    nproc = 1;		/* don't bother multiprocessing */
  if ((nextflush > 0) & (nproc > nextflush)) {
    error(WARNING, "reducing number of processes to match flush interval");
    nproc = nextflush;
  }
  if (nproc > 1) {		/* start multiprocessing */
    ray_popen(nproc);
    ray_fifo_out = printvals;
  }
}

extern void
rtrace_call( /* run rtrace process */
        char *fname,
        int nproc
)
{
  rtrace_setup(nproc);
  unsigned long  vcount = (hresolu > 1) ? (unsigned long)hresolu*vresolu
                                        : (unsigned long)vresolu;
  long  nextflush = (!vresolu | (hresolu <= 1)) * hresolu;
  int  something2flush = 0;
  FILE  *fp;
  double	d;
  FVECT  orig, direc;
  /* set up input */
  if (fname == NULL)
    inpfp = stdin;
  else if ((inpfp = fopen(fname, "r")) == NULL) {
    sprintf(errmsg, "cannot open input file \"%s\"", fname);
    error(SYSTEM, errmsg);
  }
#ifdef getc_unlocked
  flockfile(inpfp);		/* avoid lock/unlock overhead */
  flockfile(stdout);
#endif
  if (inform != 'a')
          SET_FILE_BINARY(inpfp);
  if (hresolu > 0) {
    if (vresolu > 0)
      fprtresolu(hresolu, vresolu, stdout);
    else
      fflush(stdout);
  }
  /* process input rays */
  while ((d = nextray(orig, direc)) >= 0.0) {
    if (d == 0.0) {				/* flush request? */
      if (something2flush) {
        if (ray_pnprocs > 1 && ray_fifo_flush() < 0)
          error(USER, "child(ren) died");
        bogusray();
        fflush(stdout);
        nextflush = (!vresolu | (hresolu <= 1)) * hresolu;
        something2flush = 0;
      } else
        bogusray();
    } else {				/* compute and print */
      rtcompute(orig, direc, lim_dist ? d : 0.0);
      if (!--nextflush) {		/* flush if time */
        if (ray_pnprocs > 1 && ray_fifo_flush() < 0)
          error(USER, "child(ren) died");
        fflush(stdout);
        nextflush = hresolu;
      } else
        something2flush = 1;
    }
    if (ferror(stdout))
      error(SYSTEM, "write error");
    if (vcount && !--vcount)		/* check for end */
      break;
  }
  if (ray_pnprocs > 1) {				/* clean up children */
    if (ray_fifo_flush() < 0)
      error(USER, "unable to complete processing");
    ray_pclose(0);			/* close child processes */

    if (shm_boundary != NULL) {	/* clear shared memory boundary */
      free((void *)shm_boundary);
      shm_boundary = NULL;
    }
  }
  if (vcount)
    error(WARNING, "unexpected EOF on input");
  if (fflush(stdout) < 0)
    error(SYSTEM, "write error");
  if (fname != NULL) {
    fclose(inpfp);
    inpfp = NULL;
  }
  nextray(NULL, NULL);
#ifdef getc_unlocked
  funlockfile(stdout);
#endif
  ambdone();
}

static void
oputrad(				/* print value -o spec: Z*/
        RAY  *r
)
{
  RREAL	lum = bright(r->rcol);
  (*putreal)(&lum, 1);
}

int
setoutput2(char  *vs, char of)			/* provides additional outputspec Z to output radiance (1 component)*/
{
  oputf_t **table = ray_out;
  int  ncomp = 0;

  if (!*vs)
    error(USER, "empty output specification");

  castonly = 1;			/* sets castonly as side-effect */
  do
    switch (*vs) {
      case 'T':				/* trace sources */
        if (!vs[1]) break;
        trace_sources();
        /* fall through */
      case 't':				/* trace */
        if (!vs[1]) break;
        *table = NULL;
        table = every_out;
        trace = ourtrace;
        castonly = 0;
        break;
      case 'o':				/* origin */
        *table++ = oputo;
        ncomp += 3;
        break;
      case 'd':				/* direction */
        *table++ = oputd;
        ncomp += 3;
        break;
      case 'r':				/* reflected contrib. */
        *table++ = oputr;
        ncomp += 3;
        castonly = 0;
        break;
      case 'R':				/* reflected distance */
        *table++ = oputR;
        ncomp++;
        castonly = 0;
        break;
      case 'x':				/* xmit contrib. */
        *table++ = oputx;
        ncomp += 3;
        castonly = 0;
        break;
      case 'X':				/* xmit distance */
        *table++ = oputX;
        ncomp++;
        castonly = 0;
        break;
      case 'v':				/* value */
        *table++ = oputv;
        ncomp += 3;
        castonly = 0;
        break;
      case 'V':				/* contribution */
        *table++ = oputV;
        ncomp += 3;
        castonly = 0;
        if (ambounce > 0 && (ambacc > FTINY || ambssamp > 0))
          error(WARNING,
                "-otV accuracy depends on -aa 0 -as 0");
        break;
      case 'l':				/* effective distance */
        *table++ = oputl;
        ncomp++;
        castonly = 0;
        break;
      case 'c':				/* local coordinates */
        *table++ = oputc;
        ncomp += 2;
        break;
      case 'L':				/* single ray length */
        *table++ = oputL;
        ncomp++;
        break;
      case 'p':				/* point */
        *table++ = oputp;
        ncomp += 3;
        break;
      case 'n':				/* perturbed normal */
        *table++ = oputn;
        ncomp += 3;
        castonly = 0;
        break;
      case 'N':				/* unperturbed normal */
        *table++ = oputN;
        ncomp += 3;
        break;
      case 's':				/* surface */
        *table++ = oputs;
        ncomp++;
        break;
      case 'w':				/* weight */
        *table++ = oputw;
        ncomp++;
        break;
      case 'W':				/* coefficient */
        *table++ = oputW;
        ncomp += 3;
        castonly = 0;
        if (ambounce > 0 && (ambacc > FTINY) | (ambssamp > 0))
          error(WARNING,
                "-otW accuracy depends on -aa 0 -as 0");
        break;
      case 'm':				/* modifier */
        *table++ = oputm;
        ncomp++;
        break;
      case 'M':				/* material */
        *table++ = oputM;
        ncomp++;
        break;
      case '~':				/* tilde */
        *table++ = oputtilde;
        break;
      case 'Z':
        *table++ = oputrad;
        castonly = 0;
        ncomp++;
        break;
      default:
        sprintf(errmsg, "unrecognized output option '%c'", *vs);
        error(USER, errmsg);
    }
  while (*++vs);

  *table = NULL;
  if (*every_out != NULL)
    ncomp = 0;
  /* compatibility */
  if ((do_irrad | imm_irrad) && castonly)
    error(USER, "-I+ and -i+ options require some value output");
  for (table = ray_out; *table != NULL; table++) {
    if ((*table == oputV) | (*table == oputW))
      error(WARNING, "-oVW options require trace mode");
    if ((do_irrad | imm_irrad) &&
        (*table == oputr) | (*table == oputR) |
        (*table == oputx) | (*table == oputX))
      error(WARNING, "-orRxX options incompatible with -I+ and -i+");
  }
  switch (of) {
    case 'z': break;
    case 'a': putreal = puta; break;
    case 'f': putreal = putf; break;
    case 'd': putreal = putd; break;
    case 'c':
      if (outvals[1] || !strchr("vrx", outvals[0]))
        error(USER, "color format only with -ov, -or, -ox");
      putreal = putrgbe; break;
    default:
      error(CONSISTENCY, "botched output format");
  }


  return(ncomp);
}



#ifdef __cplusplus
}
#endif
