/*
 * Created on Nov 22, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package jone;

import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
import java.util.Hashtable;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import org.apache.log4j.*; 
import org.apache.log4j.xml.DOMConfigurator;
import java.util.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
/**
 * @author brian
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class Arena {

	final static int North = 0;
	final static int East = 1;
	final static int South = 2;
	final static int West = 3;
	static boolean debug = false; // turns logging on and off
	static boolean hasPermissions = false; // turns writing the fitness curve on and off
	static private Arena defaultInstance;
	static Logger logger; 
	private Font myFont;
	static public Arena getInstance()
	{
		if(defaultInstance == null) {
			if(debug) {
				DOMConfigurator.configure("D:\\xmllog4jconfig.xml");
				logger = Logger.getRootLogger();
				logger.debug("test");
			}
			defaultInstance = new Arena();
		}
		return defaultInstance;
	}
	
	private int maxTimeStepsForGeneration;
	private int numTimeStep;

	public void initializeArena(int numWolves, int numSheep, 
			int numGrassPatches, double mutateRate, double selectedProportion,
			int epochLength, double birthcost, double overconsumptionPenalty, 
			double foodbonus, double mutateFrac)
			 
	{
		
		double radius = .05;
		int totalanimals = numSheep + numWolves;
		double scalefactor = .01;
		int fontsize = 14;
		double newradius = Math.sqrt(radius * radius * (100.0/totalanimals));
		int newfontsize = (int) Math.round(14*(newradius/radius));
		
		Sheep.radius = Wolf.radius = newradius;
		GrassPatch.radius = 2*Sheep.radius;
		myFont = new Font("arial",Font.PLAIN,newfontsize);
		
		if(debug) {
			Arena.logger.debug("Arena initialize at" + (GregorianCalendar.getInstance()).getTime());
		}
		nWolves = numWolves;
		nSheep = numSheep;
		nGrassPatches = numGrassPatches;
		nCritters = nWolves + nSheep + nGrassPatches;
		critters = new Critter[nCritters];
		grass = new GrassPatch[numGrassPatches];
		this.birthCost = birthcost;
		this.overConsumptionPenalty = overconsumptionPenalty;
		this.foodBonus = foodbonus;
		this.mutateRate = mutateRate;
		this.selectedProportion = selectedProportion;
		this.mutateFrac = mutateFrac;
		
		for(int i = 0; i < nGrassPatches; i++) {
			critters[i] = grass[i]= new GrassPatch();	
			grass[i].respawn(null);
		}
		
		sheep = new Sheep [nSheep];
		for(int i = 0; i < nSheep; i++) {
				
			critters[i+nGrassPatches+nWolves] = sheep[i] = new Sheep();
			sheep[i].respawn(grass);
		}	
		wolves = new Wolf[nWolves];
		for(int i = 0; i < nWolves; i++) {
			critters[i+nGrassPatches] = wolves[i] = new Wolf();
			wolves[i].respawn(sheep);
		}
		randNumGen = RandomSingleton.getDefaultInstance();
	}
	
	public void draw(Graphics g)
	{
		Graphics2D g2d = (Graphics2D) g;
		g2d.setFont(this.myFont);
		for(int i = 0; i < nCritters; i++) {
			critters[i].draw(g);
		}	
	}
	public void timeToThink()
	{
		double [] numCrittersInEachQuadrant = new double [Sheep.inputsize];
		double [] avgDistanceOfCrittersEachQ = new double [Sheep.inputsize];
		for(int i = 0; i < nSheep; i++) {
			for(int j = 0; j < Sheep.inputsize; j++) {
				numCrittersInEachQuadrant[j] = 0;
				avgDistanceOfCrittersEachQ[j] = 0;
			}
			
			for(int j = 0; j < nGrassPatches; j++) {
				int quad = getQuadrant(sheep[i].position, grass[j].position);
				numCrittersInEachQuadrant[quad+4]++;
				avgDistanceOfCrittersEachQ[quad+4] += sheep[i].position.distance(grass[j].position);
			}
			
			for(int j = 0; j < nWolves; j++) {
				int quad = getQuadrant(sheep[i].position, wolves[j].position);
				numCrittersInEachQuadrant[quad]++;
				avgDistanceOfCrittersEachQ[quad] += sheep[i].position.distance(wolves[j].position);
			}
			for(int j = 0; j < avgDistanceOfCrittersEachQ.length; j++)
			{
				if(numCrittersInEachQuadrant[j] == 0)
					avgDistanceOfCrittersEachQ[j] =  0;
				else
					avgDistanceOfCrittersEachQ[j] /= numCrittersInEachQuadrant[j];
			}
			sheep[i].think(avgDistanceOfCrittersEachQ);
			
		}
		
		numCrittersInEachQuadrant = new double [Wolf.inputsize];
		avgDistanceOfCrittersEachQ = new double [Wolf.inputsize];
		for(int i = 0; i < nWolves; i++) {
			for(int j = 0; j < Wolf.inputsize; j++) {
				numCrittersInEachQuadrant[j] = 0;
				avgDistanceOfCrittersEachQ[j] = 0;
			}
			
			for(int j = 0; j < nGrassPatches; j++) {
				int quad = getQuadrant(wolves[i].position, grass[j].position);
				numCrittersInEachQuadrant[quad+4]++;
				avgDistanceOfCrittersEachQ[quad+4] += wolves[i].position.distance(grass[j].position);
			}
			
			for(int j = 0; j < nSheep; j++) {
				int quad = getQuadrant(wolves[i].position, sheep[j].position);
				numCrittersInEachQuadrant[quad]++;
				avgDistanceOfCrittersEachQ[quad] += wolves[i].position.distance(sheep[j].position);
			}
			for(int j = 0; j < avgDistanceOfCrittersEachQ.length; j++)
			{
				if(numCrittersInEachQuadrant[j] == 0)
					avgDistanceOfCrittersEachQ[j] =  0;
				else
					avgDistanceOfCrittersEachQ[j] /= numCrittersInEachQuadrant[j];
			}
			wolves[i].think(avgDistanceOfCrittersEachQ);
			
		}
//		 FIXME need to add the wolves to state vector, and sensors for wolves
		
	}
	 
	static public int getQuadrant(Point2D.Double p1, Point2D.Double p2)
	{
		/* find the angle between the vector of p1 and the x-axis and 
		 * p2, then find its quadrant
		 */
		double theta = Math.atan2(p1.y - p2.y,p1.x - p2.x ); 
		double pi = 3.14;
		
		/*if(theta < 0) 
			return (theta < -1*pi/2) ? Quad3 : Quad4;
		
		return (theta < pi/2) ? Quad1 : Quad2;
		*/
		if(theta >= pi/4.0 && theta <= 3.0*pi/4.0)
			return North;
		else if(theta >= -3.0*pi/4 && theta <= -1.0*pi/4.0)
			return South;
		else if(Math.abs(theta) >= 3.0*pi/4)
			return East;
		return West;
	}
	private void move()
	{
		for(int i = 0; i < nSheep; i++) {
			sheep[i].move();
		}
		for(int i = 0; i < nWolves; i++) {
			wolves[i].move();
		}
	}
	/* runs current setup timeSteps number of times */
	public void run(int timeSteps)
	{
		for(int i = 0; i < timeSteps; i++) {
			
			//double [] s = computeState();
			timeToThink();
			move();
			eat();
			if(debug) {
				//Arena.logger.debug("after move");
			}
			//Action(computeActionState(),s);
		}
		
	}
	
	private void eat()
	{
		for(int i = 0; i < nSheep; i++) {
			sheep[i].eat(grass);
		}
		for(int i = 0; i < nWolves; i++) {
			wolves[i].eat(sheep);
		}
	}
	
	/*public void setNumSheepWolvesGrass(int newNumSheep,int newNWolves,int newNGrass)
	{	
		if(sheep == null || wolves == null || grass == null) {
			return;
		}
		
		nSheep = newNumSheep;
		nWolves = newNWolves;
		nGrassPatches = newNGrass;
		
		Sheep [] nsheep = new Sheep[newNumSheep];
		Arrays.sort(sheep,new AnimalComparator());
		int i = 0;
		int j = sheep.length - 1;
		while(i < newNumSheep && j >= 0) {
			nsheep[i] = sheep[j];
			i++;
			j--;
		}
		for(int p = i; p < newNumSheep; p++) {
			nsheep[p] = new Sheep();
		}
		sheep = nsheep;
		
		Wolf [] nwolves = new Wolf[newNWolves];
		Arrays.sort(wolves,new AnimalComparator());
		i = 0;
		j = wolves.length - 1;
		while(i < newNWolves && j >= 0) {
			nwolves[i] = wolves[j];
			i++;
			j--;
		}
		for(int p = i; p < newNWolves; p++) {
			nwolves[p] = new Wolf();
		}
		
		wolves = nwolves;
			
		for( i = 0; i < nGrassPatches; i++) {
			critters[i] = grass[i] = new GrassPatch();	
		}
		
		for (i = 0; i < nWolves; i++) {
			critters[i+nGrassPatches] = wolves[i];	
		}
		
		for( i = 0; i < nSheep; i++) {
			critters[i+nGrassPatches+nWolves] = sheep[i];	
		}	
	}
	*/
	public FitnessGraphInfo evolve()
	{
		/* select the n best individuals from the animal groups, perform 
		 * evolve step for animal array? or separately for sheep and wolves 
		 * sort by fitness in each group, copy the n best, save 1 copy 
		 * of each, perform mutations and crossovers for the rest
		 */
		 computeFitnessOfIndividuals();
		 
		AnimalComparator ac = new AnimalComparator();
		/* sort the sheep by fitness */
		Arrays.sort(sheep,ac);
		Collections.reverse(Arrays.asList(sheep));
		Arrays.sort(wolves,ac);
		Collections.reverse(Arrays.asList(wolves));
		FitnessGraphInfo fitness = new FitnessGraphInfo();
		
		if(sheep != null && sheep.length != 0) {
			fitness.BestSheep =  sheep[0];
			fitness.bestSheepFitness = fitness.BestSheep.getFitness();
			fitness.bestSheepAge = fitness.BestSheep.age;
		}
		
		
		if(wolves != null && wolves.length != 0) {
			fitness.BestWolf = wolves[0];
			fitness.bestWolfFitness = fitness.BestWolf.getFitness();
			fitness.bestWolfAge = fitness.BestWolf.age;
		}
		
		
		
		if(debug)
		{
			if(nSheep > 0) {
				Arena.logger.debug("best sheep had fitness :" + sheep[0].getFitness());
			}
			if(nWolves > 0) {
				Arena.logger.debug("best wolf had fitness :" + wolves[0].getFitness());
				
			}
		}
		int saveNumSheep = Math.max(1,(int) (sheep.length * selectedProportion));
		int mutateNumSheep = Math.max(1,(int) ((sheep.length - saveNumSheep)*mutateFrac));
		
		/* set sheep in critter list properly */
		for(int i = saveNumSheep; i <= mutateNumSheep+saveNumSheep && i < nSheep; i++) {
			sheep[i] = (Sheep) sheep[randNumGen.nextInt(saveNumSheep)].mutate();
			
		}
		
		for(int i = saveNumSheep + mutateNumSheep; i < nSheep; i++) {
			sheep[i] = (Sheep) sheep[randNumGen.nextInt(saveNumSheep)].mateWith(sheep[randNumGen.nextInt(saveNumSheep)]);	
		}
		
		/* set sheep position, do I need to make sure sheep are not next to each other, 
		 * i could set the sheep to all new random positions or a subset of positions,
		 * if the new position is within the radius of any other sheep, select a new position
		 */
		/*boolean done;
		for(int i = 0; i < nSheep; i++) {
			done = false;
			while(!done) { 
				sheep[i].setPosition(randNumGen.nextDouble(),randNumGen.nextDouble());
				int j;
				for(j = 0; j < i && !sheep[j].tooCloseTo(sheep[i]) ; j++) ;
				done = (i == j);
			}
		}*/
		
		/* reset the location of the grass patches
		for(int i = 0; i < nGrassPatches; i++) {
			grass[i].setPosition(randNumGen.nextDouble(),randNumGen.nextDouble());	
		} */
		
		/* respawn all the critters for the next round */
		
		
		
		
		int saveNumWolves = Math.max(1,(int) (wolves.length * selectedProportion));
		int mutateNumWolves = Math.max(1,(int) ((wolves.length - saveNumWolves)*mutateFrac));
		for(int i = saveNumWolves; i < saveNumWolves + mutateNumWolves && i < nWolves; i++) {
			wolves[i] = (Wolf) wolves[randNumGen.nextInt(saveNumWolves)].mutate(); 	
		}
		
		for(int i = saveNumWolves + mutateNumWolves; i < nWolves; i++) {
			wolves[i] = (Wolf) wolves[randNumGen.nextInt(saveNumWolves)].mateWith(wolves[randNumGen.nextInt(saveNumWolves)]);	
		}
		
		for(int i = 0; i < nGrassPatches; i++) {
			critters[i] = grass[i];	
			grass[i].respawn(grass);
		}
		for(int i = 0; i < nSheep; i++) {
			critters[i+nGrassPatches+nWolves] = sheep[i];	
			sheep[i].respawn(grass);
		}	
		for(int i = 0; i < nWolves; i++) {
			critters[i+nGrassPatches] = wolves[i];
			wolves[i].respawn(sheep);
		}
		
		

		if(debug)
		{
			logger.debug(this.printSummary());
			//System.out.println(this.printSummary());
			/*for(int i = 0; i < nSheep; i++) {
				logger.debug("sheep " + i + " fitness =" + sheep[i].getFitness());	
			}*/	
		}
		return fitness;
	}
	
	private void computeFitnessOfIndividuals() 
	{
		//int [][] fitness = new int[2][];
		//fitness[0] = new int[nSheep];
		for(int i = 0; i < nSheep; i++) {
			
			 sheep[i].computeFitness(birthCost,overConsumptionPenalty,foodBonus);
		}	
		//fitness[1] = new int[nWolves];
		for(int i = 0; i < nWolves; i++) {
			/* should wolves have a different fitness function */
			 wolves[i].computeFitness(birthCost,overConsumptionPenalty,foodBonus);
		}
	}
	
	public String printSummary()
	{
		String summary = "";
		try {
		
			summary = "Num wolves =" + wolves.length + " Num sheep = " + sheep.length + 
			" Num grass = 2" + grass.length + " mutateFrac = " + this.mutateFrac + 
			" mutateRate = " + this.mutateRate + " epochLength = "+ this.epochLength + 
			" foodBonus = " + this.foodBonus + " overconsumptionPenalty = " + 
			this.overConsumptionPenalty + "birthCost = " + this.birthCost;
		}
		catch (Exception e) {}
		return summary;
	}
	
	private int nWolves;
	private int nSheep;
	private int nGrassPatches;
	private int nCritters;
	private Critter [] critters;
	private Wolf [] wolves;
	private Sheep [] sheep;
	private GrassPatch [] grass;
	public double mutateRate; 
	public  double selectedProportion;
	public  int epochLength;
	public  double birthCost; 
	public  double overConsumptionPenalty;
	public  double foodBonus;
	public  double mutateFrac; //FIXME set this
	private Random randNumGen;
	//private double [] state;
	//private double [] actionState;
	//private int rowoctrants;
	// int coloctrants;
}
