/* +-------------------------------------------------------------------+ */
/* | 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>
#include <X11/Xos.h>
#include <sys/time.h>
#include "xpaint.h"
#include "Paint.h"
#include <math.h>
#include "misc.h"

#ifndef NOSTDHDRS
#include <stdlib.h>
#include <unistd.h>
#endif

typedef struct {
	Widget		w;
	int		useGauss;
	int		x, y;
	XtIntervalId    id;
	Boolean		gcFlag, isTiled, trackDrawn, drawing;
	int		lastX, lastY;
	int		zoom;
	/*
	**  Borrowed from the info
	*/
	GC		gc, gcx;
	Pixmap		drawable;
	Boolean		isFat;
} LocalInfo;

static int	radius = 10, density = 10, radius2 = 10 * 10, rate = 100;
static Boolean	style = True;

/*
**  By default everything uses drand48(),
**    I was making more exceptions than inclusinsion.
*/
#define USE_DRAND

#if defined(SVR4) || defined(__osf__)
#define SHORT_RANGE
#else
#ifndef random
long	random(void);
#endif
#ifndef srandom
int	srandom(unsigned int);
#endif
#endif

#ifdef USE_DRAND
# ifdef random
#  undef random
#  undef srandom
# endif
# define srandom	srand48
# define RANDOM(s, f)	(drand48() * (f - s) + s)
#else
# ifdef SHORT_RANGE
#  define RANGE		0x00000fff
# else
#  define RANGE		0x0fffffff
# endif
# define RANDOM(s, f)	(((double)(random() % RANGE) / (double)RANGE) * (f - s) + s)
#endif

static void gauss(int range, int *x, int *y)
{
	float		fac, r, v1, v2;

	do {
		v1 = RANDOM(-1.0, 1.0);
		v2 = RANDOM(-1.0, 1.0);
		r  = v1 * v1 + v2 * v2;
	} while (r >= 1.0);

	fac = -2.0 * log(r) / r;
	if (fac <= 0.0)
		fac = 0.0;
	else
		fac = sqrt(fac) / 2.0;

	*x = (v1 * fac) * range;
	*y = (v2 * fac) * range;
}

static void draw(LocalInfo *l)
{
	int		i;
	XRectangle	rect;
	union {
		XSegment	s[512];
		XPoint		p[512];
	} p;
    
	UndoGrow(l->w, l->x - radius, l->y - radius);
	UndoGrow(l->w, l->x + radius, l->y + radius);

	for (i = 0; i < density; i++) {
		int	rx, ry;

		do {
			if (l->useGauss) {
				gauss(radius, &rx, &ry);
			} else {
				rx = RANDOM(-radius, radius);
				ry = RANDOM(-radius, radius);
			}
		} while (rx * rx + ry * ry > radius2);

		if (l->isTiled) {
			p.s[i].x1  = l->x + rx;
			p.s[i].y1  = l->y + ry;
			p.s[i].x2  = l->x + rx;
			p.s[i].y2  = l->y + ry;
		} else {
			p.p[i].x   = l->x + rx;
			p.p[i].y   = l->y + ry;
		}
	}

	XYtoRECT(l->x - radius, l->y - radius,
		 l->x + radius, l->y + radius, &rect);

	if (l->isTiled) {
		XDrawSegments(XtDisplay(l->w), l->drawable, l->gc, p.s, density);
		if (!l->isFat)
			XDrawSegments(XtDisplay(l->w), XtWindow(l->w), l->gc, p.s, density);
	} else {
		XDrawPoints(XtDisplay(l->w), l->drawable, l->gc, p.p, density, CoordModeOrigin);
		if (!l->isFat)
			XDrawPoints(XtDisplay(l->w), XtWindow(l->w), l->gc, p.p, density, CoordModeOrigin);
	}

	PwUpdate(l->w, &rect, False);
}

static void drawEvent(LocalInfo *l)
{
	draw(l);
	l->id = XtAppAddTimeOut(XtWidgetToApplicationContext(l->w), 
				rate, (XtTimerCallbackProc)drawEvent, (XtPointer)l);
}

static void	drawOutline(Widget w, LocalInfo *l, int x, int y, Boolean flag)
{
	Display	*dpy   = XtDisplay(w);
	Window	window = XtWindow(w);
	XArc	arc;

	arc.width  = radius * l->zoom * 2;
	arc.height = radius * l->zoom * 2;
	arc.angle1 = 0;
	arc.angle2 = 360 * 64;

	if (l->trackDrawn) {
		arc.x = l->lastX - radius * l->zoom;
		arc.y = l->lastY - radius * l->zoom;
		XDrawArcs(dpy, window, l->gcx, &arc, 1);
		l->trackDrawn = False;
	}

	if (flag) {
		arc.x = x - radius * l->zoom;
		arc.y = y - radius * l->zoom;
		XDrawArcs(dpy, window, l->gcx, &arc, 1);

		l->lastX = x;
		l->lastY = y;
		l->trackDrawn = True;
	}
}


static void	press(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info) 
{
	GC	sgc;
	int	width, rule, rule2;
	/*
	**  Check to make sure all buttons are up, before doing this
	*/
	if ((event->state & (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)) != 0)
		return;

	l->x = event->x;
	l->y = event->y;

	l->useGauss = style;
	l->drawing = True;
	drawOutline(w, l, 0, 0, False);

	UndoStartPoint(w, info, event->x, event->y);

	l->drawable = info->drawable;
	l->isFat    = info->isFat;

	XtVaGetValues(w, XtNlineWidth, &width, 
			 XtNfillRule, &rule, 
			 XtNlineFillRule, &rule2, 
			 NULL);
	if (!l->gcFlag)
		l->gcFlag = (width != 0);
	
	if (event->button == Button2) {
		sgc = info->second_gc;
		l->isTiled = (rule2 != FillSolid);
	} else if (event->button == Button1) {
		sgc = info->first_gc;
		l->isTiled = (rule  != FillSolid);
	} else
		return;

	if (l->gcFlag) {
		if (l->gc == None)
			l->gc = XCreateGC(XtDisplay(w), info->drawable, 0, NULL);
		XCopyGC(XtDisplay(w), sgc, ~GCLineWidth, l->gc);
	} else {
		l->gc       = sgc;
	}

	if (l->id == (XtIntervalId)NULL)
		drawEvent(l);
	else
		draw(l);
}

static void	motion(Widget w, LocalInfo *l, XMotionEvent *event, OpInfo *info) 
{
	if (l->drawing && info->surface == opPixmap) {
		l->x = event->x;
		l->y = event->y;

		draw(l);
	} else if (!l->drawing && info->surface == opWindow) {
		l->zoom = info->zoom;
		drawOutline(w, l, event->x, event->y, True);
	}
}

static void	release(Widget w, LocalInfo *l, XButtonEvent *event, OpInfo *info) 
{
	int	mask;

	/*
	**  Check to make sure all buttons are up, before doing this
	*/
	mask = Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask;
	switch (event->button) {
	case Button1:	mask ^= Button1Mask; break;
	case Button2:	mask ^= Button2Mask; break;
	case Button3:	mask ^= Button3Mask; break;
	case Button4:	mask ^= Button4Mask; break;
	case Button5:	mask ^= Button5Mask; break;
	}
	if ((event->state & mask) != 0)
		return;

	if (l->id != (XtIntervalId)NULL)
		XtRemoveTimeOut(l->id);

	l->id = (XtIntervalId)NULL;
	l->drawing = False;
}

static void	leave(Widget w, LocalInfo *l, XEvent *event, OpInfo *info) 
{
	drawOutline(w, l, 0, 0, False);
}


/*
**  Those public functions
*/
void SpraySetParameters(int r, int d, int sp)
{
	radius = r;
	radius2 = r * r;
	density = d;
	rate = sp * 10;
}
Boolean SprayGetStyle()
{
	return style;
}
void SpraySetStyle(Boolean flag)
{
	style = flag;
}

static void *commonSprayAdd(Widget w, Boolean flag)
{
	LocalInfo	*l = XtNew(LocalInfo);
	static int	inited = False;

	if (!inited) {
		srandom((int)time(0));
		inited = True;
	}

	l->w   = w;
	l->id  = (XtIntervalId)NULL;
	l->gc  = None;
	l->gcx = GetGCX(w);
	l->gcFlag = False;
	l->useGauss = flag;
	l->trackDrawn = False;
	l->drawing  = False;

	XtVaSetValues(w, XtNcompress, False,
	                 XtVaTypedArg, XtNcursor, XtRString, "spraycan", sizeof(Cursor), NULL);

	OpAddEventHandler(w, opPixmap, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpAddEventHandler(w, opWindow|opPixmap, PointerMotionMask, FALSE, (OpEventProc)motion, l);
	OpAddEventHandler(w, opPixmap, ButtonReleaseMask, FALSE, (OpEventProc)release, l);
	OpAddEventHandler(w, opWindow, LeaveWindowMask, FALSE, (OpEventProc)leave, l);

	return l;
}
void *SprayAdd(Widget w)
{
	return commonSprayAdd(w, style);
}
void SprayRemove(Widget w, LocalInfo *l)
{
	OpRemoveEventHandler(w, opPixmap, ButtonPressMask, FALSE, (OpEventProc)press, l);
	OpRemoveEventHandler(w, opWindow|opPixmap, PointerMotionMask, FALSE, (OpEventProc)motion, l);
	OpRemoveEventHandler(w, opPixmap, ButtonReleaseMask, FALSE, (OpEventProc)release, l);
	OpRemoveEventHandler(w, opWindow, LeaveWindowMask, FALSE, (OpEventProc)leave, l);

	if (l->gcFlag)
		XFreeGC(XtDisplay(w), l->gc);
	if (l->id != (XtIntervalId)NULL)
		XtRemoveTimeOut(l->id);

	XtFree((XtPointer)l);
}

void *SmearAdd(Widget w)
{
	return commonSprayAdd(w, False);
}
void SmearRemove(Widget w, LocalInfo *l)
{
	SprayRemove(w, l);
}
