/*
Parallel Implementation of Life
Copyright 2000 by Andres Santiago Perez-Bergquist,
All rights reserved.  Permission is granted to copy or
modify this code for personal, noncommercial use,
provided this entire notice is maintained intact.
No guarantee of any sort is made about the correctness or
robustness of the provided code, and you use it entirely
at your own risk, releasing the author from any liability
whatsoever.
For more information, visit
http://santiago.mapache.org/toys/life/
*/

import java.awt.*;
import java.applet.Applet;
import java.util.Random;

public class LifeApplet extends Applet
{
	protected CyclicBarrier sync;
	protected Cruncher[][] threads;
	protected /*final*/ int threadHeight, threadWidth, threadsX, threadsY, zoom;

	// Need real mod function, not C++ style (i.e. need -1 mod 5 = 4, not -1)
	public int mod(int operand, int modulus)
	{
		int x = operand % modulus;
		if (x < 0)
			x += modulus;
		return x;
	}

	public void init()
	{
		try
		{
			threadHeight = Integer.parseInt(getParameter("threadHeight"));
		}
		catch (Exception e)
		{
			threadHeight = 100; // default
		}
		try
		{
			threadWidth = Integer.parseInt(getParameter("threadWidth"));
		}
		catch (Exception e)
		{
			threadWidth = 100; // default
		}
		try
		{
			threadsX = Integer.parseInt(getParameter("threadsX"));
		}
		catch (Exception e)
		{
			threadsX = 1; // default
		}
		try
		{
			threadsY = Integer.parseInt(getParameter("threadsY"));
		}
		catch (Exception e)
		{
			threadsY = 1; // default
		}
		try
		{
			zoom = Integer.parseInt(getParameter("zoom"));
		}
		catch (Exception e)
		{
			zoom = 1; // default
		}
		
		sync = new CyclicBarrier(threadsX * threadsY); // Total number of threads
		
		threads = new Cruncher[threadsX][threadsY];
		for(int x=0; x < threadsX; x++)
			for(int y=0; y < threadsY; y++)
				threads[x][y] = new Cruncher(threadWidth, threadHeight, 
												x*threadWidth*zoom, y*threadHeight*zoom, zoom);
		for(int x=0; x < threadsX; x++)
			for(int y=0; y < threadsY; y++)
				threads[x][y].init( threads[x][mod(y-1, threadsY)],
									threads[mod(x+1, threadsX)][mod(y-1, threadsY)],
									threads[mod(x+1, threadsX)][y],
									threads[mod(x+1, threadsX)][mod(y+1, threadsY)],
									threads[x][mod(y+1, threadsY)],
									threads[mod(x-1, threadsX)][mod(y+1, threadsY)],
									threads[mod(x-1, threadsX)][y],
									threads[mod(x-1, threadsX)][mod(y-1, threadsY)],
									sync,
									this);
		
	}
	
	public void start()
	{
		System.out.println("starting...");
		for(int x=0; x < threadsX; x++)
			for(int y=0; y < threadsY; y++)
				(new Thread(threads[x][y])).start();
	}
	
	public void stop()
	{
		System.out.println("stopping...");
	}
	
	public void destroy()
	{
		System.out.println("preparing to unload...");
	}
	
	public void update(Graphics g) // Override clearing behavior
	{
		paint(g);
	}
	
	public void paint(Graphics g) // Paint each cruncher
	{
		System.out.println("Paint");
		for(int x=0; x < threadsX; x++)
			for(int y=0; y < threadsY; y++)
				threads[x][y].paint(g);
	}
}

// Implements synchronization barrier
// Code taken from Concurrent Programming in Java, 2nd Edition by Doug Lea, p. 363
class CyclicBarrier
{
	protected final int parties;
	protected int count;
	protected int resets = 0;
	
	CyclicBarrier(int c) { count = parties = c; }
	
	synchronized int barrier() throws InterruptedException
	{
		int index = --count;
		if (index > 0)
		{
			int r = resets;
			do { wait(); } while (resets == r);
		}
		else
		{
			count = parties;
			++resets;
			notifyAll();
		}
		
		return index;
	}
}

class Cruncher implements Runnable
{
	protected final int X, Y, xBase, yBase, zoom; // width, height, left, top, pixels^2/cell
	protected int[][] current;				// current generation of cells
	protected int[][] previous;				// previous generation of cells
	protected /*final*/ Cruncher N;			// Crunchers lying in the correct direction
	protected /*final*/ Cruncher NE;
	protected /*final*/ Cruncher E;
	protected /*final*/ Cruncher SE;
	protected /*final*/ Cruncher S;
	protected /*final*/ Cruncher SW;
	protected /*final*/ Cruncher W;
	protected /*final*/ Cruncher NW;
	protected /*final*/ CyclicBarrier sync;
	protected /*final*/ LifeApplet parent;
	protected /*final*/ Image offscreen;	// Offscreen buffer, to speed up & parallelize drawing
	
	Cruncher(int width, int height, int left, int top, int z)
	{
		current = new int[width][height];
		previous = new int[width][height];
		X = width;
		Y = height;
		xBase = left;
		yBase = top;
		zoom = z;
	}
	
	// Sets both previous and current generation to random pattern, 30% alive
	void randomize() 
	{
		final Random r = new Random();
	
		for(int x=0; x < X; x++)
			for(int y=0; y < Y; y++)
			{
				if (r.nextFloat() < .7)
					current[x][y] = previous[x][y] = 0;
				else
					current[x][y] = previous[x][y] = 1;
			}
	}
	
	// Links to required components -- can't do in constructor, due to cyclic dependency
	void init(Cruncher North, Cruncher NorthE, Cruncher East, Cruncher SouthE, 
			  Cruncher South, Cruncher SouthW, Cruncher West, Cruncher NorthW,
			  CyclicBarrier b, LifeApplet p)
	{
		N  = North;
		NE = NorthE;
		E  = East;
		SE = SouthE;
		S  = South;
		SW = SouthW;
		W  = West;
		NW = NorthW;
		sync = b;
		parent = p;
		offscreen = p.createImage(X*zoom, Y*zoom);
	}
	
	// Exchange current and previous generations
	protected void swap()
	{
		int[][] temp;
		
		temp = previous;
		previous = current;
		current = temp;
	}
	
	// Actually compute the next generation
	protected void iterate()
	{
		final int X1 = X-1; // Frequently used values, to avoid recomputing
		final int Y1 = Y-1;
	
		/* Do edges clockwise from top left, then do middle.  Edges require
		extracting data from edges of neighboring crunchers. */
	
		//NW Corner
		{
			final int x = 0;
			final int y = 0;
			boolean alive = (previous[x][y] == 1);
			int neighbors = NW.previous[X1][Y1] + N.previous[x][Y1] + N.previous[x+1][Y1] +
							W.previous[X1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
							W.previous[X1][y+1] + previous[x][y+1] + previous[x+1][y+1];
			if (alive) 
			{
				if (neighbors < 2 || neighbors > 3)
					current[x][y] = 0;
				else
					current[x][y] = 1;
			}
			else
			{
				if (neighbors == 3)
					current[x][y] = 1;
				else
					current[x][y] = 0;
			}
		}
			
		// Top
		{
			final int y = 0;
			for(int x=1; x < X1; x++)
			{
				boolean alive = (previous[x][y] == 1);
				int neighbors = N.previous[x-1][Y1] + N.previous[x][Y1] + N.previous[x+1][Y1] +
								previous[x-1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
								previous[x-1][y+1] + previous[x][y+1] + previous[x+1][y+1];
				if (alive) 
				{
					if (neighbors < 2 || neighbors > 3)
						current[x][y] = 0;
					else
						current[x][y] = 1;
				}
				else
				{
					if (neighbors == 3)
						current[x][y] = 1;
					else
						current[x][y] = 0;
				}
			}
		}
		
		//NE Corner
		{
			final int x = X1;
			final int y = 0;
			boolean alive = (previous[x][y] == 1);
			int neighbors = N.previous[x-1][Y1] + N.previous[x][Y1] + NE.previous[0][Y1] +
							previous[x-1][y] /*+ previous[x][y]*/ + E.previous[0][y] +
							previous[x-1][y+1] + previous[x][y+1] + E.previous[0][y+1];
			if (alive) 
			{
				if (neighbors < 2 || neighbors > 3)
					current[x][y] = 0;
				else
					current[x][y] = 1;
			}
			else
			{
				if (neighbors == 3)
					current[x][y] = 1;
				else
					current[x][y] = 0;
			}
		}
			
		// Right
		{
			final int x = X1;
			for(int y=1; y < Y1; y++)
			{
				boolean alive = (previous[x][y] == 1);
				int neighbors = previous[x-1][y-1] + previous[x][y-1] + E.previous[0][y-1] +
								previous[x-1][y] /*+ previous[x][y]*/ + E.previous[0][y] +
								previous[x-1][y+1] + previous[x][y+1] + E.previous[0][y+1];
				if (alive) 
				{
					if (neighbors < 2 || neighbors > 3)
						current[x][y] = 0;
					else
						current[x][y] = 1;
				}
				else
				{
					if (neighbors == 3)
						current[x][y] = 1;
					else
						current[x][y] = 0;
				}
			}
		}
		
		//SE Corner
		{
			final int x = X1;
			final int y = Y1;
			boolean alive = (previous[x][y] == 1);
			int neighbors = previous[x-1][y-1] + previous[x][y-1] + E.previous[0][y-1] +
							previous[x-1][y] /*+ previous[x][y]*/ + E.previous[0][y] +
							S.previous[x-1][0] + S.previous[x][0] + SE.previous[0][0];
			if (alive) 
			{
				if (neighbors < 2 || neighbors > 3)
					current[x][y] = 0;
				else
					current[x][y] = 1;
			}
			else
			{
				if (neighbors == 3)
					current[x][y] = 1;
				else
					current[x][y] = 0;
			}
		}
			
		// Bottom
		{
			final int y = Y1;
			for(int x=1; x < X1; x++)
			{
				boolean alive = (previous[x][y] == 1);
				int neighbors = previous[x-1][y-1] + previous[x][y-1] + previous[x+1][y-1] +
								previous[x-1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
								S.previous[x-1][0] + S.previous[x][0] + S.previous[x+1][0];
				if (alive) 
				{
					if (neighbors < 2 || neighbors > 3)
						current[x][y] = 0;
					else
						current[x][y] = 1;
				}
				else
				{
					if (neighbors == 3)
						current[x][y] = 1;
					else
						current[x][y] = 0;
				}
			}
		}
		
		//SW Corner
		{
			final int x = 0;
			final int y = Y1;
			boolean alive = (previous[x][y] == 1);
			int neighbors = W.previous[X1][y-1] + previous[x][y-1] + previous[x+1][y-1] +
							W.previous[X1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
							SW.previous[X1][0] + S.previous[x][0] + E.previous[x+1][0];
			if (alive) 
			{
				if (neighbors < 2 || neighbors > 3)
					current[x][y] = 0;
				else
					current[x][y] = 1;
			}
			else
			{
				if (neighbors == 3)
					current[x][y] = 1;
				else
					current[x][y] = 0;
			}
		}
			
		// Left
		{
			final int x = 0;
			for(int y=1; y < Y1; y++)
			{
				boolean alive = (previous[x][y] == 1);
				int neighbors = W.previous[X1][y-1] + previous[x][y-1] + previous[x+1][y-1] +
								W.previous[X1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
								W.previous[X1][y+1] + previous[x][y+1] + previous[x+1][y+1];
				if (alive) 
				{
					if (neighbors < 2 || neighbors > 3)
						current[x][y] = 0;
					else
						current[x][y] = 1;
				}
				else
				{
					if (neighbors == 3)
						current[x][y] = 1;
					else
						current[x][y] = 0;
				}
			}
		}
			
		// Core
		for(int x=1; x < X1; x++)
			for(int y=1; y < Y1; y++)
			{
				boolean alive = (previous[x][y] == 1);
				int neighbors = previous[x-1][y-1] + previous[x][y-1] + previous[x+1][y-1] +
								previous[x-1][y] /*+ previous[x][y]*/ + previous[x+1][y] +
								previous[x-1][y+1] + previous[x][y+1] + previous[x+1][y+1];
				if (alive) 
				{
					if (neighbors < 2 || neighbors > 3)
						current[x][y] = 0;
					else
						current[x][y] = 1;
				}
				else
				{
					if (neighbors == 3)
						current[x][y] = 1;
					else
						current[x][y] = 0;
				}
			}
			
		// Create visual representation for later blitting to screen by paint routine
		// Allows for drawing to be done offscreen (faster) and in parallel
		// Done by clearing to black and filling in live cells -- faster than vice versa
		//   due to sparseness of live cells
		{
			Graphics g = offscreen.getGraphics();
			g.setClip(0, 0, X*zoom, Y*zoom);
			
			g.setColor(Color.black);
			g.fillRect(0, 0, X*zoom, Y*zoom);		// Clear to black
			g.setColor(Color.white);
			for(int x=0; x < X; x++)
				for(int y=0; y < Y; y++)
					if(current[x][y] == 1)			// Fill in live cells
						g.fillRect(x*zoom, y*zoom, zoom, zoom);
		}
	}
	
	// Just splat the pre-computed image to screen
	public void paint(Graphics g)
	{
		g.drawImage(offscreen, xBase, yBase, null);
	}

	public void run()
	{
		try
		{
			randomize();
			sync.barrier();
			while(true)
			{
				// Update
				iterate();
				// Draw
				if (sync.barrier() == 0)
					parent.repaint();    // Only first one to reach barrier issues paint order
				sync.barrier();
				// Swap
				swap();
				sync.barrier();
			}
		}
		catch(InterruptedException e)
		{
			return;
		}
	}
}
