
import java.io.*;
import java.util.*;


/**
 * This class generates a Mandelbrot fractal and outputs to an XPM.
 * The fractal is an escape map. Each pixel in the map array corresponds
 * with a coordinate (on the complex plane). 
 *
 * <pre>
 * Copyright (c) 1997 Jon Lin <jonlin@tesuji.org>
 *
 * 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.
 * </pre>
 *
 * @author jonlin@tesuji.org
 */
public class MandelbrotGenerator
{
	// Cache for the mapping of colors to letters
	private static String[] letters;

	// constants for sweeping variations
	public static final int LINEAR_SWEEP = 0;
	public static final int LOG_SWEEP = 1;
	public static final int EXP_SWEEP = 2;

	// constants for bailout test variations
	public static final int ZMAG_TEST = 0;
	public static final int REAL_TEST = 1;
	public static final int IMAG_TEST = 2;
	public static final int ABS_TEST = 3;
	

	// flags for various sweeping
	boolean sweep_X_coef = false;
	boolean sweep_Y_coef = false;
	boolean sweep_X_shift = false;
	boolean sweep_Y_shift = false;
	boolean sweep_X_perturb = false;
	boolean sweep_Y_perturb = false;
	boolean zoom = false;
	boolean random_zoom = false;

	// vars used for coef sweeping.
	int coef_sweep = 0;
	double x_coef_sweep_value = 0;
	double y_coef_sweep_value = 0;
	int x_coef_sweep_period = 1;
	int y_coef_sweep_period = 1;

	// vars used for shift sweeping.
	int shift_sweep = 0;
	double x_shift_sweep_value = 0;
	double y_shift_sweep_value = 0;
	int x_shift_sweep_period = 1;
	int y_shift_sweep_period = 1;

	// vars used for perturn sweeping.
	int perturb_sweep = 0;
	double x_perturb_sweep_value = 0;
	double y_perturb_sweep_value = 0;
	int x_perturb_sweep_period = 1;
	int y_perturb_sweep_period = 1;

	// vars used for zooming.
	int start_x_zoom_value = 0;
	int start_y_zoom_value = 0;
	int start_x_zoom_period = 1;
	int start_y_zoom_period = 1;
	int end_x_zoom_value = 0;
	int end_y_zoom_value = 0;
	int end_x_zoom_period = 1;
	int end_y_zoom_period = 1;
	int x_zoom_shift = 0;
	int y_zoom_shift = 0;
	int x_zoom_shift_period = 1;
	int y_zoom_shift_period = 1;
	int x_zoom_limit = -1;
	int y_zoom_limit = -1;
	double x_zoom_limit_point = -1;
	double y_zoom_limit_point = -1;
	double x_zoom_limit_error = -1;
	double y_zoom_limit_error = -1;
	
	// vars used for generating.
	double start_x   = -2;
	double end_x     = 2;
	double start_y   = -2;
	double end_y     = 2;
	double x_coef    = 0;
	double y_coef    = 0;
	double x_perturb = 0;
	double y_perturb = 0;
	double x_shift   = 30;
	double y_shift   = 0;

	double bailout = 16;
	static int bailout_test = 0;
	int max_iter   = 150;
	int width      = 640;
	int height     = 480;

	int num_frames = 1;
	int frame_offset = 0;
	int frame_count = 0;

	int num_colors;
	String[] colorMap = null;
	String colorMapFile = null;
	String outputPrefix = "mandel";

	// random number generator for Random zooming.
	Random rand;

	public MandelbrotGenerator(String[] args)
	{
		// First thing, parse the arguments.
		parseArgs(args);

		rand = new Random();

		// Make the color map to use.
		colorMap = makeColorMap(colorMapFile,num_colors);

		// This is the array which stores all the escape values.
		int[][] map = new int[width][height];


		// This loop continually generates and changes the parameters
		for(int x=0;x<num_frames;x++) {
			frame_count = x+frame_offset;
			print("Generating map: ("+start_x+","+start_y+") ("+end_x+","+end_y+")");
			// generate using the current parameters
			generate(map,width,height,start_x,end_x,start_y,end_y,x_coef,y_coef,
						x_shift,y_shift,x_perturb,y_perturb,bailout,max_iter);
			String status = printStatus();
			System.out.println(status);
			// print results
			printXPM(map,x+frame_offset,status);

			// Change parameters for zoom
			if(random_zoom) {
				if((x_zoom_limit>-1) && (y_zoom_limit>-1)) {
					zoomRandom();
					zoomLimit();
				}
				else {
					zoomRandom();
				}
			}
			else if(zoom) {
				if((x_zoom_limit>-1) && (y_zoom_limit>-1)) {
					zoomLinear();
					zoomLimit();
				}
				else {
					zoomLinear();
					zoomShift();
				}
			}
			
			// Change parameters for coef sweep
			if(coef_sweep == LINEAR_SWEEP) {
				sweepCoefLinear();
			}
			else if(coef_sweep == LOG_SWEEP) {
				sweepCoefLogrithmic();
			}
			else if(coef_sweep == EXP_SWEEP) {
				sweepCoefExponential();
			}

			// Change parameters for shift sweep
			if(shift_sweep == LINEAR_SWEEP) {
				sweepShiftLinear();
			}
			else if(shift_sweep == LOG_SWEEP) {
				sweepShiftLogrithmic();
			}
			else if(shift_sweep == EXP_SWEEP) {
				sweepShiftExponential();
			}

			// Change parameters for perturb sweep
			if(perturb_sweep == LINEAR_SWEEP) {
				sweepPerturbLinear();
			}
			else if(perturb_sweep == LOG_SWEEP) {
				sweepPerturbLogrithmic();
			}
			else if(perturb_sweep == EXP_SWEEP) {
				sweepPerturbExponential();
			}
		}

		System.exit(1);
	}



	/**
	 * shrinks the bounding box (start_x, start_y),(end_x, end_y)
	 * according to the zooming parameters.
	 */
	public void zoomLinear()
	{
		double x_unit = (end_x - start_x)/width;
		double y_unit = (end_y - start_y)/height;

		if((frame_count % start_x_zoom_period)==0) {
			start_x = start_x + (x_unit * start_x_zoom_value);
		}
		if((frame_count % end_x_zoom_period)==0) {
			end_x = end_x - (x_unit * end_x_zoom_value);
		}

		if((frame_count % start_y_zoom_period)==0) {
			start_y = start_y + (y_unit * start_y_zoom_value);
		}
		if((frame_count % end_y_zoom_period)==0) {
			end_y = end_y - (y_unit * end_y_zoom_value);
		}
	}

	/**
	 * shifts the bounding box (start_x, start_y),(end_x, end_y)
	 * accoring to the shifting parameters.
	 */
	public void zoomShift()
	{
		double x_unit = (end_x - start_x)/width;
		double y_unit = (end_y - start_y)/height;

		if((frame_count % x_zoom_shift_period)==0) {
			start_x = start_x + (x_unit * x_zoom_shift);
			end_x = end_x + (x_unit * x_zoom_shift);
		}
		
		if((frame_count % y_zoom_shift_period)==0) {
			start_y = start_y + (y_unit * y_zoom_shift);
			end_y = end_y + (y_unit * y_zoom_shift);
		}
	}

	/**
	 * shifts the bounding box (start_x, start_y),(end_x, end_y)
	 * according to the zoom limit. The center of the bounding box
	 * approaches the zoom limit parameters.
	 */
	public void zoomLimit()
	{
		// First find the units per pixel
		double x_unit = (end_x - start_x)/width;
		double y_unit = (end_y - start_y)/height;
		// Then the center coordinates of the current map
		double x_center = (x_unit*width)/2 + start_x;
		double y_center = (y_unit*height)/2 + start_y;
		// Then the lengths from the center to the zoom limit
		double x_length = Math.abs(x_zoom_limit_point - x_center);
		double y_length = Math.abs(y_zoom_limit_point - y_center);

		// If the length is smaller than the limit error, bail.
		if((x_length < x_zoom_limit_error) && (y_length < y_zoom_limit_error)) {
			print("Reached zoom limit! Exiting.");
			System.exit(0);
		}

		// These are the x,y lengths to shift.
		double x_shift = 20*x_unit;
		double y_shift = 20*y_unit;

		// If the zoom shift parameters are used, reset the lengths to the parameters
		if(x_zoom_shift>0) {
			x_shift = Math.abs(x_zoom_shift)*x_unit;
		}
		if(y_zoom_shift>0) {
			y_shift = Math.abs(y_zoom_shift)*y_unit;
		}

		// If the shift exceeds the distance to the limit point, just shift by the distance
		if(x_shift > x_length) {
			x_shift = x_length;
		}
		if(y_shift > y_length) {
			y_shift = y_length;
		}

		// scale the shift according to the incident angle of the limit point
		if(x_length>=y_length) {
			y_shift = (y_length*x_shift)/x_length;
		}
		else {
			x_shift = (x_length*y_shift)/y_length;
		}

		// Shift the bounding box
		if(x_center > x_zoom_limit_point) {
			start_x = start_x - (x_shift);
			end_x = end_x - (x_shift);
		}
		else {
			start_x = start_x + (x_shift);
			end_x = end_x + (x_shift);
		}
		if(y_center > y_zoom_limit_point) {
			start_y = start_y - (y_shift);
			end_y = end_y - (y_shift);
		}
		else {
			start_y = start_y + (y_shift);
			end_y = end_y + (y_shift);
		}

	}

	/** 
	 * shrinks and shifts the bounding box (start_x, start_y),(end_x, end_y)
	 * according to the bounds set by the zoom parameters.
	 */
	public void zoomRandom()
	{
		double x_unit = (end_x - start_x)/width;
		double y_unit = (end_y - start_y)/height;

		int tmp_start_x_zoom = Math.abs(rand.nextInt()%start_x_zoom_value);
		int tmp_end_x_zoom = Math.abs(rand.nextInt()%end_x_zoom_value);
		int tmp_start_y_zoom = Math.abs(rand.nextInt()%start_y_zoom_value);
		int tmp_end_y_zoom = Math.abs(rand.nextInt()%end_y_zoom_value);

		if((frame_count % start_x_zoom_period)==0) {
			start_x = start_x + (x_unit * tmp_start_x_zoom);
		}
		if((frame_count % end_x_zoom_period)==0) {
			end_x = end_x - (x_unit * tmp_end_x_zoom);
		}

		if((frame_count % start_y_zoom_period)==0) {
			start_y = start_y + (y_unit * tmp_start_y_zoom);
		}
		if((frame_count % end_y_zoom_period)==0) {
			end_y = end_y - (y_unit * tmp_end_y_zoom);
		}


		if((x_zoom_limit==-1) && (y_zoom_limit==-1)) {
			x_unit = (end_x - start_x)/width;
			y_unit = (end_y - start_y)/height;

			int tmp_x_shift = Math.abs(rand.nextInt()%x_zoom_shift);
			int tmp_y_shift = Math.abs(rand.nextInt()%y_zoom_shift);

			if((frame_count % x_zoom_shift_period)==0) {
				start_x = start_x + (x_unit * tmp_x_shift);
				end_x = end_x + (x_unit * tmp_x_shift);
			}
			
			if((frame_count % y_zoom_shift_period)==0) {
				start_y = start_y + (y_unit * tmp_y_shift);
				end_y = end_y + (y_unit * tmp_y_shift);
			}
		}
	}


	

	/**
	 * Sweeps the coef vector linearly according to the coef sweep parameters.
	 */
	public void sweepCoefLinear()
	{
		if(sweep_X_coef && ((frame_count % x_coef_sweep_period)==0)) {
			x_coef = x_coef + x_coef_sweep_value;
		}
		if(sweep_Y_coef && ((frame_count % y_coef_sweep_period)==0)) {
			y_coef = y_coef + y_coef_sweep_value;
		}
	}

	/**
	 * Sweeps the coef vector logrithmicly according to the coef sweep parameters.
	 */
	public void sweepCoefLogrithmic()
	{
		if(sweep_X_coef && ((frame_count % x_coef_sweep_period)==0)) {
			x_coef = (x_coef + x_coef_sweep_value) / 2;
		}
		if(sweep_Y_coef && ((frame_count % y_coef_sweep_period)==0)) {
			y_coef = (y_coef + y_coef_sweep_value) / 2;
		}
	}

	/**
	 * Sweeps the coef vector exponentially according to the coef sweep parameters.
	 */
	public void sweepCoefExponential()
	{
		if(sweep_X_coef && ((frame_count % x_coef_sweep_period)==0)) {
			x_coef = (2 * x_coef) - x_coef_sweep_value;
		}
		if(sweep_Y_coef && ((frame_count % y_coef_sweep_period)==0)) {
			y_coef = (2 * y_coef) - y_coef_sweep_value;
		}
	}



	/**
	 * Sweeps the shift vector linearly according to the shift sweep parameters.
	 */
	public void sweepShiftLinear()
	{
		if(sweep_X_shift && ((frame_count % x_shift_sweep_period)==0)) {
			x_shift = x_shift + x_shift_sweep_value;
		}
		if(sweep_Y_shift && ((frame_count % y_shift_sweep_period)==0)) {
			y_shift = y_shift + y_shift_sweep_value;
		}
	}

	/**
	 * Sweeps the shift vector logrithmicly according to the shift sweep parameters.
	 */
	public void sweepShiftLogrithmic()
	{
		if(sweep_X_shift && ((frame_count % x_shift_sweep_period)==0)) {
			x_shift = (x_shift + x_shift_sweep_value) / 2;
		}
		if(sweep_Y_shift && ((frame_count % y_shift_sweep_period)==0)) {
			y_shift = (y_shift + y_shift_sweep_value) / 2;
		}
	}

	/**
	 * Sweeps the shift vector exponentially according to the shift sweep parameters.
	 */
	public void sweepShiftExponential()
	{
		if(sweep_X_shift && ((frame_count % x_shift_sweep_period)==0)) {
			x_shift = (2 * x_shift) - x_shift_sweep_value;
		}
		if(sweep_Y_shift && ((frame_count % y_shift_sweep_period)==0)) {
			y_shift = (2 * y_shift) - y_shift_sweep_value;
		}
	}



	/**
	 * Sweeps the perturb vector linearly according to the perturb sweep parameters.
	 */
	public void sweepPerturbLinear()
	{
		if(sweep_X_perturb && ((frame_count % x_perturb_sweep_period)==0)) {
			x_perturb = x_perturb + x_perturb_sweep_value;
		}
		if(sweep_Y_perturb && ((frame_count % y_perturb_sweep_period)==0)) {
			y_perturb = y_perturb + y_perturb_sweep_value;
		}
	}

	/**
	 * Sweeps the perturb vector logrithmically according to the perturb sweep parameters.
	 */
	public void sweepPerturbLogrithmic()
	{
		if(sweep_X_perturb && ((frame_count % x_perturb_sweep_period)==0)) {
			x_perturb = (x_perturb + x_perturb_sweep_value) / 2;
		}
		if(sweep_Y_perturb && ((frame_count % y_perturb_sweep_period)==0)) {
			y_perturb = (y_perturb + y_perturb_sweep_value) / 2;
		}
	}

	/**
	 * Sweeps the perturb vector exponentially according to the perturb sweep parameters.
	 */
	public void sweepPerturbExponential()
	{
		if(sweep_X_perturb && ((frame_count % x_perturb_sweep_period)==0)) {
			x_perturb = (2 * x_perturb) - x_perturb_sweep_value;
		}
		if(sweep_Y_perturb && ((frame_count % y_perturb_sweep_period)==0)) {
			y_perturb = (2 * y_perturb) - y_perturb_sweep_value;
		}
	}





	/**
	 * This method parses the arguments passed to the MandelbrotGenerator.
	 * These arguments determine what this class will do.
	 */
	public void parseArgs(String[] args)
	{
		try {
			// First, read in the required parameters
			width = Integer.parseInt(args[0]);
			height = Integer.parseInt(args[1]);
			colorMapFile = args[2];
			num_colors = Integer.parseInt(args[3]);

			// if there are more arguments (the options), parse them
			if(args.length>4) {
				for(int x=4;x<args.length;x++) {

					// These are the initial bounding box coordinates
					if(args[x].equalsIgnoreCase("-start_x")) 
						start_x = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-start_y")) 
						start_y = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-end_x")) 
						end_x = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-end_y")) 
						end_y = Double.valueOf(args[++x]).doubleValue();

					// These are the initial coef, shift, and perturb values
					else if(args[x].equalsIgnoreCase("-x_coef")) 
						x_coef = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_coef")) 
						y_coef = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-x_shift")) 
						x_shift = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_shift")) 
						y_shift = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-x_perturb")) 
						x_perturb = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_perturb")) 
						y_perturb = Double.valueOf(args[++x]).doubleValue();

					// These are the iteration parameters
					else if(args[x].equalsIgnoreCase("-bailout")) 
						bailout = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-max_iter")) 
						max_iter = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-bailout_test")) 
						bailout_test = Integer.parseInt(args[++x]);

					// These are the zoom parameters
					else if(args[x].equalsIgnoreCase("-zoom")) 
						zoom = true;
					else if(args[x].equalsIgnoreCase("-random_zoom")) 
						random_zoom = true;
					else if(args[x].equalsIgnoreCase("-start_x_zoom_value")) 
						start_x_zoom_value = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-start_y_zoom_value")) 
						start_y_zoom_value = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-end_x_zoom_value")) 
						end_x_zoom_value = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-end_y_zoom_value")) 
						end_y_zoom_value = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-x_zoom_shift")) 
						x_zoom_shift = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-y_zoom_shift")) 
						y_zoom_shift = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-start_x_zoom_period")) 
						start_x_zoom_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-start_y_zoom_period")) 
						start_y_zoom_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-end_x_zoom_period")) 
						end_x_zoom_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-end_y_zoom_period")) 
						end_y_zoom_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-x_zoom_shift_period")) 
						x_zoom_shift_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-y_zoom_shift_period")) 
						y_zoom_shift_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-zoom_limit")) {
						x_zoom_limit = Integer.parseInt(args[++x]);
						y_zoom_limit = Integer.parseInt(args[++x]);
					}

					// These are the coef sweep parameters
					else if(args[x].equalsIgnoreCase("-sweep_x_coef")) 
						sweep_X_coef = true;
					else if(args[x].equalsIgnoreCase("-sweep_y_coef")) 
						sweep_Y_coef = true;
					else if(args[x].equalsIgnoreCase("-x_coef_sweep_value")) 
						x_coef_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_coef_sweep_value")) 
						y_coef_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-coef_sweep_type")) 
						coef_sweep = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-x_coef_sweep_period")) 
						x_coef_sweep_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-y_coef_sweep_period")) 
						y_coef_sweep_period = Integer.parseInt(args[++x]);

					// These are the shift sweep parameters
					else if(args[x].equalsIgnoreCase("-sweep_x_shift")) 
						sweep_X_shift = true;
					else if(args[x].equalsIgnoreCase("-sweep_y_shift")) 
						sweep_Y_shift = true;
					else if(args[x].equalsIgnoreCase("-x_shift_sweep_value")) 
						x_shift_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_shift_sweep_value")) 
						y_shift_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-shift_sweep_type")) 
						shift_sweep = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-x_shift_sweep_period")) 
						x_shift_sweep_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-y_shift_sweep_period")) 
						y_shift_sweep_period = Integer.parseInt(args[++x]);

					// These are the perturb sweep parameters
					else if(args[x].equalsIgnoreCase("-sweep_x_perturb")) 
						sweep_X_perturb = true;
					else if(args[x].equalsIgnoreCase("-sweep_y_perturb")) 
						sweep_Y_perturb = true;
					else if(args[x].equalsIgnoreCase("-x_perturb_sweep_value")) 
						x_perturb_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-y_perturb_sweep_value")) 
						y_perturb_sweep_value = Double.valueOf(args[++x]).doubleValue();
					else if(args[x].equalsIgnoreCase("-perturb_sweep_type")) 
						perturb_sweep = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-x_perturb_sweep_period")) 
						x_perturb_sweep_period = Integer.parseInt(args[++x]);
					else if(args[x].equalsIgnoreCase("-y_perturb_sweep_period")) 
						y_perturb_sweep_period = Integer.parseInt(args[++x]);

					// The number of frames to generate
					else if(args[x].equalsIgnoreCase("-num_frames")) 
						num_frames = Integer.parseInt(args[++x]);
					// The filename prefix of the output file(s)
					else if(args[x].equalsIgnoreCase("-outputPrefix")) 
						outputPrefix = args[++x];
					// The offset to start output file numbering
					else if(args[x].equalsIgnoreCase("-frame_offset")) 
						frame_offset = Integer.parseInt(args[++x]);

					// The help argument
					else if(args[x].equalsIgnoreCase("-help")) {
						if(args[++x].startsWith("-")) {
							throw(new Exception());
						}
						else {
							if(args[x].equalsIgnoreCase("bounding_box")) {
								print("Bounding box:");
								print("  The '-start_x', '-start_y', '-end_x', and '-end_y' options set the");
								print("  bounding box of the fractal to calculate. These are coordinates of");
								print("  the lower and upper vertices of the box. ");
								print(" ");
							}
							else if(args[x].equalsIgnoreCase("fractal_params")) {
								print("Fractal parameters:");
								print("  -x_coef, -y_coef        These are the x and y coefficient parameters.");
								print("  -x_shift, -y_shift      These are the x and y function shift parameters.");
								print("  -x_perturb, -y_perturb  These are the x and y perturbation parameters.");
								print(" ");
							}
							else if(args[x].equalsIgnoreCase("iter_params")) {
								print("Iteration parameters:");
								print("  -bailout      This is the value used to determine where 'infinity' starts.");
								print("                A coordinate is at 'infinity' (it escaped) if the distance from");
								print("                the origin squared is greater than this value.");
								print("  -bailout_test This is the test to use to determine whether a coordinate ");
								print("                has reached infinity. The valid arguments are 0,1,2 and 3.");
								print("                0 = magnitude of coordinate, 1 = x^2, 2 = y^2, 3 = |x|+|y|");
								print("                Default bailout test is 0.");
								print("  -max_iter     This is the maximum number of times to iterate the mandelbrot");
								print("                function before the coordinate is assumed to be part of the");
								print("                attractor (it never escapes).");
								print(" ");
							}
							else if(args[x].equalsIgnoreCase("zooming")) {
								print("Zooming:");
								print("  -zoom         This option turns on zooming, enabling the other zoom options.");
								print("  -random_zoom  This option is the same as -zoom except the zoom and shift");
								print("                are randomly determined.");
								print(" ");
								print("  Zooming consist of two processes, the shrinking of the bounding box and the");
								print("  shifting of the bounding box. Both the shrink and the shift parameters are");
								print("  given as pixels. The shrink parameters are as follows:");
								print(" ");
								print("  -start_x_zoom_value  The number of pixels the 'start_x' of the bounding box");
								print("                       will shrink.");
								print("  -start_y_zoom_value  The number of pixels the 'start_y' of the bounding box");
								print("                       will shrink.");
								print("  -end_x_zoom_value    The number of pixels the 'end_x' of the bounding box");
								print("                       will shrink.");
								print("  -end_y_zoom_value    The number of pixels the 'end_y' of the bounding box");
								print("                       will shrink.");
								print("");
								print("  If random zooming is turned on, these values will be the upper bound of a ");
								print("  random shrink on each of the four bounding box values. The shift parameters");
								print("  are as follows:");
								print(" ");
								print("  -x_zoom_shift  The number of pixels (+ or -) to shift on the x axis.");
								print("  -y_zoom_shift  The number of pixels (+ or -) to shift on the y axis.");
								print(" ");
								print("  The period parameters regulate how often the shrink or shift occurs. If 150");
								print("  frames are being generated and the period is set to 3, the shift or shrink");
								print("  will occur at every 3 frames. These options set the period at which each");
								print("  shrink or shift occurs:");
								print(" ");
								print("  -start_x_zoom_period");
								print("  -start_y_zoom_period");
								print("  -end_x_zoom_period");
								print("  -end_y_zoom_period");
								print("  -x_zoom_shift_period");
								print("  -y_zoom_shift_period");
								print("     ");
								print("  The -zoom_limit option takes two parameters, a X pixel and a Y pixel. If ");
								print("  this option is used, the coordinate represented by the x,y pixel ");
								print("  parameters is used as a zooming limit. The bounding box will be shifted ");
								print("  toward this limit until the granularity has exceeded the original");
								print("  distance between pixels.");
								print(" ");
							}
							else if(args[x].equalsIgnoreCase("sweeping")) {
								print("Sweeping:");
								print("  There are three parameters in the mandelbrot function which can be modified.");
								print("  Each has options to increment or decrement the value of the parameter and");
								print("  the period which the parameter is to be modified. They can be modified ");
								print("  linearly, logrithmically, or exponentially.");
								print(" ");
								print("  These options turn on the sweeping of various parameters.");
								print(" ");
								print("  -sweep_x_coef");
								print("  -sweep_y_coef");
								print("  -sweep_x_shift");
								print("  -sweep_y_shift");
								print("  -sweep_x_perturb");
								print("  -sweep_y_perturb");
								print(" ");
							}
							else if(args[x].equalsIgnoreCase("output_params")) {
								print("Output parameters:");
								print("  The frames generated are outputed to a file as an XPM. The output file");
								print("  will be in the form '<filename>#####.xpm'.");
								print(" ");
								print("  -num_frames    The number of frames to generate. Each frame will undergo ");
								print("                 the specified zoom, shift, and sweeps.");
								print("  -outputPrefix  The filename prefix of the output file(s).");
								print("  -frame_offset  The offset to start the output file numbering.");
								print(" ");
							}
							else {
								print("Valid help options are: ");
								print("  bounding_box, fractal_params, iter_params, zooming, sweeping, output_params");
							}
							System.exit(2);
						}
					}
				}
			}
		}
		catch(Exception e) {
			print("Error: "+e);
			e.printStackTrace();
			print("Usage: MandelbrotGenerator [x_size] [y_size] [colorMapFile] [num_colors] <options>");
			print("     -start_x [double]");
			print("     -start_y [double]");
			print("     -end_x [double]");
			print("     -end_y [double]");

			print("     -x_coef [double]");
			print("     -y_coef [double]");
			print("     -x_shift [double]");
			print("     -y_shift [double]");
			print("     -x_perturb [double]");
			print("     -y_perturb [double]");

			print("     -bailout [double]");
			print("     -bailout_test [0|1|2|3] (zmag,real,imag,abs)");
			print("     -max_iter [integer]");
			print("     -num_frames [integer]");
			print("     -outputPrefix [filename]");
			print("     -frame_offset [integer]");

			print("     -coef_sweep_type [0|1|2] (linear,log,exp)");
			print("     -sweep_x_coef  ");
			print("     -sweep_y_coef  ");
			print("     -x_coef_sweep_value [double]");
			print("     -y_coef_sweep_value [double]");
			print("     -x_coef_sweep_period [integer]");
			print("     -y_coef_sweep_period [integer]");

			print("     -shift_sweep_type [0|1|2] (linear,log,exp)");
			print("     -sweep_x_shift  ");
			print("     -sweep_y_shift  ");
			print("     -x_shift_sweep_value [double]");
			print("     -y_shift_sweep_value [double]");
			print("     -x_shift_sweep_period [integer]");
			print("     -y_shift_sweep_period [integer]");

			print("     -perturb_sweep_type [0|1|2] (linear,log,exp)");
			print("     -sweep_x_perturb  ");
			print("     -sweep_y_perturb  ");
			print("     -x_perturb_sweep_value [double]");
			print("     -y_perturb_sweep_value [double]");
			print("     -x_perturb_sweep_period [integer]");
			print("     -y_perturb_sweep_period [integer]");

			print("     -zoom  ");
			print("     -random_zoom");
			print("     -start_x_zoom_value [integer]  (pixels)");
			print("     -start_y_zoom_value [integer]  (pixels)");
			print("     -end_x_zoom_value [integer]  (pixels)");
			print("     -end_y_zoom_value [integer]  (pixels)");
			print("     -x_zoom_shift [integer]  (pixels)");
			print("     -y_zoom_shift [integer]  (pixels)");
			print("     -start_x_zoom_period [integer]");
			print("     -start_y_zoom_period [integer]");
			print("     -end_x_zoom_period [integer]");
			print("     -end_y_zoom_period [integer]");
			print("     -x_zoom_shift_period [integer]");
			print("     -y_zoom_shift_period [integer]");
			print("     -zoom_limit [integer] [integer] (x y) (pixels)");

			print("     -help <bounding_box | fractal_params | iter_params | zooming | sweeping | output_params>");

			System.exit(-1);
		}

		// If zooming to a limit, calculate the initial zoom limit parameters
		if((x_zoom_limit>-1) && (y_zoom_limit>-1)) {
			x_zoom_limit_error = (end_x - start_x)/width;
			y_zoom_limit_error = (end_y - start_y)/height;
			x_zoom_limit_point = ((x_zoom_limit_error)*x_zoom_limit) + start_x;
			y_zoom_limit_point = ((y_zoom_limit_error)*y_zoom_limit) + start_y;
		}

		System.out.println(printStatus());
	}

	/**
	 * This method returns a status String.
	 */
	public String printStatus()
	{
		StringBuffer rval = new StringBuffer();

		rval.append("  Image size: "+width+" x "+height+", color map: "+colorMapFile+" ("+num_colors+")\n");
		if(bailout_test==REAL_TEST) {
			rval.append("  Maximum number of iterations: "+max_iter+", Bailout value: "+bailout+" (x*x)\n");
		}
		else if(bailout_test==IMAG_TEST) {
			rval.append("  Maximum number of iterations: "+max_iter+", Bailout value: "+bailout+" (y*y)\n");
		}
		else if(bailout_test==ABS_TEST) {
			rval.append("  Maximum number of iterations: "+max_iter+", Bailout value: "+bailout+" (|x|+|y|)\n");
		}
		else {
			rval.append("  Maximum number of iterations: "+max_iter+", Bailout value: "+bailout+" ((x*x)+(y*y))\n");
		}
		rval.append("  Bounding box: ("+start_x+","+start_y+") , ("+end_x+","+end_y+")\n");
		rval.append("  Coeficient vector: ["+x_coef+","+y_coef+"]\n");
		rval.append("  Shift vector: ["+x_shift+","+y_shift+"]\n");
		rval.append("  Perturb vector: ["+x_perturb+","+y_perturb+"]\n");
		
		if(random_zoom) {
			rval.append("  Random zooming and shifting (bounded by given values)\n");
		}
		else if(zoom) {
			rval.append("  Zooming and shifting\n");
		}
		if(random_zoom||zoom) {
			rval.append("   Bounding box shrink (pixels): +("+start_x_zoom_value+","+start_y_zoom_value+") , -("+end_x_zoom_value+","+end_y_zoom_value+") \n");
			rval.append("   Bounding box shift (pixels): ("+x_zoom_shift+","+y_zoom_shift+")\n");
			if((x_zoom_limit>-1) && (y_zoom_limit>-1)) {
				rval.append("   Zoom approaches the point ("+x_zoom_limit_point+","+y_zoom_limit_point+") +/- ("+x_zoom_limit_error+","+y_zoom_limit_error+")\n");
			}
		}

		if(sweep_X_coef || sweep_Y_coef) {
			rval.append("  Sweeping coeficient vector ");
			if(coef_sweep==LINEAR_SWEEP) {
				rval.append("linearly ");
			}
			else if(coef_sweep==LOG_SWEEP) {
				rval.append("logrithmically ");
			}
			else if(coef_sweep==EXP_SWEEP) {
				rval.append("exponentially ");
			}
			rval.append("by ("+x_coef_sweep_value+","+y_coef_sweep_value+")\n");
		}

		if(sweep_X_shift || sweep_Y_shift) {
			rval.append("  Sweeping shift vector ");
			if(shift_sweep==LINEAR_SWEEP) {
				rval.append("linearly ");
			}
			else if(shift_sweep==LOG_SWEEP) {
				rval.append("logrithmically ");
			}
			else if(shift_sweep==EXP_SWEEP) {
				rval.append("exponentially ");
			}
			rval.append("by ("+x_shift_sweep_value+","+y_shift_sweep_value+")\n");
		}

		if(sweep_X_perturb || sweep_Y_perturb) {
			rval.append("  Sweeping perturb vector ");
			if(perturb_sweep==LINEAR_SWEEP) {
				rval.append("linearly ");
			}
			else if(perturb_sweep==LOG_SWEEP) {
				rval.append("logrithmically ");
			}
			else if(perturb_sweep==EXP_SWEEP) {
				rval.append("exponentially ");
			}
			rval.append("by ("+x_perturb_sweep_value+","+y_perturb_sweep_value+")\n");
		}

		return(rval.toString());
	}

	/**
	 * This method prints the XPM file.
	 */
	public void printXPM(int[][] map, int frame, String status)
	{
		String filename = outputPrefix;
		String number = ""+frame;
		while(number.length()<5) {
			number = "0"+number;
		}
		filename = filename+number+".xpm";

		String XPM = generateXPM(filename,map,width,height,colorMap,status);
		
		print("Writing XPM: "+filename);

		try {
			File xpmFile = new File(filename);
			BufferedWriter writer = new BufferedWriter(new FileWriter(xpmFile));
			writer.write(XPM);
			writer.flush();
			writer.close();
		}
		catch(Exception e) {
			print("Error writing XPM: "+e);
			e.printStackTrace();
		}
	}





	/** 
	 * Generates an escape map using the mandelbrot attractor.
	 * The escape values are stored the map array.
	 * @param map This is where the results are stored.
	 * @param width The number of pixels on the X axis.
	 * @param height The number of pixels on the Y axis.
	 * @param start_x The starting X coordinate.
	 * @param end_x The ending X coordinate.
	 * @param start_y The starting Y coordinate.
	 * @param end_y The ending Y coordinate.
	 * @param x_coef The coeficient added to the X value.
	 * @param y_coef The coeficient added to the Y value.
	 * @param x_perturb The initial X value.
	 * @param y_perturb The initial Y value.
	 * @param bailout_value This is the square of the magnitude of the complex number denoting infinity.
	 * @param max_iter This is the maximum number of iterations.
	 */
	public static void generate(int[][] map, 
										 int width, int height,
										 double start_x, double end_x,
										 double start_y, double end_y,
										 double x_coef, double y_coef,
										 double x_shift, double y_shift,
										 double x_perturb, double y_perturb,
										 double bailout_value,
										 int max_iter)
	{
		double k=0;
		double l=0;

		double x=0;
		double y=0;
		double tx=0;
		double ty=0;

		double tmp_k = ((end_x-start_x) + x_coef) / width;
		double tmp_l = ((end_y-start_y) + y_coef) / height;

		double last_x_sqr = 0;
		double last_y_sqr = 0;

		// f(z) = z*z : f(x+(i)y) = ((x*x)-(y*y)-k) + (i)((2*x*y)-l)
		for(int q=0;q<height;q++) {
			for(int p=0;p<width;p++) {
				k = start_x + (tmp_k * (p + x_shift));
				l = start_y + (tmp_l * (q + y_shift));

				x = x_perturb;
				y = y_perturb;

				// Do first iteration so last squares are cached
				tx = (x*x) - (y*y) - k;
				ty = (2*x*y) - l;

				last_x_sqr = (tx*tx);
				last_y_sqr = (ty*ty);

				if((last_x_sqr + last_y_sqr) > bailout_value) {
					map[p][q] = 1;
				}
				else {
					x = tx;
					y = ty;

					boolean bailed = false;

					for(int iteration=2; iteration <= max_iter; iteration++) {
						// calculate f(x+(i)y)
						tx = last_x_sqr - last_y_sqr - k;
						ty = (2 * x * y) - l;
						
						// calculate the new squares for x and y, these are cached for the next iteration
						last_x_sqr = tx*tx;
						last_y_sqr = ty*ty;
						
						if(((bailout_test==ZMAG_TEST) && ((last_x_sqr + last_y_sqr) > bailout_value)) ||
							((bailout_test==REAL_TEST) && (last_x_sqr > bailout_value)) ||
							((bailout_test==IMAG_TEST) && (last_y_sqr > bailout_value)) ||
							((bailout_test==ABS_TEST) && ((Math.abs(tx) + Math.abs(ty)) > bailout_value))) {
							map[p][q] = iteration;
							iteration = max_iter;
							bailed = true;
						}
						else {
							x = tx;
							y = ty;
						}
					}
					
					if(!bailed) {
						map[p][q] = 0;
					}
				}
			}
		}
	}


	public static void main(String[] args)
	{
		new MandelbrotGenerator(args);
	}

	public static void print(String s)
	{
		System.out.println("Mandelbrot: "+s);
	}








	/**
	 * This method generates a XPM from a 2 dimensional array. The XPM is returned as a String.
	 * @param xpmName The name of this XPM.
	 * @param image The array of pixels representing the image. The orientation of the image is assumed to be image[x][y].
	 * @param width The number of columns in the image map (x size).
	 * @param height The number of rows in the image map (y size).
	 * @param colorMap An array of Hex color values. (i.e. #FF00AA).
	 * @param comment The comment to include in the XPM. If null, no comment is included.
	 * @return Returns the XPM of this image as a String.
	 */
	public static String generateXPM(String xpmName, int[][] image, int width, int height, String[] colorMap, String comment)
	{
		StringBuffer rval = new StringBuffer();
		rval.append(generateHeader(xpmName, comment));
		String[] colorMapping = makeColorMapping(colorMap);
		rval.append(generateColorMapping(width, height, colorMap, colorMapping));

		rval.append("/* pixels */\n");
		for(int y=0;y<height;y++) {
			rval.append("\"");
			for(int x=0;x<width;x++) {
				if(image[x][y]<0) {
					image[x][y] = 0;
				}
				rval.append(colorMapping[image[x][y]%colorMapping.length]);
			}
			rval.append("\",\n");
		}
		rval.append("};\n");

		return(rval.toString());
	}	


	/**
	 * This method generates a XPM from a 1 dimensional array. The XPM is returned as a String.
	 * @param xpmName The name of this XPM.
	 * @param image The array of pixels representing the image.
	 * @param width The number of columns in the image map (x size).
	 * @param height The number of rows in the image map (y size).
	 * @param colorMap An array of Hex color values. (i.e. #FF00AA).
	 * @param comment The comment to include in the XPM. If null, no comment is included.
	 * @return Returns the XPM of this image as a String.
	 */
	public static String generateXPM(String xpmName, int[] image, int width, int height, String[] colorMap, String comment)
	{
		StringBuffer rval = new StringBuffer();
		rval.append(generateHeader(xpmName, comment));
		String[] colorMapping = makeColorMapping(colorMap);
		rval.append(generateColorMapping(width, height, colorMap, colorMapping));

		rval.append("/* pixels */\n");
		for(int x=0;x<image.length;x++) {
			if(x%width==0) {
				rval.append("\"");
			}
			if(image[x]<0) {
				image[x] = 0;
			}
			rval.append(colorMapping[image[x]%colorMapping.length]);
			if((x+1)%width == 0) {
				rval.append("\",\n");
			}
		}
		rval.append("};\n");

		return(rval.toString());
	}



	/**
	 * This method is used to generate a color map from a file. The file is a text
	 * file with base 10 RGB values seperated by spaces. The rgb values will must be 
	 * in this format:
	 * <PRE>
	 * 0 0 0
	 * 0 4 0
	 * 0 16 0
	 * </PRE>
	 * And so on.
	 * @param filename The full pathname of the file which contains the RGB values.
	 * @param num_colors The number of colors expected in this file.
	 * @return Returns a String array of colors suitable for the 'generateXPM()' method.
	 */
	public static String[] makeColorMap(String filename, int num_colors)
	{
		String[] colors = new String[num_colors];
		File mapFile = new File(filename);
		BufferedReader reader = null;
		try {
			int cIndex = 0;
			reader = new BufferedReader(new FileReader(mapFile));
			String line = reader.readLine();
			while(line!=null) {
				String thiscolor = "#";
				for(int x=0;x<3;x++) {
					line = line.trim();
					int end = 0;
					try {
						while(line.charAt(end++)!=' ') {
							;
						}
					}
					catch(StringIndexOutOfBoundsException esadas) {
						end=line.length();
					}
					thiscolor = thiscolor+getHexValue(Integer.parseInt(line.substring(0,end).trim()));
					line = line.substring(end);
				}
				colors[cIndex++] = thiscolor;
				line = reader.readLine();
			}
			if(cIndex<colors.length) {
				for(int x=cIndex;x<colors.length;x++) {
					colors[x] = "#000000";
				}
			}
		}
		catch(Exception e) {
			if(reader!=null) {
				try {
					reader.close();
				}
				catch(Exception e2) {
					;
				}
			}
			e.printStackTrace();
		}
		return(colors);
	}
					
					

	/**
	 * This method takes a base10 number and returns a Hex value as a String.
	 * @param decimalValue The base10 integer to convert to Hex. This integer must be positive and less than 256.
	 * @return Returns a two character String representing the Hex value of the decimalValue. Decimal values greater than 255 are converted to 'FF'.
	 */
	public static String getHexValue(int decimalValue)
	{
		String rval = "";
		int rem = decimalValue%16;
		decimalValue = (int)(decimalValue/16);
		if(rem>9) {
			if(rem==10) {
				rval = "A";
			}
			else if(rem==11) {
				rval = "B";
			}
			else if(rem==12) {
				rval = "C";
			}
			else if(rem==13) {
				rval = "D";
			}
			else if(rem==14) {
				rval = "E";
			}
			else if(rem==15) {
				rval = "F";
			}
		}
		else {
			rval = rem+"";
		}
		rem = decimalValue%16;
		decimalValue = (int)(decimalValue/16);
		if(decimalValue>0) {
			rval = "FF";
		}
		else {
			if(rem>9) {
				if(rem==10) {
					rval = "A"+rval;
				}
				else if(rem==11) {
					rval = "B"+rval;
				}
				else if(rem==12) {
					rval = "C"+rval;
				}
				else if(rem==13) {
					rval = "D"+rval;
				}
				else if(rem==14) {
					rval = "E"+rval;
				}
				else if(rem==15) {
					rval = "F"+rval;
				}
			}
			else {
				rval = rem+rval;
			}
		}
		return(rval);
	}


	
	private static String[] makeColorMapping(String[] colorMap)
	{
		String[] mapping = new String[colorMap.length];

		if(letters == null) {
			makeLetters();
		}

		try {
			int x=0;
			while(true) {
				mapping[x]=letters[x%64];
				x++;
			}
		}
		catch(ArrayIndexOutOfBoundsException e) {;}

		try {
			if(mapping.length>64) {
				try {
					int x=0;
					int z=0;
					while(true) {
						for(int y=0;y<64;y++) {
							mapping[x+y]=letters[z]+mapping[x+y];
						}
						z++;
						x=x+64;
					}
				}
				catch(ArrayIndexOutOfBoundsException e) {;}
			}
			if(mapping.length>(64*64)) {
				try {
					int x=0;
					int z=0;
					while(true) {
						for(int y=0;y<(64*64);y++) {
							mapping[x+y]=letters[z]+mapping[x+y];
						}
						z++;
						x=x+(64*64);
					}
				}
				catch(ArrayIndexOutOfBoundsException e) {;}
			}
			if(mapping.length>(64*64*64)) {
				try {
					int x=0;
					int z=0;
					while(true) {
						for(int y=0;y<(64*64*64);y++) {
							mapping[x+y]=letters[z]+mapping[x+y];
						}
						z++;
						x=x+(64*64*64);
					}
				}
				catch(ArrayIndexOutOfBoundsException e) {;}
			}
		}
		catch(Exception e) {;}

		return(mapping);
	}


	private static String generateColorMapping(int width, int height, String[] colorMap, String[] colorMapping)
	{
		StringBuffer rval = new StringBuffer();
		
		rval.append("/* width height num_colors chars_per_pixel */\n");
		rval.append("\"  "+width+"  "+height+"  "+colorMap.length+"  "+colorMapping[0].length()+"\",\n");
		rval.append("/* colors */\n");
		for(int x=0;x<colorMap.length;x++) {
			rval.append("\""+colorMapping[x]+" c "+colorMap[x]+"\",\n");
		}

		return(rval.toString());
	}

	
	private static String generateHeader(String name, String comment)
	{
		StringBuffer rval = new StringBuffer();
		
		rval.append("/* XPM */\n");
		if((comment!=null) && (!comment.trim().equals(""))) {
			rval.append("/* \n");
			rval.append(comment);
			rval.append("\n */\n");
		}
		rval.append("static char *"+name+"[] = {\n");

		return(rval.toString());
	}

	private static void makeLetters()
	{
		letters = new String[64];
		letters[0] = ".";
		letters[1] = "#";
		letters[2] = "a";
		letters[3] = "b";
		letters[4] = "c";
		letters[5] = "d";
		letters[6] = "e";
		letters[7] = "f";
		letters[8] = "g";
		letters[9] = "h";
		letters[10] = "i";
		letters[11] = "j";
		letters[12] = "k";
		letters[13] = "l";
		letters[14] = "m";
		letters[15] = "n";
		letters[16] = "o";
		letters[17] = "p";
		letters[18] = "q";
		letters[19] = "r";
		letters[20] = "s";
		letters[21] = "t";
		letters[22] = "u";
		letters[23] = "v";
		letters[24] = "w";
		letters[25] = "x";
		letters[26] = "y";
		letters[27] = "z";
		letters[28] = "A";
		letters[29] = "B";
		letters[30] = "C";
		letters[31] = "D";
		letters[32] = "E";
		letters[33] = "F";
		letters[34] = "G";
		letters[35] = "H";
		letters[36] = "I";
		letters[37] = "J";
		letters[38] = "K";
		letters[39] = "L";
		letters[40] = "M";
		letters[41] = "N";
		letters[42] = "O";
		letters[43] = "P";
		letters[44] = "Q";
		letters[45] = "R";
		letters[46] = "S";
		letters[47] = "T";
		letters[48] = "U";
		letters[49] = "V";
		letters[50] = "W";
		letters[51] = "X";
		letters[52] = "Y";
		letters[53] = "Z";
		letters[54] = "0";
		letters[55] = "1";
		letters[56] = "2";
		letters[57] = "3";
		letters[58] = "4";
		letters[59] = "5";
		letters[60] = "6";
		letters[61] = "7";
		letters[62] = "8";
		letters[63] = "9";

	}


}
