/* xscreensaver, Copyright (c) 1992, 1994, 1996, 1998, 2001, 2005, 2006
 *  Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

/* Draw some fireworks.  Inspired from TI Explorer Lisp code by 
   John S. Pezaris <pz@hx.lcs.mit.edu>
 */

#include <math.h>
#include "screenhack.h"

struct projectile {
  int x, y;	/* position */
  int dx, dy;	/* velocity */
  int decay;
  int size;
  int fuse;
  Bool primary;
  Bool dead;
  XColor color;
  struct projectile *next_free;
};

#define PI_2000 6284


struct state {
  Display *dpy;
  Window window;

   struct projectile *projectiles, *free_projectiles;
   struct projectile **sorted_projectiles;

   GC draw_gc, erase_gc;
   unsigned int default_fg_pixel;
   Colormap cmap;

   int how_many, frequency, scatter, delay;

   int sin_cache[PI_2000];
   int cos_cache[PI_2000];

   int draw_xlim, draw_ylim, real_draw_xlim, real_draw_ylim;

   unsigned long last_pixel;
};



/* Slightly whacked, for better explosions
 */

static void
cache(struct state *st)
{               /*needs to be run once. Could easily be */
  int i;        /*reimplemented to run and cache at compile-time,*/
  double dA;    /*saving on init_pyro time */
  for (i=0; i<PI_2000; i++)
    {
      dA=sin(((double) (random() % (PI_2000/2)))/1000.0);
      /*Emulation of spherical distribution*/
      dA+=asin(frand(1.0))/M_PI_2*0.1;
      /*Approximating the integration of the binominal, for
        well-distributed randomness*/
      st->cos_cache[i]=(int) (cos(((double)i)/1000.0)*dA*2500.0);
      st->sin_cache[i]=(int) (sin(((double)i)/1000.0)*dA*2500.0);
    }
}


static struct projectile *
get_projectile (struct state *st)
{
  struct projectile *p;
  if (st->free_projectiles)
    {
      p = st->free_projectiles;
      st->free_projectiles = p->next_free;
      p->next_free = 0;
      p->dead = False;
      return p;
    }
  else
    return 0;
}

static void
free_projectile (struct state *st, struct projectile *p)
{
  p->next_free = st->free_projectiles;
  st->free_projectiles = p;
  p->dead = True;
}

static void
launch (struct state *st, 
        int xlim, int ylim, int g)
{
  struct projectile *p = get_projectile (st);
  int x, dx, xxx;
  if (! p) return;

  do {
    x = (random () % xlim);
    dx = 30000 - (random () % 60000);
    xxx = x + (dx * 200);
  } while (xxx <= 0 || xxx >= xlim);

  p->x = x;
  p->y = ylim;
  p->dx = dx;
  p->size = 8000;
  p->decay = 0;
  p->dy = (random () % 4000) - 13000;
  p->fuse = ((((random () % 500) + 500) * abs (p->dy / g)) / 1000);
  p->primary = True;

  /* cope with small windows -- those constants assume big windows. */
  {
    int dd = 1000000 / ylim;
    if (dd > 1)
      p->fuse /= dd;
  }

  if (! mono_p)
    {
      hsv_to_rgb (random () % 360, 1.0, 1.0,
		  &p->color.red, &p->color.green, &p->color.blue);
      p->color.flags = DoRed | DoGreen | DoBlue;
      if (!XAllocColor (st->dpy, st->cmap, &p->color))
	{
	  p->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
	  p->color.red = p->color.green = p->color.blue = 0xFFFF;
	}
    }
}

static struct projectile *
shrapnel (struct state *st, struct projectile *parent)
{
  struct projectile *p = get_projectile (st);
  int v;
  if (! p) return 0;
  p->x = parent->x;
  p->y = parent->y;
  v=random () % PI_2000;
  p->dx =(st->sin_cache[v]) + parent->dx;
  p->dy =(st->cos_cache[v]) + parent->dy;
  p->decay = (random () % 50) - 60;
  p->size = (parent->size * 2) / 3;
  p->fuse = 0;
  p->primary = False;

  p->color = parent->color;
  if (! mono_p)
    XAllocColor (st->dpy, st->cmap, &p->color); /* dup the lock */
  
  return p;
}

static void *
pyro_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  int i;
  XGCValues gcv;
  XWindowAttributes xgwa;
  st->dpy = dpy;
  st->window = window;
  XGetWindowAttributes (st->dpy, st->window, &xgwa);
  st->last_pixel = ~0;
  st->cmap = xgwa.colormap;
  st->delay = get_integer_resource (st->dpy, "delay", "Integer");
  st->how_many = get_integer_resource (st->dpy, "count", "Integer");
  st->frequency = get_integer_resource (st->dpy, "frequency", "Integer");
  st->scatter = get_integer_resource (st->dpy, "scatter", "Integer");
  if (st->how_many <= 0) st->how_many = 100;
  if (st->frequency <= 0) st->frequency = 30;
  if (st->scatter <= 0) st->scatter = 20;
  st->projectiles = 0;
  st->free_projectiles = 0;
  st->projectiles = (struct projectile *)
    calloc (st->how_many, sizeof (*st->projectiles));
  st->sorted_projectiles = (struct projectile **)
    calloc (st->how_many, sizeof (*st->sorted_projectiles));
  for (i = 0; i < st->how_many; i++)
    free_projectile (st, &st->projectiles [i]);
  for (i = 0; i < st->how_many; i++)
    st->sorted_projectiles[i] = &st->projectiles[i];
  gcv.foreground = st->default_fg_pixel =
    get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
  st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
  gcv.foreground = get_pixel_resource (st->dpy, st->cmap, "background", "Background");
  st->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
  XClearWindow (st->dpy, st->window);
  cache(st);  

  return st;
}


static int
projectile_pixel_sorter (const void *a, const void *b)
{
  struct projectile *pa = *(struct projectile **) a;
  struct projectile *pb = *(struct projectile **) b;
  if (pa->color.pixel == pb->color.pixel) return 0;
  else if (pa->color.pixel < pb->color.pixel) return -1;
  else return 1;
}

static void
sort_by_pixel (struct state *st, int length)
{
  qsort ((void *) st->sorted_projectiles,
         length,
         sizeof(*st->sorted_projectiles),
         projectile_pixel_sorter);
}


static unsigned long
pyro_draw (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  XWindowAttributes xgwa;
  int g = 100;
  int resort = 0;
  int i;
  
  for (i = 0; i < st->how_many; i++)
    {
      struct projectile *p = st->sorted_projectiles [i];
      int old_x, old_y, old_size;
      int size, x, y;
      if (p->dead) continue;
      old_x = p->x >> 10;
      old_y = p->y >> 10;
      old_size = p->size >> 10;
      size = (p->size += p->decay) >> 10;
      x = (p->x += p->dx) >> 10;
      y = (p->y += p->dy) >> 10;
      p->dy += (p->size >> 6);
      if (p->primary) p->fuse--;

      /* erase old one */
      if (old_size > 0)
        {
          if (old_size == 1)
	    XDrawPoint (st->dpy, st->window, st->erase_gc, old_x, old_y);
          else
            XFillRectangle (st->dpy, st->window, st->erase_gc, old_x, old_y,
                            old_size, old_size);
        }

      if ((p->primary ? (p->fuse > 0) : (p->size > 0)) &&
	  x < st->real_draw_xlim &&
	  y < st->real_draw_ylim &&
	  x > 0 &&
	  y > 0)
	{
          if (size > 0)
            {
              unsigned long pixel;

              if (mono_p || p->primary)
                pixel = st->default_fg_pixel;
              else
                pixel = p->color.pixel;

              if (pixel != st->last_pixel)
                {
                  st->last_pixel = pixel;
                  XSetForeground (st->dpy, st->draw_gc, pixel);
                }

              if (size == 1)
                XDrawPoint (st->dpy, st->window, st->draw_gc, x, y);
              else if (size < 4)
                XFillRectangle (st->dpy, st->window, st->draw_gc, x, y, size, size);
              else
                XFillArc (st->dpy, st->window, st->draw_gc, x, y, size, size, 0, 360*64);
            }
        }
      else
	{
	  free_projectile (st, p);
	  if (! mono_p)
	    if (p->color.pixel != WhitePixel (st->dpy, DefaultScreen (st->dpy)))
	      XFreeColors (st->dpy, st->cmap, &p->color.pixel, 1, 0);
	}

      if (p->primary && p->fuse <= 0)
	{
	  int j = (random () % st->scatter) + (st->scatter/2);
	  while (j--)
	    shrapnel (st, p);
          resort = 1;
	}
    }

  if ((random () % st->frequency) == 0)
    {
      XGetWindowAttributes (st->dpy, st->window, &xgwa);
      st->real_draw_xlim = xgwa.width;
      st->real_draw_ylim = xgwa.height;
      st->draw_xlim = st->real_draw_xlim * 1000;
      st->draw_ylim = st->real_draw_ylim * 1000;
      launch (st, st->draw_xlim, st->draw_ylim, g);
      resort = 1;
    }

  /* being sorted lets us avoid changing the GC's foreground color as often. */
  if (resort)
    sort_by_pixel (st, st->how_many);

  return st->delay;
}

static void
pyro_reshape (Display *dpy, Window window, void *closure, 
              unsigned int w, unsigned int h)
{
}

static Bool
pyro_event (Display *dpy, Window window, void *closure, XEvent *event)
{
  return False;
}

static void
pyro_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  free (st);
}



static const char *pyro_defaults [] = {
  ".background:	black",
  ".foreground:	white",
  "*count:	600",
  "*delay:	5000",
  "*frequency:	30",
  "*scatter:	100",
  "*geometry:	800x500",
  0
};

static XrmOptionDescRec pyro_options [] = {
  { "-delay",		".delay",	XrmoptionSepArg, 0 },
  { "-count",		".count",	XrmoptionSepArg, 0 },
  { "-frequency",	".frequency",	XrmoptionSepArg, 0 },
  { "-scatter",		".scatter",	XrmoptionSepArg, 0 },
  { 0, 0, 0, 0 }
};

XSCREENSAVER_MODULE ("Pyro", pyro)
