#include "psmod.h"

// fixme: may be it's better to store particle parameters to
// separate place (ps->ctrl_extra_args[] ?)

#define PS_CUR_RADIUS(ps)	PS_CUR_PARTICLE(ps).radius
#define PS_CUR_MASS(ps)		PS_CUR_PARTICLE(ps).mass

void ufxFree(unary_effect_p fx)
{
	fx->destroy(fx);
	memFree(fx);
}

/*
 * Force effect
 */

typedef struct
{
	unary_effect_t 		base;
	vec3_controller_p	force;
}	force_fx_t, *force_fx_p;

V_LOCAL vboolean
force_fx_entry(struct unary_effect_s *self,particle_system_p ps,vec3_t dest)
{
	CALL_VEC3_CTL(((force_fx_p)self)->force,ps,dest);
	return true;
}

V_LOCAL void
force_fx_destroy(struct unary_effect_s *self)
{
	if(((force_fx_p)self)->force)
		ctlFree(((force_fx_p)self)->force);
}

vboolean
ufxForceEffect(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(ufxForceEffect)
	force_fx_p fx = (force_fx_p)memAllocZ(sizeof(force_fx_t));
	V_ASSERT(fx);
	fx->base.entry = force_fx_entry;
	fx->base.destroy = force_fx_destroy;
	fx->base.update = NULL;
	fx->force = ctlParseVec3(script);
	if(!fx->force)
	{
		ERROR_GENERIC("error parsing force controller");
		ufxFree((unary_effect_p)fx);
		return false;
	}
	vpsPush(&ps->unary_effects,fx);
	return true;
}

/*
 * Accel effect
 */

typedef struct
{
	unary_effect_t 		base;
	vec3_controller_p	accel;
}	accel_fx_t, *accel_fx_p;

V_LOCAL vboolean
accel_fx_entry(struct unary_effect_s *self,particle_system_p ps,vec3_t dest)
{
	CALL_VEC3_CTL(((accel_fx_p)self)->accel,ps,dest);
	/* F = m*a */
	vec3Scale(dest,dest,PS_CUR_MASS(ps));
	return true;
}

V_LOCAL void
accel_fx_destroy(struct unary_effect_s *self)
{
	if(((accel_fx_p)self)->accel)
		ctlFree(((accel_fx_p)self)->accel);
}

vboolean
ufxAccelEffect(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(ufxAccelEffect)
	accel_fx_p fx = (accel_fx_p)memAllocZ(sizeof(accel_fx_t));
	V_ASSERT(fx);
	fx->base.entry = accel_fx_entry;
	fx->base.destroy = accel_fx_destroy;
	fx->base.update = NULL;
	fx->accel = ctlParseVec3(script);
	if(!fx->accel)
	{
		ERROR_GENERIC("error parsing accel controller");
		ufxFree((unary_effect_p)fx);
		return false;
	}
	vpsPush(&ps->unary_effects,fx);
	return true;
}

/*
 * Boundary plane effect
 */

typedef struct
{
	unary_effect_t base;
	vec3_controller_p	normal;
	float_controller_p	dist;
	vec3_t		cur_normal;
	vec_t		cur_dist;
	float_controller_p	force;
}	plane_fx_t, *plane_fx_p;

V_LOCAL vboolean
plane_fx_update(struct unary_effect_s *self,particle_system_p ps)
{
	CALLER_NAME(plane_fx_update)
	vec_t nl;
	plane_fx_p fx = (plane_fx_p)self;
	CALL_VEC3_CTL(fx->normal,ps,fx->cur_normal);
	fx->cur_dist = CALL_FLOAT_CTL(fx->dist,ps);
	nl = vec3Dot(fx->cur_normal,fx->cur_normal);
	if(!IsZero(nl-1))
	{
		/* normalize */
		if(IsZero(nl))
		{
			WARNING_GENERIC("degenerate plane");
			return false;
		}
		nl = 1 / sqrt(nl);
		vec3Scale(fx->cur_normal,fx->cur_normal,nl);
	}
	return true;
}

V_LOCAL vboolean
plane_fx_entry(struct unary_effect_s *self,particle_system_p ps,vec3_t dest)
{
	CALLER_NAME(plane_fx_entry)
	plane_fx_p fx = (plane_fx_p)self;
	vec_t dist, force, saved_arg;
	/* distance from plane */
	dist = vec3Dot(fx->cur_normal,PS_CUR_POS(ps)) - fx->cur_dist - PS_CUR_RADIUS(ps);
	if(dist < MIN_DIST)
	{
		WARNING_GENERIC("particle too close to plane");
		return false;
	}
	saved_arg = ps->ctl_args[0];
	ps->ctl_args[0] = dist;
	force = CALL_FLOAT_CTL(fx->force,ps);
	ps->ctl_args[0] = saved_arg;
	vec3Scale(dest,fx->cur_normal,force);
	return true;
}

V_LOCAL void
plane_fx_destroy(struct unary_effect_s *self)
{
	plane_fx_p fx = (plane_fx_p)self;
	if(fx->normal)
		ctlFree(fx->normal);
	if(fx->dist)
		ctlFree(fx->dist);
	if(fx->force)
		ctlFree(fx->force);
}

vboolean ufxPlaneEffect(vhandle_t script,particle_system_p ps)
{
	plane_fx_p fx = (plane_fx_p)memAllocZ(sizeof(plane_fx_t));
	V_ASSERT(fx);
	fx->base.entry = plane_fx_entry;
	fx->base.destroy = plane_fx_destroy;
	fx->base.update = plane_fx_update;
	fx->normal = ctlParseVec3(script);
	fx->dist = ctlParseFloat(script);
	fx->force = ctlParseFloat(script);
	if(!fx->normal || !fx->dist || !fx->force)
	{
		ufxFree((unary_effect_p)fx);
		return false;
	}
	vpsPush(&ps->unary_effects,fx);
	return true;
}

/*
 * Boudary sphere effect
 */

typedef struct
{
	unary_effect_t base;
	vboolean solid;
	vec3_controller_p	pos;
	float_controller_p	radius;
	vec3_t	cur_pos;
	vec_t	cur_radius;
	float_controller_p	force;
}	sphere_fx_t, *sphere_fx_p;

V_LOCAL vboolean
sphere_fx_update(struct unary_effect_s *self,particle_system_p ps)
{
	sphere_fx_p fx = (sphere_fx_p)self;
	CALL_VEC3_CTL(fx->pos,ps,fx->cur_pos);
	fx->cur_radius = CALL_FLOAT_CTL(fx->radius,ps);
	return true;
}

V_LOCAL vboolean
sphere_fx_entry(struct unary_effect_s *self,particle_system_p ps,vec3_t dest)
{
	CALLER_NAME(sphere_fx_entry)
	sphere_fx_p fx = (sphere_fx_p)self;
	vec_p pos = PS_CUR_POS(ps);
	vec3_t delta;
	vec_t force, delta_len, dist, saved_arg;
	vec3Sub(delta,pos,fx->cur_pos);
	delta_len = vec3Abs(delta);
	/* catch degenerate case when particle is in the center of sphere */
	if(IsZero(delta_len))
	{
		vec3Zero(dest);
		return fx->solid ? false : true;
	}
	/* unify delta */
	vec3Scale(delta,delta,1 / delta_len);
	if(fx->solid)
	{
		/* distance from outside */
		dist = delta_len - fx->cur_radius - PS_CUR_RADIUS(ps);
	}
	else
	{
		/* distance from inside */
		vec3Negate(delta,delta);
		dist = fx->cur_radius - delta_len - PS_CUR_RADIUS(ps);
	}
	if(dist < MIN_DIST)
	{
		WARNING_GENERIC("particle too close to sphere");
		return false;
	}
	saved_arg = ps->ctl_args[0];
	ps->ctl_args[0] = dist;
	force = CALL_FLOAT_CTL(fx->force,ps);
	ps->ctl_args[0] = saved_arg;
	vec3Scale(dest,delta,force);
	return true;
}

V_LOCAL void
sphere_fx_destroy(struct unary_effect_s *self)
{
	sphere_fx_p fx = (sphere_fx_p)self;
	if(fx->pos)
		ctlFree(fx->pos);
	if(fx->radius)
		ctlFree(fx->radius);
	if(fx->force)
		ctlFree(fx->force);
}

vboolean ufxSphereEffect(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(ufxSphereEffect)
	sphere_fx_p fx = (sphere_fx_p)memAllocZ(sizeof(sphere_fx_t));
	V_ASSERT(fx);
	fx->base.entry = sphere_fx_entry;
	fx->base.update = sphere_fx_update;
	fx->base.destroy = sphere_fx_destroy;
	if(!icCheckSolid(script,&fx->solid))
	{
		ERROR_GENERIC("error parsing sphere effect");
		ufxFree((unary_effect_p)fx);
		return false;
	}
	fx->pos = ctlParseVec3(script);
	fx->radius = ctlParseFloat(script);
	fx->force = ctlParseFloat(script);
	if(!fx->pos || !fx->radius || !fx->force)
	{
		ERROR_GENERIC("error parsing controllers for sphere effect");
		ufxFree((unary_effect_p)fx);
		return false;
	}
	vpsPush(&ps->unary_effects,fx);
	return true;
}

/*
 * Environment effect
 */

typedef struct
{
	unary_effect_t base;
	vec3_controller_p velocity;
	float_controller_p eta;
}	env_fx_t, *env_fx_p;

V_LOCAL void
env_fx_destroy(struct unary_effect_s *self)
{
	env_fx_p fx = (env_fx_p)self;
	if(fx->velocity)
		ctlFree(fx->velocity);
	if(fx->eta)
		ctlFree(fx->eta);
}

V_LOCAL vboolean
env_fx_entry(struct unary_effect_s *self,particle_system_p ps,vec3_t dest)
{
	env_fx_p fx = (env_fx_p)self;
	vec3_t vel;
	vec_t eta;
	CALL_VEC3_CTL(fx->velocity,ps,vel);
	eta = CALL_FLOAT_CTL(fx->eta,ps);
	/* F = 6 * Pi * eta * R * velocity */
	eta *= 6 * M_PI * PS_CUR_RADIUS(ps);
	vec3Scale(dest,vel,eta);
	return true;
}

vboolean
ufxEnvEffect(vhandle_t script,particle_system_p ps)
{
	CALLER_NAME(ufxEnvEffect)
	env_fx_p fx = (env_fx_p)memAllocZ(sizeof(env_fx_t));
	V_ASSERT(fx);
	fx->base.entry = env_fx_entry;
	fx->base.update = NULL;
	fx->base.destroy = env_fx_destroy;
	fx->velocity = ctlParseVec3(script);
	fx->eta = ctlParseFloat(script);
	if(!fx->velocity || !fx->eta)
	{
		ERROR_GENERIC("error parsing controllers for env effect");
		ufxFree((unary_effect_p)fx);
		return false;
	}
	vpsPush(&ps->unary_effects,fx);
	return true;
}

vboolean ufxUpdateEffects(particle_system_p ps)
{
	u32_t i, num_fx;
	unary_effect_p *effects;
	effects = (unary_effect_p*)vpsGetStorage(&ps->unary_effects);
	num_fx = vpsCount(&ps->unary_effects);
	for(i=0;i < num_fx;i++)
		if(!effects[i]->update(effects[i],ps))
			return false;
	return true;
}

vboolean ufxCallEffects(particle_system_p ps,vec3_t dest)
{
	u32_t i, num_fx;
	unary_effect_p *effects;
	vec3_t force;
	effects = (unary_effect_p*)vpsGetStorage(&ps->unary_effects);
	num_fx = vpsCount(&ps->unary_effects);
	vec3Zero(dest);
	for(i=0;i < num_fx;i++)
	{
		if(!effects[i]->entry(effects[i],ps,force))
			return false;
		vec3Add(dest,dest,force);
	}
	return true;
}

void ufxDestroyEffects(particle_system_p ps)
{
	void_p ptr;
	while(vpsPop(&ps->unary_effects,&ptr))
		ufxFree((unary_effect_p)ptr);
	vpsClear(&ps->unary_effects);
}

/*
 * Boundary cylinder effect
vboolean ufxCylinderEffect(vhandle_t script,particle_system_p ps)
{
	assert(0);
}
 */
