/*
 * Mouton.java
 *
 * Created on 11 mai 2005, 15:16
 */

package examples;

import java.awt.Color;
import java.awt.Image;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
import modelling.CircularArea;
import modelling.Eatable;
import modelling.Receptive;
import modelling.RectangularArea;
import modelling.Satisfactor;
import modelling.Selector;
import sema.Area;
import sema.Element;
import sema.Signal;
import sema.World;


//C'est l'élément cadavre de mouton qui sera mangeable.
public class Mouton extends MovingAgent2 implements Receptive {
    
    public Mouton(World world, Area area, float delay, Image image) {
        super(world, area, delay, image, 3f, world.nature.getGoodKind("herbe"));
        setKind("mouton");
        this.hungrylevel = new IsHungry(10, 0, 30);
        this.consomption = new Consuming(60,3);
        this.currentAction = new Manger();
        this.imageCadavre = image;
        this.imageBouffant = image;
        this.imageCourante = image;
        this.imageNormale = image;
        setCompatibleElements();
        setDrawingPriority(5);
    }
    
    public Mouton(World world, Area area, float delay, Image image, Image imageCadavre) {
        this(world, area, delay, image);
        this.imageCadavre = imageCadavre;
    }
    
    public Mouton(World world, Area area, float delay, Image image, Image imageCadavre, Image imageBouffant) {
        this(world, area, delay, image);
        this.imageCadavre = imageCadavre;
        this.imageBouffant = imageBouffant;
    }
    
    
    protected Image imageCadavre;
    protected Image imageBouffant;
    protected Image imageCourante;
    protected Image imageNormale;
    
    
    public Image getImage(){
        return imageCourante;
    }
    
    private Action currentAction;
    
    public boolean enEveil = false;
    
    
    protected class PeutEtrePietine extends Selector {
        public PeutEtrePietine(){
            super();
        }
        
        public boolean isFiting(Element el){
            return (el.getKind().equals("herbe") || el.getKind().equals("cadavre") || el.getKind().equals("cadavreSec"));
        }
    }
    
    
    
    public void setCompatibleElements(){
        this.compatibleElements = new PeutEtrePietine();
    }
    
    
    
    protected void setCurrentAction(Action act){
        currentAction = act;
    }
    
    protected Action getCurrentAction(){
        return currentAction;
    }
    
    
    
    /**
     * Determinates whether the agent is currentyle able to receive this signal.
     */
    protected boolean canCatch(Signal sig){
        return true;
    }
    
    
    
    
    protected void sigToAction(Signal sig){
        if (sig.getContents().equals("loup") && !enEveil){
            enEveil = true;
            new AttentionAuLoup(getDelay()/2);
        }
        if (sig.getContents().equals("bê") && !enEveil){
            enEveil = true;
            new AttentionAuLoup2(getDelay()/2);
        }
        
    }
    
    /**
     * Receives a signal.
     * Eventually dismisses it if cannot be catch (canCatch method).
     */
    public void receiveSignal(Signal sig){
        if (canCatch(sig)) {
            sigToAction(sig);
        }
    }
    
    
    
    
    protected class MoveRandomly extends MoveTo{
        
        public MoveRandomly(float distance, float angle){
            super( Math.max(0,Math.min(getArea().getX() + (float) (distance * Math.cos(angle)),world.map.xLength - 1)),Math.max(0, Math.min(getArea().getY() + (float) (distance * Math.sin(angle)),world.map.yLength - 1)));
            this.distance = distance;
        }
        private float distance;
        
        public MoveRandomly(){
            this((float) Math.random()*10, (float) Math.random()*20);
        }
        
        public float step(){
            if (successiveFailures>3) {
                float angle = (float)Math.random()*20;
                x=(float)Math.max(0,Math.min(getArea().getX() + (float) (distance * Math.cos(angle)),world.map.xLength - 1));
                y=(float)Math.max(0, Math.min(getArea().getY() + (float) (distance * Math.sin(angle)),world.map.yLength - 1));
                successiveFailures = 0;
                randomize = false;
                failure = false;
                b = 0;
                return getDelay();
            } else return 3*super.step();
        }
        
        public void whenEnd(){
            setCurrentAction(new Manger());
        }
    }
    
    
    
    
    private class isNear extends Selector {
        
        public isNear(float x, float y, float radius){
            super();
            this.x = x;
            this.y = y;
            this.radius = radius;
        }
        
        float x;
        float y;
        float radius;
        
        public boolean isFiting(Element el){
            float xd = el.getArea().getX() - x;
            float yd = el.getArea().getY() - y;
            return (Math.sqrt(xd*xd + yd*yd) < radius);
        }
    }
    
    
    
    private Selector isHerbe = world.nature.getGoodKind("herbe");
    
    
    
    private Selector personnalVision;
    
    float visionRadius = 13f;
    
    /**
     * Returns the vision radius of the element.
     */
    protected float getVisionRadius(){
        return visionRadius;
    }
    
    /**
     * Determinates the area where the element is watching.
     */
    protected Area getAreaOfVision(){
        float x = getArea().getX();
        float y = getArea().getY();
        return new RectangularArea(world.map, x, y, 2*getVisionRadius(), 2*getVisionRadius(), getArea().getAngle());
    }
    
    protected Area getLineVision(float angle){
        float x = (float) (getArea().getX()+Math.cos(angle)*getVisionRadius()/2);
        float y = (float) (getArea().getY()+Math.sin(angle)*getVisionRadius()/2);
        return new RectangularArea(world.map, x, y, getVisionRadius(), 0, angle);//AREA.KLENGTH
    }
    
    
    
    
    /**
     * Determinates whether the given element can be seen by our agent, according to
     */
    public boolean canSee(Element element){
        return personnalVision.isFiting(element) && (world.nature.getDistance(this, element) < getVisionRadius());
    }
    
    
    /**
     * Returns the visible elements, with the restrictions of the vision ability.
     */
    public Vector watch(){
        Vector seen = new Vector();
        TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
        Set cles = areaOfVision.keySet();
        Iterator iterator = cles.iterator();
        while (iterator.hasNext()) {
            Element element = (Element) areaOfVision.get(iterator.next());
            if (canSee(element))
                seen.add(element);
        }
        return seen;
        
    }
    
    /**
     * Returns the nearest visible element
     */
    public Element watchNearest(){
        float nearest = Float.MAX_VALUE;
        Element curentNearest = null;
        TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
        Set cles = areaOfVision.keySet();
        Iterator iterator = cles.iterator();
        while (iterator.hasNext()) {
            Element element = (Element) areaOfVision.get(iterator.next());
            float distance = world.nature.getDistance(element, this);
            if ((distance < nearest) && personnalVision.isFiting(element)) {
                curentNearest = element;
                nearest = (float) distance;
            }
        }
        return curentNearest;
    }
    
    
    /**
     * Returns the nearest element responding to the criterium induced by the given Selector <br>
     * in the vision field of the agent.
     */
    public Vector watchFor(Selector sel){
        Vector seen = new Vector();
        TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
        Set cles = areaOfVision.keySet();
        Iterator iterator = cles.iterator();
        while (iterator.hasNext()) {
            Element element = (Element) areaOfVision.get(iterator.next());
            if (canSee(element) && sel.isFiting(element))
                seen.add(element);
        }
        return seen;
    }
    
    
    /**
     * Returns the nearest visible element fiting the given selector.
     */
    public Element watchForNearest(Selector sel){
        float nearest = Float.MAX_VALUE;
        Element curentNearest = null;
        TreeMap areaOfVision = world.nature.getElements(getAreaOfVision());
        Set cles = areaOfVision.keySet();
        Iterator iterator = cles.iterator();
        while (iterator.hasNext()) {
            Element element = (Element) areaOfVision.get(iterator.next());;
            float distance = world.nature.getDistance(element, this);
            if ((distance < nearest) && sel.isFiting(element) ) {
                curentNearest = element;
                nearest = (float) distance;
            }
        }
        return curentNearest;
    }
    
    
    
    
    
    
    
    
    
    protected class IsHungry extends Satisfactor {
        
        
        /**
         * Constructor for a countable hunger.
         */
        public IsHungry(float hunger, float hungryFloor, float hungryCeil){
            super();
            this.hunger = hunger;
            this.hungryFloor = hungryFloor;
            this.hungryCeil = hungryCeil;
            this.unfiniteHungry = false;
        }
        
        
        /**
         * Constructor for an infinite hunger
         */
        public IsHungry(){
            this.unfiniteHungry = true;
        };
        
        protected float hunger;
        protected float hungryFloor;
        protected float hungryCeil;
        protected boolean unfiniteHungry;
        
        
        
        /**
         * Returns wether the agent may still eat.
         */
        public boolean isSatisfying(){
            return (hunger > hungryCeil)|| unfiniteHungry ;
        }
        
        /**
         * Informs of the achievement of this goal.
         */
        public int heuristic(){
            return (int) Math.floor(hungryCeil - hunger);
        }
        
        
        /**
         * Return the amongs of food avaiable.
         */
        public float getReserves() {
            return (float) (hunger - hungryFloor);
        }
        
        /**
         * Allows to set the current hunger.
         */
        public void changeHunger(float difference){
            hunger += difference;
        }
    }
    
    
    /**
     * Action making the hunger regulary increase.
     */
    protected class Consuming extends Action {
        
        public Consuming(float delay, float among){
            super(Math.random()*20);
            this.delay = delay;
            this.among = among;
        }
        
        private float delay;
        private float among;
        
        protected float step() {
            getHungryLevel().changeHunger(- among);
            return delay;
        }
    }
    
    
    protected IsHungry hungrylevel;
    protected Consuming consomption;
    
    /**
     *
     */
    protected IsHungry getHungryLevel() {
        return hungrylevel;
    }
    
    protected Action getConsomption() {
        return consomption ;
    }
    
    
    /**
     * Allows to get an among of nutrition.
     */
    protected void eat(float x) {
        hungrylevel.changeHunger(x);
    }
    /**
     * Allows to consum an among of nutrition.
     */
    protected void consums(float x) {
        hungrylevel.changeHunger(- x);
    }
    
    /**
     * Action of eating a given element.
     */
    protected class EatElement extends Action {
        
        public EatElement(Element goal){
            super(getDelay());
            this.goal = goal;
        }
        /**
         * Eated element.
         */
        protected Element goal;
        /**
         * One step of the eating. Controls if it is still possible, and eat.
         */
        protected float step() {
            //si on est trop loin de l'herbe, ou qu'on en a marre de manger,
            if ((world.nature.getDistance(Mouton.this, goal) > 2) ||(Math.random() > 0.85)) {
                setCurrentAction(new MoveRandomly());
                return -1;
            }else
                try{ Eatable a = (Eatable) goal;
                if (a.canEat(Mouton.this)){
                    getHungryLevel().changeHunger(a.eaten(Mouton.this)/3);
                    return (float) Math.random() * 7;
                }else{
                    setCurrentAction(new Dormir());
                    return -1;
                }
                } catch (Exception e) {
                    setCurrentAction(new Dormir());
                    return -1;
                }
        }
        
        
        public void close(){
            imageCourante = imageNormale;
        }
        
        
        
    }
    
    
    
    
    
    
    
    
    
    
    public class Manger extends Action {
        
        public Manger(){
            super(getDelay());
        }
        
        
        public float step(){
            Herbe herbe = (Herbe) watchForNearest(isHerbe);
            if (!(herbe == null)){
                float distance = world.nature.getDistance(Mouton.this, herbe);
                if (distance < 0.5){
                    //on peut la bouffer!!!!
                    imageCourante = imageBouffant;
                    setCurrentAction(new EatElement(herbe));
                    return -1;
                }else{
                    float x = herbe.getArea().getX();
                    float y = herbe.getArea().getY();
                    orientate(x,y);
                    walk();//va vers le mouton
                    return stepDelay;
                }
            }else{
                if (Math.random() > 0.9) {
                    setCurrentAction(new Dormir());
                    return -1;
                }
                setCurrentAction(new MoveRandomly());
                return -1;
            }
        }
    }
    
    
    public class Dormir extends Action {
        
        public Dormir(){
            super(getDelay());
            this.dureeDuSommeil = 0;
        }
        
        private float dureeDuSommeil;
        
        public float step(){
            dureeDuSommeil += 100;
            if ((dureeDuSommeil > 200f + 100*Math.random()) || (hungrylevel.getReserves() < 0f)) {
                setCurrentAction(new Manger());
                return -1;
            }else{
                return 100;
            }
        }
    }
    
    // Si le mouton est mangé par le loup, il crée un élément carcasse qui contient de la viande.
    public boolean kill(Element el){
        new CadavreMouton(world, new CircularArea2(world.map, getArea().getX(), getArea().getY(), 0.8f, 0),Math.min(hungrylevel.hunger,50), imageCadavre);
        super.kill(el);
        return true;
    }
    
    
    
    
    public class AttentionAuLoup extends Action {
        
        public AttentionAuLoup(){
            super();
        }
        
        public AttentionAuLoup(double t){
            super(t);
            firstTime = world.engine.getTime();
        }
        
        double firstTime;
        public float firstStep(){
            enEveil = true;
            try{
                Dormir dodo = (Dormir) getCurrentAction();
                
                if (Math.random() > 0.2)
                    return -1; //une chance sur 10 de se réveiller.
            } catch (Exception e) {}
            Loup predator = (Loup) watchForNearest(world.nature.getGoodKind("loup"));
            if (predator!=null){
                getCurrentAction().stop();
                setCurrentAction(this);
                world.nature.broadcastSignal(new Message2(world.map, "bê" , 9f , getArea().getX() , getArea().getY(), world.nature.getGoodKind("mouton")));
                return step();
            }else
                
                return -1;
            
            
            
        }
        
        
        
        public float step(){
            if (world.engine.getTime()-firstTime>5*(float)Math.random()+8) {
                setCurrentAction(new MoveRandomly());
                return -1;                
            }
            try{
                Loup predator = (Loup) watchForNearest(world.nature.getGoodKind("loup"));
                float distance = world.nature.getDistance(predator, Mouton.this);
                float x = getArea().getX()+(.5f+.5f*(float)Math.random())*(getArea().getX() - predator.getArea().getX());
                float y = getArea().getY()+(.5f+.5f*(float)Math.random())*(getArea().getY() - predator.getArea().getY());
                orientate(x,y);
                walk();//fuit le loup
                return stepDelay/2;
                
                
            }catch (Exception e){
                setCurrentAction(new MoveRandomly());
                return -1;
            }
        }
        
        protected void close(){
            enEveil = false;
        }
        
    }
    
    
    
    public class AttentionAuLoup2 extends Action {
        
        public AttentionAuLoup2(){
            super();
        }
        
        public AttentionAuLoup2(double t){
            super(t);
            firstTime = world.engine.getTime();
        }
        
        double firstTime;
        
        
        public float firstStep(){
            enEveil = true;
            try{
                Dormir dodo = (Dormir) getCurrentAction();
                
                if (Math.random() > 0.3)
                    return -1; //une chance sur 10 de se réveiller.
            } catch (Exception e) {}
            Loup predator = (Loup) watchForNearest(world.nature.getGoodKind("loup"));
            if (predator!=null){
                getCurrentAction().stop();
                setCurrentAction(this);
                return step();
            }else
                
                return -1;
            
            
            
        }
        
        
        
        public float step(){
            if (world.engine.getTime()-firstTime>5*(float)Math.random()+8) {
                setCurrentAction(new MoveRandomly());
                return -1;                
            }
            try{
                Loup predator = (Loup) watchForNearest(world.nature.getGoodKind("loup"));
                float distance = world.nature.getDistance(predator, Mouton.this);
                float x = getArea().getX()+(.5f+.5f*(float)Math.random())*(getArea().getX() - predator.getArea().getX());
                float y = getArea().getY()+(.5f+.5f*(float)Math.random())*(getArea().getY() - predator.getArea().getY());
                orientate(x,y);
                walk();//fuit le loup
                return stepDelay/2;
                
                
            }catch (Exception e){
                setCurrentAction(new MoveRandomly());
                return -1;
            }
        }
        
        protected void close(){
            enEveil = false;
        }
        
    }
    
    
    
    public String getActivity(){
        currentAction.getClass();
        if (currentAction.getClass() == Dormir.class){
            return "Dormir";}
        if (currentAction.getClass() == Manger.class){
            return "Chercher de l'herbe";}
        if (currentAction.getClass() == MoveRandomly.class){
            return "Déambuler";}
        if (currentAction.getClass() == EatElement.class){
            return "Brouter";}
        if (currentAction.getClass() == AttentionAuLoup.class){
            return "Fuir un loup";}
        if (currentAction.getClass() == AttentionAuLoup2.class){
            return "Fuir un loup car un copain a bêlé";} else{return "Je ne sais pas ce que je fais";}
    }
    
    
    
    
    public String[][] getProperties() {
        String[][] ret0 = getArea().getProperties();
        String[][] ret = new String[7][2];
        String[] p1 = {"kind",getKind()};
        String[] p2 = {"Activité", getActivity()};
        String[] p3 = {"Réserves d'énergie", new Float(hungrylevel.hunger).toString()};
        String[] p4 = {"Vigilant", (new Boolean(enEveil)).toString()};
        String[] p5 = {"height",new Float(getHeight()).toString()};
        String[] p6 = {"Drawing priority",new Float(getDrawingPriority()).toString()};
        String[] p7 = {"ID",id.toString()};
        ret[0] = p1; ret[1] = p2; ret[2] = p3; ret[3] = p4; ret[4] = p5; ret[5]=p6;ret[6] = p7;
        return(union(ret0,ret)); //propriétés de l'area et de l'élément
    }
    
    
    
    public void setProperties(String property, String value) {
        try {
            getArea().setProperties(property,value);
            if (property.equals("height")) {
                setHeight(Float.parseFloat(value));
            } else
                if (property.equals("kind")) {
                setKind(value);
                } else
                    if (property.equals("Drawing priority")) {
                float priority = Float.parseFloat(value);
                setDrawingPriority(priority);
                
                    }
        } catch (Exception e) {}
    }
    
    
    public void draw() {
        world.drawing.setColor(Color.BLUE);
        if (getCurrentAction().getClass() == Dormir.class){
            float backA = getArea().getAngle();
            getArea().setAngle(0);
            super.draw();
            getArea().setAngle(backA);
            world.drawing.drawString("Zzz",getArea().getX(), getArea().getY()-.5f);
        } else
            if (getCurrentAction().getClass() ==Manger.class){
            super.draw();
            world.drawing.drawString("?",getArea().getX(), getArea().getY()-.5f);
            } else
                if (getCurrentAction().getClass() ==EatElement.class){
            float backA = getArea().getAngle();
            getArea().setAngle(0);
            super.draw();
            getArea().setAngle(backA);
            world.drawing.drawString("Mmm",getArea().getX(), getArea().getY()-.5f);
                } else
                    if (getCurrentAction().getClass() == AttentionAuLoup.class){
            super.draw();
            world.drawing.setColor(Color.RED);
            world.drawing.drawString("BEE!!", getArea().getX(), getArea().getY()-.5f);
                    } else
                        if (getCurrentAction().getClass() == AttentionAuLoup2.class){
            super.draw();
            world.drawing.setColor(Color.RED);
            world.drawing.drawString("Bê!", getArea().getX(), getArea().getY()-.5f);
                        } else {
            super.draw();
            world.drawing.setColor(Color.BLUE);
            world.drawing.drawString("...", getArea().getX(), getArea().getY()-.5f);
                        }
        if (isMarked())
            getArea().highlight(world.drawing,new Color(0,1,0,.3f));
    }
    
    
}

