package se.feomedia.orion.system;

import com.artemis.*;
import com.artemis.systems.IteratingSystem;
import com.badlogic.gdx.math.OrionKryoSerialization;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import com.esotericsoftware.kryo.Kryo;
import se.feomedia.orion.Executor;
import se.feomedia.orion.Operation;
import se.feomedia.orion.OperationTree;
import se.feomedia.orion.component.Operative;

import static com.artemis.Aspect.all;

public class OperationSystem extends IteratingSystem {
	private ComponentMapper<Operative> operativeMapper;

	private ObjectMap<Class<? extends Executor>, Executor> executors = new ObjectMap<>();

	private Operative voidEntityOperations = new Operative();
	public Kryo kryo;

	public OperationSystem() {
		super(all(Operative.class));
	}

	/**
	 * Constructor with specialized aspect. Can be used to add <code>exclude</code>
	 * components for entities otherwise marked as disabled.
	 *
	 * @param base Useful for excluding disabled entities in projects.
	 */
	public OperationSystem(Aspect.Builder base) {
		super(base.all(Operative.class));
	}

	@Override
	protected void initialize() {
		kryo = new Kryo();
		OrionKryoSerialization.configure(kryo);
	}

	public <T extends Operation> T copy(T operation) {
		return kryo.copy(operation);
	}

	public Array<OperationTree> getVoidEntityOperations() {
		return voidEntityOperations.operations;
	}

	public void register(int entityId, OperationTree operation) {
		Operative operative = operativeMapper.create(entityId);
		if (operative != null) { // null == pending deletion
			operative.operations.add(operation);
			operation.initialize(this, entityId);
		}
	}

	public void register(OperationTree operation) {
		operation.initialize(this, -1);
		voidEntityOperations.operations.add(operation);
	}

	@Override
	protected void inserted(int entityId) {
		Array<OperationTree> operations = operativeMapper.get(entityId).operations;
		for (int i = 0, s = operations.size; s > i; i++) {
			operations.get(i).initialize(this, entityId);
		}
	}

	public Executor getExecutor(Operation operation, OperationTree.Friend friend) {
		friend.hashCode();

		Executor executor = executors.get(operation.executorType());
		if (executor == null) {
			executor = createExecutor(operation);
			executors.put(operation.executorType(), executor);
		}

		return executor;
	}

	private Executor createExecutor(Operation operation) {
		try {
			Executor executor = operation.executorType().newInstance();

			world.inject(executor);
			executor.initialize(world);

			return executor;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	protected void process(int e) {
		Array<OperationTree> operations = operativeMapper.get(e).operations;
		process(operations);

		if (operations.size == 0) //hehe, 1.2.0 bug... world.edit(e).remove(Operative.class);
			operativeMapper.remove(e);
	}

	private void process(Array<OperationTree> operations) {
		for (int i = 0; operations.size > i; i++) {
			OperationTree ot = operations.get(i);
			ot.act(world.delta);
			if (ot.isComplete()) {
				OperationTree node = operations.removeIndex(i--);
				node.clear();
			}
		}
	}

	@Override
	protected void end() {
		process(voidEntityOperations.operations);
	}

	@Override
	protected void removed(int entityId) {
		clear(entityId);
	}

	public void clear() {
		clear(voidEntityOperations.operations);
	}

	public void clear(int entityId) {
		if (!operativeMapper.has(entityId))
			return;

		Array<OperationTree> operations = operativeMapper.get(entityId).operations;
		clear(operations);

		if (world.getEntityManager().isActive(entityId))
			operativeMapper.remove(entityId);
	}

	private static void clear(Array<OperationTree> operations) {
		for (int i = 0; i < operations.size; i++) {
			operations.get(i).clear();
		}

		operations.clear();
	}
}