#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include "galaxy.h"

int extraproc = -1, throttle;

static QLock *golock;
static Rendez *gorend;
static int *go;

static QLock runninglock;
static Rendez runningrend;
static int running;

static void
calcproc(void *v)
{
	Body *b, *e;
	int nbody;
	int pid;

	pid = (uintptr)v;

	for(;;) {
		qlock(golock+pid);
		while(!go[pid])
			rsleep(gorend+pid);
		go[pid] = 0;
		qunlock(golock+pid);

		nbody = glxy.nb / (extraproc+1);
		b = glxy.a + nbody * pid;
		e = b + nbody;
		while(b < e) {
			b->a.x = b->newa.x;
			b->a.y = b->newa.y;
			b->newa.x = b->newa.y = 0;
			quadcalc(b++, space, LIM);
		}	

		qlock(&runninglock);
		if(--running == 0)
			rwakeup(&runningrend);
		qunlock(&runninglock);
	}
}

static void
startprocs(void)
{
	int pid;

	golock = calloc(extraproc, sizeof(*golock));
	if(golock == nil)
		sysfatal("Could not create go locks: %r");

	gorend = calloc(extraproc, sizeof(*gorend));
	if(gorend == nil)
		sysfatal("Could not create go rendez: %r");

	go = calloc(extraproc, sizeof(*go));
	if(go == nil)
		sysfatal("Could not create go flags: %r");

	for(pid = 0; pid < extraproc; pid++)
		gorend[pid].l = golock+pid;
	runningrend.l = &runninglock;

	for(pid = 0; pid < extraproc; pid++) 
		proccreate(calcproc, (void*)pid, STK);
}

/* verlet barnes-hut */
void
simulate(void*)
{
	Body *b, *s;
	int nbody, pid;
	double f;

	threadsetname("simulate");

	startprocs();

	for(;;) {
		qlock(&glxy);

		if(throttle)
			sleep(throttle);

		drawglxy();

Again:
		space.t = EMPTY;
		quads.l = 0;
		STATS(quaddepth = 0;)
		for(b = glxy.a; b < glxy.a + glxy.nb; b++) {
			if(quadins(b, LIM) == -1) {
				growquads();
				goto Again;
			}
		}

		running = extraproc;
		for(pid = 0; pid < extraproc; pid++) {
			qlock(golock+pid);
			go[pid] = 1;
			rwakeup(gorend+pid);
			qunlock(golock+pid);
		}

		STATS(avgcalcs = 0;)
		nbody = glxy.nb / (extraproc+1);
		s = glxy.a + nbody * (extraproc);
		for(b = s; b < glxy.a + glxy.nb; b++) {
			b->a.x = b->newa.x;
			b->a.y = b->newa.y;
			b->newa.x = b->newa.y = 0;
			STATS(calcs = 0;)
			quadcalc(b, space, LIM);
			STATS(avgcalcs += calcs;)
		}
		STATS(avgcalcs /= glxy.nb;)

		qlock(&runninglock);
		while(running > 0)
			rsleep(&runningrend);
		qunlock(&runninglock);

		for(b = glxy.a; b < glxy.a + glxy.nb; b++) {
			b->x += dt*b->v.x + dt²*b->a.x/2;
			b->y += dt*b->v.y + dt²*b->a.y/2;
			b->v.x += dt*(b->a.x + b->newa.x)/2;
			b->v.y += dt*(b->a.y + b->newa.y)/2;
			CHECKLIM(b, f);
		}

		qunlock(&glxy);
	}
}
