import java.io.*;
import java.util.*;

public class GeneticOperators
{
	private static double mutationChance = 0.02;
	private static int numberOfMutations = 1;
	private static double crossOverChance = 0.40;
	private static boolean moreThenOneCrossOverPoint = false;
	private static Random random = new Random();
	private static boolean whichParent = true;
	private static boolean allowCrossOverWithMutation = true;
	private static int functions = 0; // 0 = complex functions, 1 = basic

	public GeneticOperators(Random r)
    {
		random = r;
    }

	public GeneticOperators()
    {
    }

    public static Chromozome generateChromozome(int nodesize)
    {
		return new Chromozome(nodesize, random);
	}

	public void setAllowCrossOverWithMutation(boolean b)
	{
		allowCrossOverWithMutation = b;
	}

	public void setFunctionSet(int func)
	{
		functions = func;
	}

    public void setMutationChance(double chance)
    {
		mutationChance = chance;
	}

	public static Random getRandom()
	{
		return random;
	}

	public void setNumberOfMutations(int num)
	{
		numberOfMutations = num;
	}

	public void setCrossOverChance(double chance)
	{
		crossOverChance = chance;
	}

	public void setAllowMoreThenOneCrossOverPoint(boolean b)
	{
		moreThenOneCrossOverPoint = b;
	}

    public static Chromozome[] crossOver(Chromozome a, Chromozome b)
    {
		// create some child Entities
		Chromozome c = new Chromozome(a.getNodeSize(), a.getRandomGenerator());
		Chromozome d = new Chromozome(a.getNodeSize(), a.getRandomGenerator());

		// if more then one cross over point is allowed, do this method
		// else just randomly choose an index, and cross over from that
		if(moreThenOneCrossOverPoint)
		{
			for(int i = 0; i < ((a.getNodeSize()*4)+3); i++)
			{
				double ranCrossOver = random.nextDouble();

				//flip to other parent
				if(ranCrossOver <= crossOverChance) whichParent = !whichParent;

				if(whichParent) // c gets parent a
				{
					c.setValueAtIndex(i, a.getValueAtIndex(i));
					d.setValueAtIndex(i, b.getValueAtIndex(i));
				}
				else // c gets parent b
				{
					c.setValueAtIndex(i, b.getValueAtIndex(i));
					d.setValueAtIndex(i, a.getValueAtIndex(i));
				}
			}
		}
		else
		{	// choose any index to cross over at
			int crossOverPoint = random.nextInt( ( ( a.getNodeSize() * 4 ) + 3 ) );

			// from 0 to cross over point, c gets a, d gets b
			for(int i = 0; i < crossOverPoint; i++)
			{
				c.setValueAtIndex(i, a.getValueAtIndex(i));
				d.setValueAtIndex(i, b.getValueAtIndex(i));
			}

			// from cross over point to end of array, c gets b, d gets a
			for(int i = crossOverPoint; i < ((a.getNodeSize()*4)+3); i++)
			{
				c.setValueAtIndex(i, b.getValueAtIndex(i));
				d.setValueAtIndex(i, a.getValueAtIndex(i));
			}
		}

		// check for mutation on c
		if(allowCrossOverWithMutation && random.nextDouble() <= mutationChance)
		{
			for(int i = 0; i < numberOfMutations; i++)
				c.mutate();
		}

		// check for mutation on d
		if(allowCrossOverWithMutation && random.nextDouble() <= mutationChance)
		{
			for(int i = 0; i < numberOfMutations; i++)
				d.mutate();
		}

		Chromozome[] array = {c, d};
		return array;
	}

	public static Chromozome mutate(Chromozome a)
	{
		Chromozome copy = a.copy();

		for(int i = 0; i < numberOfMutations; i++)
			copy.mutate();

		return copy;
	}

	public static int[] calculate(Chromozome a, int[] variables, boolean debugMode)
	{
		// result array is the 2 inputs X, Y + the size of the genotype
		int[] resultArray = new int[a.getNodeSize()+2];

		// insert the variables into the result array
		resultArray[0] = variables[0];
		resultArray[1] = variables[1];


		// for every other entry, calculate the result of doing the operation in the entry at node i
		// start at 2 cause we need to keep the input results
		for(int i = 2; i < resultArray.length; i++)
		{
			// get FunctionID
			// i-2 to translate to the correct node (starting at 0)
			// * 4 to get to the correct location in the array
			// + 2 to get the the function ID of that node
			int functionID = a.getValueAtIndex(((i-2)*4)+2);

			// get parameter
			int parameter = a.getValueAtIndex(((i-2)*4)+3);

			//get pointers for inputs
			int inputOne = a.getValueAtIndex((i-2)*4);
			int inputTwo = a.getValueAtIndex(((i-2)*4)+1);

			//interpret and calculate the Entry
			resultArray[i] = interpretAndCalculate(inputOne, inputTwo, functionID, parameter, resultArray);

			if(resultArray[i] > 255 || resultArray[i] < 0)
			{
				for(int j = 0; j <= i; j++)
				{
					System.out.println(j+": "+resultArray[j]);
				}

				System.out.println("P1: "+inputOne+" P2: "+inputTwo+" fID: "+functionID+" param: "+parameter);
			}

		}
		// return the last index which is the final result
		if(debugMode && (resultArray[resultArray.length-1]) != 0)
		{
			System.out.println();
			for(int i = 0; i < resultArray.length; i++)
			{
				System.out.println(i+": "+resultArray[i]);
			}
			System.out.println();
		}

		int red = resultArray[a.getValueAtIndex( (a.getNodeSize()*4) )];
		int green = resultArray[a.getValueAtIndex( ( ( a.getNodeSize()*4 ) + 1) )];
		int blue = resultArray[a.getValueAtIndex( ( ( a.getNodeSize()*4 ) + 2 ) )];

		int[] color = {red, green, blue};
		return color;
	}

	private static int interpretAndCalculate(int inputOne1, int inputTwo1, int functionID, int parameter, int[] resultArray)
	{
		int answer = 0;

		int inputOne = resultArray[inputOne1];
		int inputTwo = resultArray[inputTwo1];

		if(functions == 0)
		{
			switch(functionID)
			{

			case 0: //
				answer = inputOne | inputTwo;
				break;

			case 1: //
				answer = parameter & inputOne;
				break;

			case 2: //
				answer = (int)((double)inputOne / (1.0D + (double)inputTwo + (double)parameter));


			case 3: //
				answer = (inputOne * inputTwo) % 255;


			case 4: //
				answer = (inputOne + inputTwo) % 255;


			case 5: //
				if(inputOne > inputTwo)
					answer = inputOne - inputTwo;
				else
					answer = inputTwo - inputOne;
				break;

			case 6: //
				answer = (inputOne - 255) * -1;
				break;

			case 7: //
				answer = (int)Math.abs(Math.cos(inputOne) * 255D);
				break;

			case 8: //
				answer = inputOne;
				break;

			case 9: //
				answer = (int)Math.abs(Math.tan(((double)(inputOne % 45) * 3.1415926535897931D) / 180D) * 255D);
				break;

			case 10: //
				answer = (int)Math.abs(Math.tan(inputOne) * 255D) % 255;
				break;

			case 11: //
				answer = (int)Math.sqrt((inputOne - parameter) * (inputOne - parameter) + (inputTwo - parameter) * (inputTwo - parameter));
				if(answer > 255)
					answer = 255;
				break;

			case 12: //
				answer = inputOne % (parameter + 1) + (255 - parameter);
				break;

			case 13: //
				answer = (inputOne + inputTwo) / 2;
				break;

			case 14: //
				if(inputOne > inputTwo)
					answer = 255 * ((inputTwo + 1) / (inputOne + 1));
				else
					answer = 255 * ((inputOne + 1) / (inputTwo + 1));
				break;

			case 15: //
				answer = (int)Math.abs(Math.sqrt((inputOne - parameter) * (inputOne + parameter) + (inputTwo - parameter) * (inputTwo + parameter)) % 255D);
				break;

			case 16: //
				if(inputOne > inputTwo)
					answer = (int)(255D * (((double)inputTwo + 1.0D) / ((double)inputOne + 1.0D)));
				else
					answer = (int)(255D * (((double)inputOne + 1.0D) / ((double)inputTwo + 1.0D)));
				break;

			default:
				answer = 0;
				break;
			}
		}
		else
		{
			switch(functionID)
			{

			case 0: //
				answer = (inputOne + inputTwo)%255;
				break;

			case 1: //
				answer = inputOne - inputTwo;
				break;

			case 2: //
				answer = (inputOne * inputTwo) % 255;
				break;

			case 3: //
				answer = (int)((double)(inputOne) / (double)(inputTwo+1));
				break;

			case 4: //
				answer = (int)((Math.cos(inputTwo)*255D)%255);
				break;

			case 5: //
				answer = (int)((Math.sin(inputOne)*255D)%255);
				break;

			case 6: //
				answer = (int)(Math.exp(inputOne)%255);
				break;

			case 7: //
				if(inputTwo == 0)
					answer = 0;
				else
					answer = (int)(Math.log(inputTwo));
				break;

			case 8: //
				answer = inputTwo;
				break;

			case 9: //
				if(inputOne == 0)
					answer = 0;
				else
					answer = (int)((Math.atan(inputOne)*255D)%255);
				break;

			case 10: //
				answer = (int)(Math.sqrt(inputOne));
				break;

			case 11: //
				answer = ((inputOne^inputTwo)%255);
				break;

			case 12: //
				answer = inputTwo % (inputOne+1);
				break;

			case 13: //
				answer = (inputOne + inputTwo) / 2;
				break;

			case 14: //
				answer = (255 - inputOne);
				break;

			case 15: //
				answer = ((255 - inputTwo) * inputOne)%255;
				break;

			case 16: //
				answer = inputOne - (inputTwo % 128);
				break;

			default:
				answer = 0;
				break;
			}
		}

        if(answer < 0)
            answer *= -1;
        if(answer > 255)
            answer = 255;
        return answer;
	}

	public static int[][] calculateImage(Chromozome rgb, int width, int height)
	{
		// the double array will hold each color value independantly
		int[][] image = new int[3][width*height];
		int[] tempColor;

		for(int i = 0; i < width-1; i++)
		{
			for(int j = 0; j < height-1; j++)
			{
				int[] vars = {i, j};
				// get (i, j) pixel RGB value
				tempColor = calculate(rgb, vars, false);

				//RED
				image[0][i * width + j] = tempColor[0];

				//GREEN
				image[1][i * width + j] = tempColor[1];

				//BLUE
				image[2][i * width + j] = tempColor[2];
			}
		}

		return image;
	}
}

