import java.util.*;

public class annPlayer implements player
{
	public int numInputs = 6;
	public int internalSize = 12;
	NeuralNet net;
	public int fitness = 0;
	public boolean print = true;

	public annPlayer()
	{
		FileIn wtfile = new FileIn("annBest");
		double[][] w1 = new double[numInputs][internalSize];
		double[] w2 = new double[internalSize];
		for(int i = 0; i < numInputs; i++)
		{
			for(int j = 0; j < internalSize; j++)
			{			
				w1[i][j] = wtfile.getNum();
			}
		}
		for(int i = 0; i < internalSize; i++)
		{
			w2[i] = wtfile.getNum();
		}
		net = new NeuralNet(numInputs, internalSize, w1, w2);
		wtfile.close();
	}

	public annPlayer(NeuralNet ANN)
	{
		net = ANN;
	}

	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[] dfeatures = new double[features.length];
		for(int i = 0; i < features.length; i++)
		{
			dfeatures[i] = (double) features[i];
		}
		
		return net.getVal(dfeatures);
	}

	public void train(int numTimes) 
    {
		int popsize = 40;
		double breedrate = .8;
		double mutrate = .10;
		int startpoint = 0;
		annPlayer mom, dad;
		annPlayer[] pop = new annPlayer[popsize];
		annPlayer[]	newpop = new annPlayer[popsize];
		FileOut history = new FileOut("annTraining");
		FileIn weights = new FileIn("annBest");
		double[][] w1 = new double[numInputs][internalSize];
		double[] w2 = new double[internalSize];
		boolean[][] pb = new boolean[numInputs][internalSize];
		boolean bot[] = new boolean[internalSize];
		Random rn = new Random();
		for(int p = 0; p < popsize; p++)
		{
			for(int i = 0; i < numInputs; i++)
			{
				for(int j = 0; j < internalSize; j++)
				{			
					w1[i][j] = weights.getNum();
				}
			}
			for(int i = 0; i < internalSize; i++)
			{
				w2[i] = weights.getNum();
			}
			pop[p] = new annPlayer(new NeuralNet(numInputs, internalSize, w1, w2));
		}

		weights.close();

		int trainNum = 0;
		System.out.println(trainNum);
		getFitness(pop, popsize);
		printFitness(pop, popsize);
		while(maxFitness(pop, popsize) < 9 ||trainNum < numTimes)
		{
			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);
				pb = new boolean[numInputs][internalSize];
				bot = new boolean[internalSize];
				for(int q = 0; q < internalSize; q++)
				{
					for(int p = 0; p < numInputs; p++)
					{
						pb[p][q] = (abs(rn.nextInt()) % 2 == 0);
					}
				}
				for(int p = 0; p < internalSize; p++)
				{
					bot[p] = (abs(rn.nextInt()) % 2 == 0);
				}
				while(mom == dad)
				{
					mom = randomPlayer(pop, popsize);
					dad = randomPlayer(pop, popsize);
				}
				newpop[i] = breed(mom, dad, pb, bot);
				newpop[i + 1] = breed(dad, mom, pb, bot);
			}
			mutate(newpop, mutrate);
			pop = newpop;
			getFitness(pop, popsize);
			printFitness(pop, popsize);
			storeInFile(pop, history);
		}

		history.close();

		FileOut update = new FileOut("annBest");		
		storeInFile(pop, update);
		update.close();

//		pop = sort(pop, 0, popsize - 1);
    }

	public void getFitness(annPlayer[] pop, int popsize)
	{
		System.out.println("Getting Fitness");
		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)))
				if(true)
				{
					pop[i].fitness = pop[i].fitness + 1;
				}
				if (!(Game.runGame(randomPlayer(pop, popsize), pop[i])))
				if(true)
				{
					pop[i].fitness = pop[i].fitness + 1;
				}
			}
		}
	}

	public annPlayer[] getNewPop(annPlayer[] 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;
		annPlayer[] npop = new annPlayer[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 annPlayer randomPlayer(annPlayer[] pop, int popsize)
	{
		Random rn = new Random();
		return pop[abs(rn.nextInt()) % popsize];
	}

	public annPlayer breed(annPlayer parent1, annPlayer parent2, boolean[][] pb, boolean[] bot)
	{
		NeuralNet childNet = new NeuralNet(numInputs, internalSize);
		NeuralNet net1 = parent1.net;
		NeuralNet net2 = parent2.net;
		childNet = childNet.breed(parent1.net, parent2.net, pb, bot);
/*		for(int j = 0; j < internalSize; j++)
		{
			if(pb[j])
			{
				childNet.internal[j] = net1.internal[j].getClone();
				System.out.println ("A");
			}
			else
			{
				childNet.internal[j] = net2.internal[j].getClone();
				System.out.println ("B");
			}
		}
		if (bot)
			childNet.output = net1.output;
		else
			childNet.output = net2.output;*/
		annPlayer child = new annPlayer(childNet);
		return child;
	}

	public void mutate(annPlayer[] pop, double mutrate)
	{
		Random rn = new Random();
		int mutNum = 0;
		for(int i = 0; i < mutrate * pop.length; i++)
		{
			mutNum = abs(rn.nextInt()) % pop.length;
			pop[mutNum].net.mutate();
		}
	}

	public void storeInFile(annPlayer[] pop, FileOut history)
	{
		for(int i = 0; i < pop.length; i++)
		{
			pop[i].net.storeInFile(history);
			history.print("\n");
		}
	}

	public annPlayer[] sort(annPlayer[] pop, int low, int high)
	{
		if (low < high - 1)
		{
			int mid = (low + high)/2;
			annPlayer[] pop1 = sort(pop, low, mid);
			annPlayer[] pop2 = sort(pop, mid + 1, high);
			return merge(pop1, pop2);
		}
		else
		{
		    int length = high - low +1;
		    annPlayer[] r = new annPlayer[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 annPlayer[] merge(annPlayer[] pop1, annPlayer[] pop2)
	{
		annPlayer[] pop = new annPlayer[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(annPlayer[] 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(annPlayer[] pop, int popsize)
	{
		for(int i = 0; i < popsize; i++)
		{
			System.out.print(pop[i].fitness);
			System.out.print(" ");
		}
		System.out.print("\n");
	}
}