#include <v_lib.h>
#include <stdlib.h>

#define	BBOX_MIN			{(vec_t)-1e-3,(vec_t)-1e-3,0}
#define	BBOX_MAX			{(vec_t)1e-3,(vec_t)1e-3,0}

#define	NUM_PARTICLES		256

typedef struct
{
	vec_t	radius;
	vec_t	mass;
	vec_t	charge;
	vec3_t	pos;
	vec3_t	velocity;
}	particle_t, *particle_p;

/*
 * For given particles and distance, return force modulus
 */
vec_t	GetForceModulus(particle_t *p1,particle_t *p2,vec_t dist)
{
#define	U0		1e-12
#define	r0		1e-5
	vec_t rd = (vec_t)1.0 / dist;
	vec_t df = (vec_t)(r0 * rd);
	vec_t p6, p12;
	/* First derivative of Lennard - jones potential: U=U0*(df^12 - 2*df^6) */
	p6 = df * df * df;
	p6 *= p6;
	p12 = p6 * p6;
	return (vec_t)(12 * U0 * rd * (p6 - p12));
}

/*
 * Returns force acting to particle #p1 from particle #p2
 */
void	GetForce(vec3_t dest,particle_t *p1,particle_t *p2)
{
	vec_t dist;
	vec3Sub(dest,p2->pos,p1->pos);
	dist = vec3Abs(dest) - (p1->radius + p2->radius);
	if(dist < FLOAT_ZERO_EPSILON)
		dist = FLOAT_ZERO_EPSILON;
	vec3Scale(dest,dest,GetForceModulus(p1,p2,dist));
}

vec_t	SpeedLimit(vec_t force_modulus,vec_t viscosity,vec_t radius)
{
	return force_modulus / (6 * M_PI * viscosity * radius);
}

vec_t	FrictionForce(vec_t viscosity,vec_t radius,vec_t speed_modulus)
{
	return 6 * M_PI * viscosity * radius * speed_modulus;
}

/*
 * All particles
 */
particle_t	particles[NUM_PARTICLES];
/*
 * Viscousity of the environment
 */
vec_t		env_viscousity = (vec_t)1e-6;
/*
 * Density of particles
 */
vec_t		particles_density = (vec_t)2.65;
/*
 * Sizes of particles
 */
vec_t		psize_min = (vec_t)1e-6, psize_max = (vec_t)10e-6;

#define		f_epsilon	(vec_t)1e-25
#define		dt			(vec_t)1e-5

u32_t	frame, moved, inertial;

void MoveParticle(int particle)
{
	int p;
	vec3_t	f, sum = {0,0,0};
	vec_t	spd_modulus, spd_limit, f_mod;
	particle_t *p1 = particles + particle;
	for(p=0;p<particle;p++)
	{
		GetForce(f, p1, particles + p);
		vec3Add(sum,sum,f);
	}
	for(p=particle+1;p<NUM_PARTICLES;p++)
	{
		GetForce(f, p1, particles + p);
		vec3Add(sum,sum,f);
	}
	/* Now, re - calculate speed; it cannot exceed speed limit */
	f_mod = vec3Abs(sum);
	if(f_mod > f_epsilon)
	{
		spd_modulus = dt * f_mod / p1->mass;
		spd_limit = SpeedLimit(f_mod,env_viscousity,p1->radius);
		if(spd_modulus < spd_limit)
		{
			vec_t ff;
			ff = FrictionForce(env_viscousity,p1->radius,spd_modulus);
			f_mod -= ff;
			V_ASSERT(f_mod > 0);
			spd_modulus = dt * f_mod / p1->mass;
			inertial++;
		}
		else
			spd_modulus = spd_limit;
		/* Update particle position */
		vec3Unit(sum,sum); /* direction of movement */
		vec3Scale(sum,sum,dt * spd_modulus);
		vec3Add(p1->pos,p1->pos,sum);
		moved++;
	}
}

vec_t FloatRandom(vec_t min,vec_t max)
{
	vec_t scale = (vec_t)1 / (vec_t)RAND_MAX;
	return min + (max - min) * scale * rand();
}

void	PlaceParticles()
{
	int i;
	particle_t *p, *endp;
	vec3_t min = BBOX_MIN, max = BBOX_MAX;
	p = particles;
	endp = p + NUM_PARTICLES;
	/* Distribute particles uniformly */
	for(;p < endp;p++)
	{
		p->charge = 0;
		p->radius = FloatRandom(psize_min, psize_max);
		p->mass = 4.0f / 3.0f * M_PI * p->radius * p->radius * p->radius * particles_density;
		for(i=0;i<3;i++)
			p->pos[i] = FloatRandom(min[i],max[i]);
		vec3Zero(p->velocity);
	}
}

void	MoveParticles()
{
	int p;
	moved=inertial=0;
	for(p=0;p < NUM_PARTICLES;p++)
		MoveParticle(p);
	conlPrintf(LOG_TRACE,
		"MoveParticles(): %u particles moved (%u inertial) in frame %u\n",moved,inertial,frame);
	frame++;
}

void	DrawParticles()
{
	vec_t alpha;
	particle_t *p, *endp;
	p = particles;
	endp = p + NUM_PARTICLES;
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POINTS);
	for(;p < endp;p++)
	{
		alpha = (p->radius - psize_min) / (psize_max - psize_min);
		glColor4f(alpha,1,1,.5f + alpha * .5f);
		glVertex3f(p->pos[0],p->pos[1],p->pos[2]);
	}
	glEnd();
}

int main()
{
	input_record_t in;
	vboolean pause, done;
	vec3_t min = BBOX_MIN, max = BBOX_MAX;
	
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	
	vglCreateWindow("particles",0,0,512,512);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(min[0],max[0],min[1],max[1],-1,1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glScalef(.9f,.9f,1);

	glColor4f(1,1,1,0.5f);
	glPointSize((vec_t)3.5);
	glEnable(GL_POINT_SMOOTH);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);

	PlaceParticles();
	pause = true;
	done = false;

	conPuts("[P] pauses, [ESC] exits\n");
	
	while(!done)
	{
		if(!pause)
			MoveParticles();
		while(vglPending())
		{
			vglNextEvent(&in);
			switch(in.event)
			{
				case ev_key_down:
					switch(in.first_code)
					{
						case key_P:
						case key_pause:
							pause = !pause;
							break;
						case key_escape:
							done = true;
							break;
						default:
							break;
					}
					break;
				default:
					break;
			}
		}
		timeDelay(10);
		DrawParticles();
		vglSwapBuffers();
	}

	vglDestroyWindow(vglGetCurrentWindow());
	return 0;
}
