/* -*- Mode: C; tab-width: 4 -*- */
/* lyapunov --- animated lyapunov space */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)lyapunov.c	5.09 2003/06/30 xlockmore";

#endif

/*-
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Pictures discovered by Mario Markus and Benno Hess while trying to
 * simulate how enzymes break down carbohydrates.  By adjusting a pair of
 * parameter's they found they could make the simulated enzymes behave in
 * either a orderly or chaotic manner.  Images are based on a formula
 * named after the Russian mathematician Aleksandr M. Lyapunov.
 * See  A.K. Dewdney's "Computer Recreations", Scientific American
 * Magazine" Sep 1991 for more info.
 *
 * Revision History:
 * 30-Jun-2003: Changed writeable mode to be more consistent with
 *              xscreensaver's starfish
 * 01-Nov-2000: Allocation checks
 * 17-Dec-1998: Used mandelbrot.c as basis
 */


#ifdef STANDALONE
#define MODE_lyapunov
#define PROGCLASS "Lyapunov"
#define HACK_INIT init_lyapunov
#define HACK_DRAW draw_lyapunov
#define lyapunov_opts xlockmore_opts
#define DEFAULTS "*delay: 25000 \n" \
 "*count: 600 \n" \
 "*cycles: 20000 \n" \
 "*ncolors: 200 \n"
#define SMOOTH_COLORS
#define WRITABLE_COLORS
#include "xlockmore.h"		/* from the xscreensaver distribution */
#else /* !STANDALONE */
#include "xlock.h"		/* from the xlockmore distribution */
#include "color.h"
#endif /* !STANDALONE */

#ifdef MODE_lyapunov

#define MINPOWER 2
#define DEF_INCREMENT  "1.00"
/* 4.0 is best for seeing if a point is inside the set, 13 is better if
 * you want to get a pretty corona
 */

#define FLOATRAND(min,max) ((min)+((double) LRAND()/((double) MAXRAND))*((max)-(min)))

#define DEF_CYCLE "True"

static Bool cycle_p;

static XrmOptionDescRec opts[] =
{
  {(char *) "-cycle", (char *) ".lyapunov.cycle", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+cycle", (char *) ".lyapunov.cycle", XrmoptionNoArg, (caddr_t) "off"}
};

static argtype vars[] =
{
  {(void *) & cycle_p, (char *) "cycle", (char *) "Cycle", (char *) DEF_CYCLE
, t_Bool}
};
static OptionStruct desc[] =
{
  {(char *) "-/+cycle", (char *) "turn on/off colour cycling"}
};

ModeSpecOpt lyapunov_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   lyapunov_description =
{"lyapunov", "init_lyapunov", "draw_lyapunov", "release_lyapunov",
 (char *) NULL, "init_lyapunov", (char *) NULL, &lyapunov_opts,
 25000, 600, 20000, 1, 64, 1.0, "",
 "Shows lyapunov space", 0, NULL};

#endif

#define ROUND_FLOAT(x,a) ((float) ((int) ((x) / (a) + 0.5)) * (a))
#define MAXPOWER 14 /* (32 - 2) for 32 bit unsigned long */

#ifdef DEBUG
static int debug;
#endif

typedef struct {
	int         column;
	Bool        backwards, cycle_p, mono_p, no_colors;
	int         ncolors;
	unsigned int cur_color;
	GC          gc;
	Colormap    cmap;
	unsigned long blackpixel, whitepixel, fg, bg;
	int direction;
	XColor     *colors;
	int         screen_width;
	int         screen_height;
	float       reptop;
	unsigned long periodic_forcing;
	int firstone;
	ModeInfo   *mi;
	unsigned long seq;
	int          na, nb, hbit;
	int          iter_local;
	double       norm;
} lyapstruct;

static lyapstruct *lyaps = (lyapstruct *) NULL;
#define OFFSET 3.0  /* Slight changes here may have unexpected results! */

static double
reps(lyapstruct * lp, unsigned long periodic_forcing, int firstone, int iter, double a, double b)
{
	#define ABS_LESS(v, limit) ((-(limit) < (v)) && ((v) < (limit)))
	#define ABS_MORE(v, limit) (((v) < -(limit)) || ((limit) < (v)))

	#define ta_max 512
	double  ta[ta_max];
	int     ta_idx = 0;
	int     i, j = firstone;
	double  f = 0.5, t = 1.0, r;
	double  log_b;

	a += OFFSET;
	b += OFFSET; log_b = log(b);

	/* update cached values */
	if (lp->seq != periodic_forcing || lp->hbit != firstone)
	{
		lp->seq = periodic_forcing;
		lp->hbit = firstone;

		for (i=lp->hbit, lp->nb=0; i>=0; i--)
      			if (lp->seq & (1 << i))
				lp->nb++;
		lp->na = lp->hbit - lp->nb;
	}

  	if (iter != lp->iter_local)
	{
		lp->iter_local = iter;
		lp->norm = 1.0 / (log(2.0) * iter);
	}

	for (i = 0; i < iter; i++) {
		r = (lp->seq & (1 << j)) ? b : a;
		if (! j--)
			j = lp->hbit;

		f *= r*(1-f);
		t *= 1-2*f;

		if (ta_idx < ta_max)
		{
			if (ABS_LESS(t, 1e-50)) { ta[ta_idx++] = t; t = 1.0; }
			if (ABS_MORE(t, 1e+50)) { ta[ta_idx++] = t; t = 1.0; }
		}
	}

	/* post process */
	r = log(ABS(t)) + lp->na*log(ABS(a)) + lp->nb*log_b;
	for (i=0; i<ta_idx; i++)
        	r += log(ABS(ta[i]));

      	return r < 0 ? (r * lp->norm) : r;
}

static void
free_lyapunov(Display *display, lyapstruct *lp)
{
	ModeInfo *mi = lp->mi;

	if (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2) {
		MI_WHITE_PIXEL(mi) = lp->whitepixel;
		MI_BLACK_PIXEL(mi) = lp->blackpixel;
#ifndef STANDALONE
		MI_FG_PIXEL(mi) = lp->fg;
		MI_BG_PIXEL(mi) = lp->bg;
#endif
		if (lp->colors != NULL) {
			if (lp->ncolors && !lp->no_colors)
				free_colors(display, lp->cmap, lp->colors,
					lp->ncolors);
			free(lp->colors);
			lp->colors = (XColor *) NULL;
		}
		if (lp->cmap != None) {
			XFreeColormap(display, lp->cmap);
			lp->cmap = None;
		}
	}
	if (lp->gc != None) {
		XFreeGC(display, lp->gc);
		lp->gc = None;
	}
}

#ifndef STANDALONE
extern char *background;
extern char *foreground;
#endif

void
init_lyapunov(ModeInfo * mi)
{
	Window      window = MI_WINDOW(mi);
	Display    *display = MI_DISPLAY(mi);
	int i = NRAND(MAXPOWER);
	unsigned long power2;
	lyapstruct *lp;

	if (lyaps == NULL) {
		if ((lyaps = (lyapstruct *) calloc(MI_NUM_SCREENS(mi),
					     sizeof (lyapstruct))) == NULL)
			return;
	}
	lp = &lyaps[MI_SCREEN(mi)];
	lp->mi = mi;

#ifdef DEBUG
	debug = MI_IS_DEBUG(mi);
#endif
	lp->screen_width = MI_WIDTH(mi);
	lp->screen_height = MI_HEIGHT(mi);
	lp->backwards = (Bool) (LRAND() & 1);
	if (lp->backwards)
		lp->column = lp->screen_width - 1;
	else
		lp->column = 0;

	MI_CLEARWINDOW(mi);

	lp->reptop = 0.6;

	/* To force a number, say <i = 2;> has  i + 2 (or 4) binary digits */
  power2 = 1 << (i + 1);
  /* Do not want numbers which in binary are all 1's. */
  lp->periodic_forcing = NRAND(power2 - 1) + power2;
	lp->firstone = 1;
	for (i = 32 - 2; i >= 1; i--)
		if (lp->periodic_forcing & (1 << i)) {
			lp->firstone = i;
			break;
		}

  if (MI_IS_VERBOSE(mi)) {
		(void) printf("0x%lx forcing number", lp->periodic_forcing);
		switch (lp->periodic_forcing) {
			case 0x2:
				(void) printf(", swallow\n");
				break;
      case 0x34:
				(void) printf(", jellyfish\n");
				break;
			case 0xFC0: /* Yeah, I think it should be "city" too, but its not. */
				(void) printf(", zircon zity\n");
				break;
			default:
				(void) printf("\n");
				break;
		}
	}

	if (!lp->gc) {
		if (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2) {
			XColor      color;

#ifndef STANDALONE
			lp->fg = MI_FG_PIXEL(mi);
			lp->bg = MI_BG_PIXEL(mi);
#endif
			lp->blackpixel = MI_BLACK_PIXEL(mi);
			lp->whitepixel = MI_WHITE_PIXEL(mi);
			if ((lp->cmap = XCreateColormap(display, window,
					MI_VISUAL(mi), AllocNone)) == None) {
				free_lyapunov(display, lp);
				return;
			}
			XSetWindowColormap(display, window, lp->cmap);
			(void) XParseColor(display, lp->cmap, "black", &color);
			(void) XAllocColor(display, lp->cmap, &color);
			MI_BLACK_PIXEL(mi) = color.pixel;
			(void) XParseColor(display, lp->cmap, "white", &color);
			(void) XAllocColor(display, lp->cmap, &color);
			MI_WHITE_PIXEL(mi) = color.pixel;
#ifndef STANDALONE
			(void) XParseColor(display, lp->cmap, background, &color);
			(void) XAllocColor(display, lp->cmap, &color);
			MI_BG_PIXEL(mi) = color.pixel;
			(void) XParseColor(display, lp->cmap, foreground, &color);
			(void) XAllocColor(display, lp->cmap, &color);
			MI_FG_PIXEL(mi) = color.pixel;
#endif
			lp->colors = (XColor *) NULL;
			lp->ncolors = 0;
		}
		if ((lp->gc = XCreateGC(display, MI_WINDOW(mi),
			     (unsigned long) 0, (XGCValues *) NULL)) == None) {
			free_lyapunov(display, lp);
			return;
		}
	}
	MI_CLEARWINDOW(mi);

  /* Set up colour map */
  lp->direction = (LRAND() & 1) ? 1 : -1;
  if (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2) {
    if (lp->colors != NULL) {
      if (lp->ncolors && !lp->no_colors)
        free_colors(display, lp->cmap, lp->colors, lp->ncolors);
      free(lp->colors);
      lp->colors = (XColor *) NULL;
    }
    lp->ncolors = MI_NCOLORS(mi);
    if (lp->ncolors < 2)
      lp->ncolors = 2;
    if (lp->ncolors <= 2)
      lp->mono_p = True;
    else
      lp->mono_p = False;

    if (lp->mono_p)
      lp->colors = (XColor *) NULL;
    else
      if ((lp->colors = (XColor *) malloc(sizeof (*lp->colors) *
          (lp->ncolors + 1))) == NULL) {
        free_lyapunov(display, lp);
        return;
      }
    lp->cycle_p = has_writable_cells(mi);
    if (lp->cycle_p) {
      if (MI_IS_FULLRANDOM(mi)) {
        if (!NRAND(8))
          lp->cycle_p = False;
        else
          lp->cycle_p = True;
      } else {
        lp->cycle_p = cycle_p;
      }
    }
    if (!lp->mono_p) {
      if (!(LRAND() % 10))
        make_random_colormap(
#if STANDALONE
            display, MI_WINDOW(mi),
#else
            mi,
#endif
              lp->cmap, lp->colors, &lp->ncolors,
              True, True, &lp->cycle_p);
      else if (!(LRAND() % 2))
        make_uniform_colormap(
#if STANDALONE
            display, MI_WINDOW(mi),
#else
            mi,
#endif
                  lp->cmap, lp->colors, &lp->ncolors,
                  True, &lp->cycle_p);
      else
        make_smooth_colormap(
#if STANDALONE
            display, MI_WINDOW(mi),
#else
            mi,
#endif
                 lp->cmap, lp->colors, &lp->ncolors,
                 True, &lp->cycle_p);
    }
    XInstallColormap(display, lp->cmap);
    if (lp->ncolors < 2) {
      lp->ncolors = 2;
      lp->no_colors = True;
    } else
      lp->no_colors = False;
    if (lp->ncolors <= 2)
      lp->mono_p = True;

    if (lp->mono_p)
      lp->cycle_p = False;

  }
  if (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2) {
                if (lp->mono_p) {
			lp->cur_color = MI_BLACK_PIXEL(mi);
		}
  }
	lp->seq = (unsigned long) -1;
	lp->hbit = -1;
	lp->iter_local = -1;
}

void
draw_lyapunov(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	int         h;
	lyapstruct *lp;

	if (lyaps == NULL)
		return;
	lp = &lyaps[MI_SCREEN(mi)];

	MI_IS_DRAWN(mi) = True;

        if (MI_IS_INSTALL(mi) && MI_NPIXELS(mi) > 2) {
                if (lp->mono_p) {
                        XSetForeground(display, lp->gc, lp->cur_color);
                } else {
                        lp->cur_color = (lp->cur_color + 1) % lp->ncolors;
                        XSetForeground(display, lp->gc, lp->colors[lp->cur_color].pixel);
                }
        } else {
                if (MI_NPIXELS(mi) > 2)
                        XSetForeground(display, lp->gc, MI_PIXEL(mi, lp->cur_color));
                else if (lp->cur_color)
                        XSetForeground(display, lp->gc, MI_BLACK_PIXEL(mi));
                else
                        XSetForeground(display, lp->gc, MI_WHITE_PIXEL(mi));
                if (++lp->cur_color >= (unsigned int) MI_NPIXELS(mi))
                        lp->cur_color = 0;
        }

  /* Rotate colours */
  if (lp->cycle_p) {
    rotate_colors(display, lp->cmap, lp->colors, lp->ncolors,
      lp->direction);
    if (!(LRAND() % 1000))
      lp->direction = -lp->direction;
  }
	/* so we iterate columns beyond the width of the physical screen,
        ** so that we just wait around and show what we've done
	*/
	if ((!lp->backwards && (lp->column >= 4 * lp->screen_width)) ||
	    (lp->backwards && (lp->column < -3 * lp->screen_width))) {
		/* reset to left edge of screen */
		init_lyapunov(mi);
		return;
		/* select a new region! */
	} else if (lp->column >= lp->screen_width || lp->column < 0) {
		/* delay a while */
		if (lp->backwards)
			lp->column--;
		else
			lp->column++;
		return;
	}
	for (h = 0; h < lp->screen_height; h++) {
		unsigned int color;
		double  result;

#define MULT 1.0
		result = reps(lp, lp->periodic_forcing, lp->firstone, MI_COUNT(mi),
			((double) lp->column) / (MULT * lp->screen_width),
			((double) h) / (MULT * lp->screen_height));
#ifdef DEBUG
		if (debug)
		(void) printf("a %g, b %g, result %g\n",
			 ((double) lp->column) / lp->screen_width,
			 ((double) h) / lp->screen_height, result);
#endif
		if (result > 0.0)
			XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
		else {
			color=(unsigned int) ((MI_NPIXELS(mi) * (float)-result) / lp->reptop);
			color = color % MI_NPIXELS(mi);
			XSetForeground(display, gc, MI_PIXEL(mi, color));
		}
		/* we no longer have vertical symmetry - so we compute all points
		** and do not draw with redundancy
		*/
		XDrawPoint(display, window, gc, lp->column, h);
	}
	if (lp->backwards)
		lp->column--;
	else
		lp->column++;
}

void
release_lyapunov(ModeInfo * mi)
{
	if (lyaps != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_lyapunov(MI_DISPLAY(mi), &lyaps[screen]);
		free(lyaps);
		lyaps = (lyapstruct *) NULL;
	}
}

#endif /* MODE_lyapunov */
