import java.io.*;
import java.util.*;

public class Chromozome
{
	private int[] nodes; // holds all genotype information
	private Random random;
	private int numberOfNodes; // number of genotype nodes
	private final int MAX_NUM_FUNCTIONS = 16; // maximum number of functions
	//private static int functionParameterValues[] = {0, 256, 50, 0, 0, 0, 0, 0, 0, 0,0, 256, 255, 0, 0, 256};

	public Chromozome(int numNodes, Random r)
    {
		// random is passed as a parameter so that all genotypes are using the same seed
		random = r;

		numberOfNodes = numNodes;

		/*  we need the array size to be numberofnodes * 4 because I am squishing
			a [n][4] array into a 1D array and each location needs 4 values
			and plus 3 for the final outputs pointers(RGB).
			Each "node" consists of 4 things.  First two things are the X, Y
			pixel input, the second is the function being used (refrenced by
			number), and finally the last is used to store the value of the
			paramter that the function might use (not all functions use this,
			but is needed anyway, incase of mutation).
		*/
		nodes = new int[ (numberOfNodes * 4) +3];

		// fill all nodes except the last 3 with randomly generated values
		// last 3 values are reserved for output pointers
		for(int i = 0; i < nodes.length-3; i+=4)
		{
			nodes[i] = generatePointer(i/4); // input 1
			nodes[i+1] = generatePointer(i/4); // input 2
			nodes[i+2] = generateFunctionID(); // function
			nodes[i+3] = generateParamter(); //parameter
		}

		// start at where you left off last loop, to the end of the array
		// basically fill the last 3 locations with the output pointers for RGB values
		for(int i = nodes.length-3; i < nodes.length; i++)
		{
			nodes[i] = generateOutputPointer();
		}
    }

    public int getNodeSize()
    {
		return numberOfNodes;
	}

	public Random getRandomGenerator()
	{
		return random;
	}

	public void setRandomGenerator(Random r)
	{
		random = r;
	}

	public int generatePointer(int currentNodeIndex)
	{	// reason for adding 2 is because there are 2 ghost pointers
		// are not in the array, and these refer to the inputs (X, Y)
		// returns an pointer between 0 and currentNode -1
		// nodes can point to any node below them, but not above
		return (random.nextInt(currentNodeIndex+2));
	}

	public int generateFunctionID()
	{	// return a number between 0 and NUM_FUNCTIONS -1 (0 to 14)
		return (random.nextInt(MAX_NUM_FUNCTIONS));
	}

	public int generateParamter()
	{	// since I restrict all values (including function outputs) to 255
		// I only want parameter values between 0 and 255
		return (random.nextInt(256));
	}

	public int generateOutputPointer()
	{	// output nodes can be any input or function node, +2 is to account for being able to select ghost input nodes
		// output pointers can't point to other outputs
		return (random.nextInt(numberOfNodes+2));
	}

	public int getValueAtIndex(int index)
	{
		return nodes[index];
	}

	public void setValueAtIndex(int index, int value)
	{
		nodes[index] = value;
	}

	public Chromozome copy()
	{
		Chromozome copy = new Chromozome(this.getNodeSize(), this.getRandomGenerator());

		for(int i = 0; i < (this.getNodeSize() * 4) + 3; i++)
		{
			copy.setValueAtIndex(i, this.getValueAtIndex(i));
		}

		return copy;
	}

	public void print()
	{
		for(int i = 0; i < numberOfNodes; i++)
		{
			System.out.print((i+2)+":");
			for(int j = 0; j < 4; j++)
			{
				System.out.print(" "+nodes[i*4+j]);
			}
			System.out.println();
		}
		System.out.println("Red: "+nodes[numberOfNodes*4] + " "+
						   "Green: "+nodes[numberOfNodes*4+1] + " "+
						   "Blue: "+nodes[numberOfNodes*4+2]);
	}

	public void printLinear()
	{
		for(int i = 0; i < nodes.length; i++)
		{
			System.out.print(nodes[i]+" ");
		}

		System.out.println();
	}

	public String toString()
	{
		String s = "";

		s = s + numberOfNodes + " { ";

		for(int i = 0; i < nodes.length; i++)
		{
			s = s + nodes[i] + ",";
		}

		s = s + " }";

		return s;
	}

	public void mutate()
	{
		// mutation can occur anywhere in the array
		int mutationPoint = random.nextInt(nodes.length);

		// determine what the exact index at the mutation point represents
		// 0 = pointer 1
		// 1 = pointer 2
		// 2 = functionID
		// 3 = parameter
		int pointType = (mutationPoint % 4);

		// determines what node is being mutated
		int nodeNumber = (mutationPoint / 4);

		// determine if the mutation point is outside the node boundry
		// if its not, mutate the index value according to pointType
		// if it is, then we need to mutate an output pointer
		if(nodeNumber < numberOfNodes)
		{
			switch(pointType)
			{
				case 0:  nodes[mutationPoint] = generatePointer(nodeNumber);
					     break;
				case 1:  nodes[mutationPoint] = generatePointer(nodeNumber);
					     break;
				case 2:  nodes[mutationPoint] = generateFunctionID();
					     break;
				case 3:
						 nodes[mutationPoint] = generateParamter();
					     break;
				//default: break;
			}
		}
		else
		{
			nodes[mutationPoint] = generateOutputPointer();
		}

		//System.out.println("Mutation index: "+mutationPoint + " pointType: "+pointType+ " nodeNumber: "+(nodeNumber+2));
	}

}

