/*
 * 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.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.util.LinkedList;
import java.util.Random;
import java.awt.*;

/**
 * @author brian
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class Sheep extends Animal {

	LinkedList pastFitnesses;
	static int inputsize = 8;
	static int outputsize = 2;
	static int numHidden = 8;
	static double radius = .05;
	static int [] numNeuronsInEachLayer = new int[]{ inputsize,numHidden,outputsize};
	static int [] neuronTypesForLayers = new int [] {NeuralNetTBox.tansig,NeuralNetTBox.tansig,NeuralNetTBox.tansig};
	static char [] viewChars = new char[] {'S'};
	int numSpawnsSinceStartOfEpoch;
	Point2D.Double lastPosition;
	//final static  int creationCost = -10;
	/* (non-Javadoc)
	 * @see jone.Animal#mateWith(jone.Animal)
	 */
	public Sheep()
	{
		this.brain = new Brain(numNeuronsInEachLayer,neuronTypesForLayers,
				inputsize, outputsize);	
		this.age = 0;
		
	}
	public Sheep(Brain b)
	{
		this.brain = b;
		this.age = 0;
		
		//super.Animal();
	}
	public Animal mateWith(Animal animal) {
		// TODO Auto-generated method stub
		Sheep partner = (Sheep) animal;
		Random rand = RandomSingleton.getDefaultInstance();
		Sheep sheep = new Sheep(Brain.splice(rand.nextDouble(),this.brain,partner.brain));
		
		return sheep;
	}

	/* (non-Javadoc)
	 * @see jone.Animal#mutate()
	 */
	public Animal mutate() {
		// TODO Auto-generated method stub
		
		return new Sheep(this.brain.mutate(.5));
	}


	/* (non-Javadoc)
	 * @see jone.Critter#respawn()
	 */
	public void respawn(Critter [] critters) {
		// TODO Auto-generated method stub
		super.respawn(critters);
		
		this.directionMagnitude = .025;
		
		this.numSpawnsSinceStartOfEpoch = 1;
		this.lastPosition = this.position;
		
		this.foodConsumed = 0;
		this.foodInStomach = 0;
		this.overEatPenalty = 0;
		this.fitness = 0;
		this.optimalFoodCapacity = 50;
		this.health = 1;
		this.age++;
	}

	public double getRadius()
	{
		return Sheep.radius;
	}
	
	private void die()
	{
		Random random = RandomSingleton.getDefaultInstance();
		//this.numSpawnsSinceStartOfEpoch++;//i should reset this
		this.position = new Point2D.Double(random.nextDouble(),random.nextDouble());
		this.directionMagnitude = .025;
		this.lastPosition = this.position;
		this.numSpawnsSinceStartOfEpoch++;
		//this.foodConsumed = 0;
		//this.foodInStomach = 0;
		//this.overEatPenalty = 0;
		//this.fitness = 0;
		//this.optimalFoodCapacity = 50;
		this.health = 10;
	}
	/* (non-Javadoc)
	 * @see jone.Critter#init()
	 */
	public void init() {
		// TODO Auto-generated method stub
		super.init();
	}

	/* (non-Javadoc)
	 * @see jone.Animal#computeFitness(double, double, double)
	 */
	public int computeFitness(double birthCost, double overConsumptionPenalty, double foodBonus) {
		// TODO Auto-generated method stub
		
		int fitness = (int)( birthCost * this.numSpawnsSinceStartOfEpoch + 
				this.foodConsumed * foodBonus 
		/* + this.overEatPenalty*overConsumptionPenalty*/);
		if( this.pastFitnesses == null) {
			this.pastFitnesses = new LinkedList();
		}
		this.pastFitnesses.add(new Integer(fitness));
		double sum = 0;
		for(int i = 0; i < this.pastFitnesses.size(); i++) {
			sum += ((Integer) this.pastFitnesses.get(i)).intValue();
		}
		this.fitness = (int) Math.round(sum/(1.0*this.pastFitnesses.size()));
		if(Arena.debug)
		{
			Arena.logger.debug("sheep's age: " + this.age + " fitness is " + this.fitness + "spawn num: " + this.numSpawnsSinceStartOfEpoch +
					" food cons: " + this.foodConsumed + " overeat: " + this.overEatPenalty + 
					" overeat * penalty =" + this.overEatPenalty*overConsumptionPenalty);
		}
		return this.fitness;
	}

	/* (non-Javadoc)
	 * @see jone.Animal#setPosition(double, double)
	 */
	public void setPosition(double x, double y) {
		// TODO Auto-generated method stub
		this.lastPosition = this.position; // this should work
		this.position = new Point2D.Double(x,y);
	}

	/* (non-Javadoc)
	 * @see jone.Animal#eat()
	 */
	public void eat(Critter [] critters) {
		// TODO Auto-generated method stub
		/* in order to create grazing pressure to increase the fitness of 
		 * individuals that eat alone, i need to use the number of sheep around
		 * to affect the consumption rate, 
		 */
		GrassPatch [] food = (GrassPatch []) critters;
		for(int i = 0; i < food.length; i++)
		{
			double grass = food[i].deplete(this);
			
			this.foodInStomach += grass;
			this.foodConsumed += grass;
			/*if(this.foodInStomach > this.optimalFoodCapacity)
			{
				this.overEatPenalty += grass;
			}*/
			if(grass > 0) {
				return;
			}
			if(Arena.debug && grass > 0)
			{
				Arena.logger.debug("sheep is eating " + grass + " grass");
				return;
			}
		}
		
	}

	public double underAttack(Wolf w)
	{
		if(this.position.distance(w.getPosition()) < 
				Wolf.radius /*+ this.getRadius()*/)
		{
			//health /= 2; //-= this.foodSupplyDensity;
			health--;
			if(this.health < 0) {
				if(Arena.debug)
				{
					Arena.logger.debug("sheep died with fitness: " + this.fitness);
				}
				this.die();
			}
			return 1;
		}
		Random rand = RandomSingleton.getDefaultInstance();
		if(rand.nextDouble() < .001) {
			//this.respawn();
		}
		return 0.0;
		
	}
	/* (non-Javadoc)
	 * @see jone.Animal#getFitness()
	 */
	public int getFitness() {
		// TODO Auto-generated method stub
		return this.fitness;
	}
	/* (non-Javadoc)
	 * @see jone.Animal#think(double[])
	 */
	public void think(double[] state) {
		// TODO Auto-generated method stub
		
		this.direction = this.brain.think(state);
		if(Arena.debug)
		{
			//Arena.logger.debug("sheep input " + NeuralNetTBox.printVector(state,state.length) + "\n");
			//Arena.logger.debug("direction : " + this.direction + "\n");
			
		}
	}
	/* (non-Javadoc)
	 * @see jone.Animal#move(double[], double[])
	 */
	public void move() {
		/* for all positions within a certain distance sum the force in the x 
		 * and y components to get the sum of the forces
		 */
		/*double xcomp = this.acceleration.x, ycomp = this.acceleration.y;
		for(int i = 0; i < forces.length; i+=2)
		{
			if(this.position.distance(positions[i],positions[i+1]) < this.radius) {
				xcomp += forces[i];
				ycomp += forces[i+1];
			}								 
		}
		/* if the sheep direction is small, increase penalty constant,
		 * otherwise, set the penaltyconst devaluation to 1
		 FIXME check for position equality using .equals
		
		this.acceleration = new Point2D.Double(xcomp )*/
		/* bounds checking, the critter should not be able to move off the screen */
		double delta = this.directionMagnitude/Math.sqrt(this.direction.x * this.direction.x + 
				this.direction.y * this.direction.y);
		double newx = /*delta * */ this.direction.x +  this.position.x;
		if(newx < 0 || newx > 1)
			newx = this.position.x;
		double newy = /*delta * */  this.direction.y + this.position.y;
		if(newy < 0 || newy > 1)
			newy = this.position.y;
		this.position = new Point2D.Double(newx,newy );
	}
	/* (non-Javadoc)
	 * @see jone.Critter#getWeight()
	 */
	public double getWeight() {
		// TODO Auto-generated method stub
		return this.foodInStomach + weight;
	}
	final static double weight = 10;
	/* (non-Javadoc)
	 * @see jone.Animal#move()
	 */
	
	/* (non-Javadoc)
	 * @see jone.Critter#draw(java.awt.Graphics)
	 */
	public void draw(Graphics g) {
		
		ArenaPanel ap = ArenaPanel.getInstance();
		
		Graphics2D g2d = (Graphics2D) g;
		if(g == null)
			return;
	    // pull info out of graphics to know the start position of drawing
		int x = (int) (this.position.x * ap.width() - 9*(Sheep.radius/.05)); //+ ap.startX();
		int y = (int)(this.position.y * (ap.height()) -  1*(Sheep.radius/.05)); //+ ap.startY();
		if(Arena.debug)
		{
			//Arena.logger.debug("printing sheep at x = " + x + " y = " + y);
		}
		g2d.setPaint(Color.blue); 
		String sage = this.age + "";
		g2d.drawChars(sage.toCharArray()/*Sheep.viewChars*/,0,1,x,y); //  pull array out
			
		x = (int) ((this.position.x - GrassPatch.xd*Sheep.radius) * ap.width()); //+ ap.startX();
		y = (int) ((this.position.y - GrassPatch.yd*Sheep.radius) * (ap.height()));
		int r = (int) (Sheep.radius * ap.width()); // why doesnt adding to the height do ne thing
		if(Arena.debug)
		{
			//Arena.logger.debug("printing sheep oval at x = " + x + " y = " + y);
		}
		
		
		g2d.setStroke(new BasicStroke(2));
		g2d.drawOval(x,y,r,r);
		
	}
	/* (non-Javadoc)
	 * @see jone.Animal#move()
	 */
	
}
