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

  CS 3600 Project #4
  By Kenny Brown

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

#include <fstream.h>
#include <math.h>
#include <stdlib.h>
//#include <GL/glut.h>
#include "glut.h"

// define a few constants

#define SIZE_X 400
#define SIZE_Y 400

#define ASPECT_RATIO 1.0
#define MAX_PARTICLES 300

int mx,my;
bool mdown;

GLfloat camX, camY, camZ;
GLfloat camPitch = 0.0, camYaw = 0.0, camRadius = 35.0;
GLfloat rotX, rotY;

GLfloat light0pos[] = { 4.0,4.0,4.0,1.0 };
GLfloat light0col[] = { 1.0,0.15,0.15,1.0 };
GLfloat light0amb[] = { 0.0,0.0,0.0,1.0 };

GLfloat light1pos[] = { -4.0,-4.0,4.0,1.0 };
GLfloat light1col[] = { 0.15,0.15,1.0,1.0 };
GLfloat light1amb[] = { 0.0,0.0,0.0,1.0 };

GLuint texName;
GLubyte tex[64*64*4];

typedef struct particle {
	GLfloat x,y,z;
	GLfloat vx,vy,vz;
	int life;
	GLfloat r,g,b,a;
	GLfloat dr,dg,db,da;
	GLfloat sizeSm, sizeLg;
	GLfloat dsize;
	int type; // currently unused
} particle;

class particleSystem {
public:
	particleSystem();
	~particleSystem() {}

	bool add(GLfloat x, GLfloat y, GLfloat z,
		     GLfloat vx, GLfloat vy, GLfloat vz,
		     GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1,
		     GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2,
		     GLfloat size1, GLfloat size2,int life, int type);

	void update();
	void draw();

private:
	particle theSys[MAX_PARTICLES];
};

particleSystem pSys;

particleSystem::particleSystem() {
	for(int i = 0; i < MAX_PARTICLES; i++) {
		theSys[i].life = 0;
	}
}

bool particleSystem::add(GLfloat x, GLfloat y, GLfloat z,
						 GLfloat vx, GLfloat vy, GLfloat vz,
						 GLfloat r1, GLfloat g1, GLfloat b1, GLfloat a1,
						 GLfloat r2, GLfloat g2, GLfloat b2, GLfloat a2,
						 GLfloat size1, GLfloat size2,int life, int type) {
	// find a dead particle
	for(int i = 0; i < MAX_PARTICLES; i++) {
		// add it
		if(theSys[i].life <= 0) {
			theSys[i].life = life;

			theSys[i].x  =  x; theSys[i].y  =  y; theSys[i].z  =  z;
			theSys[i].vx = vx; theSys[i].vy = vy; theSys[i].vz = vz;

			theSys[i].r = r1; theSys[i].g = g1; theSys[i].b = b1;
			theSys[i].a = a1;

			theSys[i].dr = (r2-r1)/(GLfloat)life;
			theSys[i].dg = (g2-g1)/(GLfloat)life;
			theSys[i].db = (b2-b1)/(GLfloat)life;
			theSys[i].da = (a2-a1)/(GLfloat)life;

			// this makes it easier to define the quad in
			// rendering without doing too many needless
			// multiplications and divides
			theSys[i].sizeSm = (-1.0*size1)/2.0;
			theSys[i].sizeLg = size1/2.0;
			theSys[i].dsize = (size2-size1)/(2.0*(GLfloat)life);

			theSys[i].type = type;

			return true;
		}
	}

	return false;
}

void particleSystem::update() {
	for(int i = 0; i < MAX_PARTICLES; i++) {
		if(theSys[i].life > 0) {
			theSys[i].x += theSys[i].vx;
			theSys[i].y += theSys[i].vy;
			theSys[i].z += theSys[i].vz;

			theSys[i].vz += 0.001;

			theSys[i].r += theSys[i].dr;
			theSys[i].g += theSys[i].dg;
			theSys[i].b += theSys[i].db;
			theSys[i].a += theSys[i].da;

			theSys[i].sizeLg += theSys[i].dsize;
			theSys[i].sizeSm -= theSys[i].dsize;

			theSys[i].life--;
		}
	}
}

void particleSystem::draw() {
	for(int i = 0; i < MAX_PARTICLES; i++) {
		if(theSys[i].life > 0) {
			glColor4f(theSys[i].r,theSys[i].g,theSys[i].b,
					theSys[i].a);
			glPushMatrix();

			glTranslatef(theSys[i].x, theSys[i].y, theSys[i].z);

			glRotatef(camYaw*(180.0/3.14159265358)+90.0,0.0,0.0,1.0);
			glRotatef(-1.0*camPitch*(180.0/3.14159265358),1.0,0.0,0.0);

			glBegin(GL_QUADS);
				glTexCoord2f(0.0,0.0);
				glVertex3f(theSys[i].sizeSm,0.0,theSys[i].sizeLg);

				glTexCoord2f(0.0,1.0);
				glVertex3f(theSys[i].sizeSm,0.0,theSys[i].sizeSm);

				glTexCoord2f(1.0,1.0);
				glVertex3f(theSys[i].sizeLg,0.0,theSys[i].sizeSm);

				glTexCoord2f(1.0,0.0);
				glVertex3f(theSys[i].sizeLg,0.0,theSys[i].sizeLg);
			glEnd();
			glPopMatrix();
		}
	}
}

void display(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// convert from spherical coords to cartesian
	camX = cos(camYaw) * cos(camPitch) * camRadius;
	camY = sin(camYaw) * cos(camPitch) * camRadius;
	camZ = sin(camPitch) * camRadius;

	// set our eyepoint
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt( camX, camY, camZ,  0.0, 0.0, 0.0,  0.0, 0.0, 1.0);

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texName);
		pSys.draw();
	glDisable(GL_TEXTURE_2D);

	// flip the buffers
	glutSwapBuffers();
}

void idle(void) {
	if(!mdown) {
	static int i = 0;
	i++;

	if(1) {//(i >> 3) & 1) {
		srand(rand()+glutGet(GLUT_ELAPSED_TIME));
		pSys.add(0.0,0.0,0.0, // pos
			(float)((rand()%200)-100)/5000.0, // vx
			(float)((rand()%200)-100)/5000.0, // vy
			0.0,//(float)((rand()%200)-100)/1000.0, // vz
			0.5,0.5,1.0,1.0, // col1
			1.0,0.0,0.0,2.0, // col2
			1.0,0.0, // size1 & 2
			100,0); // life & type
	}

	pSys.update();
	}

	glutPostRedisplay();
}

static void key(unsigned char c, int x, int y) {

	switch(c) {
	// move up the sphere
	case 'w':
		// 0.05 less than pi
		if(camPitch < 1.57)
			camPitch += 0.05;
		break;
	// rotate left
	case 'a':
		camYaw -= 0.05;
		if(camYaw < 0.0)
			camYaw = 6.28+camYaw;
		break;
	// move down the sphere
	case 's':
		if(camPitch > -1.57)
			camPitch -= 0.05;
		break;
	// rotate right
	case 'd':
		camYaw += 0.05;
		// wrap values back to zero
		if(camYaw > 6.28)
			camYaw = camYaw-6.28;
		break;
	// less radius
	case 'z':
		if(camRadius > 1.0)
			camRadius -= 1.0;
		break;
	// more radius
	case 'x':
		camRadius += 1.0;
		break;
	}
}

void mouse(int button, int state, int x, int y) {
	mx = x;
	my = y;
	if(state == GLUT_DOWN)
		mdown = true;
	else
		mdown = false;
}

void motion(int x, int y) {
	camYaw += 0.02*(GLfloat)(mx-x);

	if(camYaw < 0.0)
			camYaw = 6.28+camYaw;
	else if(camYaw > 6.28)
			camYaw = camYaw-6.28;

	camPitch += 0.02*(GLfloat)(y-my);

	if(camPitch > 1.57)
		camPitch = 1.57;
	else if(camPitch < -1.57)
		camPitch = -1.57;

	mx = x;
	my = y;
}

int main(int argc, char **argv) {
	glutInitWindowSize(SIZE_X, SIZE_Y);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	glutInit(&argc, argv);
	glutCreateWindow("");

	// register the necessary callbacks
	glutDisplayFunc(display);
	glutKeyboardFunc(key);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutIdleFunc(idle);

	//glEnable(GL_DEPTH_TEST); // depth buffer
	glShadeModel(GL_SMOOTH);
	glEnable(GL_CULL_FACE);

	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	glEnable(GL_BLEND);

	ifstream file;
	file.open("cloud.raw", ios::binary);

	//file.setmode(filebuf::binary);

	for(int i = 0; i < 64*64; i++) {
		tex[(i*4)+0] = file.get();
		tex[(i*4)+1] = file.get();
		tex[(i*4)+2] = file.get();
		if((tex[(i*4)+0]+tex[(i*4)+1]+tex[(i*4)+2]) < 25) //== 0)
			tex[(i*4)+3] = 1;
		else
			tex[(i*4)+3] = 255;
	}

	glGenTextures(1,&texName);
	glBindTexture(GL_TEXTURE_2D, texName);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64,64, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex);

	glClearColor(0.0, 0.0, 0.0, 0.0);
	
	// set up our projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective( 60.0, ASPECT_RATIO, 1.0, 1000.0);

	glutMainLoop();

	return 0;
}