/* 
 * Fireworks in OpenGL (very simple, messy play-around-with code.. sorry)
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glut.h>
#include <math.h>



/* gravitational constant -- not to scale ;) */
#define G 0.07
/* this controls the size of the initial blob of particles 
 * i.e. the size of the explosion
 */
#define EXPL 3

enum {
    SCROLL_LEFT = 1,
    SCROLL_RIGHT,
    SCROLL_UP,
    SCROLL_DOWN
} type = SCROLL_RIGHT;


typedef struct _star {
    float x, y;
    float vx, vy;
    int r, g, b;
} star;


star* stars = NULL;
int num_stars = 500;

int x, y;
int w, h;

int cnt = 0;

int continuous = 0;

#define CNT_MAX 0

void init_stars() {
	/* initialize stars' velocities
	 * make sure they start off in a circle, rather than the square the
	 * random values tend to put them in
	 */
	int i;

    for (i = 0; i < num_stars; i++) {
		stars[i].x = x;
		stars[i].y = y;
		do {
			stars[i].vx = (rand()%2 ? 1 : -1) * (rand() / (float)RAND_MAX * EXPL);
			stars[i].vy = (rand()%2 ? 1 : -1) * (rand() / (float)RAND_MAX * EXPL);
		} while (
			(stars[i].vx*stars[i].vx + stars[i].vy*stars[i].vy) > EXPL*EXPL);
	
		stars[i].r = rand()%255;
		stars[i].g = rand()%255;
		stars[i].b = rand()%255;
    }
}
void
reshape(int width, int height)
{

    w = width;
    h = height;
    x = w/2;
    y = h/2;

    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glColor3ub(255, 255, 255);

	init_stars();

}



void explode(int i) {
    stars[i].x = x;
    stars[i].y = y;
     do {
		stars[i].vx = (rand()%2 ? 1 : -1) * (rand() / (float)RAND_MAX * EXPL);
		stars[i].vy = (rand()%2 ? 1 : -1) * (rand() / (float)RAND_MAX * EXPL);
    } while ( (stars[i].vx*stars[i].vx + stars[i].vy*stars[i].vy) > EXPL*EXPL);
}
void
display(void)
{
    int i;

    glClear(GL_COLOR_BUFFER_BIT);

    for (i = 0; i < num_stars; i++) {
		stars[i].x += stars[i].vx;
		stars[i].y += stars[i].vy;
		if (stars[i].x < w && stars[i].x > 0 
			&& stars[i].y > 0 && stars[i].y < h) {
		    glBegin(GL_LINE_STRIP);
		    glColor3ub(0, 0, 0);
		    glVertex2i(stars[i].x-stars[i].vx*4, stars[i].y-stars[i].vy*4);
		    glColor3ub(	stars[i].r , stars[i].g , stars[i].b );
		    glVertex2i(stars[i].x, stars[i].y);
		    glEnd();
		} else {
			explode(i);
		}
		/* do some sort of gravity simulation */
		stars[i].vy -=  G;
    }

    cnt++;

    if(!continuous)
	    if (cnt > CNT_MAX)
		    x = w + 100, y = h + 100;
    
    glutSwapBuffers();
}

int framecnt = 0;	/* count idle frames and explode randomly */
int delay=100;	/* how many frames should we wait for */

void idle(void) {
	int i;

	if(!(++framecnt % delay)) {
		x = rand()%w;
		y = rand()%(h/2) + h/2;	/* have explosions only in the upper half of the screen */
		for(i = 0; i < num_stars; i++)
			if(stars[i].x > w && stars[i].x < 0 && stars[i].y < 0 && stars[i].y > h) 
				explode(i);

		delay = rand()%50 + 50; /* next explosion in at least 50 frames */


	}
    glutPostRedisplay();
}

void
bail(int code)
{
    free(stars);
    exit(code);
}

#ifdef SCREEN_SAVER_MODE
void
ss_keyboard(char key, int x, int y)
{
    bail(0);
}

void
ss_mouse(int button, int state, int x, int y)
{
    bail(0);
}

void
ss_passive(int x, int y)
{
    static int been_here = 0;

    /* for some reason, GLUT sends an initial passive motion callback
       when a window is initialized, so this would immediately
       terminate the program.  to get around this, see if we've been
       here before. (actually if we've been here twice.) */

    if (been_here > 1)
	bail(0);
    been_here++;
}

#else
void
ss_mouse(int button, int state, int ix, int iy)
{
	if(button == 2) 
		continuous = continuous == 0 ? 1 : 0;
	cnt = 0;
	x = ix;
	y = h - iy;
}

void
keyboard(unsigned char key, int x, int y)
{
    static int old_x = 50;
    static int old_y = 50;
    static int old_width = 320;
    static int old_height = 320;

    switch (key) {
    case 27:
        bail(0);
	break;
    case 'w':
        glutPositionWindow(old_x, old_y);
        glutReshapeWindow(old_width, old_height);
	break;
    case 'f':
	if (glutGet(GLUT_WINDOW_WIDTH) < glutGet(GLUT_SCREEN_WIDTH)) {
	    old_x = glutGet(GLUT_WINDOW_X);
	    old_y = glutGet(GLUT_WINDOW_Y);
	    old_width = glutGet(GLUT_WINDOW_WIDTH);
	    old_height = glutGet(GLUT_WINDOW_HEIGHT);
	    glutFullScreen();
	}
	break;
    }
}

#endif

int
main(int argc, char** argv)
{
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(50, 50);
    glutInitWindowSize(512, 512);
    glutInit(&argc, argv);

    glutCreateWindow("FireWorks + gravity");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
#ifdef SCREEN_SAVER_MODE
    glutPassiveMotionFunc(ss_passive);
    glutKeyboardFunc(ss_keyboard);
    glutMouseFunc(ss_mouse);
    glutSetCursor(GLUT_CURSOR_NONE);
    glutFullScreen(); 
#else
    glutKeyboardFunc(keyboard);
    glutMouseFunc(ss_mouse);
#endif

    if (argc > 1) {
	if (strcmp(argv[1], "-h") == 0) {
	    fprintf(stderr, "%s [stars]\n", argv[0]);
	    exit(0);
	}
	sscanf(argv[1], "%d", &num_stars);
    }      

    stars = (star*)malloc(sizeof(star) * num_stars);

    glutIdleFunc(idle);
    glutMainLoop();
    return 0;
}
