/* xscreensaver, Copyright (c) 1992-2018 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.
 */

/* decayscreen
 *
 * Based on slidescreen program from the xscreensaver application and the
 * decay program for Sun framebuffers.  This is the comment from the decay.c
 * file:

 * decay.c
 *   find the screen bitmap for the console and make it "decay" by
 *   randomly shifting random rectangles by one pixelwidth at a time.
 *
 *   by David Wald, 1988
 *        rewritten by Natuerlich!
 *   based on a similar "utility" on the Apollo ring at Yale.

 * X version by
 *
 *  Vivek Khera <khera@cs.duke.edu>
 *  5-AUG-1993
 *
 *  Hacked by jwz, 28-Nov-97 (sped up and added new motion directions)
 
 *  R. Schultz
 *  Added "melt" & "stretch" modes 28-Mar-1999
 *
 */

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

struct state {
  Display *dpy;
  Window window;
  XWindowAttributes xgwa;
  Pixmap saved;
  int saved_w, saved_h;

  int sizex, sizey;
  int delay;
  int duration;
  GC gc;
  int mode;
  int random_p;
  time_t start_time;

  int fuzz_toggle;
  const int *current_bias;

  async_load_state *img_loader;
};


#define SHUFFLE		0
#define UP		1
#define LEFT		2
#define RIGHT		3
#define DOWN		4
#define UPLEFT		5
#define DOWNLEFT	6
#define UPRIGHT		7
#define DOWNRIGHT	8
#define IN		9
#define OUT		10
#define MELT		11
#define STRETCH		12
#define FUZZ		13

static void
decayscreen_load_image (struct state *st)
{
  XWindowAttributes xgwa;
  XGetWindowAttributes (st->dpy, st->window, &xgwa);
  st->sizex = xgwa.width;
  st->sizey = xgwa.height;
  if (st->img_loader) abort();

  st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
                                            st->window, 0, 0);
}

static void *
decayscreen_init (Display *dpy, Window window)
{
  struct state *st = (struct state *) calloc (1, sizeof(*st));
  XGCValues gcv;
  long gcflags;
  unsigned long bg;
  char *s;

  st->dpy = dpy;
  st->window = window;
  st->random_p = 0;

  s = get_string_resource(st->dpy, "mode", "Mode");
  if      (s && !strcmp(s, "shuffle")) st->mode = SHUFFLE;
  else if (s && !strcmp(s, "up")) st->mode = UP;
  else if (s && !strcmp(s, "left")) st->mode = LEFT;
  else if (s && !strcmp(s, "right")) st->mode = RIGHT;
  else if (s && !strcmp(s, "down")) st->mode = DOWN;
  else if (s && !strcmp(s, "upleft")) st->mode = UPLEFT;
  else if (s && !strcmp(s, "downleft")) st->mode = DOWNLEFT;
  else if (s && !strcmp(s, "upright")) st->mode = UPRIGHT;
  else if (s && !strcmp(s, "downright")) st->mode = DOWNRIGHT;
  else if (s && !strcmp(s, "in")) st->mode = IN;
  else if (s && !strcmp(s, "out")) st->mode = OUT;
  else if (s && !strcmp(s, "melt")) st->mode = MELT;
  else if (s && !strcmp(s, "stretch")) st->mode = STRETCH;
  else if (s && !strcmp(s, "fuzz")) st->mode = FUZZ;
  else {
    if (s && *s && !!strcmp(s, "random"))
      fprintf(stderr, "%s: unknown mode %s\n", progname, s);
    st->random_p = 1;
    st->mode = random() % (FUZZ+1);
  }
  if (s) free (s);

  st->delay = get_integer_resource (st->dpy, "delay", "Integer");
  if (st->delay < 0) st->delay = 0;

  st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
  if (st->duration < 1) st->duration = 1;

  XGetWindowAttributes (st->dpy, st->window, &st->xgwa);

  gcv.function = GXcopy;
  gcv.subwindow_mode = IncludeInferiors;
  bg = get_pixel_resource (st->dpy, st->xgwa.colormap, "background", "Background");
  gcv.foreground = bg;

  gcflags = GCForeground | GCFunction;
  if (use_subwindow_mode_p(st->xgwa.screen, st->window)) /* see grabscreen.c */
    gcflags |= GCSubwindowMode;
  st->gc = XCreateGC (st->dpy, st->window, gcflags, &gcv);

  st->start_time = time ((time_t *) 0);
  decayscreen_load_image (st);

  return st;
}


/*
 * perform one iteration of decay
 */
static unsigned long
decayscreen_draw (Display *dpy, Window window, void *closure)
{
    struct state *st = (struct state *) closure;
    int left, top, width, height, toleft, totop;

#define L 101
#define R 102
#define U 103
#define D 104
    static const int no_bias[]        = { L,L,L,L, R,R,R,R, U,U,U,U, D,D,D,D };
    static const int up_bias[]        = { L,L,L,L, R,R,R,R, U,U,U,U, U,U,D,D };
    static const int down_bias[]      = { L,L,L,L, R,R,R,R, U,U,D,D, D,D,D,D };
    static const int left_bias[]      = { L,L,L,L, L,L,R,R, U,U,U,U, D,D,D,D };
    static const int right_bias[]     = { L,L,R,R, R,R,R,R, U,U,U,U, D,D,D,D };

    static const int upleft_bias[]    = { L,L,L,L, L,R,R,R, U,U,U,U, U,D,D,D };
    static const int downleft_bias[]  = { L,L,L,L, L,R,R,R, U,U,U,D, D,D,D,D };
    static const int upright_bias[]   = { L,L,L,R, R,R,R,R, U,U,U,U, U,D,D,D };
    static const int downright_bias[] = { L,L,L,R, R,R,R,R, U,U,U,D, D,D,D,D };

    int off = 1;
    if (st->sizex > 2560) off *= 2;  /* Retina displays */

    if (st->img_loader)   /* still loading */
      {
        st->img_loader = load_image_async_simple (st->img_loader, 
                                                  0, 0, 0, 0, 0);
        if (! st->img_loader) {  /* just finished */

          st->start_time = time ((time_t *) 0);
          if (st->random_p)
            st->mode = random() % (FUZZ+1);

          if (st->mode == MELT || st->mode == STRETCH)
            /* make sure screen eventually turns background color */
            XDrawLine (st->dpy, st->window, st->gc, 0, 0, st->sizex, 0); 

          if (!st->saved) {
            st->saved = XCreatePixmap (st->dpy, st->window,
                                       st->sizex, st->sizey,
                                       st->xgwa.depth);
            st->saved_w = st->sizex;
            st->saved_h = st->sizey;
          }
          XCopyArea (st->dpy, st->window, st->saved, st->gc, 0, 0,
                     st->sizex, st->sizey, 0, 0);
        }
      return st->delay;
    }

    if (!st->img_loader &&
        st->start_time + st->duration < time ((time_t *) 0)) {
      decayscreen_load_image (st);
    }

    switch (st->mode) {
      case SHUFFLE:	st->current_bias = no_bias; break;
      case UP:		st->current_bias = up_bias; break;
      case LEFT:	st->current_bias = left_bias; break;
      case RIGHT:	st->current_bias = right_bias; break;
      case DOWN:	st->current_bias = down_bias; break;
      case UPLEFT:	st->current_bias = upleft_bias; break;
      case DOWNLEFT:	st->current_bias = downleft_bias; break;
      case UPRIGHT:	st->current_bias = upright_bias; break;
      case DOWNRIGHT:	st->current_bias = downright_bias; break;
      case IN:		st->current_bias = no_bias; break;
      case OUT:		st->current_bias = no_bias; break;
      case MELT:	st->current_bias = no_bias; break;
      case STRETCH:	st->current_bias = no_bias; break;
      case FUZZ:	st->current_bias = no_bias; break;
     default: abort();
    }

#define nrnd(x) ((x) ? random() % (x) : x)

    if (st->mode == MELT || st->mode == STRETCH) {
      left = nrnd(st->sizex/2);
      top = nrnd(st->sizey);
      width = nrnd( st->sizex/2 ) + st->sizex/2 - left;
      height = nrnd(st->sizey - top);
      toleft = left;
      totop = top+off;

    } else if (st->mode == FUZZ) {  /* By Vince Levey <vincel@vincel.org>;
                                   inspired by the "melt" mode of the
                                   "scrhack" IrisGL program by Paul Haeberli
                                   circa 1991. */
      left = nrnd(st->sizex - 1);
      top  = nrnd(st->sizey - 1);
      st->fuzz_toggle = !st->fuzz_toggle;
      if (st->fuzz_toggle)
        {
          totop = top;
          height = off;
          toleft = nrnd(st->sizex - 1);
          if (toleft > left)
            {
              width = toleft-left;
              toleft = left;
              left++;
            }
          else
            {
              width = left-toleft;
              left = toleft;
              toleft++;
            }
        }
      else
        {
          toleft = left;
          width = off;
          totop  = nrnd(st->sizey - 1);
          if (totop > top)
            {
              height = totop-top;
              totop = top;
              top++;
            }
          else
            {
              height = top-totop;
              top = totop;
              totop++;
            }
        }

    } else {

      left = nrnd(st->sizex - 1);
      top = nrnd(st->sizey);
      width = nrnd(st->sizex - left);
      height = nrnd(st->sizey - top);
      
      toleft = left;
      totop = top;
      if (st->mode == IN || st->mode == OUT) {
	int x = left+(width/2);
	int y = top+(height/2);
	int cx = st->sizex/2;
	int cy = st->sizey/2;
	if (st->mode == IN) {
	  if      (x > cx && y > cy)   st->current_bias = upleft_bias;
	  else if (x < cx && y > cy)   st->current_bias = upright_bias;
	  else if (x < cx && y < cy)   st->current_bias = downright_bias;
	  else /* (x > cx && y < cy)*/ st->current_bias = downleft_bias;
	} else {
	  if      (x > cx && y > cy)   st->current_bias = downright_bias;
	  else if (x < cx && y > cy)   st->current_bias = downleft_bias;
	  else if (x < cx && y < cy)   st->current_bias = upleft_bias;
	  else /* (x > cx && y < cy)*/ st->current_bias = upright_bias;
	}
      }
      
      switch (st->current_bias[random() % (sizeof(no_bias)/sizeof(*no_bias))]) {
      case L: toleft = left-off; break;
      case R: toleft = left+off; break;
      case U: totop = top-off; break;
      case D: totop = top+off; break;
      default: abort(); break;
      }
    }
    
    if (st->mode == STRETCH) {
      XCopyArea (st->dpy, st->window, st->window, st->gc,
                 0, st->sizey-top-off*2, st->sizex, top+off, 
		 0, st->sizey-top-off);
    } else {
      XCopyArea (st->dpy, st->window, st->window, st->gc,
                 left, top, width, height,
		 toleft, totop);
    }

#undef nrnd

    return st->delay;
}

static void
decayscreen_reshape (Display *dpy, Window window, void *closure, 
                 unsigned int w, unsigned int h)
{
  struct state *st = (struct state *) closure;
  if (! st->saved) return; /* Image might not be loaded yet */
  XClearWindow (st->dpy, st->window);
  XCopyArea (st->dpy, st->saved, st->window, st->gc,
             0, 0, st->saved_w, st->saved_h,
             ((int)w - st->saved_w) / 2,
             ((int)h - st->saved_h) / 2);
  st->sizex = w;
  st->sizey = h;
}

static Bool
decayscreen_event (Display *dpy, Window window, void *closure, XEvent *event)
{
  struct state *st = (struct state *) closure;
  if (screenhack_event_helper (dpy, window, event))
    {
      st->start_time = 0;
      return True;
    }
  return False;
}

static void
decayscreen_free (Display *dpy, Window window, void *closure)
{
  struct state *st = (struct state *) closure;
  XFreeGC (dpy, st->gc);
  free (st);
}



static const char *decayscreen_defaults [] = {
  ".background:			Black",
  ".foreground:			Yellow",
  "*dontClearRoot:		True",
  "*fpsSolid:			True",

#ifdef __sgi	/* really, HAVE_READ_DISPLAY_EXTENSION */
  "*visualID:			Best",
#endif

  "*delay:			10000",
  "*mode:			random",
  "*duration:			120",
#ifdef HAVE_MOBILE
  "*ignoreRotation:             True",
  "*rotateImages:               True",
#endif
  0
};

static XrmOptionDescRec decayscreen_options [] = {
  { "-delay",		".delay",		XrmoptionSepArg, 0 },
  { "-mode",		".mode",		XrmoptionSepArg, 0 },
  { "-duration",	".duration",		XrmoptionSepArg, 0 },
  { 0, 0, 0, 0 }
};


XSCREENSAVER_MODULE ("DecayScreen", decayscreen)
