package sema;

import java.awt.Color;
import java.util.Vector;
import javax.swing.Icon;
import java.awt.Image;
import java.util.Collection;
import java.util.TreeMap;


/**
 * An element is the basic world entity.
 * They are every thing in the world (excepted the immaterial things, such as: the {@link Nature}, the {@link Signal} and the {@link Event }).<br>
 * The <code>Element</code> class is principaly meant to implement inanimate things of the world,
 * in the meanwhile, the {@link Agent} extends the possibilities of an element. <br>
 * The elements have the encapsulated basic event : {@link Element.ElementEvent } which provides basic functionalities.
 */
public class Element  implements Comparable {
    
    /**
     * Constructs an element of the specified world and register itself to this world.
     * @param world the world which it have to register to.
     * @param area the area of it's 'body'.
     * @param image the main image of the element : used as an identity icon.
     * @see Area
     */
    public Element(World world, Area area, Image image) {
	this.id = world.register(this);
	this.world = world;
	kind = "object";
	this.area = area;
	area.setElement(this);
	this.image = image;
    }
    
    /**
     * World in which is this element.
     */
    public final World world;
    
    /**
     * ID (in a world, there is unicity of the ID).
     */
    public final Integer id;
    
    /**
     * Champs privés.
     */
    private Area area;
    private String kind;
    private Image image;
    private float height = 0;
    private float drawingPriority = 0;
    
    /**
     * Returns the drawing priority.
     * The world elements are drawn by growing drawing priority,
     * so that the element with the greatest priority is on the top and entirely visible.
     * By default, it is 0.
     * @return the drawing priority.
     * @see Drawing
     */
    public float getDrawingPriority(){
	return drawingPriority;
    }
    /**
     * Sets the drawing priority
     * The world elements are drawn by growing priority,
     * so that the element with the greatest priority is finally on the top and entirely visible.
     * @param priority the drawing priority.
     */
    protected void setDrawingPriority(float priority){
	drawingPriority = priority;
    }
    
    /**
     * Returns the height of the element.
     * By default it is 0.
     * @return  The height of the element.
     */
    public float getHeight(){
	return height;
    }
    /**
     * Sets the height of the element.
     * @param h the height of the element.
     */
    protected void setHeight(float h){
	height = h;
    }
    
    /**
     * Returns the kind of the element for example : "ant".
     * By default : "object".
     * @return the kind of the element
     */
    public String getKind(){
	return kind;
    }
    /**
     * Sets the kind of the element for example : "ant".
     * @param s the kind of the element.
     */
    protected void setKind(String s){
	kind = s;
    }
    
    /**
     * Returns the name.
     * @return the name.
     */
    public String getName() {
	return (kind+id.toString());
    }
    
    /**
     * Returns the main image.
     * @return the main image.
     * @see Element#Element(World world, Area area, Image image)
     */
    public Image getImage(){
	return image;
    }
    
    
    /**
     * Returns the Area.
     * @return the Area.
     * @see Area
     */
    public Area getArea() {
	return area;
    }
    
    private boolean alive = true;
    
    /**
     * Mother class for the event of an element.
     * It encapsulates an automatic verification of the death before executing the action,
     * and uses only a delay to register itself (relative time rather than absolute date).
     * @see Event
     */
    protected abstract class ElementEvent extends Event {
	
	/**
	 * Constructs an event registered at the engine associated to the world of the element.
	 * @param relativeDate the date of the event relative to the date of the register.
	 */
	public ElementEvent(double relativeDate) {
	    super(relativeDate+world.engine.getTime(),world.engine);
	}
	/**
	 * The code of the event.
	 * @return whether it executed.
	 */
	public boolean execute(){
	    if (alive)
		return super.execute();
	    return false;
	}
	
	protected void whatToDo() {
	}
    }
    
    

    
    
    /**
     * Determinates whether the given element can take this one.
     * @param e the element wanting to take this one.
     * @return whether the given element can take this one.
     */
    public boolean canTake(Element e) {
	return true;
    }
    
    
    /**
     *  Death of the element.
     *  Is called by itself only.
     *  Unregister it from its occupied boxes, from the world and prevent every event it has planned to arrive.
     */
    protected void death() {
	world.unregister(this);
	alive = false;
	Vector intersectedBoxes = getArea().getIntersectedBoxes();
	for (int i=0; i<intersectedBoxes.size(); i++) {
	    {
		((Box) intersectedBoxes.get(i)).unregister(this);
	    }
	}
    }
    
    
    /**
     * Tries to kill this element by the specified one.
     * The killer can be null which correspond to a call by the interface to delete it.
     * @param killer the element trying to kill this one.
     * @return the success of the murder.
     */
    public boolean kill(Element killer){
	death();
	return true;
    }
    
    /**
     * Draws the element.
     * @see Drawing
     */
    public void draw() {
	getArea().draw(world.drawing);
	if (isMarked())
	    getArea().highlight(world.drawing,new Color(0,1,0,.3f));
    }
    
    /**
     * Highlights the element (used to draw that the element is selected in the interface).
     */
    public void highlight(Color c) {
	getArea().highlight(world.drawing, c);
    }
    public void highlight() {
	getArea().highlight(world.drawing, new Color(1,0,0,0.5f));	
    }
    
    private boolean marked = false;
    /**
     * Sets the marked property.
     * It indicates that the element should draw itself differently,
     * it permit in the interface to visually follow a set of element.
     * @param b the marked property.
     */
    public void setMarked(boolean b){
	marked = b;
    }
    /**
     * Returns whether the element is marked.
     * @return whether the element is marked.
     */
    public boolean isMarked(){
	return marked;
    }
    
    /**
     * Returns the associated icon.
     * It has to be an icon of dimension smaller than 100x100.
     * It is used as the identity icon in the graphical interface.<br>
     * By default it is the main image resized and transformed into an icon.
     * @return the associated icon.
     * @see javax.swing.Icon
     */
    public Icon getIcon(){
	if (image !=null) {
	    float f = Math.max((float)image.getWidth(null)/100,(float)image.getHeight(null)/100);
	    if (f==0) f=1;
	    int x = (int)(image.getWidth(null)/f);
	    int y = (int)(image.getHeight(null)/f);
	    return(new javax.swing.ImageIcon(image.getScaledInstance(x,y,0)));
	}
	return null;
    }
    
    /**
     * Returns the element's properties.
     * @see Engine#getProperties()
     * @return the element's properties.
     */
    public String[][] getProperties() {
	String[][] ret0 = getArea().getProperties();
	String[][] ret = new String[4][2];
	String[] p1 = {"kind",getKind()};
	String[] p2 = {"height",new Float(getHeight()).toString()};
	String[] p3 = {"Drawing priority",new Float(getDrawingPriority()).toString()};
	String[] p4 = {"ID",id.toString()};
	ret[0] = p1; ret[1] = p2; ret[2] = p3; ret[3] = p4;
	return(union(ret0,ret)); //propriétés de l'area et de l'élément
    }
    
    
    public static String[][] union(String[][] p1, String[][] p2) { //fait l'union de deux listes de propriétés
	int n1 = p1.length;
	int n2 = p2.length;
	String[][] np = new String[n1+n2][2];
	for (int i=0; i<n1;i++) {
	    np[i] = p1[i];
	}
	for (int i=0; i<n2;i++) {
	    np[n1+i] = p2[i];
	}
	return(np);
    }
    
    
    /**
     * Tries to set the value to the property of the element.
     * 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.
     */
    public void setProperties(String property, String value) {
	try {
	    area.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) {}
    }
    
    /**
     * Returns the String representing the element.
     * By default it is the name.
     * @return the String representing the element.
     */
    public String toString(){
	return getName();
    }
    
    /**
     * Used to compare two elements.
     * Sorted by growing drawing priority, and if equality, by growing ID.
     * @param o the element to compare to this one.
     * @return {@link Comparable}
     */
    public int compareTo(Object o) {
	Element el = null;
	try { el=(Element) o;} catch (Exception e) {return(-1);};
	if (this.getDrawingPriority()>el.getDrawingPriority()) {
	    return(1);
	} else
	    if (this.getDrawingPriority()<el.getDrawingPriority()) {
	    return(-1);
	    } else return(this.id.compareTo(el.id));
    }
    
}