package CK.IA;

public class Entity implements Comparable<Entity> {

	private static int id_counter = 0;
	protected EnvironmentAgent environment;
	protected float health;
	protected int id;
	protected boolean paused;
	protected float pitch, roll, yaw;
	protected Vector3d position;
	protected float radius;
	protected EntityType type;

	/**
	 * Constructs a new entity.
	 * 
	 * @param health
	 *            The initial health of this entity.
	 * @param position
	 *            The initial position of this entity.
	 * @param radius
	 *            The initial radius of this entity.
	 * @param type
	 *            The type of this entity.
	 * @param environment
	 *            The environment where this entity resides.
	 */
	public Entity(float health, Vector3d position, float radius, EntityType type, EnvironmentAgent environment) {
		this.position = position;
		this.environment = environment;
		this.radius = radius;
		this.id = id_counter;
		this.health = health;
		this.type = type;
		this.paused = true;
		id_counter++;
		this.pitch = 0.0f;
		this.roll = 0.0f;
		this.yaw = 0.0f;
	}

	/**
	 * Compares this entity's ID to the given one.
	 */
	public int compareTo(Entity o) {
		int my_id = this.getId();
		int others_id = o.getId();
		if (my_id < others_id)
			return -1;
		else if (my_id == others_id)
			return 0;
		else
			return +1;
	}

	/**
	 * Damages this entity, i.e., it's health is reduced by the given amount.
	 * 
	 * @param damage
	 *            The amount of damage to be caused.
	 */
	public void damage(Float damage) {
		this.health -= damage;
		if (this.health < 0) {
			this.health = 0.0f;
		}
	}

	private void debugStatus(String text) {
		if (GoalSimulatorConfig.DEBUG)
			System.out.println("Entity:" + this.toString() + " " + text);
	}

	/**
	 * Checks if this entity and another one are the same.
	 * 
	 * @param e
	 *            The entity to compare to.
	 * @return True if the entities are the same (only if they have the same ID).
	 */
	public boolean equals(Entity e) {
		return this.getId() == e.getId();
	}

	/**
	 * Executes a single moment of this entity. Basically does nothing, because this entity has no activity.
	 */
	public void executeCycle() {
		if (GoalSimulatorConfig.DEBUG)
			this.debugStatus("executeCycle()");
	}

	/**
	 * Returns the environment where this entity lives.
	 * 
	 * @return The environment.
	 */
	public EnvironmentAgent getEnvironment() {
		return environment;
	}

	/**
	 * Gets this entity's health.
	 * 
	 * @return The health.
	 */
	public float getHealth() {
		return health;
	}

	/**
	 * Returns this entity's identifier. The identifier is a unique integer for each entity.
	 * 
	 * @return the entity's ID.
	 */
	public int getId() {
		return id;
	}

	/**
	 * Returns this entity's pitch (rotation on plane xOy).
	 * 
	 * @return the pitch on radians.
	 */
	public float getPitch() {
		return pitch;
	}

	/**
	 * Returns this entity's position on the environment.
	 * 
	 * @return the position.
	 */
	public Vector3d getPosition() {
		return position;
	}

	/**
	 * Returns this entity's radius.
	 * 
	 * @return the radius.
	 */
	public float getRadius() {
		return radius;
	}

	/**
	 * Returns this entity's roll (rotation on plane yOz).
	 * 
	 * @return the roll on radians
	 */
	public float getRoll() {
		return roll;
	}

	/**
	 * Returns this entity (specialization) type.
	 * 
	 * @return it's type.
	 */
	public EntityType getType() {
		return type;
	}

	/**
	 * Returns this entity's yaw (rotation on plane xOz).
	 * 
	 * @return the yaw on radians.
	 */
	public float getYaw() {
		return yaw;
	}

	/**
	 * Returns a unique hashing code for each entity, so it returns this entity's ID.
	 * 
	 * @return this entity's ID.
	 */
	public int hashcode() {
		return this.getId();
	}

	/**
	 * Returns true if this entity is has health lower or equal to zero.
	 * 
	 * @return true if this entity has no health.
	 */
	public boolean isDead() {
		return this.getHealth() <= 0.0f;
	}

	/**
	 * Returns true if this entity has it's execution paused.
	 * 
	 * @return true if it is paused.
	 */
	public boolean isPaused() {
		return paused;
	}

	/**
	 * Updates this entity's structures according to the given message (if it is an information), and also answers the caller about the message, if it
	 * is a request.
	 * 
	 * @param original
	 *            The message to decode.
	 * @return True if the message was successfully decoded. False otherwise.
	 */
	public boolean receiveMessage(Message original) {
		boolean message_processed = false;
		// this.debugStatus("receiveMessage() " + original.toString());
		// the message must be for us
		if (original.getDestination_id() == this.getId()) {
			if (original.getPerformative() == Performative.REQUEST) {
				// about an entity
				if (original.getSubject() == Subject.ID_ENTITY) {
					// requiring it's class
					if (original.getProperty() == Property.ENTITY) {
						// and the entity is us
						if (original.getSubject_id() == this.getId()) {
							// reply to the sender with our class as content
							Message answer = new Message(Performative.INFORM, Subject.ID_ENTITY, Property.ENTITY, this.getId(), original
									.getSender_id(), this.getId());
							answer.setContent(this);
							sendMessage(answer);
							message_processed = true;
						}
					}
				}
			} else
			// is it an information?
			if (original.getPerformative() == Performative.INFORM) {
				// is it regarding us?
				if (original.getSubject() == Subject.ID_ENTITY && original.getSubject_id() == this.getId()) {
					// is it about our health?
					if (original.getProperty() == Property.HEALTH_FLOAT) {
						this.setHealth((Float) original.getContent());
						message_processed = true;
					} else
					// is it about our position?
					if (original.getProperty() == Property.POSITION_VECTOR3D) {
						this.setPosition((Vector3d) original.getContent());
						message_processed = true;
					} else
					// is it about our pause status?
					if (original.getProperty() == Property.PAUSED_BOOLEAN) {
						this.setPaused((Boolean) original.getContent());
						message_processed = true;
					} else
					// is it about our yaw?
					if (original.getProperty() == Property.YAW_FLOAT) {
						this.setYaw((Float) original.getContent());
						message_processed = true;
					}
					// is it some damage I've received?
					else {
						if (original.getProperty() == Property.DAMAGE_FLOAT) {
							this.damage((Float) original.getContent());
							message_processed = true;
						}
					}
				}
			}
		}
		/**
		 * if (!message_processed) { System.out.println("Entity:SOMETHING's MISSING HERE!!!"); }
		 **/
		return message_processed;
	}

	/**
	 * sends the message to the environment
	 * 
	 * @param m
	 */
	public void sendMessage(Message m) {
		environment.processMessage(m);
	}

	/**
	 * Sets this entity's health to the given value.
	 * 
	 * @param health
	 *            the health to set to.
	 */
	public void setHealth(float health) {
		this.health = health;
	}

	/**
	 * Pauses this entity's execution.
	 * 
	 * @param paused
	 *            true to pause, false otherwise.
	 */
	public void setPaused(boolean paused) {
		this.paused = paused;
	}

	/**
	 * Sets this entity's pitch (rotation on plane xOy).
	 * 
	 * @param pitch
	 *            the new pitch on radians.
	 */
	public void setPitch(float pitch) {
		this.pitch = pitch;
	}

	/**
	 * Sets this entity's position on the environment.
	 * 
	 * @param position
	 *            the new position.
	 */
	public void setPosition(Vector3d position) {
		this.position = position;
	}

	/**
	 * Sets this entity's radius.
	 * 
	 * @param radius
	 *            the radius.
	 */
	public void setRadius(float radius) {
		this.radius = radius;
	}

	/**
	 * Sets this entity's roll (rotation on plane yOz).
	 * 
	 * @param roll
	 *            the roll on radians
	 */
	public void setRoll(float roll) {
		this.roll = roll;
	}

	/**
	 * Sets this entity (specialization) type.
	 * 
	 * @param type
	 *            it's type.
	 */
	public void setType(EntityType type) {
		this.type = type;
	}

	/**
	 * Sets this entity's yaw (rotation on plane xOz).
	 * 
	 * @param yaw
	 *            the yaw on radians.
	 */
	public void setYaw(float yaw) {
		this.yaw = yaw;
	}

	public String toString() {
		Vector3d position2 = this.getPosition();
		String string = position2 == null ? "" : position2.toString();
		return "Entity:" + this.getId() + " type:" + this.getType() + " radius:" + this.getRadius() + " position:" + string + " paused:"
				+ this.isPaused();
	}
}
