use Time;
use Random;
use BlockDist;
use ReplicatedDist;

use Vec3;
use Particle;

config const noParticles = 1000; // "config" makes these constants to be set via command line args
config const noIterations = 1000;

proc printModel(const ref model) {
	var i = 0;
	for p in model do {
		writeln(i, "X: ", p.location.x);
		i+=1;
		if (i >= 10) then break;
	}
}

proc main() {
	var timer: Timer;
	timer.start();
	param delta_t = 1e-5;

	const Dbase = {0..#noParticles}; // default-distributed domain
	const D: domain(1) dmapped Block(Dbase)=Dbase; // block-mapped distribution (similar to OpenMP schedule static)
	var model : [D] Particle;
	const Ddist = Dbase dmapped ReplicatedDist(); // replicated distribution
	var repModel : [Ddist] Particle; // this will be a copy of model, replicated to each locale for performance
	
	// init Particles random
	var realStream = new RandomStream();
	forall pIdx in D do { // this is nicely parallelized to multiple locales
		model[pIdx].mass = 1.0;
		model[pIdx].location.x = realStream.getNext()*2-1;	// range [-1,+1]
		model[pIdx].location.y = realStream.getNext()*2-1; 
		model[pIdx].location.z = realStream.getNext()*2-1;
	}
	delete realStream;
	repModel = model;

	writeln("\nInitialization done.");
	printModel(model);

	for step in 0..#noIterations do {
		// calc forces, update velocity and location
		forall p1 in model do { // parallel
			var force: Vec3; // to be accumulated
			for p2 in repModel do {
				const dist = Distance(p1.location, p2.location);
				if (dist > 1e-8) then {
					const ref f = (p1.mass * p2.mass) / dist**2; // f = G *((m1 * m2) / r^2)
					const ref f_direction = (p1.location - p2.location) / dist;
					force += f_direction * f;
				}
			}
		  const acceleration = force / p1.mass;
		  p1.velocity += acceleration * delta_t;
			p1.location += p1.velocity * delta_t;
		}
		repModel = model;
	}
	
	writeln("End Simulation");
	printModel(model);
	const elapsed = timer.elapsed();
	writeln('Execution time:', elapsed);
}
