	
#ifdef _DEBUG
	#include <stdlib.h>
	#include "../mmgr.h"
#endif

#pragma warning(disable: 4244)

#include <math.h>

#include "stream.hpp"
#include "../mathematics.hpp"
#include "../primitives.hpp"

void Stream::draw()
{
	const float pos = (time - startTime) / (endTime - startTime);
	float alpha = 1.0f;

	const float fadeinstart = 0.0f;
	const float fadeinstop = 0.05f;
	const float fadeoutstart = 0.85f;
	const float fadeoutstop = 0.97f;

	if (pos >= fadeinstart && pos <= fadeinstop)
		alpha *= (pos-fadeinstart) / (fadeinstop-fadeinstart);
	if (pos >= fadeoutstart && pos <= fadeoutstop)
		alpha *= 1-(pos-fadeoutstart) / (fadeoutstop-fadeoutstart);
	
	if(pos >= fadeoutstop)
		alpha = 0;

	renderScene(pos, alpha);

}

void RenderWaterPlane(float y)
{
	// Render quad
	glBegin(GL_QUADS);
		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(300.0f, y, 300.0f);

		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(300.0f, y, -300.0f);

		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-300.0f, y, -300.0f);

		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-300.0f, y, 300.0f);
	glEnd();
}

Vector turbulenceFunc1(float t,Vector &pos)
{
    const float m = 1.8f;

    const float x = 0.7f*cosf(pos.x*m*t*12)+1.1f*sinf(pos.y*m*t*8)+0.52f*cosf(pos.z*m*t*17); 
    const float y = 0.9f*cosf(pos.x*m*t*19)+1.6f*sinf((pos.y-0.6f*pos.x)*m*t*18)+1.22f*cosf(pos.z*m*t*6); 
    const float z = 1.4f*cosf(pos.x*m*t*9)+0.98f*sinf(pos.y*m*t*12)+1.42f*cosf((pos.z-0.6f*pos.x)*m*t*6); 

    return Vector(x, y, z);

}

void Stream::drawParticles(float pos, float alpha)
{

	int i;
//	glTranslatef(-2.5, 0, -2.5);


	Vector x, y, z;
	Mathematics::antiRotate(&x, &y, &z);

	
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_DEPTH_TEST);


	for(i=0; i<smokeCount; i++)
	{

		Vector adjust = turbulenceFunc1(pos, smoke[i])*0.00172f;

		smoke[i] += Vector(0.0095f*(fmodf(i, 1.5f) - 0.75f)*pos, adjust.z, adjust.y);

		Vector point = smoke[i];
		const float size = 2.5f;
		//const float size = 1.0750f;

		const int sm = smokeCount/4;

		if(i==0)   		 glBindTexture(GL_TEXTURE_2D, dmsGetTexture("smoke1.jpg")->getID());
		else if(i==sm)   glBindTexture(GL_TEXTURE_2D, dmsGetTexture("smoke2.jpg")->getID());
		else if(i==sm*2) glBindTexture(GL_TEXTURE_2D, dmsGetTexture("smoke3.jpg")->getID());
		else if(i==sm*3) glBindTexture(GL_TEXTURE_2D, dmsGetTexture("smoke4.jpg")->getID());

		glColor3f(0.09713f*alpha,0.09713f*alpha,((i%4)*0.0173f + 0.09239713f)*alpha);

		glBegin(GL_QUADS);

			Vector v1 = point - x*size - y*size;
			Vector v2 = point + x*size - y*size;
			Vector v3 = point + x*size + y*size;
			Vector v4 = point - x*size + y*size;

			glTexCoord2f(0, 0);
			glVertex3fv((float *)&v1);
			glTexCoord2f(1, 0);
			glVertex3fv((float *)&v2);
			glTexCoord2f(1, 1);
			glVertex3fv((float *)&v3);
			glTexCoord2f(0, 1);
			glVertex3fv((float *)&v4);

		glEnd();
	}
/**/

	const float fadein = Mathematics::calcPosFloat(pos, 0.05f, 0.175f);
	const float fadeoutparticles = 1 - Mathematics::calcPosFloat(dmsGetModulePosition(), 285500, 286500);

	glPushMatrix();
	
		glRotatef(40+pos*150, 0.11f, 1.0f, -0.15f);
		Mathematics::antiRotate(&x, &y, &z);

		glBindTexture(GL_TEXTURE_2D, dmsGetTexture("fireparticle.jpg")->getID());

		for (i = 0; i < scount; i++)
		{
			StreamSpline *s = splines[i];

			float t = 0.0f;

			glBegin(GL_QUADS);
			for (int j = 0; j < s->pcount; j ++)
			{
				StreamParticle *p = &s->particles[j];
				float post = fmodf(p->positionphase + p->positionspeed*pos, 1);

				float colort = fmodf(p->colorphase + p->colorspeed*pos, 1);
				float movet1 = fmodf(p->movephase1 + p->movespeed1*pos, 1);
				float movet2 = fmodf(p->movephase2 + p->movespeed2*pos, 1);
				float movet3 = fmodf(p->movephase3 + p->movespeed3*pos, 1);

				const float a = powf(sinf(colort*3.141592f), 3) * sinf(post*3.141592f)*alpha;
				Vector displace = Vector(cosf(movet1*3.141592f*2), sinf(movet2*3.141592f*2), cosf(movet3*3.141592f*2));

				Vector position = s->polku->getValue(post) + displace*0.04f*cosf(colort*3.141592f*2);
				Vector actualposition = Vector(position.x, position.y, position.z);

				glColor4f(1, 1, 1, a*a*a*0.8f*fadeoutparticles*fadein);
				float size = 0.01f + 0.08f*(1-a*a*a);

				Vector v1 = actualposition - x*size - y*size;
				Vector v2 = actualposition + x*size - y*size;
				Vector v3 = actualposition + x*size + y*size;
				Vector v4 = actualposition - x*size + y*size;

				glTexCoord2f(0, 0);
				glVertex3fv((float *)&v1);
				glTexCoord2f(1, 0);
				glVertex3fv((float *)&v2);
				glTexCoord2f(1, 1);
				glVertex3fv((float *)&v3);
				glTexCoord2f(0, 1);
				glVertex3fv((float *)&v4);

			}

			glEnd();
		}
	
	glPopMatrix();

/**/


	glPushMatrix();

	//glDisable(GL_DEPTH_TEST);

	const int namecount = 7;
	const char* nimet[namecount] = 
	{
		"credits_bridgeclaw.png",
		"credits_critikill.png",
		"credits_kofeiini.png",
		"credits_napsa.png",
		"credits_preacher.png",
		"credits_rale.png",
		"credits_romeoknight.png",
	};

	const float ajat[namecount] = 
	{
		0.15f, 0.15f,
		0.35f, 0.35f,
		0.55f, 0.55f,
		0.7f
	};

	Vector paikat[namecount] = 
	{
		Vector(-0.4f, -0.7f, -13.2f),
		Vector( 0.3f,  0.7f, -13.2f),
		Vector(-0.3f, -0.7f, -13.2f),
		Vector( 0.5f,  0.7f, -13.2f),
		Vector(0.37f,  0.7f, -13.2f),
		Vector(-0.4f, -0.7f, -13.2f),
		Vector(-0.0f,  0.0f, -13.2f),
	};

	glLoadIdentity();
	glTranslatef(0, 0, -15);

	glMatrixMode(GL_MODELVIEW_MATRIX);

	// fade out alku
	// 285500

	for (i = 0; i < namecount; i++)
	{
		glPushMatrix();
		Vector p = paikat[i];

		const float move = Mathematics::calcPosFloat(pos, ajat[i], ajat[i]+0.25f);
		glTranslatef(p.x*5 + (i%2? move : -move), p.y*5, p.z);
		Texture *t = dmsGetTexture(nimet[i]);
		const float w = t->getWidth() * 0.025f;
		const float h = t->getHeight() * 0.025f;

		glTranslatef(-w * 0.5f, 0, 0);

		glEnable(GL_TEXTURE_2D);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		float fade = Mathematics::calcPosFloat(pos, ajat[i], ajat[i]+ 0.075f) * 
						   (1-Mathematics::calcPosFloat(pos, ajat[i]+0.12f, ajat[i]+0.14f));
		if(i == namecount -1 )
			fade = Mathematics::calcPosFloat(pos, ajat[i], ajat[i]+ 0.075f) * 
						   (1-Mathematics::calcPosFloat(pos, ajat[i]+0.12f, ajat[i]+0.24f));
			
		glColor4f(1,1,1,alpha * fade);
		glBindTexture(GL_TEXTURE_2D, t->getID());

		glBegin(GL_QUADS);
		glTexCoord2f(0, 1);
		glVertex3f(0, 0, 0);
		glTexCoord2f(1, 1);
		glVertex3f(w, 0, 0);
		glTexCoord2f(1, 0);
		glVertex3f(w, h, 0);
		glTexCoord2f(0, 0);
		glVertex3f(0, h, 0);

		glEnd();
		glPopMatrix();
		
	}


	glPopMatrix();
	
	
	glDisable(GL_BLEND);
}

void lookAt()
{
	gluLookAt(  3.90f, 1.0319729724f, 4.29f,
				0,0,0,
				0,1,0);

}

void Stream::RenderReflection(float pos, float alpha)
{
	glExt.bindTextureFBO512(glExt.glowTexture512ID);

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		lookAt();

		glPushMatrix();
		glTranslatef(0.0f, 0.0f, 0.0f);
		glScalef(1.0, -1.0, 1.0);
		double plane[4] = {0.0, 1.0, 0.0, 0.0}; //water at y=0
		glEnable(GL_CLIP_PLANE0);
		glClipPlane(GL_CLIP_PLANE0, plane);
			drawParticles(pos, alpha);
		glDisable(GL_CLIP_PLANE0);
		glPopMatrix();

	glExt.unbindFBO();
}

void Stream::RenderRefractionAndDepth(float pos, float alpha)
{
    glExt.bindDepthFBO();

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glLoadIdentity();
		lookAt();

		glPushMatrix();
		glTranslatef(0.0f, 0.0f, 0.0f);
		//normal pointing along negative y
		double plane[4] = {0.0, -1.0, 0.0, 0.0};
		glEnable(GL_CLIP_PLANE0);
		glClipPlane(GL_CLIP_PLANE0, plane);
			drawParticles(pos, alpha);
		glDisable(GL_CLIP_PLANE0);
		glPopMatrix();

	glExt.unbindFBO();
}

void Stream::RenderWater(float pos, float alpha)
{

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	shaders.waterReflect->bind();

	shaders.waterReflect->setUniform1f("fade",  alpha*alpha*alpha);	// time!

	const float spdmod = pos;

	shaders.waterReflect->setUniform1f("time",  4.5f*spdmod);	// time!
	shaders.waterReflect->setUniform1f("time2", 7.0f*spdmod);
	
	shaders.waterReflect->setUniform4f("viewpos", 1, 1, 1 ,1);
	shaders.waterReflect->setUniform4f("lightpos", -209.5, 1, 7.5, 1);

	shaders.waterReflect->setUniform4f("waterColor", 0.1473f*alpha, 0.2163f*alpha, 0.2756f*alpha, 1.0f*alpha);

	glColor3f(1,1,1);

    glActiveTextureARB(GL_TEXTURE0_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, glExt.glowTexture512ID);
	shaders.waterReflect->setUniform1i("water_reflection", 0);

    glActiveTextureARB(GL_TEXTURE1_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, glExt.depthColTex); //refraction
	shaders.waterReflect->setUniform1i("water_refraction", 1);

    glActiveTextureARB(GL_TEXTURE2_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, dmsGetTexture("water_normalmap.jpg")->getID());
	shaders.waterReflect->setUniform1i("water_normalmap", 2);

    glActiveTextureARB(GL_TEXTURE3_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, dmsGetTexture("water_distort.jpg")->getID());
	shaders.waterReflect->setUniform1i("water_dudvmap", 3);

    glActiveTextureARB(GL_TEXTURE4_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, glExt.depthTex);
	shaders.waterReflect->setUniform1i("water_depthmap", 4);

    glActiveTextureARB(GL_TEXTURE5_ARB);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, dmsGetTexture("envmap.jpg")->getID());
	shaders.waterReflect->setUniform1i("water_envmap", 5);

    glBegin(GL_QUADS);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 5.0f);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE2_ARB, 0.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE3_ARB, 0.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE4_ARB, 0.0f, 1.0f);
    glVertex3f(-5.0f, 0.0f, 5.0f);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE2_ARB, 0.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE3_ARB, 0.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE4_ARB, 0.0f, 0.0f);
    glVertex3f(-5.0f, 0.0f, -5.0f);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 5.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE2_ARB, 1.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE3_ARB, 1.0f, 0.0f);
    glMultiTexCoord2fARB(GL_TEXTURE4_ARB, 1.0f, 0.0f);
    glVertex3f(5.0f, 0.0f, -5.0f);
    glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 5.0f, 5.0f);
    glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE2_ARB, 1.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE3_ARB, 1.0f, 1.0f);
    glMultiTexCoord2fARB(GL_TEXTURE4_ARB, 1.0f, 1.0f);
    glVertex3f(5.0f, 0.0f, 5.0f);
    glEnd();
	
    glActiveTextureARB(GL_TEXTURE0_ARB);
    glDisable(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE1_ARB);
	glDisable(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE2_ARB);
	glDisable(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE3_ARB);
	glDisable(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE4_ARB);
    glDisable(GL_TEXTURE_2D);

    glActiveTextureARB(GL_TEXTURE5_ARB);
    glDisable(GL_TEXTURE_2D);

	glActiveTextureARB(GL_TEXTURE0_ARB);

	shaders.unbind();

}

void Stream::renderScene(float pos, float alpha)
{

	glLoadIdentity();
	lookAt();
	drawParticles(pos, alpha);

    RenderReflection(pos, alpha);
     RenderRefractionAndDepth(pos, alpha);

    //reset viewport to screensize
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

	lookAt();

//	filter.init();
	glEnable(GL_DEPTH_TEST);

		RenderWater(pos, alpha);

		drawParticles(pos, alpha);


	glDisable(GL_DEPTH_TEST);
//	filter.glow(3, 0.004f, 0.004f, 1.0f, 0.98f);





}	

StreamSpline::StreamSpline(int pcount, Vector &start, Vector &end)
{
    const int splinepoints = 8;

    int i;
    this->pcount = pcount;
    this->polku = new CatmullRom(splinepoints);
    this->particles = new StreamParticle[this->pcount];

    for (i=0;i<splinepoints;i++)
    {
        const float it = i / (splinepoints*1.0f - 1.0f);
        Vector p = start + (end - start)*it + Mathematics::randVector()*0.25f*powf(sinf(it*3.141592f), 3);
		p.y += 1.14f; 
        polku->addPoint(p);
    }

    for (i = 0; i < this->pcount; i++)
    {
        this->particles[i].colorphase = Mathematics::randFloat();
        this->particles[i].colorspeed = 5.3f + 7.5f * Mathematics::randFloat();
        this->particles[i].positionphase = Mathematics::randFloat();
        this->particles[i].positionspeed = 1.0f + 2.0f * Mathematics::randFloat();
        this->particles[i].movephase1 = Mathematics::randFloat();
        this->particles[i].movephase2 = Mathematics::randFloat();
        this->particles[i].movephase3 = Mathematics::randFloat();
        this->particles[i].movespeed1 = 3 + 4.0f * Mathematics::randFloat();
        this->particles[i].movespeed2 = 3 + 4.0f * Mathematics::randFloat();
        this->particles[i].movespeed3 = 1 + 2.0f * Mathematics::randFloat();
        this->particles[i].moveradius = 0.05f + 0.10f*Mathematics::randFloat();
    }

}

StreamSpline::~StreamSpline()
{
    delete [] this->particles;
    delete this->polku;
}


Stream::Stream()
{	
    int i;
    scount = 14;
	/**/
    const int particles_per_stream = 160;

    srand(106923);

    splines = new StreamSpline*[scount];
    for (i = 0; i < scount; i++)
    {
        Matrix rot;
        rot.makeRotation(Mathematics::randFloat()*2*3.141592f,
                         Mathematics::randFloat()*2*3.141592f,
                         Mathematics::randFloat()*2*3.141592f);

        Vector v1 = Mathematics::randVector()*8;
		v1.y *= 0.3f;
		v1.z *= 0.4f;
        Vector v2 = Mathematics::randVector()*8;
		v2.y *= 0.3f;
		v2.z *= 0.4f;

        splines[i] = new StreamSpline(particles_per_stream, v1, v2);
	}
	/***/
    srand(106919);

	smokeCount = 80;
	smoke = new Vector[smokeCount];
	for(i=0; i<smokeCount; i++)
	{
		float x = Mathematics::randBetween(-1.7f,	-20.0f);
		float y = Mathematics::randBetween( 0.15f,  4.5f);
		float z = Mathematics::randBetween(-1.7f,	-20.0f);
		smoke[i] = Vector(x,y,z);
	}

}

Stream::~Stream()
{
  
	/*
    for (i = 0; i < scount; i++)
    {
        delete splines[i];
    }
    delete [] splines;
	/**/

}


bool Stream::init(unsigned long s, unsigned long e)
{
	startTime = s;
	endTime = e;
	return true;
}

