package examples;

import java.awt.Image;
import java.util.Iterator;
import java.util.Vector;
import modelling.Selector;
import sema.Agent;
import sema.Agent.Action;
import sema.Area;
import sema.Box;
import sema.Element;
import sema.World;

/**
 * Un movingAgent prend éventuellement en argument un selector qui renvoie true ssi l'élément en question peut être sur la même case que lui.
 * Par défaut, lorsqu'il ne sont pas précisés en paramètre d'une fonction de déplacement, le pas vaut stepLength et la direction = angle de l'agent.
 *
 * Résumé des principales fonctions utiles:
 * -l'action MoveTo permet d'aller à un endroit
 * -l'action Errer permet de se déplacer aléatoirement
 * -l'action MoveToRandomGoals se dirige vers un point, puis un autre, puis un autre
 *  ce qui en fait une fonction Errer souvent plus intéressante!
 *
 * -walk permet de faire un pas dans une direction ou vers un point. Essaye de faire un pas aléatoire en cas d'échec.walk est bcp - intelligent que MoveTo.
 * -randomStep fait un pas aléatoire
 * -translate, rotate et orientate permettent de faire ce que vous imaginez, en gérant les collisions.
 *
 * NORMALEMENT VOUS NE DEVEZ *PAS* PASSER PAR getArea(), tout est centralisé ici.
 * Si vous avez besoin de le faire c'est qu'il manque quelquechose dans mon code!
 */
public class MovingAgent2 extends Agent{
    
    public static class AllElements extends Selector{
        public boolean isFiting(Element e){
            return true;
        }
    }
    
    public MovingAgent2(World world, Area area, float delay, Image image, float speed) {
        this(world,area,delay,image,speed,new AllElements());
    }
    
    public MovingAgent2(World world, Area area, float delay, Image image, float speed, Selector compatibleElements) {
        super(world,area,delay,image);
        setSpeed(speed);
        this.compatibleElements = compatibleElements;
    }
    
    private float speed;
    protected void setSpeed(float speed) {
        this.speed = speed;
        stepLength = Math.min(speed*getDelay(),world.map.boxLength/2.5f);
        stepDelay = stepLength/speed;
    }
    
    public final float getSpeed() {
        return speed;
    }
    
    private float stepLength;
    protected float stepDelay;
    protected Selector compatibleElements;
    
    protected boolean acceptAction(Action a) {
        return true;
    }
    
    protected boolean isCrashingElements() {
        Vector v = getArea().getIntersectedBoxes();
        for (int i = 0; i<v.size(); i++){
            Box b = (Box) v.get(i);
            if (!b.isFreeFor(this)) return true;
            Iterator it =b.getContents().iterator();
            while (it.hasNext()) {
                Element a = (Element) it.next();
                if (a!=this && !compatibleElements.isFiting(a))
                    return true;
            }
        }
        return(false);
    }
    
    
    protected class Errer extends Action {
        public Errer() {
        }
        public Errer(double t) {
            super(t);
        }
        
        public float step() { //déplacement aléatoire
            randomStep();
            return getDelay();
        }
    }
    
    protected boolean translate(float x, float y){
        if (getArea().translate(x,y)) {
            if (isCrashingElements() || !getArea().inMap())
                getArea().translate(-x, -y);
            else
                return true;
        }
        return false;
    }
    
    protected boolean rotate(float a){
        if (getArea().rotate(a)) {
            if (isCrashingElements() || !getArea().inMap())
                getArea().rotate(-a);
            else
                return true;
        }
        return false;
    }
    
    protected boolean orientate(float x, float y){
        return orientate((float)Math.atan2(y-getArea().getY(),x-getArea().getX()));
    }
    
    protected boolean orientate(float a){
        float backAngle = getArea().getAngle();
        if (getArea().setAngle(a)) {
            if (isCrashingElements() || !getArea().inMap())
                getArea().setAngle(backAngle);
            else
                return true;
        }
        return false;
    }
    
    protected boolean walk(){
        return walk(stepLength);
    }
    
    protected boolean walk(float f){
        return walk(f,getArea().getAngle());
    }
    
    protected boolean walk(float f, float a){
        if (translate(f*(float)Math.cos(a),f*(float)Math.sin(a)))
            return true;
        return randomStep();
    }
    
    protected boolean randomStep(){
        float a = (float)(2*Math.PI*Math.random());
        float f = stepLength;
        if (translate(f*(float)Math.cos(a),f*(float)Math.sin(a))) {
            orientate(a);
            return true;
        }
        return false;
    }
    
    
    
    protected class MoveTo extends Action{
        public MoveTo(float x, float y, float radius){
            this.x = x;
            this.y = y;
            this.radius = radius;
            start();
        }
        public MoveTo(float x, float y){
            this(x,y,0);
        }
        
        protected float a=0;
        protected float x;
        protected float y;
        protected float radius;
        protected int successiveFailures = 0;
        protected boolean randomize = false;
        protected boolean failure = false;
        float b = 0;
        
        private boolean randomStep(){
            if (!randomize)
                if (successiveFailures>4)
                    b = (float)(3/2*Math.PI*Math.random()+a+Math.PI/4);
                else {
                if (Math.random()>.5)
                    b = (float)(Math.PI/2*Math.random()+a+Math.PI/4);
                else
                    b = (float)(Math.PI/2*Math.random()+a+5*Math.PI/4);
                }
            if (walk(stepLength, b)) {
                rotate(b-getArea().getAngle());
                return true;
            }
            randomize = false;
            return false;
        }
        
        public float step(){
            this.a = (float)Math.atan2(y-getArea().getY(),x-getArea().getX());
            if (failure) {
                if (this.randomStep()) {
                    successiveFailures = successiveFailures/2;
                    if (Math.random()*successiveFailures<4) {
                        failure = false;
                    }
                    return stepDelay;}
                failure = false;
                successiveFailures = successiveFailures+1;
                return stepDelay;
            }
            rotate(a-getArea().getAngle());
            float xx = Math.abs(x - getArea().getX());
            float yy = Math.abs(y - getArea().getY());
            float d = (float)Math.sqrt(xx*xx+yy*yy);
            if (d<=radius) return -1;
            float xmove = (xx<stepLength) ? x - getArea().getX() : stepLength*(x-getArea().getX())/d;
            float ymove = (yy<stepLength) ? y - getArea().getY() : stepLength*(y-getArea().getY())/d;
            if (translate(xmove, ymove))
                failure = false;
            else {failure = true;
            successiveFailures++;
            this.a = (float)Math.atan2(y-getArea().getY(),x-getArea().getX());
            }
            return stepDelay;
        }
    }
    
    
    protected class MoveToRandomGoals extends MoveTo{
        public MoveToRandomGoals(float radius){
            super((float)Math.random()*(world.map.xLength-2*radius)+radius,(float)Math.random()*(world.map.yLength-2*radius)+radius,radius);
        }
        
        public float step(){
            float a = super.step();
            if (successiveFailures>5 || a<0) {
                a=0;
                x=(float)Math.random()*(world.map.xLength-2*radius)+radius;
                y=(float)Math.random()*(world.map.xLength-2*radius)+radius;
                successiveFailures = 0;
                randomize = false;
                failure = false;
                b = 0;
                return getDelay();
            }
            return a;
        }
    }
    
    protected class MoveToRandomGoal extends MoveTo{
        public MoveToRandomGoal(float radius){
            super((float)Math.random()*(world.map.xLength-2*radius)+radius,(float)Math.random()*(world.map.yLength-2*radius)+radius,radius);
        }
        public float step(){
            if (successiveFailures>5)
                return -1;
            else return super.step();
        }
    }
    
    
}
