/*
 *  Plasma.cpp - Simple plasma fire V2.0
 *               Written by Christian Bauer
 *               Public domain
 */

#include <AppKit.h>
#include <InterfaceKit.h>
#include <KernelKit.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


// Constants
const char APP_SIGNATURE[] = "application/x-vnd.cebix-Plasma";
const int DISPLAY_X = 320;
const int DISPLAY_Y = 240;

// Window/Application messages
const uint32 MSG_REDRAW = 1;




class Renderer;
class PlasmaWindow;
class BitmapView;


// Application object
class Plasma : public BApplication {
public:
	Plasma();
	virtual void ReadyToRun(void);
	virtual void AboutRequested(void);
};


// Window object
class PlasmaWindow : public BWindow {
public:
	PlasmaWindow();
	virtual bool QuitRequested(void);
	virtual void MessageReceived(BMessage *msg);

private:
	static long recalc_invoc(void *arg);
	void recalc_func(void);

	BBitmap *the_bitmap;
	BitmapView *main_view;
	uint8 *buffer[2];
	sem_id buffer_sem[2];
	thread_id recalc_thread;
	int xmod;
};


// Bitmap view
class BitmapView : public BView {
public:
	BitmapView(BRect frame, BBitmap *bitmap);
	virtual void Draw(BRect update);

private:
	BBitmap *the_bitmap;
};


// Create application object and start it
int main(int argc, char **argv)
{	
	Plasma *the_app;

	srand(real_time_clock());

	the_app = new Plasma();
	if (the_app != NULL) {
		the_app->Run();
		delete the_app;
	}

	return 0;
}


/*
 *  Application constructor
 */

Plasma::Plasma() : BApplication(APP_SIGNATURE) {}


/*
 *  Open window
 */

void Plasma::ReadyToRun(void)
{
	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;

	// Open window
	new PlasmaWindow;
}


/*
 *  About requested
 */

void Plasma::AboutRequested(void)
{
	BAlert *the_alert = new BAlert("", "Plasma by Christian Bauer\n<cbauer@iphcip1.physik.uni-mainz.de>\nPublic domain.", "Neat");
	the_alert->Go();
}


/*
 *  Window constructor
 */

PlasmaWindow::PlasmaWindow(): BWindow(BRect(0, 0, DISPLAY_X-1, DISPLAY_Y-1), "Plasma", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE)
{
	// Create color conversion table
	{
		BScreen scr(this);
		for (int i=0; i<256; i++)
			ColorConv[i] = scr.IndexForColor(PalRed[i], PalGreen[i], PalBlue[i]);
	}

	// Allocate bitmap
	the_bitmap = new BBitmap(BRect(0, 0, DISPLAY_X-1, DISPLAY_Y-1), B_COLOR_8_BIT);
	xmod = the_bitmap->BytesPerRow();

	// 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");

	// Move window to right position
	MoveTo(40, 40);

	// Create bitmap view
	Lock();
	main_view = new BitmapView(BRect(0, 0, DISPLAY_X-1, DISPLAY_Y-1), the_bitmap);
	AddChild(main_view);
	Unlock();

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

	// Show the window
	Show();
}


/*
 *  Closing the window quits the program
 */

bool PlasmaWindow::QuitRequested(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];

	// Free bitmap
	main_view->Sync();
	if (the_bitmap)
		delete the_bitmap;

	// Quit program
	be_app->PostMessage(B_QUIT_REQUESTED);
	return TRUE;
}


/*
 *  Handles redraw messages
 */

void PlasmaWindow::MessageReceived(BMessage *msg)
{
	switch (msg->what) {
		case MSG_REDRAW:

			// Prevent piling up of messages
			BMessage *msg2;
			MessageQueue()->Lock();
			while ((msg2 = MessageQueue()->FindMessage(MSG_REDRAW, 0)) != NULL)
				MessageQueue()->RemoveMessage(msg2);
			MessageQueue()->Unlock();

			// Find the number of the buffer to redraw
			int draw_buffer = msg->FindInt32("buffer");

			// Convert buffer to bitmap
			if (acquire_sem(buffer_sem[draw_buffer]) == B_BAD_SEM_ID)
				break;
			uint8 *p = buffer[draw_buffer];
			uint8 *q = (uint8 *)the_bitmap->Bits();
			for (int y=0; y<DISPLAY_Y; y++) {
				for (int x=0; x<DISPLAY_X; x++)
					q[x] = ColorConv[p[x]];
				p += DISPLAY_X;
				q += xmod;
			}
			release_sem(buffer_sem[draw_buffer]);

			// Draw bitmap
			Lock();
			main_view->Draw(BRect(0, 0, DISPLAY_X-1, DISPLAY_Y-1));
			Unlock();
			break;

		default:
			inherited::MessageReceived(msg);
	}
}


/*
 *  Main calculation (write to buffer[draw_buffer])
 */

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

void PlasmaWindow::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);

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


/*
 *  Bitmap view constructor
 */

BitmapView::BitmapView(BRect frame, BBitmap *bitmap) : BView(frame, "bitmap", B_FOLLOW_NONE, B_WILL_DRAW)
{
	the_bitmap = bitmap;
}


/*
 *  Blit the bitmap
 */

void BitmapView::Draw(BRect update)
{
	DrawBitmap(the_bitmap, update, update);
}
