/* -*- Mode: C; tab-width: 4 -*- */
/* decay --- decayscreen */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)decay.c	5.00 2000/11/01 xlockmore";

#endif

/* xscreensaver, Copyright (c) 1992, 1993, 1994, 1996, 1997
 * 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.
 */

/*
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 17-Mar-1999: Converted from xscreensaver's decayscreen
 * 
 * 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)
 */

#ifdef STANDALONE
#define MODE_decay
#define PROGCLASS "Decay"
#define HACK_INIT init_decay
#define HACK_DRAW draw_decay
#define decay_opts xlockmore_opts
#define DEFAULTS "*delay: 200000 \n" \
 "*count: 6 \n" \
 "*cycles: 30 \n" \
 "*ncolors: 200 \n"
#include "xlockmore.h"    /* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"    /* in xlockmore distribution */
#endif /* STANDALONE */

#include "iostuff.h"
#include "color.h"

#ifdef MODE_decay

extern Bool hide;

ModeSpecOpt decay_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct   decay_description =
{"decay", "init_decay", "draw_decay", "release_decay",
 "refresh_decay", "init_decay", (char *) NULL, &decay_opts,
 200000, 6, 30, 1, 64, 0.3, "",
 "Shows a decaying screen", 0, NULL};

#endif

#define DECAY_WIDTH   image_width
#define DECAY_HEIGHT    image_height
#define DECAY_BITS    image_bits
#include "decay.xbm"

#ifdef HAVE_XPM
#define DECAY_NAME    image_name
#include "decay.xpm"
#define DEFAULT_XPM 1
#endif

typedef struct
{
	XPoint   windowsize;
	int      mode;

	XPoint   randompos, randpos;
	XImage  *logo;
	GC       backGC;
	Colormap cmap;
	int      graphics_format;
	unsigned long black;
	Bool     hide;
} decaystruct;

static decaystruct *decay_info = (decaystruct *) NULL;

#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	DEGREE	1

static void
free_decay(Display * display, decaystruct * dp)
{
	if (dp->cmap != None) {
		XFreeColormap(display, dp->cmap);
		if (dp->backGC != None) {
			XFreeGC(display, dp->backGC);
			dp->backGC = None;
		}
		dp->cmap = None;
	} else
		dp->backGC = None;
	if (dp->hide && (dp->logo != None)) {
		destroyImage(&dp->logo, &dp->graphics_format);
		dp->logo = None;
	}
}

static void
alloc_decay(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	decaystruct *dp = &decay_info[MI_SCREEN(mi)];

	if (dp->hide) {
		if (dp->logo == None) {
			getImage(mi, &dp->logo, DECAY_WIDTH, DECAY_HEIGHT, DECAY_BITS,
#ifdef HAVE_XPM
				DEFAULT_XPM, DECAY_NAME,
#endif
				&dp->graphics_format, &dp->cmap, &dp->black);
			if (dp->logo == None) {
				free_decay(display, dp);
				return;
			}
		}
#ifndef STANDALONE
		if (dp->cmap != None) {
			setColormap(display, window, dp->cmap, MI_IS_INWINDOW(mi));
			if (dp->backGC == None) {
				XGCValues   xgcv;

				xgcv.background = dp->black;
				dp->backGC = XCreateGC(display, window, GCBackground, &xgcv);
				if (dp->backGC == None) {
					free_decay(display, dp);
					return;
				}
			}
		} else
#endif /* STANDALONE */
		{
			dp->black = MI_BLACK_PIXEL(mi);
			dp->backGC = MI_GC(mi);
		}
	}
#ifndef STANDALONE
	else {
		setColormap(display, window, DefaultColormapOfScreen(MI_SCREENPTR(mi)), MI_IS_INWINDOW(mi));
		dp->backGC = MI_GC(mi);
	}
#endif /* STANDALONE */
}

void
init_decay(ModeInfo * mi)
{
	Display *display = MI_DISPLAY(mi);
	Window window = MI_WINDOW(mi);
	decaystruct *dp;

	char *s = (char*) "random";

	if (decay_info == NULL) {
		if ((decay_info = (decaystruct *) calloc(MI_NUM_SCREENS(mi),
						sizeof(decaystruct))) == NULL)
			return;
	}
	dp = &decay_info[MI_SCREEN(mi)];

	if      (s && !strcmp(s, "shuffle")) dp->mode = SHUFFLE;
	else if (s && !strcmp(s, "up")) dp->mode = UP;
	else if (s && !strcmp(s, "left")) dp->mode = LEFT;
	else if (s && !strcmp(s, "right")) dp->mode = RIGHT;
	else if (s && !strcmp(s, "down")) dp->mode = DOWN;
	else if (s && !strcmp(s, "upleft")) dp->mode = UPLEFT;
	else if (s && !strcmp(s, "downleft")) dp->mode = DOWNLEFT;
	else if (s && !strcmp(s, "upright")) dp->mode = UPRIGHT;
	else if (s && !strcmp(s, "downright")) dp->mode = DOWNRIGHT;
	else if (s && !strcmp(s, "in")) dp->mode = IN;
	else if (s && !strcmp(s, "out")) dp->mode = OUT;
	else {
		if (s && *s && !!strcmp(s, "random"))
			(void) fprintf(stderr, "%s: unknown mode %s\n", ProgramName, s);
		dp->mode = (int) (LRAND() % (OUT+1));
	}

	if (MI_IS_FULLRANDOM(mi) && !hide)
		dp->hide = (Bool) (LRAND() & 1);
	else
		dp->hide = hide;


	dp->windowsize.x = MI_WIDTH(mi);
	dp->windowsize.y = MI_HEIGHT(mi);
	alloc_decay(mi);
	if (!dp->backGC)
		return;
	if (dp->hide) {
  		/* do not want any exposure events from XCopyArea */
  		XSetGraphicsExposures(display, dp->backGC, False);
		MI_CLEARWINDOWCOLORMAP(mi, dp->backGC, dp->black);
		dp->randompos.x =
			NRAND(MAX((dp->windowsize.x - dp->logo->width), 1));
                dp->randompos.y =
			NRAND(MAX((dp->windowsize.y - dp->logo->height), 1));
		if (MI_NPIXELS(mi) <= 2)
			XSetForeground(display, dp->backGC, MI_WHITE_PIXEL(mi));
		else
			XSetForeground(display, dp->backGC, MI_PIXEL(mi, NRAND(MI_NPIXELS(mi))));
		(void) XPutImage(display, window, dp->backGC, dp->logo,
			(int) (NRAND(MAX(1, (dp->logo->width - dp->windowsize.x)))),
			(int) (NRAND(MAX(1, (dp->logo->height - dp->windowsize.y)))),
                                 dp->randompos.x, dp->randompos.y,
                                 dp->windowsize.x, dp->windowsize.y);

	} else {
		XCopyArea (MI_DISPLAY(mi), MI_ROOT_PIXMAP(mi), MI_WINDOW(mi),
		       dp->backGC, 0, 0, MI_WIDTH(mi), MI_HEIGHT(mi),
		       0, 0);
		XFlush(MI_DISPLAY(mi));
	}
}


/*
 * perform one iteration of decay
 */
void
draw_decay (ModeInfo * mi)
{
    int left, top, width, height, toleft, totop;

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

    static int upleft_bias[]    = { L,L,L,L, L,R,R,R, U,U,U,U, U,D,D,D };
    static int downleft_bias[]  = { L,L,L,L, L,R,R,R, U,U,U,D, D,D,D,D };
    static int upright_bias[]   = { L,L,L,R, R,R,R,R, U,U,U,U, U,D,D,D };
    static int downright_bias[] = { L,L,L,R, R,R,R,R, U,U,U,D, D,D,D,D };
    int *bias, side;
    decaystruct * dp;

	if (decay_info == NULL)
		return;
	dp = &decay_info[MI_SCREEN(mi)];
	if (dp->backGC == None)
		return;

	MI_IS_DRAWN(mi) = True;

    switch (dp->mode) {
      case SHUFFLE:	bias = no_bias; break;
      case UP:		bias = up_bias; break;
      case LEFT:	bias = left_bias; break;
      case RIGHT:	bias = right_bias; break;
      case DOWN:	bias = down_bias; break;
      case UPLEFT:	bias = upleft_bias; break;
      case DOWNLEFT:	bias = downleft_bias; break;
      case UPRIGHT:	bias = upright_bias; break;
      case DOWNRIGHT:	bias = downright_bias; break;
      case IN:		bias = no_bias; break;
      case OUT:		bias = no_bias; break;
      default: bias = no_bias;
	 if (MI_IS_VERBOSE(mi)) {
             (void) fprintf(stderr, "Weirdness in draw_decay()\n");
             (void) fprintf(stderr, "dp->mode = %d\n", dp->mode);
         }
    }

    left = NRAND(dp->windowsize.x);
    top = NRAND(dp->windowsize.y);
    width = NRAND(dp->windowsize.x - left);
    height = NRAND(dp->windowsize.y - top);

    toleft = left;
    totop = top;

    if (dp->mode == IN || dp->mode == OUT) {
      int x = left+(width/2);
      int y = top+(height/2);
      int cx = dp->windowsize.x/2;
      int cy = dp->windowsize.y/2;
      if (dp->mode == IN) {
	if      (x > cx && y > cy)   bias = upleft_bias;
	else if (x < cx && y > cy)   bias = upright_bias;
	else if (x < cx && y < cy)   bias = downright_bias;
	else /* (x > cx && y < cy)*/ bias = downleft_bias;
      } else {
	if      (x > cx && y > cy)   bias = downright_bias;
	else if (x < cx && y > cy)   bias = downleft_bias;
	else if (x < cx && y < cy)   bias = upleft_bias;
	else /* (x > cx && y < cy)*/ bias = upright_bias;
      }
    }

    side = bias[LRAND() % (sizeof(no_bias)/sizeof(*no_bias))];
    switch (side) {
      case L: toleft = left-DEGREE; break;
      case R: toleft = left+DEGREE; break;
      case U: totop = top-DEGREE; break;
      case D: totop = top+DEGREE; break;
      default:
	 if (MI_IS_VERBOSE(mi)) {
             (void) fprintf(stderr, "Weirdness in draw_decay()\n");
             (void) fprintf(stderr, "side = %d\n", side);
         }
    }

    XCopyArea (MI_DISPLAY(mi), MI_WINDOW(mi), MI_WINDOW(mi),
	       dp->backGC, left, top, width, height,
	       toleft, totop);
    XFlush(MI_DISPLAY(mi));

}

void
refresh_decay(ModeInfo * mi)
{
#ifdef HAVE_XPM
	decaystruct *dp;

	if (decay_info == NULL)
		return;
	dp = &decay_info[MI_SCREEN(mi)];

	if (dp->graphics_format >= IS_XPM) {
		/* This is needed when another program changes the colormap. */
		free_decay(MI_DISPLAY(mi), dp);
		init_decay(mi);
		return;
	}
#endif
}

void
release_decay(ModeInfo * mi)
{
	if (decay_info != NULL) {
		int	screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_decay(MI_DISPLAY(mi), &decay_info[screen]);
		free(decay_info);
		decay_info = (decaystruct *) NULL;
	}
}

#endif /* MODE_decay */
