/*

	Well, this is kinda complicated, but beautifully simple. The main algorithm is a measley thirty or so lines, a good deal of which is curly braces. The rest is just support crap to make it convenient to work with, while still maintaining modularity (I've made it an applet, and was able to leave almost all the code unchanged.). All the functions are pretty well explained ( I hope.. ).
	There are really only three variables in this guy: initial conditions, edge conditions, and line generation rules. I've currently (6/2/02) got the last one fairly dynamic, but it still all relies on a hard-coded _three_ above/adjascent cells to generate a given cell. This idea could just as easily have been done with any number of input cells to generate the given cell, but we're doing three.
	I would like to make the initial conditions user definable somehow. Maybe give some kind of function as input? Should be able somehow specify if we want one lone ON cell in the middle, or some pseudo-random line easily... Thinking... For now, I've got it user set-able between either random or using the old static funtion, which requires a recompile to change... Better but not great.
	I have the edge conditions user set-able, but only as a static always ON or OFF. Maybe I can find a formulaic way to do it?

	I'll pull out all of the old code that is commented out later; I wanna keep it in to show progression through different versions...


// most simple commented lines are code commented out

// ** comments with doublestars are logical explanations

/*
	multi-line comments explain a method
	
	or maybe it's just a block of commented/debug code
*/

/*
<applet code="Cell.class" WIDTH=1550 HEIGHT=1100></applet>
*/

import java.util.Random;
import java.awt.*;
import java.awt.event.*;

public class Cell extends java.applet.Applet implements ActionListener, ItemListener
{
	private static boolean [] lastLine;
	private static int SIZE;
	private static int WIDTH;
	private static int REND_MULT = 5; //how many time more stuff we render than display
	private static boolean [] key = new boolean [8];
	private static int howMany = 1000; // How many lines to print out, in addition to initial
	private static boolean lEdge = false; // simple edge rule
	private static boolean rEdge = false; // simple edge rule
	private static boolean randStart = false; //else, patterned start
	private static int starts = 1; //How many points appear on first line, evenly spaced
	private static int pattern = 1; //pattern in binary to output as initConds
	private static int patPeriod = -1; //Period of pattern repitition, -1 = full width, one centered repetition

	//Variables for XPM output
	private static char on = '@';
	private static char off = ' ';
	private static boolean xpm = false;

	//More variable declarations at the applet code later on

// ** Command-line specific program code

/*
	main control... gets the wax going...
*/
	public static void main ( String [] args )
	{
		SIZE = 1500; //this is just the display size, we render out 5 times as much
		WIDTH = SIZE*REND_MULT; //render width

		parseArgs ( args );

		initConds ();

		//XPM
		if ( xpm )
		{
			off = '.';			

			System.out.println ( "/* XPM */" );
			System.out.println ( "static char *cellout[] = {" );
			System.out.println ( "/* width height num_colors chars_per_pixel */" );
			System.out.println ( "\"\t" + (WIDTH/REND_MULT) + "\t" + (howMany + 1) + "\t2\t1\"," );
			System.out.println ( "\". c #000\"," );
			System.out.println ( "\"@ c #fff\"," );
			System.out.println ( "/* pixels */" );
		}

		printout ( lastLine, 255 );

	// ** Here's where we ask for the magic, we generate and printout as many lines as this loop will run
		for ( int j = 0; j < howMany; j++ )
		{
			genLine();
			printout ( lastLine, howMany - j );
		}

		//XPM
		if ( xpm )
		{
			System.out.println ( "};" );
		}
	}

/*
	a simple function to take a line as we've defined it and print it semi-reasonably
		only useful in a massively scalable text box,
			like xterm for X11, which can display chars as pixels, almost
*/
	private static void printout ( boolean [] line, int diff )
	{
		// ** Added some trickiness.. only wanna print the middle fifth

		int renderWidth = (line.length)/REND_MULT; // the width of what we render
			// Could just look at size, but this is likely safer

		//XPM
		if ( xpm ) {
			System.out.print ( "\"" ); }

		for ( int i = renderWidth*2; i <= renderWidth*3; i++ ) // Only want to print the middle part
		{
			if ( !line[i] ) //if ( line[i] == false )
				System.out.print ( off );
			else
				System.out.print ( on );
		}
		//XPM
		if ( xpm ) {
			if ( diff > 1 )
			{
				System.out.print ("\"," );
			}
			else
			{
				System.out.print ("\"" );
			}
		}
		System.out.println ();
	}

/*
	parse the command-line arguments
*/
	private static void parseArgs ( String [] arg )
	{
		if ( arg.length < 1 )
		{
			usage();
			System.exit(1);
		}

		int rule = -42; // Soon to be our inputted argument as an integer; give a dumb default, so no compiler error/warning
		// ** Our input in integer format
		try {
			rule = Integer.parseInt ( arg[0] );
		}
		catch ( NumberFormatException e )
		{
			System.out.println ( "Rule argument must be an integer between O and 255 inclusive, not '" + arg[0] + "'" );
			usage();
			System.exit(1);
		}

		// ** How many lines to generate? We default to 1000 if the input is bad or missing
		try {
			howMany = Integer.parseInt ( arg[1] );
		}
		catch ( NumberFormatException e )
		{
			// Nothing... howMany defaults to 1000
		}
		catch ( ArrayIndexOutOfBoundsException e )
		{
			// Nothing, we're all good, we have a default
		}

		// ** How wide? We dafault to 1500
		try {
			SIZE = Integer.parseInt ( arg[2] );
			WIDTH = SIZE*REND_MULT;
		}
		catch ( NumberFormatException e )
		{
			//Nothing... SIZE defaults to 1500
		}
		catch ( ArrayIndexOutOfBoundsException e )
		{
			//Nothing... same
		}

		if ( 255 < rule || 0 > rule )
		{
			System.out.println ( "Rule argument must be an integer between O and 255 inclusive, not " + rule );
			usage();
			System.exit(1);
		}

		if ( arg.length >= 4 )
		{
			randStart = arg[3].equals("1");
		}
		if ( arg.length >= 5 )
		{
			lEdge = arg[4].equals("1");
		}

		if ( arg.length >= 6 )
		{
			rEdge = arg[5].equals("1");
		}

		if ( arg.length >= 7 )
		{
			xpm = arg[6].equals("-x");
		}

		//Realized I should seperate key[] generation from command line parsing...
		keyGen(rule);
	}

// ** General code, used by all versions of program

/*
	translate the inputted integer into a ruleset represented by key[]
*/
	private static void keyGen ( int in )
	{
		if ( in > 255 || in < 0 )
		{
			//How did this happen? Poor checking beforehand. Barf!
			System.out.println ( "Invalid value passed to keyGen(int): Bailing!" );
			System.exit(1);
		}

		// ** A useful number to help parse the input bitwise
		int div = 128;
		// ** log base 2 of current value of div
		int count = 7;

		// ** Run over the number a few times, pulling out the summed powers of 2 that make it up
		while ( div > 0 )
		{
			if ( ( in - div ) >= 0 ) {
				in -= div;
				key[count] = true;
			}
			else {
				key[count] = false;
			}
			div /= 2;
			count--;
		}

		// ** We end up with an array of booleans, that represent the inputted number in binary
		// **   indexed by power as either a 1 or a 0 in the bool array
		// ** Get it? I think it'll be useful in specifying rulesets..
	}

/*
	initialization functions
		we set the initial conditions here
		how we do that is completely arbitrary and hard-coded right now...
	all we do right now is either do it random, or do it hardcoded, based on a boolean
*/
	private static void initConds ()
	{
		lastLine = new boolean [WIDTH];

		if ( randStart )
			randConds ();
		else
			patConds ();
	}

	private static void randConds ()
	{
		Random rand = new Random();

	// **Construct the initial line, the initial condition
	// **Here we can make the first line as random or as regular as we please
	// **Wanna find a way to make this more dynamic too
		for ( int i = 0; i < lastLine.length; i++ )
		{
			int thisInt = rand.nextInt ( 3 );
			if ( 0 == thisInt )
				lastLine[i] = true;
			else
				lastLine[i] = false;
		}

	}

	private static void patConds () //patterned starting conditions
	{
		for ( int i = 0; i < lastLine.length; i++ )
		{
			lastLine[i] = false;
		}

		int section = SIZE / ( starts + 1 );
		for ( int i = 1; i <= starts; i++ )
		{
			lastLine[SIZE*2 + section*i] = true;
		}

	}

/*
	the magic happens here...
*/
	private static void genLine ()
	{
		boolean [] thisLine = new boolean [WIDTH];
		int total = 0;

// ** This loop looks at each of thre cells above and adjascent to the current one
// ** and figures out what the current one should be based on rules in key[]
		for ( int i = 0; i < thisLine.length; i++ )
		{
			// ** Cell above and to the left
			try {
				total += ( lastLine[i-1] ? 4 : 0 );
			}
			catch ( ArrayIndexOutOfBoundsException e )
			{
				total += ( lEdge ? 4 : 0 );
			}

			// ** Cell directly above
			total += ( lastLine[i] ? 2 : 0 );

			// ** Cell above and to the right
			try {
				total += ( lastLine[i+1] ? 1 : 0 );
			}
			catch ( ArrayIndexOutOfBoundsException e )
			{
				total += ( rEdge ? 1 : 0 );
			}

			// ** key[] contains our rules, each index being 
			// ** whether that total value will indicate a true or false
			// ** As such, its _really_ easy to implement here....

			try {
				thisLine[i] = key[total];
			}
			catch ( ArrayIndexOutOfBoundsException e )
			{
				//Bug catching, this should never happen... but it might...
				System.out.println ();
				System.out.println ( "Index out of bounds: " + total );
				System.exit (1);
			}

			total = 0;
		}
		lastLine = thisLine;
	}

// ** Also a command line method, but I don't wanna move it
	private static void usage ()
	{
		System.out.println ( "usage:\n   java Cell <rule-code> <lines> <width> <RandStart> <Left Edge> <Right Edge>" );

		System.out.println ( "\n   rule-code: integer between 0 and 255 inclusive" );
		System.out.println ( "   lines: # of lines to generate, beyond the initial (optional)" );
			System.out.println ( "      Defaults to 1000" );
		System.out.println ( "   width: # of cells in a line (optional)" );
			System.out.println ( "      Defaults to 1500" );
		System.out.println ( "   RandStart: 1 or 0 indicating whether to start with random initial line (optional)" );
			System.out.println ( "      Defaults to 0 (off)" );
		System.out.println ( "   Left Edge: 1 or 0 indicating state of this edge (optional)" );
			System.out.println ( "      Defaults to 0 (off)" );
		System.out.println ( "   Right Edge: 1 or 0 indicating state of this edge (optional)" );
			System.out.println ( "      Defaults to 0 (off)" );

		System.out.println ( "\nAny optional argument must be preceded by all preceding arguments" );
	}

// ** Applet stuff
	private TextField kInput = new TextField ( "", 3 ); // key[] input
	private TextField sInput = new TextField ( "", 3 ); // int starts input
	private Label keyLabel = new Label ( "KeyCode:" );
	private Label startsLabel = new Label ( "# of Starts:" );
	private Checkbox Randbox = new Checkbox ( "Random Start" );
	//private Checkbox Lbox = new Checkbox ( "Left Edge" );
	//private Checkbox Rbox = new Checkbox ( "Right Edge" );

	private int fromTop = 50; // number of pixels from top to drawing area

/*
	gotta setup our variables before we run; similar to the beginning of main() in the console version
*/
	public void init ()
	{
		// ** Initializing width and height of painting window based on HEIGHT and WIDTH
		// ** Notice they're not the same; the applet itself is bigger
		SIZE = Integer.parseInt ( getParameter ( "WIDTH" )) - 10;
		WIDTH = SIZE*REND_MULT;
		howMany = Integer.parseInt ( getParameter ( "HEIGHT" )) - fromTop;

		setSize ( SIZE + 10 , howMany + fromTop );

		add ( keyLabel );
		add ( kInput );
		add ( Randbox );
		//add ( Lbox );
		//add ( Rbox );
		add ( startsLabel );
		add ( sInput );

		kInput.addActionListener ( this );
		sInput.addActionListener ( this );

		Randbox.addItemListener ( this );
		//Lbox.addItemListener ( this );
		//Rbox.addItemListener ( this );
	}

/*
	does the same as the latter half of main()
*/
	public void paint ( Graphics page )
	{
		page.setColor ( new Color(0,0,0) );

		initConds ();

		drawLine ( lastLine , 0, page);

		for ( int i = 0; i < howMany; i++ )
		{
			genLine ();
			drawLine ( lastLine, i+1, page);
		}
	}

/*
	the one that draws a line in the applet
*/
	private void drawLine ( boolean [] line, int off, Graphics page)
	{
		// ** Only wanna draw that middle fifth again
		int drawWidth = (line.length)/REND_MULT;

		for ( int i = drawWidth*2; i < drawWidth*3; i++ )
		{
			// ** 5 is the offset from the left
			// ** fromTop is the offset from the top of the applet to the top of the cells
			// ** off is the current line number
			// ** So, we're drawing lines that are one pixel high and wide
			// ** by using the same points for each end of the "line" (effectively "point")
			if ( line[i] )
				page.drawLine ( 5+(i-drawWidth*2), fromTop+off, 5+(i-drawWidth*2), fromTop+off );
			//else
				// draw nothing and move on
		}
	}

	public String getAppletInfo ()
	{
		return  "Name: Brian Kelly\n"
				+ "Topic: Assignment 7\n"
				+ "About: iterative cell generation\n"
				+ "\nIt makes pretty patterns...";
	}

//ActionListener function
	public void actionPerformed ( ActionEvent e )
	{
		if ( e.getSource() == kInput )
		{
			int ev = 0;
			try {
				ev = Integer.parseInt (e.getActionCommand ());
			}
			catch ( NumberFormatException x )
			{
				//Do nothing, we've got a valid default
			}
		
			if ( ev < 256 && ev > -1 )
			{
				keyGen ( ev );
				repaint ();
			}
			else {
				System.out.println ( "Bad key input '" + ev + "'" );	
			}
		}
		else if ( e.getSource() == sInput )
		{
			int ev = 1;
			try {
				ev = Integer.parseInt ( e.getActionCommand ());
			}
			catch ( NumberFormatException x )
			{
				//Do nothing, we've got a valid default
			}

			if ( ev >= 0 )
			{
				starts = ev;
				repaint();
			}
			else
			{
				System.out.println ( "Bad key input '" + ev + "'" );
			}
		}
	}

//ItemListener function
	public void itemStateChanged ( ItemEvent e )
	{
		if ( e.getItem().equals ( "Random Start" ) )
		{
			if ( e.getStateChange() == ItemEvent.SELECTED )
			{
				randStart = true;
			}
			else
			{
				randStart = false;
			}
		}
		else if ( e.getItem().equals ( "Left Edge" ) )
		{
			if ( e.getStateChange() == ItemEvent.SELECTED )
			{
				lEdge = true;
			}
			else
			{
				lEdge = false;
			}
		}
		else if ( e.getItem().equals ( "Right Edge" ) )
		{
			if ( e.getStateChange() == ItemEvent.SELECTED )
			{
				rEdge = true;
			}
			else
			{
				rEdge = false;
			}
		}
		else
		{
			// Indicated we've got an item that's not properly set up
			System.out.println ( "Blech.." );
		}
	}

}
