package ecs.core;

import res.display.FrameBuffer;

using Type;

class Engine {
	final entities:Map<Int, Entity> = [];
	final namedEntities:Map<String, Array<Entity>> = [];
	final systems:Array<System> = [];
	final systemsMap:Map<String, System> = [];

	public function new() {}

	function addToEngine(entity:Entity) {
		if (!entities.exists(entity.id)) {
			entity.engine = this;

			this.entities[entity.id] = entity;

			if (entity.name != null) {
				if (namedEntities[entity.name] == null)
					namedEntities[entity.name] = [];

				namedEntities[entity.name].push(entity);
			}
		}
	}

	public function rematch(entity:Entity) {
		var hasMatchingSystems:Bool = false;

		for (system in systems) {
			if (entity.matchSystem(system)) {
				if (!system.has(entity)) {
					system.add(entity);
				}
				hasMatchingSystems = true;
			} else {
				if (system.has(entity)) {
					system.remove(entity);
				}
			}
		}

		if (hasMatchingSystems) {
			addToEngine(entity);
		} else
			remove(entity);
	}

	public function add(...entities:Entity) {
		for (entity in entities) {
			var hasMatchedSystem:Bool = false;

			for (system in systems) {
				if (entity.matchSystem(system)) {
					hasMatchedSystem = true;
					system.add(entity);
				}
			}

			if (hasMatchedSystem) {
				addToEngine(entity);
			} else
				trace('No matching system');
		}
	}

	public function getByName(name:String):Array<Entity> {
		return namedEntities[name] == null ? [] : namedEntities[name];
	}

	public function inEngine(entity:Entity) {
		return entities.exists(entity.id);
	}

	public function remove(...entities:Entity) {
		for (entity in entities) {
			for (system in systems) {
				system.remove(entity);
			}

			this.entities.remove(entity.id);

			if (entity.name != null) {
				namedEntities[entity.name].remove(entity);
			}
		}
	}

	public function addSystem(system:System) {
		final className = system.getClass().getClassName();

		if (!systemsMap.exists(className)) {
			this.systems.push(system);
			system.engine = this;

			systemsMap[className] = system;
		}

		return system;
	}

	public function addSystems(...systems:System) {
		for (system in systems) {
			addSystem(system);
		}
	}

	public function queryEntities(components:Array<Class<Component>>):Array<Entity> {
		final result = [];

		for (entity in entities) {
			if (entity.hasComponents(components))
				result.push(entity);
		}

		return result;
	}

	public function getSystem<T>(systemClass:Class<T>):T {
		return cast systemsMap[systemClass.getClassName()];
	}

	public function update(dt:Float) {
		for (system in systems)
			if (system.enabled && system.updatable)
				system.update(dt);
	}

	public function render(frameBuffer:FrameBuffer) {
		for (system in systems)
			if (system.enabled && system.renderable)
				system.render(frameBuffer);
	}
}
