/**
 * Attraction.java is ported from attraction xscreensaver module.
 */

/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998
 *  Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell 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.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

/* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
   a little like the strong and weak electromagnetic forces.  Derived from
   a Lispm screensaver by John Pezaris <pz@mit.edu>.  Mouse control and
   viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.

   John sez:

   The simulation started out as a purely accurate gravitational simulation,
   but, with constant simulation step size, I quickly realized the field being
   simulated while grossly gravitational was, in fact, non-conservative.  It
   also had the rather annoying behavior of dealing very badly with colliding
   orbs.  Therefore, I implemented a negative-gravity region (with two
   thresholds; as I read your code, you only implemented one) to prevent orbs
   from every coming too close together, and added a viscosity factor if the
   speed of any orb got too fast.  This provides a nice stable system with
   interesting behavior.

   I had experimented with a number of fields including the van der Waals
   force (very interesting orbiting behavior) and 1/r^3 gravity (not as
   interesting as 1/r^2).  An even normal viscosity (rather than the
   thresholded version to bleed excess energy) is also not interesting.
   The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
   interesting -- the orbs never collided and the threshold viscosity fixed
   the non-conservational problem.

   Philip sez:
   > An even normal viscosity (rather than the thresholded version to
   > bleed excess energy) is also not interesting.

   unless you make about 200 points.... set the viscosity to about .8
   and drag the mouse through it.   it makes a nice wave which travels
   through the field.

   And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:

   Despite what John sez, the field being simulated is always conservative.
   The real problem is that it uses a simple hack, computing acceleration
   *based only on the starting position*, instead of a real differential
   equation solver.  Thus you'll always have energy coming out of nowhere,
   although it's most blatant when balls get close together.  If it were
   done right, you wouldn't need viscosity or artificial limits on how
   close the balls can get.
 */


public class Attraction extends DoubleBufferedFrame
{
	public static final int ball_mode = 0;
	public static final int line_mode = 1;
	public static final int polygon_mode = 2;
	public static final int tail_mode = 3;
	public static final int spline_mode = 4;
	public static final int spline_filled_mode = 5;

	public static int init_radius = -1;
	public static int init_vx = 1;
	public static int init_vy = 1;
	public static int init_points = 5;
	public static int init_segments = 1;
	public static init_threshold = 20;
	public static int init_delay = 0;
	public static int init_global_size = 1;
	public static boolean init_glow = false;
	public static boolean init_orbit = false;
	public static double init_viscosity = 1;
	public static int init_mode = ball_mode;
	public static int init_colors = 8;
	public static double init_v_mult = 1.0;

	class Ball
	{
		double x, y;
		double vx, vy;
		double dx, dy;
		double mass;
		int size;
		int pixel_index;
		int hue;

		public Ball()
		{
		}

	}

	static Ball[] balls;

	static int npoints;
	static threshold;
	static int delay;
	static int global_size;
	static int segments;

	static boolean glow_p;
	static boolean orbit_p;

	static XPoint[] point_stack;
	static int point_stack_size, point_stack_fp;

	static Color[] colors;
	static int ncolors;
	static int fg_index;
	//	static int color_shift;

	/*flip mods for mouse interaction*/
	//static Bool mouse_p;
	//int mouse_x, mouse_y, mouse_mass, root_x, root_y;

	static double viscosity;

	static int mode;
	*/

	//static GC draw_gc, erase_gc;

	static int MAX_SIZE=16;

	/*
	  #define min(a,b) ((a)<(b)?(a):(b))
	  #define max(a,b) ((a)>(b)?(a):(b))
	*/


	public int rand_size()
	{
		return((int)Math.min(MAX_SIZE,8+(random() % (MAXSIZE-9))));
	}


	public void init_balls(Graphics g, int width, int height)
	{
		int i;
		//		XWindowAttributes xgwa;
		//		XGCValues gcv;
		int xlim, ylim, midx, midy, r, vx, vy;
		double th;
		//		Colormap cmap;
		//		char *mode_str;
		//		XGetWindowAttributes (dpy, window, &xgwa);
		xlim = width;
		ylim = height;
		//		cmap = xgwa.colormap;
		midx = xlim/2;
		midy = ylim/2;
		r = init_radius;
		if (r <= 0 || r > Math.min(xlim/2, ylim/2)) {
			r = min (xlim/2, ylim/2) - 50;
		}
		vx = init_vx;
		vy = init_vy;
		npoints = init_points;
		if (npoints < 1) {
			npoints = 3 + (random () % 5);
		}
		balls = new Balls[npoints];
		for(int x=0;x<npoints;x++) {balls[x] = new Balls();}
		segments = init_segments;
		if (segments < 0) {
			segments = 1;
		}
		threshold = init_threshold;
		if (threshold < 0) {
			threshold = 0;
		}
		delay = init_delay;
		if (delay < 0) {
			delay = 0;
		}
		global_size = init_global_size;
		if (global_size < 0) {
			global_size = 0;
		}
		glow_p = init_glow;
		orbit_p = init_orbit;

		//		mouse_p = get_boolean_resource ("mouse", "Boolean");
		//		mouse_mass = get_integer_resource ("mouseSize", "Integer");
		//		mouse_mass =  mouse_mass *  mouse_mass *10;
		
		viscosity = init_viscosity;
		
		//		mode_str = get_string_resource ("mode", "Mode");
		//		if (! mode_str) mode = ball_mode;
		//		else if (!strcmp (mode_str, "balls")) mode = ball_mode;
		//		else if (!strcmp (mode_str, "lines")) mode = line_mode;
		//		else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
		//		else if (!strcmp (mode_str, "tails")) mode = tail_mode;
		//		else if (!strcmp (mode_str, "splines")) mode = spline_mode;
		//		else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
		//		else {
		//			exit (1);
		//		}
	
		mode = init_mode;

		if (mode != ball_mode && mode != tail_mode) glow_p = false;
  
		if (mode == polygon_mode && npoints < 3) {
			mode = line_mode;
		}

		ncolors = init_ncolors;
		if (ncolors < 2) {
			ncolors = 2;
		}
		if (ncolors <= 2) {
			mono_p = true;
		}
		colors = null;

		if (!mono_p) {
			fg_index = 0;
			switch (mode)	{
			case ball_mode:
				if (glow_p) {
					int H = random() % 360;
					double S1 = 0.25;
					double S2 = 1.00;
					double V = frand(0.25) + 0.75;
					colors = new Color[ncolors];
					make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, colors, &ncolors,
										  False, True, False);
				}
				else 				{
					if(ncolors < npoints) {
						ncolors = npoints;
					}
					colors = new Color[ncolors];
					make_random_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
												 True, True, False, True);
				}
				break;
			case line_mode:
			case polygon_mode:
			case spline_mode:
			case spline_filled_mode:
			case tail_mode:
				colors = new Color[ncolors];
				make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
											 True, False, True);
				break;
			default:
				abort ();
			}
		}

		if (!mono_p && ncolors <= 2)	{
			colors = null
			mono_p = true;
		}

		if (mode != ball_mode) {
			int size = (segments ? segments : 1);
			point_stack_size = size * (npoints + 1);
			point_stack = new XPoint[point_stack_size];
			point_stack_fp = 0;
		}

		//		gcv.line_width = (mode == tail_mode
		//								? (global_size ? global_size : (MAX_SIZE * 2 / 3))
		//								: 1);
		//		gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);

		//		if (mono_p)
		//			gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap);
		//		else
		//			gcv.foreground = colors[fg_index].pixel;
		//		draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
		setBackground(Color.black);
		
		//		gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap);
		//		erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);


		//#define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))

		if (orbit_p && global_size==-1) {
			/* To orbit, all objects must be the same mass, or the math gets
				really hairy... */
			global_size = rand_size ();
		}

		th = frand (M_PI+M_PI);
		for (i = 0; i < npoints; i++)		{
			int new_size = (global_size ? global_size : rand_size ());
			balls [i].dx = 0;
			balls [i].dy = 0;
			balls [i].size = new_size;
			balls [i].mass = (new_size * new_size * 10);
			balls [i].x = midx + r * Math.cos (i * ((M_PI+M_PI) / npoints) + th);
			balls [i].y = midy + r * Math.sin (i * ((M_PI+M_PI) / npoints) + th);
			if (! orbit_p)	{
				balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
				balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
			}
			if (mono_p || mode != ball_mode)
				balls [i].pixel_index = -1;
			else if (glow_p)
				balls [i].pixel_index = 0;
			else
				balls [i].pixel_index = random() % ncolors;
		}

		if (orbit_p) {
			double a = 0;
			double v;
			double v_mult = init_v_mult;
			if (v_mult == 0.0) v_mult = 1.0;

			for (i = 1; i < npoints; i++) {
				double _2ipi_n = (2 * i * M_PI / npoints);
				double x = r * Math.cos (_2ipi_n);
				double y = r * Math.sin (_2ipi_n);
				double distx = r - x;
				double dist2 = (distx * distx) + (y * y);
				double dist = sqrt (dist2);
				double a1 = ((balls[i].mass / dist2) *
								 ((dist < threshold) ? -1.0 : 1.0) *
								 (distx / dist));
				a += a1;
			}

			if (a < 0.0) {
				System.out.println("domain error: forces on balls too great, a="+a);
				System.exit (-1);
			}
			v = sqrt (a * r) * v_mult;
			for (i = 0; i < npoints; i++)	{
				double k = ((2 * i * M_PI / npoints) + th);
				balls [i].vx = -v * Math.sin (k);
				balls [i].vy =  v * Math.cos (k);
			}
		}

		if (mono_p) {
			glow_p = false;
		}
		//		XClearWindow (dpy, window);
	}


	public void compute_force (int i, double[] dx_ret, double[] dy_ret)
	{
		int j;
		double x_dist, y_dist, dist, dist2;
		dx_ret = new double[1];
		dy_ret = new double[1];
		for (j = 0; j < npoints; j++)	{
			if (i == j) j=npoints;
			x_dist = balls [j].x - balls [i].x;
			y_dist = balls [j].y - balls [i].y;
			dist2 = (x_dist * x_dist) + (y_dist * y_dist);
			dist = Math.sqrt (dist2);
	      
			if (dist > 0.1) /* the balls are not overlapping */
			{
				double new_acc = ((balls[j].mass / dist2) *
										((dist < threshold) ? -1.0 : 1.0));
				double new_acc_dist = new_acc / dist;
				dx_ret[0] += new_acc_dist * x_dist;
				dy_ret[0] += new_acc_dist * y_dist;
			}
			else
			{		/* the balls are overlapping; move randomly */
				dx_ret[0] += (frand (10.0) - 5.0);
				dy_ret[0] += (frand (10.0) - 5.0);
			}
		}


		//		if (mouse_p)
		//		{
		//			x_dist = mouse_x - balls [i].x;
		//			y_dist = mouse_y - balls [i].y;
		//			dist2 = (x_dist * x_dist) + (y_dist * y_dist);
		//			dist = sqrt (dist2);
		//	
		//			if (dist > 0.1) /* the balls are not overlapping */
		//			{
		//				double new_acc = ((mouse_mass / dist2) *
		//										((dist < threshold) ? -1.0 : 1.0));
		//				double new_acc_dist = new_acc / dist;
		//				*dx_ret += new_acc_dist * x_dist;
		//				*dy_ret += new_acc_dist * y_dist;
		//			}
		//			else
		//			{		/* the balls are overlapping; move randomly */
		//				*dx_ret += (frand (10.0) - 5.0);
		//				*dy_ret += (frand (10.0) - 5.0);
		//			}
		//		}
	}


	public void run_balls (Graphics g, int width, int height)
	{
		int last_point_stack_fp = point_stack_fp;
		static int tick = 500, xlim, ylim;
		//		static Colormap cmap;
		int i;

		/*flip mods for mouse interaction*/
		//		Window  root1, child1;
		//		unsigned int mask;
		//		if (mouse_p)
		//		{
		//			XQueryPointer(dpy, window, &root1, &child1,
		//							  &root_x, &root_y, &mouse_x, &mouse_y, &mask);
		//		}

		if (tick++ == 500)
		{
			tick = 0;
			xlim = width;
			ylim = height;
		}

		/* compute the force of attraction/repulsion among all balls */
		for (i = 0; i < npoints; i++) {
			double[] tmpx = null;
			double[] tmpy = null;
			compute_force (i, tmpx, tmpy);
			balls[i].dx = tmpx[0];
			balls[i].dy = tmpy[0];
		}

		/* move the balls according to the forces now in effect */
		for (i = 0; i < npoints; i++)
		{
			double old_x = balls[i].x;
			double old_y = balls[i].y;
			double new_x, new_y;
			int size = balls[i].size;
			balls[i].vx += balls[i].dx;
			balls[i].vy += balls[i].dy;

			/* don't let them get too fast: impose a terminal velocity
				(actually, make the medium have friction) */
			if (balls[i].vx > 10)
			{
				balls[i].vx *= 0.9;
				balls[i].dx = 0;
			}
			else if (viscosity != 1)
			{
				balls[i].vx *= viscosity;
			}

			if (balls[i].vy > 10)
			{
				balls[i].vy *= 0.9;
				balls[i].dy = 0;
			}
			else if (viscosity != 1)
			{
				balls[i].vy *= viscosity;
			}

			balls[i].x += balls[i].vx;
			balls[i].y += balls[i].vy;

			/* bounce off the walls */
			if (balls[i].x >= (xlim - balls[i].size))
			{
				balls[i].x = (xlim - balls[i].size - 1);
				if (balls[i].vx > 0)
					balls[i].vx = -balls[i].vx;
			}
			if (balls[i].y >= (ylim - balls[i].size))
			{
				balls[i].y = (ylim - balls[i].size - 1);
				if (balls[i].vy > 0)
					balls[i].vy = -balls[i].vy;
			}
			if (balls[i].x <= 0)
			{
				balls[i].x = 0;
				if (balls[i].vx < 0)
					balls[i].vx = -balls[i].vx;
			}
			if (balls[i].y <= 0)
			{
				balls[i].y = 0;
				if (balls[i].vy < 0)
					balls[i].vy = -balls[i].vy;
			}

			new_x = balls[i].x;
			new_y = balls[i].y;

			if (!mono_p)			{
				if (mode == ball_mode)		{
					if (glow_p)	{
						/* make color saturation be related to particle
							acceleration. */
						double limit = 0.5;
						double s, fraction;
						double vx = balls [i].dx;
						double vy = balls [i].dy;
						if (vx < 0) vx = -vx;
						if (vy < 0) vy = -vy;
						fraction = vx + vy;
						if (fraction > limit) fraction = limit;

						s = 1 - (fraction / limit);
						balls[i].pixel_index = (int)(ncolors * s);
					}
					//					XSetForeground (dpy, draw_gc,
					//										 colors[balls[i].pixel_index].pixel);
					g.setForegroundColor(colors[balls[i].pixel_index]);
				}
			}

			if (mode == ball_mode) {
				g.fillArc((int) old_x, (int) old_y, size, size, 0, 360);
				//				XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
				//							 size, size, 0, 360*64);
			}
			else {
				point_stack [point_stack_fp].x = new_x;
				point_stack [point_stack_fp].y = new_y;
				point_stack_fp++;
			}
		}

		/* draw the lines or polygons after computing all points */
		/*   not implemented right now */
		if (mode != ball_mode) {
			//			point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
			//			point_stack [point_stack_fp].y = balls [0].y;
			//			point_stack_fp++;
			//			if (point_stack_fp == point_stack_size)
			//				point_stack_fp = 0;
			//			else if (point_stack_fp > point_stack_size) /* better be aligned */
			//				abort ();
			//			if (!mono_p)
			//			{
			//				static int tick = 0;
			//				if (tick++ == color_shift)
			//				{
			//					tick = 0;
			//					fg_index = (fg_index + 1) % ncolors;
			//					XSetForeground (dpy, draw_gc, colors[fg_index].pixel);
			//				}
			//  }
		}
	

		switch (mode)
		{
		case ball_mode:
			break;
			/*  not implemented yet */
			/*
		case line_mode:
			if (segments > 0)
				XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
								npoints + 1, CoordModeOrigin);
			XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
							npoints + 1, CoordModeOrigin);
			break;
		case polygon_mode:
			if (segments > 0)
				XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
								  npoints + 1, (npoints == 3 ? Convex : Complex),
								  CoordModeOrigin);
			XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
							  npoints + 1, (npoints == 3 ? Convex : Complex),
							  CoordModeOrigin);
			break;
		case tail_mode:
			{
				for (i = 0; i < npoints; i++)
				{
					int index = point_stack_fp + i;
					int next_index = (index + (npoints + 1)) % point_stack_size;
					XDrawLine (dpy, window, erase_gc,
								  point_stack [index].x,
								  point_stack [index].y,
								  point_stack [next_index].x,
								  point_stack [next_index].y);

					index = last_point_stack_fp + i;
					next_index = (index - (npoints + 1)) % point_stack_size;
					if (next_index < 0) next_index += point_stack_size;
					if (point_stack [next_index].x == 0 &&
						 point_stack [next_index].y == 0)
						continue;
					XDrawLine (dpy, window, draw_gc,
								  point_stack [index].x,
								  point_stack [index].y,
								  point_stack [next_index].x,
								  point_stack [next_index].y);
				}
			}
			break;
		case spline_mode:
		case spline_filled_mode:
			{
				static spline *s = 0;
				if (! s) s = make_spline (npoints);
				if (segments > 0)
				{
					for (i = 0; i < npoints; i++)
					{
						s->control_x [i] = point_stack [point_stack_fp + i].x;
						s->control_y [i] = point_stack [point_stack_fp + i].y;
					}
					compute_closed_spline (s);
					if (mode == spline_filled_mode)
						XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
										  (s->n_points == 3 ? Convex : Complex),
										  CoordModeOrigin);
					else
						XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
										CoordModeOrigin);
				}
				for (i = 0; i < npoints; i++)
				{
					s->control_x [i] = point_stack [last_point_stack_fp + i].x;
					s->control_y [i] = point_stack [last_point_stack_fp + i].y;
				}
				compute_closed_spline (s);
				if (mode == spline_filled_mode)
					XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
									  (s->n_points == 3 ? Convex : Complex),
									  CoordModeOrigin);
				else
					XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
									CoordModeOrigin);
			}
			break;
		default:
			abort ();
			*/
		}

		//		XSync (dpy, False);
	}

	/*
char *progclass = "Attraction";

char *defaults [] = {
  ".background:	black",
  ".foreground:	white",
  "*mode:	balls",
  "*points:	0",
  "*size:	0",
  "*colors:	200",
  "*threshold:	100",
  "*delay:	10000",
  "*glow:	false",
  "*mouseSize:	10",
  "*mouse:	false",
  "*viscosity:	1",
  "*orbit:	false",
  "*colorShift:	3",
  "*segments:	500",
  "*vMult:	0.9",
  0
};

XrmOptionDescRec options [] = {
  { "-mode",		".mode",	XrmoptionSepArg, 0 },
  { "-colors",		".colors",	XrmoptionSepArg, 0 },
  { "-points",		".points",	XrmoptionSepArg, 0 },
  { "-color-shift",	".colorShift",	XrmoptionSepArg, 0 },
  { "-threshold",	".threshold",	XrmoptionSepArg, 0 },
  { "-segments",	".segments",	XrmoptionSepArg, 0 },
  { "-delay",		".delay",	XrmoptionSepArg, 0 },
  { "-size",		".size",	XrmoptionSepArg, 0 },
  { "-radius",		".radius",	XrmoptionSepArg, 0 },
  { "-vx",		".vx",		XrmoptionSepArg, 0 },
  { "-vy",		".vy",		XrmoptionSepArg, 0 },
  { "-vmult",		".vMult",	XrmoptionSepArg, 0 },
  { "-mouse-size",	".mouseSize",	XrmoptionSepArg, 0 },
  { "-mouse",		".mouse",	XrmoptionNoArg, "true" },
  { "-nomouse",		".mouse",	XrmoptionNoArg, "false" },
  { "-viscosity",	".viscosity",	XrmoptionSepArg, 0 },
  { "-glow",		".glow",	XrmoptionNoArg, "true" },
  { "-noglow",		".glow",	XrmoptionNoArg, "false" },
  { "-orbit",		".orbit",	XrmoptionNoArg, "true" },
  { 0, 0, 0, 0 }
};
	*/

	public void paintContext(Graphics g, int width, int height)
	{
		if(init) {
			init = false;
			init_balls(g,width,height);
		}
		else {
			run_balls(g,width,height);
			if(delay>0) {
				try {
					Thread.currentThread().sleep(delay);
				}
				catch(Exception e) {
					System.out.println("fatal error: "+e);
					System.exit(-10);
				}
			}
		}
	}
}



