/* Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
/* File for "Particle Systems" lesson of the OpenGL tutorial on
 * www.videotutorialsrock.com
 */


 //..includes
#include "visParticle.h"



//Returns a random float from 0 to < 1
float randomFloat() 
{
	return (float)rand() / ((float)RAND_MAX + 1);
}

//Rotates the vector by the indicated number of degrees about the specified axis
Vec3f rotate(Vec3f v, Vec3f axis, float degrees) 
{
	axis = axis.normalize();
	float radians = degrees * PI / 180;
	float s = sin(radians);
	float c = cos(radians);
	return v * c + axis * axis.dot(v) * (1 - c) + v.cross(axis) * s;
}

//Returns the position of the particle, after rotating the camera
Vec3f adjParticlePos(Vec3f pos) 
{
	return rotate(pos, Vec3f(1, 0, 0), -30);
}

//Returns whether particle1 is in back of particle2
bool compareParticles(Particle* particle1, Particle* particle2) 
{
	return adjParticlePos(particle1->pos)[2] < adjParticlePos(particle2->pos)[2];
}

class ParticleEngine 
{
	private:
		GLuint textureId;
		//Particle particles[NUM_PARTICLES];
		Particle particles[iNumberOfParticles];
		//The amount of time until the next call to step().
		float timeUntilNextStep;
		//The color of particles that the fountain is currently shooting.  0
		//indicates red, and when it reaches 1, it starts over at red again.  It
		//always lies between 0 and 1.
		float colorTime;
		//The angle at which the fountain is shooting particles, in radians.
		float angle;
		
		//Returns the current color of particles produced by the fountain.
		Vec3f curColor() 
		{
			Vec3f color;
			if (colorTime < 0.166667f) 
			{
				color = Vec3f(1.0f, colorTime * 6, 0.0f);
			}
			else if (colorTime < 0.333333f) 
			{
				color = Vec3f((0.333333f - colorTime) * 6, 1.0f, 0.0f);
			}
			else if (colorTime < 0.5f) 
			{
				color = Vec3f(0.0f, 1.0f, (colorTime - 0.333333f) * 6);
			}
			else if (colorTime < 0.666667f) 
			{
				color = Vec3f(0.0f, (0.666667f - colorTime) * 6, 1.0f);
			}
			else if (colorTime < 0.833333f) 
			{
				color = Vec3f((colorTime - 0.666667f) * 6, 0.0f, 1.0f);
			}
			else 
			{
				color = Vec3f(1.0f, 0.0f, (1.0f - colorTime) * 6);
			}
			
			//Make sure each of the color's components range from 0 to 1
			for(int i = 0; i < 3; i++) 
			{
				if (color[i] < 0) 
				{
					color[i] = 0;
				}
				else if (color[i] > 1) 
				{
					color[i] = 1;
				}
			}			
			return color;
		}
		
		//Returns the average velocity of particles produced by the fountain.
		Vec3f curVelocity() 
		{
			return Vec3f(2 * cos(angle), 2.0f, 2 * sin(angle));
		}
		
		//Alters p to be a particle newly produced by the fountain.
		void createParticle(Particle* p) 
		{
			p->pos = Vec3f(0, 0, 0);
			p->velocity = curVelocity() + Vec3f(0.5f * randomFloat() - 0.25f, 0.5f * randomFloat() - 0.25f, 0.5f * randomFloat() - 0.25f);
			p->color = curColor();
			p->timeAlive = 0;
			p->lifespan = randomFloat() + 1;
		}
		
		//Advances the particle fountain by STEP_TIME seconds.
		void step() 
		{
			colorTime += STEP_TIME / 10;
			while (colorTime >= 1) 
			{
				colorTime -= 1;
			}
			
			angle += 0.5f * STEP_TIME;
			while (angle > 2 * PI) 
			{
				angle -= 2 * PI;
			}
			
			//for(int i = 0; i < NUM_PARTICLES; i++) 
			for (int i = 0; i < iNumberOfParticles; i++)
			{
				Particle* p = particles + i;
				
				p->pos += p->velocity * STEP_TIME;
				p->velocity += Vec3f(0.0f, -GRAVITY * STEP_TIME, 0.0f);
				p->timeAlive += STEP_TIME;
				if (p->timeAlive > p->lifespan) 
				{
					createParticle(p);
				}
			}
		}

	public:
		ParticleEngine(GLuint textureId1) 
		{
			textureId = textureId1;
			timeUntilNextStep = 0;
			colorTime = 0;
			angle = 0;
			//for(int i = 0; i < NUM_PARTICLES; i++) 
			for (int i = 0; i < iNumberOfParticles; i++)
			{
				createParticle(particles + i);
			}
			for(int i = 0; i < 5 / STEP_TIME; i++) 
			{
				step();
			}
		}
		
		//Advances the particle fountain by the specified amount of time.
		void advance(float dt) 
		{
			while (dt > 0) 
			{
				if (timeUntilNextStep < dt) 
				{
					dt -= timeUntilNextStep;
					step();
					timeUntilNextStep = STEP_TIME;
				}
				else 
				{
					timeUntilNextStep -= dt;
					dt = 0;
				}
			}
		}
		
		//Draws the particle fountain.
		void draw() 
		{
			vector<Particle*> ps;
			//for(int i = 0; i < NUM_PARTICLES; i++) 
			for (int i = 0; i < iNumberOfParticles; i++)
			{
				ps.push_back(particles + i);
			}
			sort(ps.begin(), ps.end(), compareParticles);
			
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, textureId);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			
			glBegin(GL_QUADS);
			for(unsigned int i = 0; i < ps.size(); i++) 
			{
				Particle* p = ps[i];
				glColor4f(p->color[0], p->color[1], p->color[2], (1 - p->timeAlive / p->lifespan));
				//float size = PARTICLE_SIZE / 2;
				size = iParticleSize / 2;
				
				Vec3f pos = adjParticlePos(p->pos);
				
				glTexCoord2f(0, 0);
				glVertex3f(pos[0] - size, pos[1] - size, pos[2]);
				glTexCoord2f(0, 1);
				glVertex3f(pos[0] - size, pos[1] + size, pos[2]);
				glTexCoord2f(1, 1);
				glVertex3f(pos[0] + size, pos[1] + size, pos[2]);
				glTexCoord2f(1, 0);
				glVertex3f(pos[0] + size, pos[1] - size, pos[2]);
			}
			glEnd();
		}
};


//Returns an array indicating pixel data for an RGBA image that is the same as
//image, but with an alpha channel indicated by the grayscale image alphaChannel
char* addAlphaChannel(Image* image, Image* alphaChannel) 
{
	char* pixels = new char[image->width * image->height * 4];
	size_t pixelsize = sizeof(pixels) / sizeof(*pixels);
	for(int y = 0; y < image->height; y++) 
	{
		for(int x = 0; x < image->width; x++) 
		{
			for(int j = 0; j < 3; j++) 
			{
				pixels[4 * (y * image->width + x) + j] = image->pixels[3 * (y * image->width + x) + j];
			}
			pixels[4 * (y * image->width + x) + 3] = alphaChannel->pixels[3 * (y * image->width + x)];
		}
	}
	if ((pixelsize > 0) && (pixelsize <= pixelsize))
	{
		return pixels;
	}
}

//Makes the image into a texture, using the specified grayscale image as an
//alpha channel and returns the id of the texture
GLuint loadAlphaTexture(Image* image, Image* alphaChannel) 
{
	char* pixels = addAlphaChannel(image, alphaChannel);
	
	GLuint textureId;
	glGenTextures(1, &textureId);
	glBindTexture(GL_TEXTURE_2D, textureId);
	glTexImage2D(GL_TEXTURE_2D,
				 0,
				 GL_RGBA,
				 image->width, image->height,
				 0,
				 GL_RGBA,
				 GL_UNSIGNED_BYTE,
				 pixels);
	
	delete pixels;
	return textureId;
}

void initRendering() 
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	Image* image = loadBMP("square.bmp");
	Image* alphaChannel = loadBMP("squareTA.bmp");
	_textureId = loadAlphaTexture(image, alphaChannel);
	delete image;
	delete alphaChannel;
	image = NULL;
	alphaChannel = NULL;
}

void handleResize(int w, int h) 
{
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, (double)w / (double)h, 1.0, 200.0);
}

void drawScene() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -10.0f);
	glScalef(2.0f, 2.0f, 2.0f);
	auto _particleEngine = std::make_unique<ParticleEngine>(_textureId);
	_particleEngine->draw();
	_particleEngine.reset();
	glutSwapBuffers();
	_particleEngine.reset();
}

void help()
{
	std::cout << std::endl << "--- " << sTitle.append(sVersion) << std::endl;
	std::cout << std::endl << "visParticle.exe is an OpenGL application with the purpose to provide a \nstress/load component to the GPU device of the Intel processor." << std::endl;
	std::cout << std::endl << "..Default execution runs visParticle.exe at window size 400 for 20 seconds.." << std::endl;
	std::cout << std::endl << "..Options are as follows: - " << std::endl;
	std::cout << " -h \t" << "     " << "= This message..\n\t" << "       " << "Example:\n\t" << "       " << "visParticle.exe -h" << std::endl;
	std::cout << std::endl << " -info \t" << "     " << "= Information switch that publishes parallel information \n\t" << "       " << "using the following scheme:" << std::endl;
	std::cout << "\t" << "       " << "\"parallel:yes|socket:yes|core:yes\" " << std::endl;
	std::cout << std::endl << " -ws \t" << "     " << "= Window Size (Width x Height), default is set to 400\n\t" << "      " << " Window Size range is between 400 - 1000\n\t""       " << "Example:\n\t" << "       " << "visParticle.exe -ws 500" << std::endl;
	std::cout << std::endl << " -m \t" << "     " << "= Run visParticle X number of minutes\n\t" << "       " << "Example:\n\t" << "       " << "visParticle.exe -m 10" << std::endl;
	std::cout << std::endl << " -s \t" << "     " << "= Run visParticle X number of seconds\n\t" << "       " << "Example:\n\t" << "       " << "visParticle.exe -s 40" << std::endl;
	std::cout << std::endl << " -ps \t" << "     " << "= Increment particle size, default particle size is 1.0\n\t" << "      " << " Particle size range is between 0.05 - 2.5\n\t" << "       " << "Increasing the particle size, increases the workload on the GPU.\n\t" << "       " << "Example:\n\t" << "       " << "visParticle.exe -ps 1.5" << std::endl;

	std::cout << std::endl << "\t" << "       " << "Combination of window size with particle size with either\n\t"<< "       " << "minutes or seconds, and resultName is permitted.\n\t" << "       " << "Example:\n\t" << "       " << "visParticle.exe -ws 700 -ps 2.1 -m 5 -resultName results_1.txt" << std::endl;

	std::cout << std::endl << "Dependencies:" << std::endl;
	std::cout << "\t" << "       " << "freeglut.dll" << std::endl;
	std::cout << "\t" << "       " << "square.bmp" << std::endl;
	std::cout << "\t" << "       " << "squareTA.bmp" << std::endl;

	std::cout << std::endl << " Copyright (C) 2016, Intel Corporation" << std::endl;

	exit(0);
}

void update(int value) 
{
	auto _particleEngine = std::make_unique<ParticleEngine>(_textureId);
	_particleEngine->advance(TIMER_MS / 1000.0f);
	_particleEngine.reset();
	glutPostRedisplay();
	glutTimerFunc(TIMER_MS, update, 0);
}

bool is_integer(const std::string & s) 
{
	

 return std::regex_match(s, std::regex("[0-9]+"));
}

bool is_float(const std::string & s)
{
	return std::regex_match(s, std::regex("[0-9]+(\\.[0-9]+)"));
}

bool isOptionValid(int iArgLocal, int argcLocal, char *argvLocal[], VarType vt)
{
	
	// Start out as Valid
	bool iRetVal = true;
	std::string sMsg = "";
	std::string sTemp = argvLocal[iArgLocal];

	if (((iArgLocal + 1) < argcLocal) && !(argvLocal[iArgLocal + 1] == NULL) && !(argvLocal[iArgLocal + 1][0] == '\0'))
	{
		if (vt == VarType::vtINT)
		{
		if (!is_integer(argvLocal[iArgLocal + 1]))
			{
				iRetVal = false;
				sMsg = "Incorrect or incomplete argument type for: " + sTemp;
				std::cout << std::endl << sMsg << std::endl;
			}
		}
		if (vt == VarType::vtFLOAT)
		{
			if (!is_float(argvLocal[iArgLocal + 1]))
			{
				iRetVal = false;
				sMsg = "Incorrect or incomplete argument type for: " + sTemp;
				std::cout << std::endl << sMsg << std::endl;
				//help();
			}
		}
	}
	else
	{
		// invalid
		iRetVal = false;
		sMsg = "Incorrect or incomplete argument type for: " + sTemp;
		std::cout << std::endl << sMsg << std::endl;
		//help();
	}

	// if there was an invalid argument
	if (iRetVal == false)
	{
		std::cout << std::endl << "..Invalid Argument.." << std::endl;
		//help();
	}
	return iRetVal;
}

//void checkArgs(int argc, char** argv)
void checkArgs(int argc, char *argv[])
{
	std::vector <string> sArgs_v;
	sArgs_v.push_back("-h");
	sArgs_v.push_back("-info");
	sArgs_v.push_back("-m");
	sArgs_v.push_back("-s");
	sArgs_v.push_back("-ps");
	sArgs_v.push_back("-ws");
	sArgs_v.push_back("-resultName");
	
	if (argc > 1)
	{
		for (int iArg = 1; iArg < argc; iArg++)
		{
			iArgIndex = iArg;
			iOptionValid = 0;

			for (iVec = 0; iVec < sArgs_v.size(); iVec++)
			{
				if (argv[iArg] == sArgs_v[iVec])
				{
					iOptionValid = 1;
					
					//..Help
					if (sArgs_v[iVec] == std::string("-h"))
					{
						iRun = 0;
						help();
					}

					if (sArgs_v[iVec] == std::string("-info"))
					{
						// Info
						iRun = 0;
						std::cout << "\"parallel:yes|socket:yes|core:yes\"" << std::endl;
					}

					//..Minutes
					if (sArgs_v[iVec] == std::string("-m"))
					{
						if (isOptionValid(iArg, argc, argv, VarType::vtINT))
						{
							iOptionMinutes = 1;
							//iMin = atoi(argv[++iArg]);
							iMin = CustomAsciiToInteger(argv[++iArg]);
							iTimeMS = iMin * 60 * 1000;
							sTime = std::to_string(iMin);
						}
						else
						{
							iRun = 0;
							iOptionValid = 0;
						}
					}

					//..Seconds
					if (sArgs_v[iVec] == std::string("-s"))
					{
						if (isOptionValid(iArg, argc, argv, VarType::vtINT))
						{
							iOptionSeconds = 1;
							//iSec = atoi(argv[++iArg]);
							iSec = CustomAsciiToInteger(argv[++iArg]);
							iTimeMS = iSec * 1000;
							sTime = std::to_string(iSec);
						}
						else
						{
							iRun = 0;
							iOptionValid = 0;
						}
					}

					//..Particle Size
					if (sArgs_v[iVec] == std::string("-ps"))
					{
						if (isOptionValid(iArg, argc, argv, VarType::vtFLOAT))
						{
							iParticleSize = (float)atof(argv[++iArg]);
							sParticleSize = std::to_wstring(iParticleSize);
						}
						else
						{
							iRun = 0;
							iOptionValid = 0;
						}
					}

					if (sArgs_v[iVec] == std::string("-resultName"))
					{
						visParticleResultsFile = argv[++iArg];
					}
					
					//..Window Size
					if (sArgs_v[iVec] == std::string("-ws"))
					{
						if (isOptionValid(iArg, argc, argv, VarType::vtINT))
						{
							//iWindowSize = atoi(argv[++iArg]);
							iWindowSize = CustomAsciiToInteger(argv[++iArg]);
							sWindowSize = std::to_string(iWindowSize);
						}
						else
						{
							iRun = 0;
							iOptionValid = 0;
						}
					}
				}
			}
			if (iOptionValid == 0)
			{
				break;
			}
		}
		if (iOptionValid == 0)
		{
			iRun = 0;
			igPassFailStatus = ReturnValueDef::InvalidArgs;
			help();
		}
	}
	//else
	//{
		// Default message if no arguments are used  ... it may be the useage message or it may be OK that no args are used
		//std::cout << "No args used!!" << std::endl;
	//}
}

void WriteResultsFile(int iPassFailStatus)
{
	// pass fail logic using iPassFailStatus  with 0=pass, 1=fail, 2=indeterminate

	// Write Results File
	td.WriteToFile(visParticleResultsFile, "IPDT visParticle Graphics Test");
	std::string sTemp = "Module Version: " + sVersion;
	td.WriteToFile(visParticleResultsFile, sTemp);
	sTemp = "Start Time: ";

	//localtime_s(&newtime, &tStartTime);
	//asctime_s(timestr, sizeof timestr, &newtime);
	//sTemp.append(timestr);
	sTemp.append(std::asctime(std::localtime(&tStartTime)));
	
	td.WriteToFileNoEndL(visParticleResultsFile, sTemp);

	if (iPassFailStatus == 0)
	{
		td.WriteToFile(visParticleResultsFile, sPassMessage);	//pass
		if ((iOptionSeconds == 1) || ((iOptionSeconds == 0) && (iOptionMinutes == 0)))
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "Successfully executed particle test for " + sTime + " seconds ");
		}
		if (iOptionMinutes == 1)
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "Successfully executed particle test for " + sTime + " minute(s) ");
		}
		//td.WriteToFileNoEndL(visParticleResultsFile, "at " + sWindowSize + " x " + sWindowSize + " window size, " + "with particle size " + L(sParticleSize));
		td.WriteToFile(visParticleResultsFile, "");
	}
	else if (iPassFailStatus == 1)
	{
		td.WriteToFile(visParticleResultsFile, sFailMessage);	//fail
		if ((iOptionSeconds == 1) || ((iOptionSeconds == 0) && (iOptionMinutes == 0)))
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "visParticle failed to run for " + sTime + " seconds ");
		}
		if (iOptionMinutes == 1)
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "visParticle failed to run for " + sTime + " minute(s) ");
		}
		//td.WriteToFileNoEndL(visParticleResultsFile, "at " + sWindowSize + " x " + sWindowSize + " window size, " + "with particle size " + sParticleSize);
		td.WriteToFile(visParticleResultsFile, "");
	}
	else if (iPassFailStatus == 2)
	{
		td.WriteToFile(visParticleResultsFile, sIndeterminateMessage); //aborted
		td.WriteToFile(visParticleResultsFile, "..!!..visParticle was aborted for some reason..!!..");
		if ((iOptionSeconds == 1) || ((iOptionSeconds == 0) && (iOptionMinutes == 0)))
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "visParticle failed to run for " + sTime + " seconds ");
		}
		if (iOptionMinutes == 1)
		{
			td.WriteToFileNoEndL(visParticleResultsFile, "visParticle failed to run for " + sTime + " minute(s) ");
		}
		//td.WriteToFileNoEndL(visParticleResultsFile, "at " + sWindowSize + " x " + sWindowSize + " window size, " + "with particle size " + sParticleSize);
		td.WriteToFile(visParticleResultsFile, "");
	}
	else
	{
		td.WriteToFile(visParticleResultsFile, sFailMessage);	//else default to fail
	}
	
	tEndTime = std::time(nullptr);
	sTemp = "End Time: ";
	sTemp.append(std::asctime(std::localtime(&tEndTime)));
	td.WriteToFileNoEndL(visParticleResultsFile, sTemp);

	double seconds = difftime(tEndTime, tStartTime);
	sTemp = "Total Time: ";
	sTemp.append(" seconds: ");
	sTemp.append(UtilConvert(seconds));

	td.WriteToFileNoEndL(visParticleResultsFile, sTemp);
}

void endDemo(int value)
{
	WriteResultsFile(igPassFailStatus);
	exit(igPassFailStatus);
}

void CleanUp()
{
	td.RemoveFile(visParticleResultsFile);
}

void SignalFun(int iSigNum)
{
	// If you want to print out the signal, do the following
	std::cout << std::endl << "..Received SIGNAL " << iSigNum << std::endl;

	// Depending on your routine, you may want to have some sort of exit message ... however you may also want to comment these out
	//cout << endl << "Do the soft clean up here" << endl;

	// Do cleanup and close up stuff here 
	CleanUp();

	// Exit the program with one of the accepted error levels. 
	igPassFailStatus = ReturnValueDef::Indeterminate;

	// if you get to this logic, most likely your answer should always be a 2
	//std::string sTemp = "..ERROR: " + UtilConvert(igPassFailStatus) + "\n..!!..Indeterminate..!!.." + "\n..!!..Exiting Test..!!..";
	std::string sTemp = "..!!..Indeterminate..!!..\n..!!..Exiting Test..!!..";
	WriteResultsFile(igPassFailStatus);
	exit(igPassFailStatus);
}

void Init(void)
{
	tStartTime = std::time(nullptr);

	igPassFailStatus = ReturnValueDef::Success;  // Success

	iSec = 0;
	iMin = 0;
	iOptionValid = 0;
	iRun = 1;
	iArgIndex = 0;
}

int main(int argc, char** argv) 
{
	// Need to include signal for Softkill functions
	// If this thread is sent a signal SIGINT, we need to send it to the signal function 
	signal(SIGINT, SignalFun);

	srand((unsigned int)time(0)); //Seed the random number generator
	
	glutInit(&argc, argv);

	checkArgs(argc, argv);

	CleanUp();
	
	if (iRun)
	{
		Init();

		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

		if ((iWindowSize == 0) || (iWindowSize < 400) || (iWindowSize > 1000))
		{
			iWindowSize = iDefaultWindowSize;
			sWindowSize = std::to_string(iWindowSize);
		}
		glutInitWindowSize(iWindowSize, iWindowSize);

		//glutInitWindowSize(1000, 1000);
	
		sTitle.append(sVersion);
		glutCreateWindow(sTitle.c_str());

		initRendering();
	
		glutDisplayFunc(drawScene);
		glutReshapeFunc(handleResize);
	
		std::unique_ptr<ParticleEngine> _particleEngine = std::make_unique<ParticleEngine>(_textureId);
		_particleEngine.reset();

		glutTimerFunc(TIMER_MS, update, 0);

		if ((iOptionMinutes == 0) && (iOptionSeconds == 0))
		{
			iTimeMS = iDefaultTimeSecs * 1000;
			sTime = std::to_string(iDefaultTimeSecs);
		}

		if ((iParticleSize == 0) || (iParticleSize < 0.05) || (iParticleSize > 2.5))
		{
			iParticleSize = iDefaultParticleSize;
		}

		glutTimerFunc(iTimeMS, endDemo, 0);
		glutMainLoop();
	}
	return igPassFailStatus;
}

