package modelling;

import java.awt.Image;
import java.util.Vector;
import sema.Agent;
import sema.Agent.Action;
import sema.Area;
import sema.Box;
import sema.World;

/**
 * Agent with abilities to move.
 */
public class MovingAgent extends Agent{
    
    /**
     * Receives a speed and the paramaters of a normal agent.
     */
    public MovingAgent(World world, Area area, float delay, Image image, float speed) {
        super(world,area,delay,image);
        setSpeed(speed);
        new MoveTo(19,19,.1f);
    }
    
    private float speed;
    /**
     * Sets the speed of the agent.
     */
    protected void setSpeed(float speed) {
        this.speed = speed;
        stepLength = Math.min(speed*getDelay(),world.map.boxLength/1.5f);
        stepDelay = stepLength/speed;
    }
    
    /**
     * Returns the speed of the agent.
     */
    public final float getSpeed() {
        return speed;
    }
    
    private float stepLength;
    private float stepDelay;
    
    
    protected boolean acceptAction(Action a) {
        return true;
    }
    
    
    /**
     * A default action to errate on the map without  verification of the collisions with other elements.
     */
    protected class Errer extends Action {
        public Errer() {
	    
	}
        public Errer(double t) {
            super(t);
        }
        
        public float step() { //déplacement aléatoire
            rotate((float)Math.random());
            walk(stepLength);
            return getDelay();
        }
    }
    
    /**
     * Translation with collision check.
     */
    protected boolean translate(float x, float y){
        if (!getArea().exactIsCrashingElements(getArea().simuleTranslate(x,y))) {getArea().confirmSimuled();
        return true;
        }
        return false;
    }
    
    /**
     * Rotation with collision check.
     */
    protected boolean rotate(float a){
        if (!getArea().exactIsCrashingElements(getArea().simuleRotate(a)))
        {getArea().confirmSimuled();
        return true;
        }
        return false;
    }
    /**
     * Walk in the direction gived by the element angle, checking collisions.
     */
    protected boolean walk(float f){
        return translate(f*(float)Math.cos(this.getArea().getAngle()),f*(float)Math.sin(this.getArea().getAngle()));
    }
    
    
    /**
     * Chooses the best direction where to move (where the largest move is possibleà and walk one step in this direction.
     */
    protected boolean fitStep(){
        float bestAngle = 0;
        float bestDistance = 0;
        for (int i =0; i<8;i++){
            float d = canSee(i*(float)Math.PI/4);
            if (d>bestDistance) {
                bestAngle = i*(float)Math.PI/4;
                bestDistance = d;
            }
        }
        rotate(bestAngle-getArea().getAngle());
        return walk(stepLength);
    }
    
    /**
     * Returns the maximum distance that can be seen in this direction.
     * Used to detect obstacles.
     */
    protected float canSee(float angle){
        float visionRadius = world.map.xLength+world.map.xLength; //looks evrywhere
        float xUp = getArea().getX()+visionRadius*(float)Math.cos(angle)/2;;
        float yUp = getArea().getY()+visionRadius*(float)Math.sin(angle)/2;;
        Area a = new modelling.RectangularArea(world.map,xUp, yUp,visionRadius,1,angle);
        Vector v = a.getIntersectedBoxes();
        float maxDist = Float.MAX_VALUE;
        float bl = world.map.boxLength;
        for (int i = 0; i<v.size(); i++){
            Box b = (Box) v.get(i);
            float dist = (float)Math.sqrt((b.x*bl-getArea().getX())*(b.x*bl-getArea().getX())+(b.y*bl-getArea().getY())*(b.y*bl-getArea().getY()));
            if (dist<maxDist)
                if (b.getContents().size()>1 || !b.isFreeFor(this)|| b.getContents().size()==1 && !(b.getContents().contains(this)))
                    maxDist = dist;
        }
        return maxDist;
    }
    
    
    protected boolean randomStep(float a){
        rotate((2*(float)Math.random()-1)*(float)Math.PI/2-a-getArea().getAngle());
        return walk((float)Math.random()*stepLength);
    }
    
    /**
     * Moves to the given point.
     */
    protected class MoveTo extends Action{
        public MoveTo(float x, float y, float radius){
            this.x = x;
            this.y = y;
            this.radius = radius;
        }
        
        protected float a=0;
        protected float x;
        protected float y;
        protected float radius;
        protected int successiveFailures = 0;
        
        public float step(){
            this.a = (float)Math.atan2(y-getArea().getY(),x-getArea().getX());
            if (Math.random()/(1+successiveFailures)<.1) {
                if (randomStep(a));
                successiveFailures--; if (successiveFailures<0) successiveFailures = 0;
                return stepDelay*(float)Math.max(1/(1+successiveFailures),1/10);
            }
            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<getSpeed()*getDelay()) ? x - getArea().getX() : stepLength*(x-getArea().getX())/d;
            float ymove = (yy<getSpeed()*getDelay()) ? y - getArea().getY() : stepLength*(y-getArea().getY())/d;
            if (translate(xmove, ymove))
                successiveFailures = successiveFailures/2;
            else {successiveFailures++;
            this.a = (float)Math.atan2(y-getArea().getY(),x-getArea().getX());
            }
            return stepDelay;
        }
        
    }
    
}
