/*****************************************************************************

	Projet	: Pulsar Sample Fire AddOn

	Extracted from "Plasma" code by Christian Bauer <cebix on #beos>
	Pulsarised by R'alf just for AppleExpo purposes, France, September 1997.

	Original app sig (check for Plasma in BeWare) :
		BAlert *the_alert = new BAlert("", "Plasma by Christian Bauer\n<cbauer@iphcip1.physik.uni-mainz.de>\nPublic domain.", "Neat");

	Fichier	: sample_addon.cpp
	Partie	: End

	Auteur	: RM
	Date		: 140997
	Format	: tabs==2

*****************************************************************************/

#include "CFilter.h"

#pragma export on
CFilter * filterInit(uint32 index);
#pragma export reset

static const bool debug=false;

//--------------------------------------------------------------------
// Size should never be hardcoded in Pulsar, but hey this is a hack !!
#define DISPLAY_X 320
#define DISPLAY_Y 240

//--------------------------------------------------------------------

//*************************************
class CFireFilter : public CFilter
//*************************************
{
public:

	CFireFilter(void) : CFilter()	{ /* nothing */ }
	virtual ~CFireFilter(void) 		{ /* nothing */ }

	virtual bool load(void);
	virtual bool prepare(void);
	virtual void terminate(void);
	virtual void processFrame8(SFrameInfo &frame);

private:
	// Plasma variables
	uint8 PalRed[256];
	uint8 PalGreen[256];
	uint8 PalBlue[256];
	uint8 ColorConv[256];

	uint8 *buffer[2];
	sem_id buffer_sem[2];
	thread_id recalc_thread;
	int xmod;

	static long recalc_invoc(void *arg);
	void recalc_func(void);
	
	int currentDrawBuffer;	// should be 0 or 1 else do nothing

}; // end of class defs for CFireFilter


//--------------------------------------------------------------------


//**********************************************
CFilter * filterInit(uint32 index)
//**********************************************
{
	// this add-on only declares ONE filter
	if (index > 0) return NULL;

	CFireFilter *info = new CFireFilter;
	if (!info) return NULL;		// memory error, give up

	// returns the instance -- the caller will check that this instance
	// is derived from CFilter but is _not_ a CFilter...
	return info;
} // end of filterInit


//*******************************
bool CFireFilter::load(void)
//*******************************
{
	sFilter.name = "Fire";
	sFilter.author = "R'alf";
	sFilter.info = "Sample code from 'Plasma' by Cebix";
	sFilter.majorVersion = 0;
	sFilter.minorVersion = 3;
	sFilter.position = kFilterPositionFirst+0.3;
	sFilter.supportedMode = (color_space)(B_COLOR_8_BIT);

	// sFilter.icon = loadBitmap("./add-ons.icons/fft.tga");

	// Plasma init code
	int i;

	// Create color palette
	for (i=0; i<256; i++)
		PalRed[i] = PalGreen[i] = PalBlue[i] = 0;
	for (i=0; i<8; i++)		// 0..7: increase blue
		PalBlue[i] = i << 3;
	for (i=0; i<8; i++)		// 8..15: decrease blue
		PalBlue[i+8] = (7-i) << 3;
	for (i=8; i<32; i++)	// 8..31: increase red
		PalRed[i] = (i-8) * 255 / (32-8+1);
	for (i=32; i<56; i++) {	// 32..55: increase green
		PalRed[i] = 255;
		PalGreen[i] = (i-32) * 255 / (56-32+1);
	}
	for (i=56; i<80; i++) {	// 56..79: increase blue
		PalRed[i] = PalGreen[i] = 255;
		PalBlue[i] = (i-56) * 255 / (80-56+1);
	}
	for (i=80; i<256; i++)
		PalRed[i] = PalGreen[i] = PalBlue[i] = 255;

	currentDrawBuffer = -1;

	return true;
}  // end of load for CFireFilter


//**********************************
bool CFireFilter::prepare(void)
//**********************************
{
	// Plasma init code (you now have a BScreen object ptr in
	// sPrepare.interfaceScreen prepared for you before this call)
	// Create color conversion table
	{
		BScreen *scr = sPrepare.interfaceScreen;
		for (int i=0; i<256; i++)
			ColorConv[i] = scr->IndexForColor(PalRed[i], PalGreen[i], PalBlue[i]);
	}

	// Allocate buffers
	buffer[0] = new uint8[DISPLAY_X * (DISPLAY_Y + 3)];
	buffer[1] = new uint8[DISPLAY_X * (DISPLAY_Y + 3)];
	memset(buffer[0], 0, DISPLAY_X * (DISPLAY_Y + 3));
	memset(buffer[1], 0, DISPLAY_X * (DISPLAY_Y + 3));

	// Create buffer semaphores
	buffer_sem[0] = create_sem(1, "Buffer 0");
	buffer_sem[1] = create_sem(1, "Buffer 1");

	// Create calculation thread
	recalc_thread = spawn_thread(recalc_invoc, "Recalc", B_NORMAL_PRIORITY, this);
	resume_thread(recalc_thread);

	return true;
} // end of prepare for CFireFilter


//**********************************
void CFireFilter::terminate(void)
//**********************************
{
	// Free buffer semaphores (this quits the thread)
	delete_sem(buffer_sem[0]);
	delete_sem(buffer_sem[1]);

	// Wait for thread to finish
	long l;
	wait_for_thread(recalc_thread, &l);

	// Delete buffers
	delete[] buffer[0];
	delete[] buffer[1];
} // end of terminate for CFireFilter


//*****************************************
long CFireFilter::recalc_invoc(void *arg)
//*****************************************
{
	((CFireFilter *)arg)->recalc_func();
	return 0;
}


//***********************************
void CFireFilter::recalc_func(void)
//***********************************
{
	int draw_buffer = 1;

	for (;;)
	{
		uint8 *p, *q, *r;
		int x, y;
		int decr;

		if (acquire_sem(buffer_sem[draw_buffer]) == B_BAD_SEM_ID)
			return;

		// Copy up by 1 pixel and interpolate
		p = buffer[draw_buffer ^ 1] + 2 * DISPLAY_X; q = buffer[draw_buffer];
		decr = 1;
		for (y=0; y<DISPLAY_Y; y++) {
			for (x=0; x<DISPLAY_X; x++) {
				uint32 sum;
				sum = p[-DISPLAY_X-1];
				sum += p[-DISPLAY_X];
				sum += p[-DISPLAY_X+1];
				sum += p[-1];
				sum += p[1];
				sum += p[DISPLAY_X-1];
				sum += p[DISPLAY_X];
				sum += p[DISPLAY_X+1];
				sum >>= 3;
				if (sum)
					sum -= decr;	// Decrement every second row
				*q++ = sum;
				p++;
			}
			decr ^= 1;
		}

		// Fill lower 3 rows (invisible) with random pixels
		p = buffer[draw_buffer] + DISPLAY_X * DISPLAY_Y + (DISPLAY_X-256) / 2;
		q = p + DISPLAY_X;
		r = q + DISPLAY_X;
		for (x=0; x<256; x++) {
			*p++ = (rand() & 0xf) + 0x40;
			*q++ = (rand() & 0xf) + 0x40;
			*r++ = (rand() & 0xf) + 0x40;
		}

		// Insert random bright spots
		q = buffer[draw_buffer] + DISPLAY_X * (DISPLAY_Y+1) + (DISPLAY_X-256) / 2;
		int num_spots = rand() & 0xf;
		for (x=0; x<num_spots; x++) {
			p = q + (rand() & 0xff);
			p[-DISPLAY_X-1] = p[-DISPLAY_X] = p[-DISPLAY_X+1]
				= p[-1] = p[0] = p[1]
				= p[DISPLAY_X-1] = p[DISPLAY_X] = p[DISPLAY_X+1] = 255;
		}
		release_sem(buffer_sem[draw_buffer]);

		// Tell window to convert and redraw the new buffer
		//BMessage winmsg(MSG_REDRAW);
		//winmsg.AddInt32("buffer", draw_buffer);
		//PostMessage(&winmsg);
		currentDrawBuffer = draw_buffer;

		// Switch to other buffer
		draw_buffer ^= 1;
	}
}

//***************************************
void CFireFilter::processFrame8(SFrameInfo &frame)
//***************************************
{
int x,y;
int h=sPrepare.sy;
int w2=sPrepare.sx/sizeof(double);

	// Find the number of the buffer to redraw
	int draw_buffer = currentDrawBuffer;
	if (draw_buffer < 0) return; // not ready yet

	// Convert buffer to bitmap
	if (acquire_sem(buffer_sem[draw_buffer]) == B_BAD_SEM_ID)
		return;

	long offset2=w2-DISPLAY_X/sizeof(double);
	long offset=(h-DISPLAY_Y)*w2+(offset2)/2;
	uchar *src=((uchar *)(buffer[draw_buffer]))-1;
	double *base = ((double *)frame.screen)-1+offset;
	for(y=DISPLAY_Y; y>0; y--)
	{
		register uint8 *conv = ColorConv;
		for(x=DISPLAY_X/sizeof(double); x>0; x--)
		{
			double temp=*(++base);
			uchar *d = ((uchar *)&temp)-1;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			if (*(++src)) *(++d) = conv[*(src)]; else ++d;
			*(base) = temp;
		}
		base += offset2;
	}

	release_sem(buffer_sem[draw_buffer]);

}  // end of processFrame8 for CFireFilter

// eoc
