/* -*- Mode: C; tab-width: 4 -*- */
/* daisy --- flowers in a field */

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

#endif

/*-
 * Copyright (c) 1996 by David Bagley.
 *
 * 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.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 10-May-1997: Compatible with xscreensaver
 * 07-Aug-1996: written.  Initially copied forest.c and made continual
 *              refinements, pyro was helpful too.  Based on a program
 *              I saw on a PC.
 *
 */

#ifdef STANDALONE
#define MODE_daisy
#define PROGCLASS "Daisy"
#define HACK_INIT init_daisy
#define HACK_DRAW draw_daisy
#define daisy_opts xlockmore_opts
#define DEFAULTS "*delay: 100000 \n" \
 "*count: 300 \n" \
 "*cycles: 350 \n" \
 "*ncolors: 200 \n" \
 "*fullrandom: True \n"
#define BRIGHT_COLORS
#define UNIFORM_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */

#endif /* STANDALONE */

#ifdef MODE_daisy

#define DEF_GARDEN  "False"

static Bool garden;

static XrmOptionDescRec opts[] =
{
	{(char *) "-garden", (char *) ".daisy.garden", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+garden", (char *) ".daisy.garden", XrmoptionNoArg, (caddr_t) "off"}
};
static argtype vars[] =
{
	{(void *) & garden, (char *) "garden", (char *) "Garden", (char *) DEF_GARDEN, t_Bool}
};
static OptionStruct desc[] =
{
	{(char *) "-/+garden", (char *) "turn on/off garden"}
};

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

#ifdef USE_MODULES
ModStruct   daisy_description =
{"daisy", "init_daisy", "draw_daisy", "release_daisy",
 "refresh_daisy", "init_daisy", (char *) NULL, &daisy_opts,
 100000, 300, 350, 1, 64, 1.0, "",
 "Shows a meadow of daisies", 0, NULL};

#endif

#define GREEN	(23 * MI_NPIXELS(mi) / 64)
#define NOTGREEN	(7 * MI_NPIXELS(mi) / 64)
#define MINDAISIES   1

#define DROOP 20		/* Percentage x with relation to y */
#define MINHEIGHT  20		/* Daisy height range */
#define MAXHEIGHT  40

typedef struct {
	int         width;
	int         height;
	int         time;	/* up time */
	int         ndaisies;
	int         meadow_y;
	Bool        garden;
	float       step;
} daisystruct;

static daisystruct *daisies = (daisystruct *) NULL;

/* always green, straight for now, parabolic later */
static void
drawstem(ModeInfo * mi, XPoint start, XPoint stop)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);

	if (MI_NPIXELS(mi) > 2)
		XSetForeground(display, gc, MI_PIXEL(mi, GREEN));
	else
		XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
#if 1
	XDrawLine(display, window, gc, start.x, start.y, stop.x, stop.y);
#else
	XDrawArc(display, window, gc, stop.x, stop.y,
		 NRAND(50) + 30, start.y - stop.y + 1, 90 * 64, 170 * 64);
#endif
}

/* not green */
static unsigned long
drawpetals(ModeInfo * mi, XPoint center,
	   int size, int circles, int delta, int offset, int petals)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	unsigned long colour = 0;
	float       start_angle, inc_angle;
	XPoint      newcenter;
	int         petal, inc;
	float       sine, cosine;

	if (MI_NPIXELS(mi) > GREEN + NOTGREEN) {
		do {
			colour = NRAND(MI_NPIXELS(mi));
		} while ((long) colour >= GREEN - NOTGREEN &&
			 (long) colour <= GREEN + NOTGREEN);
	}
	start_angle = NRAND(360) * M_PI / 180;
	inc_angle = 2 * M_PI / petals;
	XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
	if (size > 2)
		XFillArc(display, window, gc,
			 center.x - size / 2, center.y - size / 2, size, size,
			 0, 23040);
	for (petal = 0; petal < petals; petal++) {
		sine = SINF(start_angle + petal * inc_angle);
		cosine = COSF(start_angle + petal * inc_angle);
		if (size > 2)
			if (MI_NPIXELS(mi) <= 2) {
				XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
				for (inc = 0; inc < circles; inc++) {
					newcenter.x = center.x + (int) (sine * (offset + delta * inc));
					newcenter.y = center.y + (int) (cosine * (offset + delta * inc));
					XDrawArc(display, window, gc,
						 newcenter.x - size / 2, newcenter.y - size / 2, size, size,
						 0, 23040);
				}
			}
		if (MI_NPIXELS(mi) > 2 && MI_NPIXELS(mi) > GREEN + NOTGREEN)
			XSetForeground(display, gc, MI_PIXEL(mi, colour));
		else
			XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
		for (inc = 0; inc < circles; inc++) {
			newcenter.x = center.x + (int) (sine * (offset + delta * inc));
			newcenter.y = center.y + (int) (cosine * (offset + delta * inc));
			if (size < 2)
				XDrawPoint(display, window, gc, newcenter.x, newcenter.y);
			else
				XFillArc(display, window, gc,
					 newcenter.x - size / 2, newcenter.y - size / 2, size, size,
					 0, 23040);
		}
	}
	return colour;
}

/* not green */
static void
drawcenter(ModeInfo * mi, XPoint center, int size, unsigned long petalcolour)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	unsigned long colour;

	if (MI_NPIXELS(mi) > GREEN + NOTGREEN) {
		do {
			/* Insure good contrast */
			colour = (NRAND(2 * MI_NPIXELS(mi) / 3) + petalcolour +
				  MI_NPIXELS(mi) / 6) % MI_NPIXELS(mi);
		} while ((long) colour >= GREEN - NOTGREEN &&
			 (long) colour <= GREEN + NOTGREEN);
		XSetForeground(display, gc, MI_PIXEL(mi, colour));
	} else
		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
	if (size < 2)
		XDrawPoint(display, window, gc, center.x, center.y);
	else
		XFillArc(display, window, gc,
			 center.x - size / 2, center.y - size / 2, size, size, 0, 23040);
}

static void
drawdaisy(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	GC          gc = MI_GC(mi);
	daisystruct *dp = &daisies[MI_SCREEN(mi)];
	XPoint      stem_start, stem_stop;
	int         height, droop;
	unsigned long colour;

	/* Care more about the flower being on the screen rather than the stem */
#ifdef LINEAR
	stem_stop.y = dp->meadow_y + dp->time * dp->step;
	height = (NRAND(MAXHEIGHT - MINHEIGHT + 1) + MINHEIGHT) * dp->height / 300 *
		2 * (dp->time + 20) / (dp->ndaisies + 20);
#else
	stem_stop.y = dp->meadow_y + (int) ((float) dp->time * dp->time *
					    dp->step / dp->ndaisies);
	height = (NRAND(MAXHEIGHT - MINHEIGHT + 1) + MINHEIGHT) * dp->height / 300 *
		2 * (dp->time * dp->time + 1) / (dp->ndaisies * dp->ndaisies + 1);
#endif
	stem_start.y = stem_stop.y + height;
	if (dp->garden) {
		stem_stop.x = ((LRAND() & 1) ? 1 : -1) *
			NRAND(dp->width / 4 * (dp->time + 1) / (dp->ndaisies + 20) +
			      dp->width / 4 + 1) + dp->width / 2;
	} else {
		stem_stop.x = NRAND(dp->width);
	}
	/* Give about droop left or right with 25% randomness */
	droop = ((LRAND() & 1) ? 1 : -1) * DROOP * (NRAND(50) + 75) / 100;
	stem_start.x = stem_stop.x + droop * height / 100;
	XSetLineAttributes(display, gc, height / 24 + 1,
			   LineSolid, CapNotLast, JoinRound);
	drawstem(mi, stem_start, stem_stop);
	XSetLineAttributes(display, gc, 1, LineSolid, CapNotLast, JoinRound);
	colour = drawpetals(mi, stem_stop, height / 6, 5, height / 32 + 1,
			    height / 7, NRAND(6) + 6);
	drawcenter(mi, stem_stop, height / 7, colour);
}


void
init_daisy(ModeInfo * mi)
{
	daisystruct *dp;

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

	dp->width = MI_WIDTH(mi);
	dp->height = MI_HEIGHT(mi);
	dp->time = 0;
	if (MI_IS_FULLRANDOM(mi))
		dp->garden = (Bool) (LRAND() & 1);
	else
		dp->garden = garden;

	dp->ndaisies = MI_COUNT(mi);
	if (dp->ndaisies < -MINDAISIES)
		dp->ndaisies = NRAND(-dp->ndaisies - MINDAISIES + 1) + MINDAISIES;
	else if (dp->ndaisies < MINDAISIES)
		dp->ndaisies = MINDAISIES;
	dp->meadow_y = dp->height / 5;
	dp->step = (float) (dp->height - 2 * dp->meadow_y) / (dp->ndaisies + 1.0);
	MI_CLEARWINDOW(mi);
}

void
draw_daisy(ModeInfo * mi)
{
	daisystruct *dp;

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

	MI_IS_DRAWN(mi) = True;

	if (dp->time < dp->ndaisies)
		drawdaisy(mi);
	if (++dp->time > MI_CYCLES(mi))
		init_daisy(mi);
}

void
release_daisy(ModeInfo * mi)
{
	if (daisies != NULL) {
		free(daisies);
		daisies = (daisystruct *) NULL;
	}
}

void
refresh_daisy(ModeInfo * mi)
{
	daisystruct *dp;

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

	if (dp->time < dp->ndaisies) {
		MI_CLEARWINDOW(mi);
	} else {
		init_daisy(mi);
	}
}

#endif /* MODE_daisy */
