package org.openpixi.pixi.parallel.particleaccess;

import org.openpixi.pixi.physics.particles.Particle;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;

/**
 * Executes action upon particles in parallel using threads.
 *
 * Why do we use Callable interface instead of Runnable?
 * - Because we want to use the invokeAll() method of ExecutorService.
 * Why do we want to use the invokeAll() method and not the execute() or submit() method?
 * - Because execute() and submit() do not wait for the tasks to finish.
 */
public class ParallelParticleIterator implements ParticleIterator {

	/* These are exposed here for inner classes
	   since they can to be passed to them as method arguments */
	private ParticleAction action;
	private List<Particle> particles;

	private List<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
	private ExecutorService threadExecutor;

	public ParallelParticleIterator(int numOfThreads, ExecutorService threadExecutor) {
		this.threadExecutor = threadExecutor;
		for (int i = 0; i < numOfThreads; ++i) {
			tasks.add(new Task(i, numOfThreads));
		}
	}

	public void execute(List<Particle> particles, ParticleAction action) {
		this.action = action;
		this.particles = particles;

		try {
			threadExecutor.invokeAll(tasks);
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}

	private class Task implements Callable<Object> {

		private int threadIdx;
		private int numOfThreads;

		private Task(int threadIdx, int numOfThreads) {
			this.threadIdx = threadIdx;
			this.numOfThreads = numOfThreads;
		}

		public Object call() throws Exception {
			for (int particleIdx = threadIdx; particleIdx < particles.size(); particleIdx += numOfThreads) {
				action.execute(particles.get(particleIdx));
			}
			return null;
		}
	}
}
