package sema;

import modelling.Receptive;
import modelling.RectangularArea;
import modelling.Vision;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.Vector;
import java.util.Set;
import modelling.Selector;



/**
 * The Nature gouverning the world. It links the time and the space, and all the physics laws.
 * It manage the global event of the world, the broadcast of signals, the visibility, the rain etc ....
 */
public class Nature {
    
    /**
     *Constructs a Nature from the engine and a map : the Nature is a link between the time and the space.
     * Instancie une nature à partir d'un moteur et d'une carte (la nature est un lien entre temps et espace).
     */
    public Nature(Engine engine, Map map) {
        this.engine = engine;
        this.map = map;
    }
    
    private Engine engine;
    private Map map;
    
    
    /**
     * Returns the elements in the boxes intersected by the given area.
     * The efficientness depends also of the boxes thinness.
     */
    public TreeMap getElements(Area area){
        TreeMap cibles = new TreeMap();
        Vector boxes = area.getIntersectedBoxes();
        for (int i = 0; i<boxes.size(); i++) {
            Box box = (Box) boxes.get(i);
            Iterator iterator = box.getContents().iterator();
            while (iterator.hasNext()) {
                Element a = (Element) iterator.next();
                cibles.put(a.id, a);
            }
        }
        return cibles;
    }
    
    
    /**
     * Returns the distance between two elements.
     */
    public float getDistance( Element element1, Element element2){
        float x = element1.getArea().getX() - element2.getArea().getX();
        float y = element1.getArea().getY() - element2.getArea().getY();
        return (float) (Math.sqrt(x*x + y*y));
    }
    
    
    /**
     * Broadcast a signal in the world.
     */
    public void broadcastSignal(Signal sig) {
        
        TreeMap cibles = getElements(sig.getArea());
        Set cles = cibles.keySet();
        Iterator iterator = cles.iterator();
        while (iterator.hasNext()) {
            Agent ag = (Agent) cibles.get(iterator);
            if (sig.getSelector().isFiting(ag)) {
                try {Receptive receivingSignalsAgent = (Receptive) ag;
                receivingSignalsAgent.receiveSignal(sig);
                } catch (Exception e) {}
            }
        }
    }
    
    
    /**
     * Selector determinating if his element can see some other. Determinates also if some elements hide him.
     */
    public class CanSee extends Selector {
        
        public CanSee(Element ag){
            this.ag = ag;
        }
        
        private Element ag;
        
        public boolean isFiting(Element el){
            
            float x_length = el.getArea().getX() - ag.getArea().getX();
            float y_length = el.getArea().getY() - ag.getArea().getY();
            float distance = (float) Math.sqrt(x_length*x_length + y_length*y_length);
            float x = (el.getArea().getX()+ag.getArea().getX())/2;
            float y = (el.getArea().getY()+ag.getArea().getY())/2;
            float angle = (float) (Math.atan2(y_length, x_length));
            Area seeWay = new RectangularArea(map,x, y, distance, 0f, angle);
            TreeMap treemap = getElements(seeWay);
            treemap.remove(el.id);
            treemap.remove(ag.id);
            Set cles = treemap.keySet();
            Iterator iterator = cles.iterator();
            while (iterator.hasNext()) {
                try{
                    Element obstacle = (Element) treemap.get(iterator.next());
                    if (obstacle.getArea().intersect(el.getArea().getX(), el.getArea().getY(), ag.getArea().getX(), ag.getArea().getY()))
                        return false;
                } catch (Exception e) { System.out.println("Error in the Nature.CanSee - Incorrect casting");}
                
            }
            return true;
        }
    }
    
    /**
     * Method returning an implementation of the Selector CanSee
     */
    public CanSee getCanSee(Element el){
        return new CanSee(el);
    }
    
    /**
     * Generic {@link modelling.Selector } to select the elements of a given kind.
     */
    protected class GoodKind extends Selector {
        
        protected GoodKind(String kind){
            this.kind = kind;
        }
        
        String kind;
        
        public boolean isFiting(Element el) {
            if (el.getKind().equals(kind)) {
                return true;
            } else {return false;}
        }
    }
    
    /**
     * Returns an implementation of GoodKind.
     */
    public GoodKind getGoodKind(String kind){
        return new GoodKind (kind);
    }
    
    
    /**
     * Returns the properties of the nature.
     * @see Engine#getProperties()
     * @return the element's properties.
     */
    protected String[][] getProperties() {
        String[][] a = {};
        return(a);
    }
    
    /**
     * Try to sets the value to the property of the nature.
     * The new value is ignored if the property is read_only or if the value isn't valid.
     * @see Engine#setProperties
     * @param property the property to change.
     * @param value the new value for the property.
     */
    protected void setProperties(String property, String value) {
    }
    
}
