/******************************************************************************
** 
** mandelbear.c
**
**	Generate Mandelbrot sets of various sorts.
**
******************************************************************************/

/******************************************************************************
**   Include files.
******************************************************************************/

/*  -- Minimum include files to use WidgetCreate Library */
#include <X11/Intrinsic.h>

/* This makes it work with virtual root window managers. */
#include "vroot.h"

/*  -- application specific include files */
#include <stdio.h>
#include <ctype.h>
#include <malloc.h>
#include <math.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/StringDefs.h>
#include "bear.xbm"

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))


/******************************************************************************
**   Variables
******************************************************************************/

/* Display state */

Display        *dpy;
Screen	       *scr;
Window 		win;
Pixmap 		pix;
GC 		gc;
int		planes;
XtAppContext 	app;
Widget       	appShell;
Widget		canvas;

int minColor = 0;			/* lowest available */
int maxColor = 1;			/* highest available */
int nColors  = 2;			/* total number of colors */

/* Command-line options */

Boolean cubic;				/* TRUE to plot cubic */
Boolean	toRoot;				/* TRUE if plotting to root */
Boolean	toBitmap;			/* TRUE if plotting to PBM */
Boolean	toGreymap;			/* TRUE if plotting to PGM */
Boolean scanx, scany;			/* scan x0 and y0 */
int	xsteps, ysteps;			/* steps in scan */
String	outFile;
String	inFile;

/* Raster-plot state.  Initialize to improbable values for later testing */

static int dmax;			/* maximum depth */
static int xmax;			/* pixmap x size */
static int ymax;			/* pixmap y size */

static float zxc = 100;			/* center zx value */
static float zyc = 100;			/* center zy value */
static float zrad = 100;		/* z radius */
static float zx0 = 0;
static float zy0 = 0;

static int (*mapfunc)();		/* function to map */
static void (*plotfunc)();		/* function to plot a point */
static void (*eachplot)();		/* called after each 2-d plot */

static char msg[80];


/******************************************************************************
**   Function to plot a raster
******************************************************************************/

/*
** PlotRaster()
**
**	Make a map of mapfunc(zx, zy) in a raster.
**	Kludge around the fact that the y origin is on top.
*/
void PlotRaster()
{
    register int depth, ix, iy;
    register double zx0, zy0;
    register double zxr, zyr;
    register int rmax;

    rmax = MIN(xmax, ymax) / 2;
    zxr = zrad / rmax; zyr = zrad / rmax;

    for (iy = 0; iy < ymax; ++iy) {
	zy0 = zyc - zyr * (iy - ymax / 2); /* y grows down, not up. */
	for (ix = 0; ix < xmax; ++ix) {
	    zx0 = zxc + zxr * (ix - xmax / 2);
	    depth = (*mapfunc)(zx0, zy0);
	    (*plotfunc)(ix, iy, depth);
	}
    }
}

void PlotDoubleRaster()
{
    register int ix, iy;
    register double zxr, zyr;
    register int rmax;

    if (xsteps == 0) xsteps = xmax;
    if (ysteps == 0) ysteps = ymax;
    zxr = 2.0 * zrad / (xsteps -1); zyr = 2.0 * zrad / (ysteps-1);

    if (scanx && scany) {
	for (iy = 0; iy < ysteps; ++iy) {
	    zy0 = zyc - zyr * (iy - (ysteps-1) / 2.0);
	    for (ix = 0; ix < xsteps; ++ix) {
		zx0 = zxc + zxr * (ix - (xsteps-1) / 2.0);
		PlotRaster();
		if (eachplot) (*eachplot)();
	    }
	}
    } else if (scanx) {
	for (ix = 0; ix < xsteps; ++ix) {
	    zx0 = zxc + zxr * (ix - (xsteps-1) / 2.0);
	    PlotRaster();
	    if (eachplot) (*eachplot)();
	}
    } else if (scany) {
	for (iy = 0; iy < ysteps; ++iy) {
	    zy0 = zyc - zyr * (iy - (ysteps-1) / 2.0);
	    PlotRaster();
	    if (eachplot) (*eachplot)();
	}
    } else {
	PlotRaster();
	if (eachplot) (*eachplot)();
    }
}


/******************************************************************************
**   Functions to map
******************************************************************************/

static int mandelbrot(cx0, cy0)
    double cx0, cy0;
{
    register int depth;
    register double zx, zy, tx;

    zx = cx0 + zx0; zy = cy0 + zy0;
    for (depth = 0; depth < dmax && zx*zx + zy*zy < 4.0; ++depth) {
	/* we assume that the compiler eliminates common subexprs */
	tx = zx * zx - zy * zy + cx0;
	zy = 2.0 * zx * zy + cy0;
	zx = tx;
    }
    return (depth);
}

static int mandelbear(cx0, cy0)
    double cx0, cy0;
{
    register int depth;
    register double zx, zy, tx;

    zx = cx0 + zx0; zy = cy0 + zy0;
    for (depth = 0; depth < dmax && zx*zx + zy*zy < 4.0; ++depth) {
	/* we assume that the compiler eliminates common subexprs */
	tx = zx * (zx * zx - 3 * zy * zy) + cx0;
	zy = zy * (3 * zx * zx - zy * zy) + cy0;
	zx = tx;
    }
    return (depth);
}

/******************************************************************************
**   Functions to plot pixels
******************************************************************************/

static void plotPixel(x, y, depth)
    int x, y, depth;
{
    if (nColors == 2) {
	if (depth & 1)
	    XDrawPoint(dpy, pix, gc, x, y);
    } else {
	if (depth >= dmax) {
	    XSetForeground(dpy, gc, BlackPixelOfScreen(scr));
	} else {
	    XSetForeground(dpy, gc, depth % nColors + minColor);
	}
	XDrawPoint(dpy, pix, gc, x, y);
    }
    if (x == 0 && y % 4 == 3 && xmax > 200) {
	XSetWindowBackgroundPixmap(dpy, win, pix);
	XClearArea(dpy, win, 0, y - 4, xmax, 4, False);
    }
}

static void plotStdOut(x, y, depth)
    int x, y, depth;
{
    if (depth < dmax) putchar('1');
    else	      putchar('0');
    if (x == xmax - 1) putchar('\n');
}

static void plotStdOutG(x, y, depth)
    int x, y, depth;
{
    printf(" %d", depth);
    if (x == xmax - 1) putchar('\n');
}


/******************************************************************************
**  Callbacks
******************************************************************************/

/*
** MyQuitCB
**	Exit the application.  
**	There is no reason to destroy the widget tree, so don't bother.
*/

static void MyQuitCB( widget, widgetName, ignored )
    Widget  widget;
    char*   widgetName;
    caddr_t ignored;
{
    exit(0);
}

/******************************************************************************
**   navigation/file-handling
******************************************************************************/

/*
** AfterEachPlot
*/
void AfterEachPlot()
{
    XtInputMask m;

    if (scanx || scany) {
	XSetWindowBackgroundPixmap(dpy, win, pix);
	XClearWindow(dpy, win);
	/*
	 * Check for events.
	 *   === it would be better to use a work procedure  ===
	 */
	for ( ; m = XtAppPending(app); ) {
	    XtAppProcessEvent(app, XtIMAll);
	}
    }
    printf("%g+%gi        \r", zx0, zy0);
    fflush(stdout);
}

/******************************************************************************
**   Setups
******************************************************************************/


void PlotToPbm()
{
    if (dmax == 0) dmax = 8;
    if (xmax == 0) xmax = 96;
    if (ymax == 0) ymax = 96;

    if (outFile) freopen(outFile, "w", stdout);

    /* Print PBM header and bitmap */
    printf("P1\n%d %d\n", xmax, ymax);
    plotfunc = plotStdOut;
    PlotRaster();
    exit(0);
}

void PlotToPgm()
{
    if (dmax == 0) dmax = 256;
    if (xmax == 0) xmax = 96;
    if (ymax == 0) ymax = 96;

    if (outFile) freopen(outFile, "w", stdout);

    /* Print PGM header and bitmap */
    printf("P2\n%d %d %d\n", xmax, ymax, dmax);
    plotfunc = plotStdOutG;
    PlotRaster();
    exit(0);
}

int SetUpPixmap()
    /*
     * Return 1 if pixmap already set up, else 0
     */
{
    XWindowAttributes atts;

    XGetWindowAttributes(dpy, win, &atts);
    if (atts.width != xmax || atts.height != ymax) {
	if (pix) XFreePixmap(dpy, pix);
	pix = 0;
    }
    xmax = atts.width;
    ymax = atts.height;
    
    if (!pix) {
	pix = XCreatePixmap(dpy, win, xmax, ymax, planes);
	XSetForeground(dpy, gc, BlackPixelOfScreen(scr));
	XFillRectangle(dpy, pix, gc, 0, 0, xmax, ymax); 
	return (0);
    }
    return (1);
}

void PlotToRoot()
{
    win = RootWindowOfScreen(scr);

    if (dmax == 0) dmax = nColors == 2? 256 : nColors;
    plotfunc= plotPixel;

    XSetWindowBackgroundPixmap(dpy, win, pix);
    if (scanx || scany) eachplot = AfterEachPlot;
    PlotDoubleRaster();
    XSetWindowBackgroundPixmap(dpy, win, pix);
    XClearWindow(dpy, win);

    XFlush(dpy);
}

void PlotToWidget()
{
    XClearWindow(dpy, win);
    if (SetUpPixmap()) {
	XSetWindowBackgroundPixmap(dpy, win, pix);
	return;
    }

    if (dmax == 0) dmax = nColors == 2? 256 : nColors;
    plotfunc= plotPixel;
    if (scanx || scany) eachplot = AfterEachPlot;

    XSetWindowBackgroundPixmap(dpy, win, pix);
    PlotDoubleRaster();
    XSetWindowBackgroundPixmap(dpy, win, pix);
    XClearWindow(dpy, win);
}


/******************************************************************************
*   Initialization Functions
******************************************************************************/

static XtActionsRec actionTable[] = {
    {"CanvasHandleExpose",	PlotToWidget},
    {"MyQuitACT",		MyQuitCB},
};

static String fallbackResources[] = {
    "Mandelbear.geometry: 	96x96",
    "*input:			True",
    "*sensitive:		True",
    "*cursor:			left_ptr",
    "*canvas.foreground:	white",
    "*canvas.background:	black",
    "*canvas.label:		",
    0
};

/* Augmenting translations from fallback resources only gets first line */
static String canvasTranslations =
    "<Expose>: 		CanvasHandleExpose() \n\
     <Btn3Down>:	MyQuitACT()\n";

static XrmOptionDescRec options[] = {
{"-root",	".toRoot",	XrmoptionNoArg,		(caddr_t)"on"},
{"-pbm",	".toBitmap",	XrmoptionNoArg,		(caddr_t)"on"},
{"-pgm",	".toGreymap",	XrmoptionNoArg,		(caddr_t)"on"},
{"-cubic",	".cubic",	XrmoptionNoArg,		(caddr_t)"on"},
{"-scanx",	".scanx",	XrmoptionNoArg,		(caddr_t)"on"},
{"-scany",	".scany",	XrmoptionNoArg,		(caddr_t)"on"},
{"-width",	".width",	XrmoptionSepArg,	(caddr_t)NULL},
{"-height",	".height",	XrmoptionSepArg,	(caddr_t)NULL},
{"-xsteps",	".xsteps",	XrmoptionSepArg,	(caddr_t)NULL},
{"-ysteps",	".ysteps",	XrmoptionSepArg,	(caddr_t)NULL},
{"-x0",		".x0",		XrmoptionSepArg,	(caddr_t)NULL},
{"-y0",		".y0",		XrmoptionSepArg,	(caddr_t)NULL},
{"-xc",		".xc",		XrmoptionSepArg,	(caddr_t)NULL},
{"-yc",		".yc",		XrmoptionSepArg,	(caddr_t)NULL},
{"-radius",	".radius",	XrmoptionSepArg,	(caddr_t)NULL},
{"-depth",	".dmax",	XrmoptionSepArg,	(caddr_t)NULL},
{"-o",		".outFile",	XrmoptionSepArg,	(caddr_t)NULL},
{"-f",		".inFile",	XrmoptionSepArg,	(caddr_t)NULL},
};

static XtResource resources[] = {
{"cubic",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&cubic,  		XtRString, "off"},
{"scanx",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&scanx,  		XtRString, "off"},
{"scany",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&scany,  		XtRString, "off"},
{"toRoot",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&toRoot,  		XtRString, "off"},
{"toBitmap",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&toBitmap,		XtRString, "off"},
{"toGreymap",	XtCBoolean,	XtRBoolean,
     sizeof(Boolean), (int)&toGreymap,		XtRString, "off"},
{"xsteps",	"Count",	XtRInt,
     sizeof(int), (int)&xsteps,  		XtRString, "0"},
{"ysteps",	"Count",	XtRInt,
     sizeof(int), (int)&ysteps,  		XtRString, "0"},
{"width",	"Count",	XtRInt,
     sizeof(int), (int)&xmax,	  		XtRString, "96"},
{"height",	"Count",	XtRInt,
     sizeof(int), (int)&ymax,	  		XtRString, "96"},
{"x0",		"Real",		XtRFloat,
     sizeof(float), (int)&zx0,  		XtRString, "0.0"},
{"y0",		"Imaginary",	XtRFloat,
     sizeof(float), (int)&zy0,  		XtRString, "0.0"},
{"xc",		"Real",		XtRFloat,
     sizeof(float), (int)&zxc,  		XtRString, "100"},
{"yc",		"Imaginary",	XtRFloat,
     sizeof(float), (int)&zyc,  		XtRString, "100"},
{"radius",	"Real",		XtRFloat,
     sizeof(float), (int)&zrad,  		XtRString, "100"},
{"dmax",	"Depth",	XtRInt,
     sizeof(int), (int)&dmax,  			XtRString, 0},
{"outFile",	XtCString,	XtRString,
     sizeof(String),  (int)&outFile,		XtRString, 0},
{"inFile",	XtCString,	XtRString,
     sizeof(String),  (int)&inFile,		XtRString, 0},
};

/*
** InitializeColors()
**
**	Figure out how many colors we can get away with.
**	(Uses standard colormap; assumes colors are sequential)
*/
void InitializeColors()
{
    if (planes == 1) return;		/* monochrome */

    /* fake it, assuming standard map installed with xstdcmap */

    minColor = 2;
    maxColor = 125;
    nColors = maxColor - minColor;
}


/******************************************************************************
**   main program
******************************************************************************/

main(argc, argv)
    int argc;
    char* argv[];
{   
    char*        appClass;
    XWindowAttributes atts;
    XGCValues values;
    unsigned long vm = 0;
    Arg args[10];
    int ax;

    appClass = (char*) XtMalloc ( strlen ( argv[0] ) + 1 );
    strcpy (appClass, argv[0]);
    /*
     * initialize first letter to make class, or first two if
     * first is already capitalized, or don't worry about it.
     */
    if (islower(appClass[0]))
	appClass[0] = toupper(appClass[0]);
    else if (islower(appClass[1]))
        appClass[1] = toupper(appClass[1]);
    
    /* Intialize Toolkit (creating the application shell) */
    appShell = XtAppInitialize (
	&app,				/* appContext return */
	appClass,			/* app class */
	options, XtNumber(options),	/* description of cmd line options */
	&argc, argv, 
	fallbackResources, 0, 0
    );

    ax = 0;
    XtSetArg(args[ax], "iconPixmap", 
	     (XtArgVal) XCreateBitmapFromData (XtDisplay(appShell),
					       XtScreen(appShell)->root,
					       bear_bits,
					       bear_width, bear_height
					       ));		++ax;
    XtSetValues (appShell, args, ax);

    /* Register action routines */

    XtAppAddActions( app, actionTable, XtNumber(actionTable));

    /* Create a canvas widget to draw into
     * 	Set up translations, which doesn't work right
     *	when done as part of the fallback resources.
     */
    canvas = XtCreateManagedWidget("canvas", widgetClass,
				   appShell, NULL, 0);
    XtAugmentTranslations(canvas,
			  XtParseTranslationTable(canvasTranslations));

    /*
     * Get application resources and display info
     *	If output is to root, just do it.
     */
    XtGetApplicationResources(
        appShell, NULL, resources, XtNumber(resources), NULL, 0);

    dpy = XtDisplay(appShell);
    scr = XtScreen(appShell);

    /*
     * If the user didn't specify coordinates, set appropriate defaults
     * Print out the defaults so the user can see them.
     */
    if (cubic) {
	if (zxc > 99) zxc = 0.0;
	if (zyc > 99) zyc = .75;
	if (zrad> 99) zrad= .75;
	mapfunc = mandelbear;
    } else {
	if (zxc > 99) zxc = -.5;
	if (zyc > 99) zyc = 0;
	if (zrad> 99) zrad= 1.5;
	mapfunc = mandelbrot;
    }

    if (toBitmap) PlotToPbm();
    if (toGreymap) PlotToPgm();

    if (toRoot) {
	win = RootWindowOfScreen(scr);
	gc  = XCreateGC(dpy, win, vm, &values);
	planes = PlanesOfScreen(scr);
	InitializeColors();
	dmax = nColors;
	(void)SetUpPixmap();
    }

    fprintf(stderr,
	    "%s -x0 %g -y0 %g -xc %g -yc %g -width %d -height %d -radius %g, -depth %d%s%s\n",
	   argv[0], zx0, zy0, zxc, zyc, xmax, ymax, zrad, dmax,
	   cubic? " -cubic" : "",
	   toRoot? " -root" : "");

    /*
     * If plotting on the root window, go ahead and do it.
     * Otherwise, realize the widget tree.
     */
    if (toRoot) {
	PlotToRoot();
	exit(0);
    }

    XtRealizeWidget ( appShell );

    win = XtWindow(canvas);
    gc  = XCreateGC(dpy, win, vm, &values);
    planes = PlanesOfScreen(scr);
    InitializeColors();

    XGetWindowAttributes(dpy, win, &atts);

    xmax = atts.width;
    ymax = atts.height;

    XtAppMainLoop(app);
    exit(0);
}
