/*=============================================================
|
| APE - Another Particle Editor
|==============================================================
|
| Desc: main.cpp
|
| Author: Ben Vinegar
| Contact benvinegar () hotmail ! com
|
| Last Modified: 3/11/04
|
| www.wildfiregames.com/0ad
=============================================================*/

//--------------------------------------------------------
//  Includes / Compiler directives
//--------------------------------------------------------

#include "ParticleSystem.h"
#include "ParticleEmitter.h"
#include "Vector3D.h"
#include "Sprite.h"
#include "timer_.h"
#include "tex_.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <GL/glut.h>
#include <GL/glui.h>

//--------------------------------------------------------
//  Defines
//--------------------------------------------------------

#define APE_TITLE   "APE: Another Particle Editor v0.3.0"
#define APE_CREDITS "By Ben Vinegar, ben@0ad.wildfiregames.com"
#define APE_SPRITE_DIR "./sprites"
#define APE_DEFAULT_SPRITE "1.bmp"

#define APE_MAX_PARTICLES 10000
#define APE_MIN_PARTICLES 10

#define APE_MAX_LIFETIME 20.0f
#define APE_MIN_LIFETIME 1.0f

//--------------------------------------------------------
//  Global Variables
//--------------------------------------------------------

int   main_window;

CSprite g_Sprite;
CParticleEmitter * g_Emitter[1];
CParticleSystem g_PSystem;

// index of currently active/editable particle
int g_pIndex = 0;

// A convenient global structure for holding all
// the GLUI widget pointers.
struct { 
	GLUI *mainWindow;
	// GLUI *aboutWindow;

	GLUI_Spinner *origin[3];
	GLUI_Spinner *originSpread[3];
	GLUI_Spinner *velocity[3];
	GLUI_Spinner *veloSpread[3];
	GLUI_Spinner *gravity[3];
	GLUI_Spinner *colorStart[3];
	GLUI_Spinner *colorEnd[3];
	GLUI_Spinner *lifetime[2];
	GLUI_Spinner *spriteSize[2];
	GLUI_Spinner *numParticles;
} g_GLUI;

char g_loadedSpriteFilename[30];
GLUI_EditText *g_spriteTextBox;

unsigned int g_frames = 0;

//--------------------------------------------------------
//  Declarations
//--------------------------------------------------------

// Event functions linked to by GLUI
void loadSprite(int param);
void clearSpriteTextBox(int param);
void writeToFile(int param);
void showAboutWindow(int param);

void colorStartEvent(int index);
void colorEndEvent(int index);
void lifetimeEvent(int param);
void minParticlesEvent(int param);
void gravityEvent(int param);
void veloEvent(int param);
void veloSpreadEvent(int param);
void originEvent(int param);
void originSpreadEvent(int param);
void spriteSizeEvent(int param);

void quit(int retVal);

// GLUT specific
void myGlutIdle();
void myGlutReshape(int x, int y);
void myGlutIdle();
void myGlutDisplay();


// Initialize all GLUI windows, set callbacks, etc.
void initGLUI();

//--------------------------------------------------------
//  Implementation
//--------------------------------------------------------

void loadSprite(int param) {
	char filename[30];
	
	// build path name
	strcpy(filename, APE_SPRITE_DIR);
	strcat(filename, "/");
	strcat(filename, g_spriteTextBox->get_text());

	// check if file exists first
	FILE *in = fopen(filename, "r");
	if (in == NULL) {
		printf("Error: %s does not exist.\n", filename);
		g_spriteTextBox->set_text(g_loadedSpriteFilename);
		return;
	}

	// load texture
	GLuint tex;
	tex_load(filename, &tex);

	if (tex < 1) {
		printf("Error: %s is not a bitmap.\n", filename);
		g_spriteTextBox->set_text(g_loadedSpriteFilename);
		return;
	}

	g_Emitter[g_pIndex]->SetTexture(tex);
	//g_Sprite.SetTexture(tex);
	strcpy(g_loadedSpriteFilename, g_spriteTextBox->get_text()); 
}

void clearSpriteTextBox(int param) {
	g_spriteTextBox->set_text("");
}

void writeToFile(int param) {
	FILE *out = fopen("new.ape", "w");
	fwrite(&g_Emitter[g_pIndex], sizeof(CParticleEmitter), 1, out);
	fclose(out);
}

void quit(int retVal) {
	float fps = g_frames / get_time();
	FILE *out = fopen("out.txt", "w");
	fprintf(out, "%f\n", fps);
	exit(retVal);
}

// called during color start spinner event
void colorStartEvent(int param) {
	for (int i = 0; i < 3; i++) {
		if (g_GLUI.colorStart[i]->get_float_val() < g_GLUI.colorEnd[i]->get_float_val()) {
			g_GLUI.colorStart[i]->set_float_val(g_GLUI.colorEnd[i]->get_float_val());
		}
	}

	float r, g, b;
	r = g_GLUI.colorStart[0]->get_float_val();
	g = g_GLUI.colorStart[1]->get_float_val();
	b = g_GLUI.colorStart[2]->get_float_val();

	g_Emitter[g_pIndex]->SetStartColour(r, g, b, 1.0f);
}

// called during color end spinner event
void colorEndEvent(int index) {
	for (int i = 0; i < 3; i++) {
		if (g_GLUI.colorEnd[i]->get_float_val() > g_GLUI.colorStart[i]->get_float_val()) {
			g_GLUI.colorEnd[i]->set_float_val(g_GLUI.colorStart[i]->get_float_val());
		}
	}

	float r, g, b;
	r = g_GLUI.colorEnd[0]->get_float_val();
	g = g_GLUI.colorEnd[1]->get_float_val();
	b = g_GLUI.colorEnd[2]->get_float_val();

	g_Emitter[g_pIndex]->SetEndColour(r, g, b, 1.0f);
}

// called during lifetime spinner event
void lifetimeEvent(int param) {
	if (g_GLUI.lifetime[0]->get_float_val() > g_GLUI.lifetime[1]->get_float_val())
		g_GLUI.lifetime[1]->set_float_val(g_GLUI.lifetime[0]->get_float_val());
	else if (g_GLUI.lifetime[1]->get_float_val() < g_GLUI.lifetime[0]->get_float_val())
		g_GLUI.lifetime[0]->set_float_val(g_GLUI.lifetime[1]->get_float_val());

	g_Emitter[g_pIndex]->SetMinLifetime(g_GLUI.lifetime[0]->get_float_val());
	g_Emitter[g_pIndex]->SetMaxLifetime(g_GLUI.lifetime[1]->get_float_val());
}


void numParticlesEvent(int param) {
	float numParticles;
	numParticles = g_GLUI.numParticles->get_float_val();

	g_Emitter[g_pIndex]->SetMinParticles(numParticles);
}

void gravityEvent(int param) {
	CVector3D grav;
	grav.X = g_GLUI.gravity[0]->get_float_val();
	grav.Y = g_GLUI.gravity[1]->get_float_val();
	grav.Z = g_GLUI.gravity[2]->get_float_val();

	g_Emitter[g_pIndex]->SetGravity(grav);
}

void veloEvent(int param) {
	CVector3D velo;
	velo.X = g_GLUI.velocity[0]->get_float_val();
	velo.Y = g_GLUI.velocity[1]->get_float_val();
	velo.Z = g_GLUI.velocity[2]->get_float_val();

	g_Emitter[g_pIndex]->SetVelocity(velo);
}

void veloSpreadEvent(int param) {
	CVector3D spread;
	spread.X = g_GLUI.veloSpread[0]->get_float_val();
	spread.Y = g_GLUI.veloSpread[1]->get_float_val();
	spread.Z = g_GLUI.veloSpread[2]->get_float_val();

	g_Emitter[g_pIndex]->SetVelocitySpread(spread);
}

void originEvent(int param) {
	CVector3D origin;
	origin.X = g_GLUI.origin[0]->get_float_val();
	origin.Y = g_GLUI.origin[1]->get_float_val();
	origin.Z = g_GLUI.origin[2]->get_float_val();

	g_Emitter[g_pIndex]->SetOrigin(origin);
}

void originSpreadEvent(int param) {
	CVector3D spread;
	spread.X = g_GLUI.originSpread[0]->get_float_val();
	spread.Y = g_GLUI.originSpread[1]->get_float_val();
	spread.Z = g_GLUI.originSpread[2]->get_float_val();

	g_Emitter[g_pIndex]->SetOriginSpread(spread);
}

void spriteSizeEvent(int param) {
	float width, height;

	width  = g_GLUI.spriteSize[0]->get_float_val();
	height = g_GLUI.spriteSize[1]->get_float_val();

	g_Emitter[g_pIndex]->SetWidth(width);
	g_Emitter[g_pIndex]->SetHeight(height);
}
/***************************************** myGlutIdle() ***********/

void myGlutIdle( void )
{
  /* According to the GLUT specification, the current window is 
     undefined during an idle callback.  So we need to explicitly change
     it if necessary */
  if ( glutGetWindow() != main_window ) 
    glutSetWindow(main_window);  

  glutPostRedisplay();
}


/**************************************** myGlutReshape() *************/

void myGlutReshape( int x, int y )
{
  float xy_aspect;

  xy_aspect = (float)x / (float)y;
  glViewport( 0, 0, x, y );

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glFrustum( -xy_aspect*.08, xy_aspect*.08, -.08, .08, .1, 15.0 );

  glutPostRedisplay();
}

/***************************************** myGlutDisplay() *****************/

void myGlutDisplay( void )
{
	colorStartEvent(0);
	glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );


	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	glTranslatef( 0.0, 0.0, -7.0 );
 

	//g_Emitter[g_pIndex]->SetMinLifetime(g_minLifetime);
	//g_Emitter[g_pIndex]->SetMaxLifetime(g_maxLifetime);

	//g_Sprite.Render();
	//g_Emitter[g_pIndex]->Frame();

	g_PSystem.Frame();
	++g_frames;

	glutSwapBuffers(); 
}


/**************************************** main() ********************/

void main(int argc, char* argv[])
{
  /****************************************/
  /*   Initialize GLUT and create window  */
  /****************************************/

	glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
	glutInitWindowPosition( 50, 50 );
	glutInitWindowSize( 600, 600 );
 
	main_window = glutCreateWindow(APE_TITLE);
	glutDisplayFunc( myGlutDisplay );
	glutReshapeFunc( myGlutReshape );  

	glDisable(GL_LIGHTING);


	glEnable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);

  /****************************************/
  /*         Here's the GLUI code         */
  /****************************************/

	initGLUI();

	// have the system create a new emitter
	g_Emitter[g_pIndex] = g_PSystem.CreateNewEmitter();

	// load default sprite
	g_spriteTextBox->set_text(APE_DEFAULT_SPRITE);
	
	loadSprite(-1);
	g_Sprite.SetSize(1.0f, 1.0f);
	
//	g_Emitter[g_pIndex]->SetSprite(&g_Sprite);
	g_Emitter[g_pIndex]->Init();

	glutMainLoop();
}


void initGLUI() {
	// create master window
	g_GLUI.mainWindow = GLUI_Master.create_glui( "Control Panel" );

	GLUI* glui = g_GLUI.mainWindow;

	// create origin panel and spinner contents
	GLUI_Panel *origin_panel = glui->add_panel("Origin");
	g_GLUI.origin[0] = glui->add_spinner_to_panel(origin_panel, "X", 
		GLUI_SPINNER_FLOAT, NULL, -1, originEvent);
	g_GLUI.origin[1] = glui->add_spinner_to_panel(origin_panel, "Y", 
		GLUI_SPINNER_FLOAT, NULL, -1, originEvent);
	g_GLUI.origin[2] = glui->add_spinner_to_panel(origin_panel, "Z", 
		GLUI_SPINNER_FLOAT, NULL, -1, originEvent);

	// create origin spread panel and spinner contents
	GLUI_Panel *origin_spread_panel = glui->add_panel("Origin Spread");
	g_GLUI.originSpread[0] = glui->add_spinner_to_panel(origin_spread_panel, "X", 
		GLUI_SPINNER_FLOAT, NULL, -1, originSpreadEvent);
	g_GLUI.originSpread[1] = glui->add_spinner_to_panel(origin_spread_panel, "Y", 
		GLUI_SPINNER_FLOAT, NULL, -1, originSpreadEvent);
	g_GLUI.originSpread[2] = glui->add_spinner_to_panel(origin_spread_panel, "Z",
		GLUI_SPINNER_FLOAT, NULL, -1, originSpreadEvent);

	// create velocity panel and spinner contents
	GLUI_Panel *velo_panel = glui->add_panel("Velocity");
	g_GLUI.velocity[0] = glui->add_spinner_to_panel(velo_panel, "X", 
		GLUI_SPINNER_FLOAT, NULL, -1, veloEvent);
	g_GLUI.velocity[1] = glui->add_spinner_to_panel(velo_panel, "Y", 
		GLUI_SPINNER_FLOAT, NULL, -1, veloEvent);
	g_GLUI.velocity[2] = glui->add_spinner_to_panel(velo_panel, "Z", 
		GLUI_SPINNER_FLOAT, NULL, -1, veloEvent);

	// create velocity spread panel and spinner contents
	GLUI_Panel *velo_spread_panel = glui->add_panel("Velocity Spread");
	g_GLUI.veloSpread[0] = glui->add_spinner_to_panel(velo_spread_panel, "X",
		GLUI_SPINNER_FLOAT, NULL, -1, veloSpreadEvent);
	g_GLUI.veloSpread[1] = glui->add_spinner_to_panel(velo_spread_panel, "Y", 
		GLUI_SPINNER_FLOAT, NULL, -1, veloSpreadEvent);
	g_GLUI.veloSpread[2] = glui->add_spinner_to_panel(velo_spread_panel, "Z", 
		GLUI_SPINNER_FLOAT, NULL, -1, veloSpreadEvent);

	// create gravity panel and spinner contents
	GLUI_Panel *grav_panel = glui->add_panel("Gravity");
	g_GLUI.gravity[0] = glui->add_spinner_to_panel(grav_panel, "X", 
		GLUI_SPINNER_FLOAT, NULL, -1, gravityEvent);
	g_GLUI.gravity[1] = glui->add_spinner_to_panel(grav_panel, "Y", 
		GLUI_SPINNER_FLOAT, NULL, -1, gravityEvent);
	g_GLUI.gravity[2] = glui->add_spinner_to_panel(grav_panel, "Z", 
		GLUI_SPINNER_FLOAT, NULL, -1, gravityEvent);

	// add separator
	glui->add_column(true);

	// create start colour panels and spinners
	GLUI_Panel *start_color_panel = glui->add_panel("Start Colour");
	g_GLUI.colorStart[0] = glui->add_spinner_to_panel(start_color_panel, "R", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorStartEvent);
	g_GLUI.colorStart[1] = glui->add_spinner_to_panel(start_color_panel, "G", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorStartEvent);
	g_GLUI.colorStart[2] = glui->add_spinner_to_panel(start_color_panel, "B", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorStartEvent);

	// create end colour panel and spinners
	GLUI_Panel *end_color_panel = glui->add_panel("End Colour");
	g_GLUI.colorEnd[0] = glui->add_spinner_to_panel(end_color_panel, "R", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorEndEvent);
	g_GLUI.colorEnd[1] = glui->add_spinner_to_panel(end_color_panel, "G", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorEndEvent);	
	g_GLUI.colorEnd[2] = glui->add_spinner_to_panel(end_color_panel, "B", 
		GLUI_SPINNER_FLOAT, NULL, -1, colorEndEvent);
   
	// set limits, speed for all colour spinners
	for (int i = 0; i < 3; i++) {
		g_GLUI.colorStart[i]->set_float_limits(0.0f, 1.0f);
		g_GLUI.colorEnd[i]->set_float_limits(0.0f, 1.0f);
		g_GLUI.colorStart[i]->set_speed(0.5);
		g_GLUI.colorEnd[i]->set_speed(0.5);
		g_GLUI.colorStart[i]->set_float_val(1.0f);
		g_GLUI.colorEnd[i]->set_float_val(1.0f);
	}

	// create lifetime panel and spinners
	GLUI_Panel *lifetimes = glui->add_panel("Lifetimes");
	g_GLUI.lifetime[0] = glui->add_spinner_to_panel(lifetimes, "Min", 
		GLUI_SPINNER_FLOAT, NULL, -1, lifetimeEvent);
	g_GLUI.lifetime[1] = glui->add_spinner_to_panel(lifetimes, "Max", 
		GLUI_SPINNER_FLOAT, NULL, -1, lifetimeEvent);

	// set lifetime limits
	for (i = 0; i < 2; i++) {
		g_GLUI.lifetime[i]->set_float_limits(1.0f, 20.0f);
	}

	// create number of particles panel and spinner 
	GLUI_Panel *num_part_panel = glui->add_panel("# of Particles");
	g_GLUI.numParticles = glui->add_spinner_to_panel(num_part_panel, "",
		GLUI_SPINNER_INT, NULL, -1, numParticlesEvent);
	g_GLUI.numParticles->set_int_limits(2, APE_MAX_PARTICLES);

	// create sprite panel and spinners
	GLUI_Panel *sprite_panel = glui->add_panel("Sprite");
	g_GLUI.spriteSize[0] = glui->add_spinner_to_panel(sprite_panel, "Width", 
		GLUI_SPINNER_FLOAT, NULL, -1, spriteSizeEvent);
	g_GLUI.spriteSize[0]->set_float_val(1.0f);
	g_GLUI.spriteSize[1] = glui->add_spinner_to_panel(sprite_panel, "Height", 
		GLUI_SPINNER_FLOAT, NULL, -1, spriteSizeEvent);
	g_GLUI.spriteSize[1]->set_float_val(1.0f);

	g_GLUI.spriteSize[0]->set_float_limits(0.05, 5);
	g_GLUI.spriteSize[1]->set_float_limits(0.05, 5);
	
	// load / save panel
	glui->add_separator_to_panel(sprite_panel);
	g_spriteTextBox = glui->add_edittext_to_panel(sprite_panel, "Filename");
	GLUI_Button *load_button = glui->add_button_to_panel(sprite_panel, "Load", -1, loadSprite);
	glui->add_button_to_panel(sprite_panel, "Clear", -1, clearSpriteTextBox);
	GLUI_Button *save_button = glui->add_button("Save Effect", -1, writeToFile);
	save_button->disable();

	glui->add_button("Quit", -1, quit);

	glui->set_main_gfx_window( main_window );
	

	// register idle callback with GLUI -- NOT GLUT
	GLUI_Master.set_glutIdleFunc( myGlutIdle ); 
}
