import javax.swing.*;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class GoL extends Applet implements Runnable, ActionListener, MouseListener, MouseMotionListener
{
    public static final int columns = 100;
    public static final int rows = 100;
    Image buffer;
    Graphics bufferg;
    Thread main = new Thread ();
    JLabel title;
    Font titleFont;
    JButton start;
    JButton stop;
    JButton clear;
    JComboBox choice;
    Panel innerPanel;
    Panel outerPanel;
    int lastCellX;
    int lastCellY;
    int startpixel;
    int sizex;
    int sizey;
    int cellsizex;
    int cellsizey;
    Cell area [] [];
    boolean go = false;

    public void init ()
    {
	startpixel = 70;
	sizex = 600;
	sizey = 600 - startpixel;

	cellsizex = sizex / columns;
	cellsizey = sizey / rows;

	area = new Cell [columns] [rows];

	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		area [i] [j] = new Cell (i, j, false);
	    }
	}
	setSize (sizex + 1, sizey + 1);

	buffer = createImage (800, 600);
	bufferg = buffer.getGraphics ();
	outerPanel = new Panel (new BorderLayout ());
	innerPanel = new Panel (new FlowLayout ());
	titleFont = new Font ("Arial", Font.BOLD, 24);
	title = new JLabel ("The Game of Life");
	title.setFont (titleFont);
	start = new JButton ("Start");
	start.setActionCommand ("start");
	start.addActionListener (this);
	stop = new JButton ("Stop");
	stop.setActionCommand ("stop");
	stop.addActionListener (this);
	stop.setEnabled (false);
	clear = new JButton ("Clear");
	clear.setActionCommand ("clear");
	clear.addActionListener (this);
	String choices [] = {"None", "Glider", "Toad", "R-Pentamino", "Pi Pantamino", "CP-pulsar"};
	choice = new JComboBox (choices);
	choice.setActionCommand ("choose");
	choice.addActionListener (this);
	innerPanel.add (start);
	innerPanel.add (stop);
	innerPanel.add (clear);
	innerPanel.add (choice);
	outerPanel.add (title, "North");
	outerPanel.add (innerPanel, "Center");
	add (outerPanel);

	addMouseListener (this);
	addMouseMotionListener (this);
	start.requestFocus ();
    }


    public void paint (Graphics g)
    {
	showStatus ("" + main.isAlive ());
	cellsizex = sizex / columns;
	cellsizey = sizey / rows;

	outerPanel.paint (bufferg);
	bufferg.clearRect (0, 0, sizex, sizey);
	bufferg.setColor (Color.black);

	// Draw white or blue squares for each cell
	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		if (area [i] [j].getLive ())
		    bufferg.setColor (Color.blue);
		else
		    bufferg.setColor (Color.white);
		bufferg.fillRect (i * cellsizex, (j * cellsizey) + startpixel, cellsizex, cellsizey + startpixel);
		bufferg.setColor (Color.black);
		//if (area[i][j].neighbours != 0)
		//bufferg.drawString ("" + area[i][j].neighbours, (i * cellsizex) + (cellsizex/2), (j * cellsizey) + startpixel + (cellsizey /2));
	    }
	}

	// Draw grids only if the resolution is low enough
	if (columns < 101 && rows < 101)
	{
	    // Vertical lines
	    for (int i = 0 ; i <= columns ; i++)
	    {
		bufferg.drawLine (i * cellsizex, startpixel, i * cellsizex, sizey);
	    }

	    // Horizontal lines
	    for (int i = 0 ; i <= rows ; i++)
	    {
		bufferg.drawLine (0, (i * cellsizey) + startpixel, sizex, (i * cellsizey) + startpixel);
	    }
	}
	g.drawImage (buffer, 0, 0, null);
	repaint ();

    }


    public void update (Graphics g)
    {
	paint (g);
    }


    public void run ()
    {
	// Run away! Run away!
	while (true)
	{
	    try
	    {
		main.sleep (10);
	    }
	    catch (InterruptedException e)
	    {
	    }

	    if (go)
	    {
		nextGen (area);
		repaint ();
	    }
	}
    }


    public synchronized void actionPerformed (ActionEvent e)
    {
	// Widget stuff
	if (e.getActionCommand ().equals ("clear"))
	    clearField (area);
	else if (e.getActionCommand ().equals ("start"))
	{
	    main = new Thread (this);
	    main.start ();
	    // Button manipulations
	    start.setEnabled (false);
	    stop.setEnabled (true);
	    stop.requestFocus ();
	    go = true;
	}
	else if (e.getActionCommand ().equals ("stop"))
	{
	    // Button manipulations
	    start.setEnabled (true);
	    stop.setEnabled (false);
	    start.requestFocus ();
	    go = false;
	}
	else if (e.getActionCommand ().equals ("choose"))
	{
	    JComboBox cb = (JComboBox) e.getSource ();
	    String chosen = (String) cb.getSelectedItem ();
	    //show the design
	    loadDesign (chosen, area);
	}
    }


    public synchronized void clearField (Cell field [] [])
    {   // Pre: field is the area to be cleared
	// Post: sets everything dead, and with no neighbours
	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		field [i] [j].neighbours = 0;
		field [i] [j].setLive (false);
	    }
	}
	repaint ();
    }


    public synchronized void nextGen (Cell [][] field)
    {   // Pre: field is the area where the deers are
	// Post: calculates the next generation of the field
	
	// Create a temporary array so that changes don't affect the neighbour checking
	Cell temp [] [] = new Cell [columns] [rows];
	for (int i = 0 ; i < columns ; i++)
	{
	    System.arraycopy (field [i], 0, temp [i], 0, field [i].length);
	}
	
	// Clear the neighbours first
	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		field [i] [j].neighbours = 0;
	    }
	}
	
	//
	// Do the Magic
	//
	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		if (field [i] [j].getLive ())
		    giveNeighbours (field[i] [j].getX (), field[i] [j].getY (), area); //checkLiveNeighbours (area[i][j].getX(), area[i][j].getY(), area);
	    }
	}
	int neighbours = 0;
	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		if (area [i] [j].neighbours == 3)
		{
		    // A deer is born! :-) It may already be alive anyway.
		    temp [i] [j].setLive (true);
		}
		else if (field [i] [j].neighbours == 2 && field[i] [j].getLive ())
		{
		    // Do Nothing
		}
		else
		{
		    //Make sure the deers in the cell are dead anyway
		    temp [i] [j].setLive (false);
		}

	    }
	}

	// Copy back the temp area back to the original
	for (int i = 0 ; i < columns ; i++)
	{
	    System.arraycopy (temp [i], 0, field[i], 0, field[i].length);
	}
    }


    public synchronized void giveNeighbours (int locx, int locy, Cell field[][])
    {	// Pre: locx and locy are within the range of the field array.
	// Post: adds one to the neighbour count of each neighbouring neighbour

	for (int i = 0 ; i < 8 ; i++)
	{
	    // Clever way by donny to ignore locations that go out of bounds
	    try
	    {
		if (i == 0)
		    field [locx - 1] [locy - 1].neighbours++;
		else if (i == 1)
		    field [locx] [locy - 1].neighbours++;
		else if (i == 2)
		    field [locx + 1] [locy - 1].neighbours++;
		else if (i == 3)
		    field [locx - 1] [locy].neighbours++;
		else if (i == 4)
		    field [locx + 1] [locy].neighbours++;
		else if (i == 5)
		    field [locx - 1] [locy + 1].neighbours++;
		else if (i == 6)
		    field [locx] [locy + 1].neighbours++;
		else if (i == 7)
		    field [locx + 1] [locy + 1].neighbours++;
	    }
	    catch (ArrayIndexOutOfBoundsException e)
	    {
	    }
	}
    }


    public synchronized int checkLiveNeighbours (int locx, int locy, Cell cells [] [])
    { // Pre: locx and locy are coodinates of the cell being checked, area[][] is the field
	// Post: Checks and counts the number of live neighbours
	int count = 0;

	// Don't want it colliding with boundaries - just consider it as no neighbours

	for (int i = 0 ; i < 8 ; i++)
	{
	    try
	    {
		if (cells [locx - 1] [locy - 1].getLive () && i == 0)
		    count++;
		if (cells [locx - 1] [locy].getLive () && i == 1)
		    count++;
		if (cells [locx - 1] [locy + 1].getLive () && i == 2)
		    count++;
		if (cells [locx + 1] [locy - 1].getLive () && i == 3)
		    count++;
		if (cells [locx + 1] [locy].getLive () && i == 4)
		    count++;
		if (cells [locx + 1] [locy + 1].getLive () && i == 5)
		    count++;
		if (cells [locx] [locy - 1].getLive () && i == 6)
		    count++;
		if (cells [locx] [locy + 1].getLive () && i == 7)
		    count++;
	    }
	    catch (ArrayIndexOutOfBoundsException e)
	    {
	    }
	}
	return count;
    }


    public synchronized void loadDesign (String name, Cell field[][])
    {	// Pre: name is a valid name of a design
	// Post: Clears the field and loads the selected design
	clearField (field);
	if (name.compareTo ("Glider") == 0)
	{
	    field [25] [25].setLive (true);
	    field [26] [25].setLive (true);
	    field [27] [25].setLive (true);
	    field [27] [26].setLive (true);
	    field [26] [27].setLive (true);
	}
	else if (name.compareTo ("R-Pentamino") == 0)
	{
	    field [30] [30].setLive (true);
	    field [31] [30].setLive (true);
	    field [31] [29].setLive (true);
	    field [31] [31].setLive (true);
	    field [32] [29].setLive (true);
	}
	else if (name.compareTo ("Pi Pantamino") == 0)
	{
	    field [30] [30].setLive (true);
	    field [30] [29].setLive (true);
	    field [30] [28].setLive (true);
	    field [31] [28].setLive (true);
	    field [32] [28].setLive (true);
	    field [32] [29].setLive (true);
	    field [32] [30].setLive (true);
	}
	else if (name.compareTo ("Toad") == 0)
	{
	    field [50] [50].setLive (true);
	    field [50] [51].setLive (true);
	    field [51] [49].setLive (true);
	    field [52] [52].setLive (true);
	    field [53] [50].setLive (true);
	    field [53] [51].setLive (true);
	}
	else if (name.compareTo ("CP-pulsar") == 0)
	{
	    field [52] [50].setLive (true);
	    field [53] [50].setLive (true);
	    field [54] [50].setLive (true);
	    field [58] [50].setLive (true);
	    field [59] [50].setLive (true);
	    field [60] [50].setLive (true);
	    field [50] [52].setLive (true);
	    field [55] [52].setLive (true);
	    field [57] [52].setLive (true);
	    field [62] [52].setLive (true);
	    field [50] [53].setLive (true);
	    field [55] [53].setLive (true);
	    field [57] [53].setLive (true);
	    field [62] [53].setLive (true);
	    field [50] [54].setLive (true);
	    field [55] [54].setLive (true);
	    field [57] [54].setLive (true);
	    field [62] [54].setLive (true);
	    field [52] [55].setLive (true);
	    field [53] [55].setLive (true);
	    field [54] [55].setLive (true);
	    field [58] [55].setLive (true);
	    field [59] [55].setLive (true);
	    field [60] [55].setLive (true);

	    field [52] [57].setLive (true);
	    field [53] [57].setLive (true);
	    field [54] [57].setLive (true);
	    field [58] [57].setLive (true);
	    field [59] [57].setLive (true);
	    field [60] [57].setLive (true);
	    field [50] [58].setLive (true);
	    field [55] [58].setLive (true);
	    field [57] [58].setLive (true);
	    field [62] [58].setLive (true);
	    field [50] [59].setLive (true);
	    field [55] [59].setLive (true);
	    field [57] [59].setLive (true);
	    field [62] [59].setLive (true);
	    field [50] [60].setLive (true);
	    field [55] [60].setLive (true);
	    field [57] [60].setLive (true);
	    field [62] [60].setLive (true);

	    field [52] [62].setLive (true);
	    field [53] [62].setLive (true);
	    field [54] [62].setLive (true);
	    field [58] [62].setLive (true);
	    field [59] [62].setLive (true);
	    field [60] [62].setLive (true);
	}
    }


    public void mouseReleased (MouseEvent e)
    {
    }


    public synchronized void mousePressed (MouseEvent e)
    {	// Post: sets the cell where the mouse is pointing as live or dead
	int mousex = e.getX ();
	int mousey = e.getY ();

	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		if (mousex > (area [i] [j].getX () * cellsizex) && mousex < ((area [i] [j].getX () + 1) * cellsizex))
		{
		    if (mousey > ((area [i] [j].getY () * cellsizey) + startpixel) && mousey < (((area [i] [j].getY () + 1) * cellsizey) + startpixel))
		    {

			if (area [i] [j].getLive ())
			    area [i] [j].setLive (false);
			else
			    area [i] [j].setLive (true);
			lastCellX = area [i] [j].getX ();
			lastCellY = area [i] [j].getY ();

		    }
		}
	    }
	}
	repaint ();
    }


    public synchronized void mouseDragged (MouseEvent e)
    {	// Post: sets the cell where the mouse is pointing as live or dead
	int mousex = e.getX ();
	int mousey = e.getY ();

	for (int i = 0 ; i < columns ; i++)
	{
	    for (int j = 0 ; j < rows ; j++)
	    {
		// Split in two for better readibility
		if (mousex > (area [i] [j].getX () * cellsizex) && mousex < ((area [i] [j].getX () + 1) * cellsizex))
		{
		    if (mousey > ((area [i] [j].getY () * cellsizey) + startpixel) && mousey < (((area [i] [j].getY () + 1) * cellsizey) + startpixel))
		    {
			// Some quick code to prevent flickering on and off while dragging.
			if (!(lastCellX == area [i] [j].getX () && lastCellY == area [i] [j].getY ()))
			{
			    if (area [i] [j].getLive ())
				area [i] [j].setLive (false);
			    else
				area [i] [j].setLive (true);
			    lastCellX = area [i] [j].getX ();
			    lastCellY = area [i] [j].getY ();
			}
		    }
		}
	    }
	}
	repaint ();
    }


    public void mouseMoved (MouseEvent e)
    {
    }


    public void mouseExited (MouseEvent e)
    {
    }


    public void mouseEntered (MouseEvent e)
    {
    }


    public void mouseClicked (MouseEvent e)
    {
    }
}

