//the two layer nerual net player
//not actually implemented as a neural net.
import java.util.*;

public class geneticPlayer implements player
{
	public static int numWeights = 6;
	public double[] weights = new double[numWeights];
	public int fitness = 0;
	public boolean print = true;

	public geneticPlayer()
	{
		FileIn wtfile = new FileIn("geneticBest");
		for (int i = 0; i < numWeights; i++)
		{
			weights[i] = wtfile.getNum();
		}
		wtfile.close();
	}

	public geneticPlayer(double[] w)
	{
		for (int i = 0; i < numWeights; i++)
		{
			weights[i] = w[i];
		}
	}

	public int[] getMove(boolean[][][] goodPos, boolean[][][] evilPos)
	{
		return PLF.treeSearch(this, goodPos, evilPos, 2);
	}

	public double getUtility(boolean[][][] goodPos, boolean[][][] evilPos)
	{
		int[] features;
		features = PLF.fillFeatures(goodPos, evilPos);
		
		double total = 0.0;
		for(int i = 0; i < features.length; i++)
		{
			total = total + weights[i] * (double) features[i];
		}
		return total;
	}

	public void train(int numTimes) 
    {
		int popsize = 40;
		double breedrate = .8;
		double mutrate = .10;
		int startpoint = 0;
		geneticPlayer mom, dad;
		geneticPlayer[] pop = new geneticPlayer[popsize];
		geneticPlayer[]	newpop = new geneticPlayer[popsize];
		FileOut history = new FileOut("geneticTraining");
		FileIn weights = new FileIn("geneticBest");
		double[] w = new double[numWeights];
		for(int i = 0; i < popsize; i++)
		{
			
			for(int j = 0; j < numWeights; j++)
			{
				w[j] = weights.getNum();
				System.out.print(w[j]);
				System.out.print(" ");
			}
			System.out.print("\n");
			pop[i] = new geneticPlayer(w);
			pop[i].fitness = 0;
		}
		System.out.print("\n");
		weights.close();

		int trainNum = 0;
		System.out.println(trainNum);
		getFitness(pop, popsize);
		printFitness(pop, popsize);
		while(maxFitness(pop, popsize) < 9 || trainNum < numTimes)
		{
			trainNum++;
			System.out.println(trainNum);
			newpop = getNewPop(pop, popsize, 1-breedrate);
			startpoint = (int) ((1-breedrate) * (double) popsize) + 1;
			for(int i = startpoint; i < popsize; i = i + 2)
			{
				mom = randomPlayer(pop, popsize);
				dad = randomPlayer(pop, popsize);
				while(mom == dad)
				{
					mom = randomPlayer(pop, popsize);
					dad = randomPlayer(pop, popsize);
				}
				newpop[i] = breed(mom, dad);
				newpop[i + 1] = breed(dad, mom);
			}
			mutate(newpop, popsize, mutrate, numWeights);
			pop = newpop;
			getFitness(pop, popsize);
			printFitness(pop, popsize);
			storeInFile(pop, history, popsize, numWeights);
		}

		history.close();

		FileOut update = new FileOut("geneticBest");
		storeInFile(pop, update, popsize, numWeights);
		update.close();

//		pop = sort(pop, 0, popsize - 1);
    }

	public void getFitness(geneticPlayer[] pop, int popsize)
	{
		for(int k = 0; k < popsize; k++)
		{
			pop[k].fitness = 0;
		}
		for(int i = 0; i < popsize; i++)
		{
			for(int j = 0; j < 5; j++)
			{
				if (Game.runGame(pop[i], randomPlayer(pop, popsize)))
				{
					pop[i].fitness = pop[i].fitness + 1;
				}
				if (!(Game.runGame(randomPlayer(pop, popsize), pop[i])))
				{
					pop[i].fitness = pop[i].fitness + 1;
				}
			}
		}
	}

	public geneticPlayer[] getNewPop(geneticPlayer[] oldpop, int popsize, double survivalrate)
	{
		Random rn = new Random();
		boolean[] selected = new boolean[popsize];
		int curPick = 0;
		int totFit = 0;
		int countOnCur = 0;
		int selFit;
		int i = 0;
		geneticPlayer[] npop = new geneticPlayer[popsize];
		for(int j = 0; j < popsize; j++)
		{
			selected[j] = false;
			totFit = totFit + oldpop[j].fitness;
		}
		while(i < survivalrate * popsize)
		{
			selFit = abs(rn.nextInt()) % totFit;
			curPick = 0;
			countOnCur = 0;
			for(int j = 0; j < selFit; j++)
			{
				countOnCur++;
				if (oldpop[curPick].fitness < countOnCur)
				{
					curPick++;
					countOnCur = 1;
				}
			}
			if(!(selected[curPick]))
			{
				npop[i] = oldpop[curPick];
				selected[curPick] = true;
				i++;
			}
		}

		return npop;
	}

	public geneticPlayer randomPlayer(geneticPlayer[] pop, int popsize)
	{
		Random rn = new Random();
		return pop[abs(rn.nextInt()) % popsize];
	}

	public geneticPlayer breed(geneticPlayer parent1, geneticPlayer parent2)
	{
		Random rn = new Random();
		geneticPlayer child = new geneticPlayer();
		for(int i = 0; i < numWeights; i++)
		{
			if(abs(rn.nextInt()) % 2 == 0)
			{
				child.weights[i] = parent1.weights[i];
			}
			else
			{
				child.weights[i] = parent2.weights[i];
			}
		}
		return child;
	}

	public void mutate(geneticPlayer[] pop, int popsize, double mutrate, int numW)
	{
		Random rn = new Random();
		int mutNum = 0;
		int wtNum = 0;
		for(int i = 0; i < mutrate * popsize; i++)
		{
			mutNum = abs(rn.nextInt()) % popsize;
			wtNum = abs(rn.nextInt()) % numW;
			if(abs(rn.nextInt()) % 2 == 0)
			{
				pop[mutNum].weights[wtNum] = pop[mutNum].weights[wtNum] + .5;
			}
			else
			{
				pop[mutNum].weights[wtNum] = pop[mutNum].weights[wtNum] - .5;
			}
		}
	}

	public void storeInFile(geneticPlayer[] pop, FileOut history, int popsize, int numW)
	{
		for(int i = 0; i < popsize; i++)
		{
			for(int k = 0; k < numW; k++)
			{
				history.print(pop[i].weights[k]);
				history.print(" ");
			}
			history.print("\n");
		}
	}

	public geneticPlayer[] sort(geneticPlayer[] pop, int low, int high)
	{
		if (low < high - 1)
		{
			int mid = (low + high)/2;
			geneticPlayer[] pop1 = sort(pop, low, mid);
			geneticPlayer[] pop2 = sort(pop, mid + 1, high);
			return merge(pop1, pop2);
		}
		else
		{
		    int length = high - low +1;
		    geneticPlayer[] r = new geneticPlayer[length];
		    if (length == 1) r[0] = pop[low];
		    if (length == 2)
			if(pop[low].fitness > pop[high].fitness) 
			{
			    r[0] = pop[low];
			    r[1] = pop[high];
			} 
			else 
			{
			    r[0] = pop[high];
			    r[1] = pop[low];
			}
		    return r;
		}
	}

	public geneticPlayer[] merge(geneticPlayer[] pop1, geneticPlayer[] pop2)
	{
		geneticPlayer[] pop = new geneticPlayer[pop1.length + pop2.length];
		int l = 0;
		int r = 0;
		int i = 0;
		while (l < pop1.length && r < pop2.length)
		{
			if (pop1[l].fitness > pop2[r].fitness)
			{
				pop[i] = pop1[l];
				l++;
			}
			else
			{
				pop[i] = pop2[r];
				r++;
			}
			i++;
		}
		for(; l < pop1.length; l++)
		{
			pop[i] = pop1[l];
			i++;
		}
		for(; r < pop2.length; r++)
		{
			pop[i] = pop1[r];
			i++;
		}
		return pop;
	}

	public int abs(int x)
	{
		if (x < 0) return -x; else return x;
	}
	
	public int maxFitness(geneticPlayer[] pop, int popsize)
	{
		int max = -1;
		for(int i = 0; i < popsize; i++)
		{
			if(pop[i].fitness > max)
				max = pop[i].fitness;
		}
		return max;
	}
	
	public void printFitness(geneticPlayer[] pop, int popsize)
	{
		for(int i = 0; i < popsize; i++)
		{
			System.out.print(pop[i].fitness);
			System.out.print(" ");
		}
		System.out.print("\n");
	}
}