/* +-------------------------------------------------------------------+ */
/* | Copyright 1992, 1993, David Koblas (koblas@netcom.com)            | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute 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.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#ifndef NOSTDHDRS
#include <stdlib.h>
#include <unistd.h>
#endif
#include "Paint.h"
#include "misc.h"
#include "xpaint.h"

typedef struct {
	Boolean		done;
} LocalInfo;

#define ZINDEX8(x, y, img)  ((y) * img->bytes_per_line) + (x)

static Drawable	drawable;
static Display	*dpy;
static XImage	*source;
static Boolean	*mask;
static int	maskW, maskH;
static int	xMin, yMin, xMax, yMax;
static int	timeCount = 0;
static int	fillMode = 0;


/*
 * A Seed Fill Algorithm
 * by Paul Heckbert
 * from "Graphics Gems", Academic Press, 1990
 */

/*
 * fill.c : simple seed fill program
 * Calls pixelread() to read pixels, pixelwrite() to write pixels.
 *
 * Paul Heckbert        13 Sept 1982, 28 Jan 1987
 */

#define pixelwrite(x,y)	do { 			\
		mask[y * maskW + x] = True;	\
		xMin = MIN(xMin, x);		\
		yMin = MIN(yMin, y);		\
		xMax = MAX(xMax, x);		\
		yMax = MAX(yMax, y);		\
	} while (0)

#define STEP	32

static Pixel pixelread(int x, int y, Boolean first)
{
	static Pixel	ov;
	static int	xMin, yMin, xMax, yMax;
	Pixel		p;
	int		n;

	if (first) {
		xMin = MAX(x - STEP, 0);
		yMin = MAX(y - STEP, 0);
		xMax = MIN(x + STEP, maskW);
		yMax = MIN(y + STEP, maskH);

		XGetSubImage(dpy, drawable, xMin, yMin,
				xMax - xMin, yMax - yMin,
				AllPlanes, ZPixmap, source,
				xMin, yMin);

		ov = XGetPixel(source, x, y);

		return ov;
	} else if (mask[y * maskW + x]) {
		return ~ov;
	}

	if (x < xMin) {
		n = MAX(x - STEP, 0);
		XGetSubImage(dpy, drawable, n, yMin,
				xMin - n, yMax - yMin,
				AllPlanes, ZPixmap, source,
				n, yMin);
		xMin = n;
	}
	if (y < yMin) {
		n = MAX(y - STEP, 0);
		XGetSubImage(dpy, drawable, xMin, n,
				xMax - xMin, yMin - n,
				AllPlanes, ZPixmap, source,
				xMin, n);
		yMin = n;
	}
	if (x >= xMax) {
		n = MIN(x + STEP, maskW);
		XGetSubImage(dpy, drawable, xMax, yMin,
				n - xMax, yMax - yMin,
				AllPlanes, ZPixmap, source,
				xMax, yMin);
		xMax = n;
	}
	if (y >= yMax) {
		n = MIN(y + STEP, maskH);
		XGetSubImage(dpy, drawable, xMin, yMax,
				xMax - xMin, n - yMax,
				AllPlanes, ZPixmap, source,
				xMin, yMax);
		yMax = n;
	}
	
	/*
	**  Do it fast for those 8 bit displays...
	*/
	if (source->bits_per_pixel == 8) {
		p = source->data[ZINDEX8(x, y, source)]; 
	} else {
		p = XGetPixel(source, x, y);
	}
	return p;
}

/*
**  Exange pixel 1 for filled pixel value 2
*/
static void change(int sx, int sy, int width, int height)
{
	int	x, y;
	Pixel	pix = XGetPixel(source, sx, sy);

	if (source->bits_per_pixel == 8) {
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++)
				if (source->data[ZINDEX8(x, y, source)] == pix)
					pixelwrite(x, y);
			if (y % 100 == 0)
				StateTimeStep();
		}
	} else {
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++)
				if (XGetPixel(source, x, y) == pix)
					pixelwrite(x, y);
			if (y % 64 == 0)
				StateTimeStep();
		}
	}
}

typedef struct {short y, xl, xr, dy;} Segment;
/*
 * Filled horizontal segment of scanline y for xl<=x<=xr.
 * Parent segment was on line y-dy.  dy=1 or -1
 */

#define STACKSIZE 20000              /* max depth of stack */

#define PUSH(Y, XL, XR, DY)  /* push new segment on stack */ \
    if (sp<stack+STACKSIZE && Y+(DY)>=0 && Y+(DY)<=height) \
    {sp->y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; sp++;}

#define POP(Y, XL, XR, DY)    /* pop segment off stack */ \
    {sp--; Y = sp->y+(DY = sp->dy); XL = sp->xl; XR = sp->xr;}

/*
 * fill: set the pixel at (x,y) and all of its 4-connected neighbors
 * with the same pixel value to the new pixel value nv.
 * A 4-connected neighbor is a pixel above, below, left, or right of a pixel.
 */

static void fill(int x, int y, int width, int height)
{
    int start, x1, x2, dy = 0;
    Pixel ov;      /* old pixel value */
    Segment stack[STACKSIZE], *sp = stack;   /* stack of filled segments */

    ov = pixelread(x, y, True);              /* read pv at seed point */
    PUSH(y, x, x, 1);                      /* needed in some cases */
    PUSH(y+1, x, x, -1);                /* seed segment (popped 1st) */

    while (sp>stack) {
        /* pop segment off stack and fill a neighboring scan line */
        POP(y, x1, x2, dy);
        /*
         * segment of scan line y-dy for x1<=x<=x2 was previously filled,
         */
        for (x = x1; x >= 0 && pixelread(x, y, False) == ov; x--)
            pixelwrite(x, y);
        if (x >= x1) {
          for (x++; x<=x2 && pixelread(x, y, False)!=ov; x++);
          start = x;
	  if (x > x2)
	    continue;
	} else {
          start = x+1;
          if (start<x1) PUSH(y, start, x1-1, -dy);       /* leak on left? */
          x = x1+1;
	}
        do {
            for (; x<=width && pixelread(x, y, False)==ov; x++)
                pixelwrite(x, y);
            PUSH(y, start, x-1, dy);
            if (x>x2+1) PUSH(y, x2+1, x-1, -dy);       /* leak on right? */
            for (x++; x<=x2 && pixelread(x, y, False)!=ov; x++);
            start = x;
        } while (x<=x2);

	if (++timeCount % 32 == 0) 
		StateTimeStep();
    }
}

static void     press(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info)
{
	XRectangle	undo, rect;
	int		width, height;
	GC		gc, dgc;
	XGCValues	values;
	int		isBusy, depth;
	Pixmap		pix;

	if (event->button == Button1)
		dgc = info->first_gc;
	else if (event->button == Button2)
		dgc = info->second_gc;
	else
		return;

	dpy = XtDisplay(w);
	drawable = info->drawable;

	UndoStart(w, info);

	XtVaGetValues(w, XtNdrawWidth, &width, 
			 XtNdrawHeight, &height, 
			 XtNdepth, &depth, 
			 NULL);
	if (isBusy = (width * height > 6400))
		StateSetBusy(True);

	if ((mask = (Boolean*)XtCalloc(sizeof(Boolean), width * height)) == NULL)
		return;
	maskW = width;
	maskH = height;
	
	memset(mask, False, width * height * sizeof(Boolean));

	if (fillMode == 0) {
		source = NewXImage(dpy, NULL, depth, width, height);
	} else {
		source = PwGetImage(w, NULL);
	}

	xMin = xMax = info->realX;
	yMin = yMax = info->realY;

	if (fillMode == 0)
		fill(info->realX, info->realY, width - 1, height - 1);
	else
		change(info->realX, info->realY, width, height);

	rect.x      = xMin;
	rect.y      = yMin;
	rect.width  = (xMax - xMin) + 1;
	rect.height = (yMax - yMin) + 1;

	pix = MaskDataToPixmap(w, width, height, mask, &rect);

	XSetClipOrigin(dpy, dgc, rect.x, rect.y);
	XSetClipMask(dpy, dgc, pix);

	XFillRectangles(dpy, info->drawable, dgc, &rect, 1);

	if (!info->isFat)
		XFillRectangles(dpy, XtWindow(w), dgc, &rect, 1);

	XSetClipMask(dpy, dgc, None);
	XFreePixmap(dpy, pix);

	XYtoRECT(xMin, yMin, xMax + 1, yMax + 1, &undo);
	UndoSetRectangle(w, &undo);
	PwUpdate(w, &undo, False);

	if (fillMode == 0)
		XDestroyImage(source);

	if (isBusy)
		StateSetBusy(False);
}

void *FillAdd(Widget w)
{
	LocalInfo	*l = (LocalInfo*)XtMalloc(sizeof(LocalInfo));
	l->done = False;
	OpAddEventHandler(w, opPixmap, ButtonPressMask, FALSE, (OpEventProc)press, l);
	SetCrossHairCursor(w);
	return l;
}
void FillRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opPixmap, ButtonPressMask, FALSE, (OpEventProc)press, l);
	XtFree((XtPointer)l);
}

void FillSetMode(int value)
{
	fillMode = value;
}

int FillGetMode()
{
	return fillMode;
}
