package com.gemserk.componentsengine.entities;

import java.util.*;
import java.util.Map.Entry;

import com.gemserk.componentsengine.components.*;
import com.gemserk.componentsengine.messages.Message;
import com.gemserk.componentsengine.properties.*;
import com.google.common.base.Predicate;
import com.google.common.collect.*;

public class Entity implements PropertiesHolder, MessageHandler, ComponentsHolder {

	private final String id;

	PropertiesHolder propertiesHolder = new PropertiesHolderImpl();

	ComponentsHolderImpl componentsHolder = new ComponentsHolderImpl();

	Set<String> tags = new HashSet<String>();

	protected Entity parent = null;

	protected Map<String, Entity> children = new LinkedHashMap<String, Entity>(100);

	static public Multiset<String> times = HashMultiset.create();

	public Entity(String id) {
		this.id = id;
	}

	public String getId() {
		return id;
	}

	public Map<String, Entity> getChildren() {
		return children;
	}

	public void addComponent(Component component) {
		componentsHolder.addComponent(component);
		component.onAdd(this);
	}

	public void addComponents(Component... components) {
		for (Component component : components) {
			this.addComponent(component);
		}
	}

	public Component findComponentByName(String name) {
		return componentsHolder.getComponents().get(name);
	}

	public void addProperty(String key, Property<Object> value) {
		propertiesHolder.addProperty(key, value);
	}

	public Property<Object> getProperty(String key) {
		return propertiesHolder.getProperty(key);
	}

	public void handleMessage(Message message) {

		for (Entry<String, Component> entry : componentsHolder.getComponents().entrySet()) {
			if(!message.shouldProcess())
				break;
			
			Component component = entry.getValue();
			long iniTime = System.currentTimeMillis();
			component.handleMessage(message);
			long endTime = System.currentTimeMillis() - iniTime;
			times.add(component.getId(), (int) endTime);
		}

		if (message.shouldPropagate()) {
			for (Entity child : children.values()) {
				child.handleMessage(message);
			}
		}
		
		message.enablePropagation();
		message.enableProcessing();
	}

	public boolean hasTag(String tag) {
		return this.tags.contains(tag);
	}

	public void setTags(Set<String> tags) {
		this.tags = tags;
	}

	public Set<String> getTags() {
		return tags;
	}

	public void addTag(String tag) {
		tags.add(tag);
	}

	public void addTags(String... tags) {
		for (String tag : tags)
			addTag(tag);
	}

	@Override
	public String toString() {
		return "Entity [id=" + id + ", tags=" + tags + ", components=" + componentsHolder.getComponents() + ", properties=" + propertiesHolder.getProperties() + ", children: " + children.size() + "]";
	}

	public Map<String, Property<Object>> getProperties() {
		return propertiesHolder.getProperties();
	}

	public void addEntity(Entity entity) {
		this.children.put(entity.getId(), entity);
		entity.parent = this;
	}

	public void removeEntity(Entity entity) {
		this.children.remove(entity.getId());
		entity.parent = null;
	}

	public Collection<Entity> getEntities(Predicate<? super Entity> predicate) {
		return Lists.newArrayList(this.getChildrenIterable(predicate));
	}

	public Iterable<Entity> getChildrenIterable(Predicate<? super Entity> predicate) {
		List<Iterable<Entity>> iterables = new ArrayList<Iterable<Entity>>(this.children.size());

		for (Entity child : children.values()) {
			Iterable<Entity> childIterable = child.getChildrenIterable(predicate);
			iterables.add(childIterable);
		}

		if (predicate.apply(this))
			iterables.add(Lists.newArrayList(this));

		return Iterables.concat(iterables);
	}

	public Entity getEntityById(String id) {
		Entity entity = children.get(id);

		if (entity != null)
			return entity;

		for (Entry<String, Entity> entry : children.entrySet()) {
			Entity child = entry.getValue();
			entity = child.getEntityById(id);
			if (entity != null)
				return entity;
		}

		return null;
	}

	public void removeFromParent() {
		if (parent != null)
			parent.removeEntity(this);
	}

	public Entity getRoot() {
		if (parent == null)
			return this;

		return parent.getRoot();
	}
}
