/*
    bomb - automatic interactive visual stimulation
    Copyright (C) 1994  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 "defs.h"
#include "libifs.h"

double cc[NXFORMS][3][2];

#define nsamples 1000
#define nbatches 10
#define bufsize (nsamples/nbatches)
ifs_point p_buf[bufsize];


/*
 * a rotor is the sum of spinners (n=~3).  each spinner's speed&radius
 * wanders piecewise linear over a range, uniform dist.  originally from
 * Tom Lawrence (tcl@cs.brown.edu: 'flight' simulator, now at SGI), put
 * into xlock (by Steve Zellers, zellers@sun.com), maintained there by
 * Patrick J. Naughton.  
 */

#define MAX_SPINNERS 10

typedef struct {
   int radius_steps, speed_steps;
   double radius, dradius;
   double speed, dspeed;
} spinner;

double spinner_time = 0.0;
int nspinners;
spinner spinners[MAX_SPINNERS];


int nspin2;
spinner spin2[MAX_SPINNERS];


void init_spinner(int *nspin, spinner *spin)
{
   int i;
   int n = (R%2) ? 3 : ((R%3)?2:4);
   if (n > MAX_SPINNERS)
      n = MAX_SPINNERS;
   *nspin = n;
#define KRS 30000
   for (i = 0; i < n; i++) {
      spin[i].radius_steps = KRS;
      spin[i].speed_steps = KRS;
      spin[i].radius = (R%100-25)/50.0;
      spin[i].speed  = 0.1 + (R%100)/20.0;
      spin[i].dradius = (R%100-50)/(50.0*KRS);
      spin[i].dspeed  = (R%100-50)/(20.0*KRS);
   }
}

void init_rotate()
{
   init_spinner(&nspinners, spinners);
   init_spinner(&nspin2, spin2);
}



void rotate(int *nspin, spinner *spin, double *xy)
{
   double x, y;
   int i;
   int n = *nspin;

   x = y = 0.0;
   for (i = 0; i < n; i++) {
#define SI spin[i]
      x += SI.radius * cos(SI.speed * spinner_time);
      y += SI.radius * sin(SI.speed * spinner_time);
      SI.radius += SI.dradius;
      SI.speed += SI.dspeed;
      if (0 == SI.radius_steps--) {
	 double goal = (R%100)/100.0+(R%100)/100.0;
	 SI.radius_steps = KRS;
	 SI.dradius = (goal - SI.radius) / KRS;
      }
      if (0 == SI.speed_steps--) {
	 double goal = 0.1 + (R%100)/20.0;
	 SI.speed_steps = KRS;
	 SI.dspeed = (goal - SI.speed) / KRS;
      }
   }

   x = x / n;
   y = y / n;

   xy[0] = x;
   xy[1] = y;
}


int rotate_int(int *nspin, spinner *spin, int *ixy)
{
   double xy[2];
   rotate(nspin, spin, xy);
   ixy[0] = (xy[0]+1) * (XSIZE/2);
   ixy[1] = (xy[1]+1) * (YSIZE/2);
}




/* flame support code */

void pick_liss_coefs()
{
   int i, j, k;
   double r;
   for (i = 0; i < NXFORMS; i++) {
      r = 1e-6;
      for (j = 0; j < 3; j++)
	 for (k = 0; k < 2; k++) {
	    cc[j][k][i] = random_uniform11();
	    r += cc[j][k][i] * cc[j][k][i];
	 }
      r = 0.015 / sqrt(r);
      for (j = 0; j < 3; j++)
	 for (k = 0; k < 2; k++)
	    cc[j][k][i] *= r;
   }
}

int nflames;
ifs_control_point flames[MAXFLAMES];

void init_flames()
{
   char sbuffer[4000];
   char *ss = sbuffer;
   int c, i, n = 0;
   FILE *f;

   if (getenv("quick"))
      goto quick;

   f = fopen(args("flame-data", "flame-data"), "r");
   if (NULL == f)
      goto fail;
   while (1) {
      i = 0;
      ss = sbuffer;
      do {
	 c = fgetc(f);
	 if (EOF == c)
	    goto done_reading;
	 sbuffer[i++] = c;
      } while (';' != c);
      parse_control_point(&ss, flames + n);
      n++;
      if (n == MAXFLAMES)
	 goto done_reading;
   }
 done_reading:
   nflames = n;
   if (n > 0)
      return;
 fail:
   fprintf(stderr, "flame-data not read\n");
 quick:
   random_control_point(flames);
}


rotcp(rule_t *p, double by)
{
   double s, t;
   int i;
   for (i = 0; i < NXFORMS; i++) {
      double r[2][2];
      double T[2][2];
      double U[2][2];
      double dtheta = by * p->drift_speed * 2.0 * M_PI / (1000.0 * nbatches);

      r[1][1] = r[0][0] = cos(dtheta);
      r[0][1] = sin(dtheta);
      r[1][0] = -r[0][1];
      T[0][0] = p->flame_cp.xform[i].c[0][0];
      T[1][0] = p->flame_cp.xform[i].c[1][0];
      T[0][1] = p->flame_cp.xform[i].c[0][1];
      T[1][1] = p->flame_cp.xform[i].c[1][1];
      mult_matrix(r, T, U);
      p->flame_cp.xform[i].c[0][0] = U[0][0];
      p->flame_cp.xform[i].c[1][0] = U[1][0];
      p->flame_cp.xform[i].c[0][1] = U[0][1];
      p->flame_cp.xform[i].c[1][1] = U[1][1];
   }
}

rot_point(rule_t *p, double by)
{
   int i;
   for (i = 0; i < NXFORMS; i++) {
      double s[2];
      double r[2][2];
      double dtheta = by * p->drift_speed * 2.0 * M_PI / (1000.0 * nbatches);
      r[1][1] = r[0][0] = cos(dtheta);
      r[0][1] = sin(dtheta);
      r[1][0] = -r[0][1];
      s[0] = p->flame_cp.xform[i].c[2][0];
      s[1] = p->flame_cp.xform[i].c[2][1];
      
	 
      p->flame_cp.xform[i].c[2][0] =
	 s[0] * r[0][0] + s[1] * r[0][1];
      p->flame_cp.xform[i].c[2][1] =
	 s[0] * r[1][0] + s[1] * r[1][1];
   }
}

void
pen(board_t *dboard, int mask, int x, int y, int bsize)
{
   if (x >= 1 && y >= 1 && x <= XSIZE-bsize && y <= YSIZE-bsize) {
      int i, j;
      for (i = 0; i < bsize; i++)
	 for (j = 0; j < bsize; j++)
	    (*dboard)[x+i][y+j] = mask;
   }
}

void step_rule_rotorug(int frame, rule_t *p, image8_t *fb)
{
   int x,y,bx,by,i;
   board_t *sboard, *dboard;
   u_char *lp;
   int drift_type = iclamp(p->drift, 9);
   int bsize = p->bsize/15;

   if (bsize < 1) bsize = 1;
   else if (bsize > 8) bsize /= 2;
   if (bsize > 8) bsize /= 2;
   

   sboard = &board[dbuf];
   dboard = &board[1-dbuf];
   dbuf = 1-dbuf;

   for(y=1;y<=YSIZE;y++) {
      lp = fb->p + (fb->stride * (y - 1));
      for(x=1;x<=XSIZE;x++) {
	 int t;
	 t = ((((*sboard)[x  ][y-1]) +
	       ((*sboard)[x-1][y  ]<<1) +
	       ((*sboard)[x  ][y  ]<<1) +
	       ((*sboard)[x+1][y  ]<<1) +
	       ((*sboard)[x  ][y+1])
	       + p->speed) >> 3);
	 if (t<0) t = 0;
	 t &= p->mask;
	 (*dboard)[x][y]= t;
	 if (p->remap)
	    t = remap[t];
	 *(lp++)=t;
      }
   }
   if (3 >= drift_type) {
      for (i = 0; i < 200; i++)  {
	 int xy[2];
	 spinner_time += 0.04;
	 rotate_int(&nspinners, spinners, xy);
	 x = xy[0];
	 switch (drift_type) {
	  case 0:
	    y = xy[1];
	    break;
	  case 1:
	    /* y = (int)(0.987 * spinner_time * YSIZE) % YSIZE; */
	    y = (1+sin(spinner_time)) * (YSIZE/2);
	    break;
	  case 2:
	    rotate_int(&nspin2, spin2, xy);
	    y = xy[1];
	    break;
	  case 3:
	    if (1) {
	       double t = frame * 0.05;
	       double s = (sin(t * p->rhythm[0]) *
			   sin(t * p->rhythm[1]));
	       s = 1 - s * s;
	       x = (xy[0] - (XSIZE/2)) * s + (XSIZE/2);
	       y = (xy[1] - (YSIZE/2)) * s + (YSIZE/2);
	    }
	    break;
	 }
	 pen(dboard, p->mask, x, y, bsize);
      }
   } else {
      for (bx = 0; bx < nbatches; bx++) {
	 int j, k;
	 double time = p->drift_speed * (bx/(double)nbatches) + p->drift_time;

	 switch (drift_type) {
	  case 4:
	    for (i = 0; i < NXFORMS; i++)
	       for (j = 0; j < 3; j++)
		  for (k = 0; k < 2; k++)
		     p->flame_cp.xform[i].c[j][k] = sin(time * cc[i][j][k]);
	    break;
	  case 5:
	    rotcp(p, 1.0);
	    break;
	  case 6:
	    rotcp(p, 0.02);
	    break;
	  case 7:
	    if (1) {
	       int c;
	       c = p->rhythm[0];
	       if ((R%c) < (c/2))
		  rotcp(p, 2.0);
	       else
		  rotcp(p, -1.95);

	       c = p->rhythm[1];
	       if ((R%c) < (c/2))
		  rot_point(p, 2.0);
	       else
		  rot_point(p, -2.0);
	    }
	    break;
	  case 8:
	    if (1) {
	       int c;
	       c = p->rhythm[0];
	       if ((R%c) < (c/2))
		  rotcp(p, 20.0);
	       else
		  rotcp(p, -20.05);

	       c = p->rhythm[1];
	       if ((R%c) < (c/2))
		  rot_point(p, 2.0);
	       else
		  rot_point(p, -2.0);
	    }
	    break;
	 }

	 /* overhead of fuse could get in the way as bufsize drops, don't
	    really need it.  on fast rhythm mach, how small can batches go?
	    they are 100 now */
	 
	 iterate(&p->flame_cp, bufsize, 5, p_buf);
	 for (i = 0; i < bufsize; i++)  {
	    double px, py;
	    px = p_buf[i][0];
	    py = p_buf[i][1];
	    if (px < 1.0 && py < 1.0 && px > -1.0 && py > -1.0) {
	       int x, y;
	       x = (px + 1.0) * XSIZE/2;
	       y = (py + 1.0) * YSIZE/2;
	       pen(dboard, p->mask, x, y, bsize);
	    }
	 }
      }
   }
#if 0
   for (i = 0; i < nspinners; i++) {
      printf("%d r=%g s=%g\n", i, SI.radius, SI.speed);
   }
#endif
}

