/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1997  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

#include <math.h>
#include <string.h>
#include "../V/VObjects.h"
#include "pm.h"
#include "../util/memory.h"
#include "../util/prng.h"

#define effects_IMPORT
#include "effects.h"

typedef struct _explosion_data {
	struct _explosion_data * next;  /* linked list of released blocks */

	double    escale;  /* overall size (m) */
	int       duration;  /* dust expiry time (units of deltaT) */
	int       flameDuration;  /* flame expiry time (units of deltaT) */
} explosion_data;

static explosion_data * explosion_data_free_list;

/**
 * If cleanup already invoked, then the program is terminating and is killing
 * any remaining entity, so placing new explosions is useless and has no effect.
 */
static int no_more_explosions;


/*
 * Better to fill-in all the data structures because several parts of the
 * program relies on their availability and crashes otherwise. The alternative
 * would be to put several if() here and there to check for NULL entries, but
 * simply assigning something reasonable makes the program simpler, faster and
 * easier to debug.
 */
static VObject explosion_obj = {
	"Explosion",
	(VPoint) {0.0, 0.0, 0.0},
	(VPoint) {0.0, 0.0, 0.0},
	(VPoint) {0.0, 0.0, 0.0},
	(VPoint) {0.0, 0.0, 0.0},
	0.0,
	0,
	NULL,
	NULL
};

#define SPIKES			16
#define FLAME_SPIKES	8
#define SMOKE_INNER		0.2
#define SMOKE_RADIUS	1.0
#define SMOKE_MIN_RADIUS 0.5
#define SMOKE_VARIATION (SMOKE_RADIUS - SMOKE_MIN_RADIUS)
#define FLAME_RADIUS	0.5
#define FLAME_MIN_RADIUS 0.3

static VObject *explosionTemplate;

static craftType *explosion_craft;

static VColor_Type *effectBlackColor;


static void explosion_free(explosion_data * ed)
{
	ed->next = explosion_data_free_list;
	explosion_data_free_list = ed;
}


static void effects_kill_explosion(craft * e, char *reason)
{
	explosion_free( (explosion_data *) e->effects );
	e->effects = NULL;
	e->type = CT_FREE;
}

/**
 * Releases internal data structures. Also release data for any mtbl[]
 * of type CT_EXPLOSION.
 * 
 * WARNING. This also removes data that might be used by some craft of
 * type CT_EXPLOSION, so all the crafts of this type should be already
 * released.
 */
static void effects_cleanup()
{
	int i;
	craft *c;
	explosion_data * ed;

	for(i = 0; i < manifest_MAXPROJECTILES; i++ ){
		c = &mtbl[i];
		if( c->type == CT_EXPLOSION ){
			effects_kill_explosion(c, "program terminated");
		}
	}

	if( explosionTemplate ){
		VDestroyObject(explosionTemplate);
		explosionTemplate = NULL;
	}

	if( explosion_craft != NULL ){
		memory_dispose(explosion_craft);
		explosion_craft = NULL;
	}

	while( explosion_data_free_list != NULL ){
		ed = explosion_data_free_list;
		explosion_data_free_list = ed->next;
		memory_dispose(ed);
	}
	
	no_more_explosions = 1;
}


static char *
effects_explosion_calculations(craft * e)
{
	explosion_data * ed = (explosion_data *) e->effects;

	--(ed->flameDuration);
	if ((--ed->duration) <= 0)
		return "flames and smoke vanished";
	else
		return NULL;
}


static explosion_data * explosion_malloc()
{
	explosion_data * ed;

	if( explosion_data_free_list == NULL ){
		return memory_allocate( sizeof(explosion_data), NULL );
	} else {
		ed = explosion_data_free_list;
		explosion_data_free_list = ed->next;
		return ed;
	}
}


double copysign(double x, double y)
{
	return (y < 0.0) ? -fabs(x) : fabs(x);
}

static VObject *
buildExplosion(void)
{

	register int i, numSpikes, numFlame, numRed, poly;
	register VObject *obj;
	VColor_Type   *redFlameColor, *orangeFlameColor, *color;
	VPoint    vp[3];
	double    x, s;

	numSpikes = SPIKES;
	numFlame = FLAME_SPIKES;
	numRed = numFlame / 2;

	effectBlackColor = VColor_getByName("black", 0);
	redFlameColor = VColor_getByName("red", 0);
	orangeFlameColor = VColor_getByName("orange", 0);

	obj = (VObject *) memory_allocate(sizeof(VObject), NULL);
	obj->name = memory_strdup("explosion");
	obj->numPolys = numSpikes + numFlame;
	obj->polygon = (VPolygon **) memory_allocate(obj->numPolys * sizeof(VPolygon *), NULL);
	obj->order = (unsigned short *) NULL;

	poly = 0;

	for (i = 0; i < numSpikes; ++i) {
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].x = (SMOKE_MIN_RADIUS + x * SMOKE_VARIATION) * s;
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].y = (SMOKE_MIN_RADIUS + x * SMOKE_VARIATION) * s;
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].z = (SMOKE_MIN_RADIUS + x * SMOKE_VARIATION) * s;
		vp[1].x = prng_getDouble2() * SMOKE_INNER;
		vp[1].y = prng_getDouble2() * SMOKE_INNER;
		vp[1].z = prng_getDouble2() * SMOKE_INNER;
		vp[2].x = prng_getDouble2() * SMOKE_INNER;
		vp[2].y = prng_getDouble2() * SMOKE_INNER;
		vp[2].z = prng_getDouble2() * SMOKE_INNER;
		obj->polygon[poly++] = VCreatePolygon(3, vp, effectBlackColor);
	}

	for (i = 0; i < numFlame; ++i) {
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].x = (FLAME_MIN_RADIUS + x * FLAME_RADIUS) * s;
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].y = (FLAME_MIN_RADIUS + x * FLAME_RADIUS) * s;
		x = prng_getDouble2();
		s = copysign(1.0, x);
		x = fabs(x);
		vp[0].z = (FLAME_MIN_RADIUS + x * FLAME_RADIUS) * s;
		vp[1].x = prng_getDouble2() * SMOKE_INNER;
		vp[1].y = prng_getDouble2() * SMOKE_INNER;
		vp[1].z = prng_getDouble2() * SMOKE_INNER;
		vp[2].x = prng_getDouble2() * SMOKE_INNER;
		vp[2].y = prng_getDouble2() * SMOKE_INNER;
		vp[2].z = prng_getDouble2() * SMOKE_INNER;
		if (i < numRed)
			color = redFlameColor;
		else
			color = orangeFlameColor;
		obj->polygon[poly++] = VCreatePolygon(3, vp, color);
	}

	return obj;

}


static int
placeExplosion(Viewport * v, craft * obj, VMatrix * mtx, VPolySet *ps)
{
	explosion_data * ed;
	int       i, k, n;
	VPolygon **e, *p;
	VPoint    tmp, *q;

	ed = (explosion_data *) obj->effects;
	n = explosionTemplate->numPolys;
	e = explosionTemplate->polygon;

	for (i = 0; i < n; ++i) {
		if (ed->flameDuration > 0 || e[i]->color == effectBlackColor) {
			p = VCopyPolygon(e[i]);
			for ((k = 0, q = p->vertex); k < p->numVtces; (++k, ++q)) {
				tmp.x = q->x * ed->escale + obj->Sg.x;
				tmp.y = q->y * ed->escale + obj->Sg.y;
				tmp.z = q->z * ed->escale + obj->Sg.z;
				*q = tmp;
			}
			VTransformPolygon(p, &v->eyeSpace);
			VPolySet_Add(ps, p);
		}
	}

	return 0;
}


static void effects_init()
{
	memory_registerCleanup(effects_cleanup);
	explosionTemplate = buildExplosion();
	
	explosion_craft = memory_allocate( sizeof(craft), NULL );
	memset(explosion_craft, 0, sizeof(craft));
	explosion_craft->object = &explosion_obj;
	explosion_craft->objname = "Explosion";
	explosion_craft->placeProc = placeExplosion;

	explosion_data_free_list = NULL;
}


void
effects_new_explosion(VPoint * loc, VPoint *vel, double s_meters, double dur1, double dur2)
{

	int i;
	craft *e;
	explosion_data * ed;
	
	if( no_more_explosions ){
		return;
	}
	
	if( explosionTemplate == NULL )
		effects_init();

	for (i = 0; i < manifest_MAXPROJECTILES; ++i) {
		if (mtbl[i].type == CT_FREE) {

			ed = explosion_malloc();
			ed->escale = s_meters;
			ed->duration = (int) (dur1 / deltaT + 0.5);
			ed->flameDuration = (int) (dur2 / deltaT + 0.5);

			e = &mtbl[i];
			e->type = CT_EXPLOSION;
			e->effects = ed;
			memory_strcpy(e->name, sizeof(e->name), "Explosion");
			e->Sg = *loc;
			earth_XYZToLatLonAlt(&e->Sg, &e->w);
			e->Cg = *vel;
			e->cinfo = explosion_craft;
			e->curHeading = e->curRoll = e->curPitch = 0.0;
			e->update = effects_explosion_calculations;
			e->kill = effects_kill_explosion;
			break;
		}
	}
}
