/*
   flame - cosmic recursive fractal flames
   Copyright (C) 1992  Scott Draves <spot@cs.cmu.edu>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "rect.h"

#include <string.h>


/* for batch
 *   interpolate
 *   compute colormap
 *   for subbatch
 *     compute samples
 *     buckets += cmap[samples]
 *   accum += time_filter[batch] * log(buckets)
 * image = filter(accum)
 */


typedef short bucket[4];

/* if you use longs instead of shorts, you 
   get higher quality, and spend more memory */

#if 1
typedef short accum_t;
#define MAXBUCKET (1<<14)
#define SUB_BATCH_SIZE 10000
#else
typedef long accum_t;
#define MAXBUCKET (1<<30)
#define SUB_BATCH_SIZE 10000
#endif

typedef accum_t abucket[4];



/* allow this many iterations for settling into attractor */
#define FUSE 15

/* clamp spatial filter to zero at this std dev (2.5 ~= 0.0125) */
#define FILTER_CUTOFF 2.5

/* should be MAXBUCKET / (OVERSAMPLE^2) */
#define PREFILTER_WHITE (MAXBUCKET>>4)


#define bump_no_overflow(dest, delta, type) { \
   type tt_ = dest + delta;            \
   if (tt_ > dest) dest = tt_;                 \
}

/* sum of entries of vector to 1 */
void normalize_vector(v, n)
   double *v;
   int n;
{
   double t = 0.0;
   int i;
   for (i = 0; i < n; i++)
      t += v[i];
   t = 1.0 / t;
   for (i = 0; i < n; i++)
      v[i] *= t;
}


void render_rectangle(spec, out, out_width, field, nchan, progress)
   frame_spec *spec;
   unsigned char *out;
   int out_width;
   int field;
   int nchan;
   void progress(double);
{
   int i, j, k, nsamples, nbuckets, batch_size, batch_num, sub_batch;
   bucket  *buckets;
   abucket *accumulate;
   point *points;
   double *filter, *temporal_filter, *temporal_deltas;
   double bounds[4], size[2], ppux, ppuy;
   int image_width, image_height;    /* size of the image to produce */
   int width, height;               /* size of histogram */
   int filter_width;
   int oversample = spec->cps[0].spatial_oversample;
   int nbatches = spec->cps[0].nbatches;
   bucket cmap[CMAP_SIZE];
   int gutter_width;
   int sbc;

   image_width = spec->cps[0].width;
   if (field) {
      image_height = spec->cps[0].height / 2;
      if (field == field_odd)
	 out += nchan * out_width;
      out_width *= 2;
   } else
      image_height = spec->cps[0].height;

   if (1) {
      filter_width = (2.0 * FILTER_CUTOFF * oversample *
		      spec->cps[0].spatial_filter_radius);
      /* make sure it has same parity as oversample */
      if ((filter_width ^ oversample) & 1)
	 filter_width++;

      filter = (double *) malloc(sizeof(double) * filter_width * filter_width);
      /* fill in the coefs */
      for (i = 0; i < filter_width; i++)
	 for (j = 0; j < filter_width; j++) {
	    double ii = ((2.0 * i + 1.0) / filter_width - 1.0) * FILTER_CUTOFF;
	    double jj = ((2.0 * j + 1.0) / filter_width - 1.0) * FILTER_CUTOFF;
	    if (field)
	       jj *= 2.0;
	    filter[i + j * filter_width] =
	       exp(-2.0 * (ii * ii + jj * jj));
	 }

      normalize_vector(filter, filter_width * filter_width);
   }
   temporal_filter = (double *) malloc(sizeof(double) * nbatches);
   temporal_deltas = (double *) malloc(sizeof(double) * nbatches);
   if (nbatches > 1) {
      double t;
      /* fill in the coefs */
      for (i = 0; i < nbatches; i++) {
	 t = temporal_deltas[i] = (2.0 * ((double) i / (nbatches - 1)) - 1.0)
	    * spec->temporal_filter_radius;
	 temporal_filter[i] = exp(-2.0 * t * t);
      }

      normalize_vector(temporal_filter, nbatches);
   } else {
      temporal_filter[0] = 1.0;
      temporal_deltas[0] = 0.0;
   }

#if 0
   for (j = 0; j < nbatches; j++)
      fprintf(stderr, "%4f %4f\n", temporal_deltas[j], temporal_filter[j]);
   fprintf(stderr, "\n");
#endif
   
   /* the number of additional rows of buckets we put at the edge so
      that the filter doesn't go off the edge */

   gutter_width = (filter_width - oversample) / 2;
   height = oversample * image_height + 2 * gutter_width;
   width  = oversample * image_width  + 2 * gutter_width;

   nbuckets = width * height;
   if (1) {
     static char *last_block = NULL;
     static int last_block_size = 0;
     int memory_rqd = (sizeof(bucket) * nbuckets +
		       sizeof(abucket) * nbuckets +
		       sizeof(point) * SUB_BATCH_SIZE);
     if (memory_rqd > last_block_size) {
       if (last_block != NULL)
	  free(last_block);
       last_block = (char *) malloc(memory_rqd);
       if (NULL == last_block) {
	  fprintf(stderr, "render_rectangle: cannot malloc %d bytes.\n", memory_rqd);
	  exit(1);
       }
       /* else fprintf(stderr, "render_rectangle: mallocked %dMb.\n", Mb); */

       last_block_size = memory_rqd;
     }
     buckets = (bucket *) last_block;
     accumulate = (abucket *) (last_block + sizeof(bucket) * nbuckets);
     points = (point *)  (last_block + (sizeof(bucket) + sizeof(abucket)) * nbuckets);
   }

   bzero((char *) accumulate, sizeof(abucket) * nbuckets);
   for (batch_num = 0; batch_num < nbatches; batch_num++) {
      double batch_time;
      double sample_density;
      control_point cp;
      bzero((char *) buckets, sizeof(bucket) * nbuckets);
      batch_time = spec->time + temporal_deltas[batch_num];

      /* interpolate and get a control point */
      interpolate(spec->cps, spec->ncps, batch_time, &cp);

      /* compute the colormap entries.  the input colormap is 256 long with
	 entries from 0 to 1.0 */
      for (j = 0; j < CMAP_SIZE; j++) {
	 for (k = 0; k < 3; k++) {
#if 1
	    cmap[j][k] = (int) (cp.cmap[(j * 256) / CMAP_SIZE][k] *
				cp.white_level);
#else
	    /* monochrome if you don't have any cmaps */
	    cmap[j][k] = cp.white_level;
#endif	    
	 }
	 cmap[j][3] = cp.white_level;
      }

      /* compute camera */
      if (1) {
	double t0, t1, shift, corner0, corner1;
	double scale;

	scale = pow(2.0, cp.zoom);
	sample_density = cp.sample_density * scale * scale;
	
	ppux = cp.pixels_per_unit * scale;
	ppuy = field ? (ppux / 2.0) : ppux;
	switch (field) {
	case field_both: shift =  0.0; break;
	case field_even: shift = -0.5; break;
	case field_odd:  shift =  0.5; break;
	}
	shift = shift / ppux;
	t0 = (double) gutter_width / (oversample * ppux);
	t1 = (double) gutter_width / (oversample * ppuy);
	corner0 = cp.center[0] - image_width / ppux / 2.0;
	corner1 = cp.center[1] - image_height / ppuy / 2.0;
	bounds[0] = corner0 - t0;
	bounds[1] = corner1 - t1 + shift;
	bounds[2] = corner0 + image_width  / ppux + t0;
	bounds[3] = corner1 + image_height / ppuy + t1 + shift;
	size[0] = 1.0 / (bounds[2] - bounds[0]);
	size[1] = 1.0 / (bounds[3] - bounds[1]);
      }

      nsamples = (int) (sample_density * nbuckets /
			(oversample * oversample));
   
      batch_size = nsamples / cp.nbatches;

      sbc = 0;
      for (sub_batch = 0;
	   sub_batch < batch_size;
	   sub_batch += SUB_BATCH_SIZE) {

	if (progress&&!(sbc++&7))
	  (*progress)(0.5*sub_batch/(double)batch_size);

	 /* generate a sub_batch_size worth of samples */
	 points[0][0] = random_uniform11();
	 points[0][1] = random_uniform11();
	 points[0][2] = random_uniform01();
	 iterate(&cp, SUB_BATCH_SIZE, FUSE, points);
	 
	 
	 /* merge them into buckets, looking up colors */
	 for (j = 0; j < SUB_BATCH_SIZE; j++) {
	    int k, color_index;
	    double *p = points[j];
	    bucket *b;
	    if (p[0] < bounds[0] ||
		p[1] < bounds[1] ||
		p[0] > bounds[2] ||
		p[1] > bounds[3])
	       continue;
	    color_index = (int) (p[2] * CMAP_SIZE);
	    if (color_index < 0) color_index = 0;
	    else if (color_index > (CMAP_SIZE-1))
	       color_index = CMAP_SIZE-1;
	    b = buckets +
	       (int) (width * (p[0] - bounds[0]) * size[0]) +
		  width * (int) (height * (p[1] - bounds[1]) * size[1]);	    
	    for (k = 0; k < 4; k++)
	       bump_no_overflow(b[0][k], cmap[color_index][k], short);
	 }
      }
      
      if (1) {
	 double k1 =(cp.contrast * cp.brightness *
		     PREFILTER_WHITE * 268.0 *
		     temporal_filter[batch_num]) / 256;
	 double area = image_width * image_height / (ppux * ppuy);
	 double k2 = (oversample * oversample * nbatches) /
	    (cp.contrast * area * cp.white_level * sample_density);
 
	 /* log intensity in hsv space */
	 for (j = 0; j < height; j++)
	    for (i = 0; i < width; i++) {
	       abucket *a = accumulate + i + j * width;
	       bucket *b = buckets + i + j * width;
	       double c[4], ls;
	       c[0] = (double) b[0][0];
	       c[1] = (double) b[0][1];
	       c[2] = (double) b[0][2];
	       c[3] = (double) b[0][3];
	       if (0.0 == c[3])
		 continue;
	       
	       ls = (k1 * log(1.0 + c[3] * k2))/c[3];
	       c[0] *= ls;
	       c[1] *= ls;
	       c[2] *= ls;
	       c[3] *= ls;

	       bump_no_overflow(a[0][0], c[0] + 0.5, accum_t);
	       bump_no_overflow(a[0][1], c[1] + 0.5, accum_t);
	       bump_no_overflow(a[0][2], c[2] + 0.5, accum_t);
	       bump_no_overflow(a[0][3], c[3] + 0.5, accum_t);
	    }
      }
   }
   /*
    * filter the accumulation buffer down into the image
    */
   if (1) {
      int x, y;
      double t[4];
      double g = 1.0 / spec->cps[0].gamma;
      y = 0;
      for (j = 0; j < image_height; j++) {
	 if (progress && !(j&15))
	   (*progress)(0.5+0.5*j/(double)image_height);
	 x = 0;
	 for (i = 0; i < image_width; i++) {
	    int ii, jj, a;
	    unsigned char *p;
	    t[0] = t[1] = t[2] = t[3] = 0.0;
	    for (ii = 0; ii < filter_width; ii++)
	       for (jj = 0; jj < filter_width; jj++) {
		  double k = filter[ii + jj * filter_width];
		  abucket *a = accumulate + x + ii + (y + jj) * width;
		  
		  t[0] += k * a[0][0];
		  t[1] += k * a[0][1];
		  t[2] += k * a[0][2];
		  t[3] += k * a[0][3];
	       }
	    p = out + nchan * (i + j * out_width);
	    a = 256.0 * pow((double) t[0] / PREFILTER_WHITE, g) + 0.5;
	    if (a < 0) a = 0; else if (a > 255) a = 255;
	    p[0] = a;
	    a = 256.0 * pow((double) t[1] / PREFILTER_WHITE, g) + 0.5;
	    if (a < 0) a = 0; else if (a > 255) a = 255;
	    p[1] = a;
	    a = 256.0 * pow((double) t[2] / PREFILTER_WHITE, g) + 0.5;
	    if (a < 0) a = 0; else if (a > 255) a = 255;
	    p[2] = a;
	    if (nchan > 3) {
	      a = 256.0 * pow((double) t[3] / PREFILTER_WHITE, g) + 0.5;
	      if (a < 0) a = 0; else if (a > 255) a = 255;
	      p[3] = a;
	    }
	    x += oversample;
	 }
	 y += oversample;
      }
   }
   
   free(filter);
}
