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

#pragma warning(disable: 4244)

#include <math.h>

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

int scount;
StreamSpline **splines;

unsigned int waterTextureID;

static look [6][6] = {
    // GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB
    { 1.0, 0.0, 0.0, 0.0, -1.0, 0.0 }, 
    // GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB
    { -1.0, 0.0, 0.0, 0.0, -1.0, 0.0 },
    // GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB
    { 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 },
    // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB
    { 0.0, -1.0, 0.0, 0.0, 0.0, -1.0 },
    // GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB
    { 0.0, 0.0, 1.0, 0.0, -1.0, 0.0 },
    // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB
    { 0.0, 0.0, -1.0, 0.0, -1.0, 0.0 }
};


static GLenum CubeMapTarget[6] = {
  GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
  GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
  GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
  GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB,
  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB,
};


static char CubeMapFileName[6][20]={
	"cube_xpos.jpg",
	"cube_xneg.jpg",
	"cube_ypos.jpg",
	"cube_yneg.jpg",
	"cube_zpos.jpg",
	"cube_zneg.jpg"
};

float SkyboxVerts[24][3] = {

	//Right side of sky box.
	{	 10.10f,		10.10f,		-10.10f	},			//Top left.
	{	 10.10f,		10.10f,		10.10f	},			//Top right.
	{	 10.10f,		-10.10f,		10.10f	},			//Bottom right.
	{	 10.10f,		-10.10f,		-10.10f	},			//Bottom left.

	//left side of sky box.
	{	-10.10f,		10.10f,		10.10f	},			//Top left.
	{	-10.10f,		10.10f,		-10.10f	},			//Top right.
	{	-10.10f,		-10.10f,		-10.10f	},			//Bottom right.
	{	-10.10f,		-10.10f,		10.10f	},			//Bottom left.


	//Top side of sky box.
	{	-10.10f,		10.10f,		10.10f	},			//Top left.
	{	 10.10f,		10.10f,		10.10f	},			//Top right.
	{	 10.10f,		10.10f,		-10.10f	},			//Bottom right.
	{	-10.10f,		10.10f,		-10.10f	},			//Bottom left.

	//Bottom side of sky box.
	{	-10.10f,		-10.10f,		-10.10f	},			//Top left.
	{	 10.10f,		-10.10f,		-10.10f	},			//Top right.
	{	 10.10f,		-10.10f,		10.10f	},			//Bottom right.
	{	-10.10f,		-10.10f,		10.10f	},			//Bottom left.

	
	//Front side of sky box.
	{	-10.10f,		10.10f,		-10.10f	},			//Top left.
	{	 10.10f,		10.10f,		-10.10f	},			//Top right.
	{	 10.10f,		-10.10f,		-10.10f	},			//Bottom right.
	{	-10.10f,		-10.10f,		-10.10f	},			//Bottom left.

	//Back side of sky box.
	{	10.10f,		10.10f,		10.10f	},			//Top left.
	{	-10.10f,		10.10f,		10.10f	},			//Top right.
	{	-10.10f,		-10.10f,		10.10f	},			//Bottom right.
	{	 10.10f,		-10.10f,		10.10f	},			//Bottom left.
};

//These are the rotation angles, to rotate the world at, to create the proper cube map textures.
//Left and right must be inverted, so to do this we spin in Z axis, on first 2!! Hack I know..I'm working on it!
float CubeMapRots[6][4] = {			//4, cos it's angle, then XYZ component of that angle.
	{	-90.0f,		0.0f,	1.0f,	0.0f	},
	{	90.0f,		0.0f,	1.0f,	0.0f	},

	{	-90.0f,		1.0f,	0.0f,	0.0f	},
	{	90.0f,		1.0f,	0.0f,	0.0f	},

	{	180.0f,		1.0f,	0.0f,	0.0f	},
	{	180.0f,		0.0f,	0.0f,	1.0f	},
};




void texEnvCube(float pos);

void genMap(float pos)
{
    int face;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90, 1.0, 0.1f, 5000.0f);
    glViewport(0, 0, 512, 512);
    glMatrixMode(GL_MODELVIEW);

	glDisable(GL_DEPTH_TEST);
	glDepthMask(0);

    // Generate the maps for all 6 sides
    for(face=0; face<6; face++) {
        glPushMatrix();
        glClear(GL_DEPTH_BUFFER_BIT);
 
        glLoadIdentity();
        gluLookAt(0.0, 0.0, 0.0, // Camera at center
                  look[face][0], look[face][1], look[face][2], // Direction 
                  look[face][3], look[face][4], look[face][5]  // Up
        );

        // Display the scene
		glEnable(GL_TEXTURE_2D);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glColor3f(1,1,1);
		
		texEnvCube(pos);
		//glDisable(GL_TEXTURE_2D);

        // Copy the scene to texture
        glEnable(GL_TEXTURE_CUBE_MAP_ARB);
        glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, waterTextureID);
        glCopyTexSubImage2D(CubeMapTarget[face], 0, 0, 0, 0, 0, 512, 512);
        glDisable(GL_TEXTURE_CUBE_MAP_ARB);

        glPopMatrix();
    }

    glEnable(GL_DEPTH_TEST);
	glDepthMask(1);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluPerspective( 40.0, 1.0, 0.1f, 1000.0f); // FIX ME
	dmsResetViewport();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

}


void texEnvCube(float pos)
{	
	unsigned int i;

/*
	//Turn z writing off.


	glPushMatrix();
	//glRotatef(180,1,0,0);

	//Need to disable cube mapping, as cube mapping takes priority over 2D textures.
	glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	glEnable(GL_TEXTURE_2D);

	for(unsigned int i=0; i<6; i++)
	{
		//g_skybox[i]->Bind();
		glBindTexture(GL_TEXTURE_2D, dmsGetTexture(CubeMapFileName[i])->getID());

		int v_start = i * 4;

		glBegin(GL_QUADS);

			glTexCoord2f(0.0f, 0.0f);				//Top left of texture.
			glVertex3f(SkyboxVerts[v_start+0][0], SkyboxVerts[v_start+0][1], SkyboxVerts[v_start+0][2]);

			glTexCoord2f(1.0f, 0.0f);				//Top right of texture.
			glVertex3f(SkyboxVerts[v_start+1][0], SkyboxVerts[v_start+1][1], SkyboxVerts[v_start+1][2]);

			glTexCoord2f(1.0f, 1.0f);				//Bottom right of texture.
			glVertex3f(SkyboxVerts[v_start+2][0], SkyboxVerts[v_start+2][1], SkyboxVerts[v_start+2][2]);

			glTexCoord2f(0.0f, 1.0f);				//Bottom left of texture.
			glVertex3f(SkyboxVerts[v_start+3][0], SkyboxVerts[v_start+3][1], SkyboxVerts[v_start+3][2]);


		glEnd();

	}

	glPopMatrix();


*/

    glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glDisable(GL_DEPTH_TEST);
    glBindTexture(GL_TEXTURE_2D, dmsGetTexture("fireparticle.jpg")->getID());
	Vector x, y, z;
	Mathematics::antiRotate(&x, &y, &z);

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

        float t = 0.0f;
/*
        glDisable(GL_TEXTURE_2D);
        glColor4f(0.6f,0.8f,1.0f,0.2f*alpha);
        glBegin(GL_LINE_STRIP);
        for (t = 0.0f; t < 1.0f; t += 1.0f / 160)
        {
            Vector splinepoint = s->polku->getValue(t);
            glVertex3fv((float *)&splinepoint);

        }
        glEnd();
*/

	    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);
            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*0.8f);
            float size = 0.01f + 0.08f*(1-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();
    }



}

#define noiseWidth 65
#define noiseHeight 65
#define noiseDepth 32

float noise[noiseWidth][noiseHeight][noiseDepth]; //the noise array

void generateNoise()
{
    for(int x = 0; x < noiseWidth; x++)
    for(int y = 0; y < noiseHeight; y++)
    for(int z = 0; z < noiseDepth; z++)
    {
        noise[x][y][z] = (rand() % 32768) / 32768.0;
    }
}

float smoothNoise(float x, float y, float z)
{ 
   //get fractional part of x and y
   float fractX = x - int(x);
   float fractY = y - int(y);
   float fractZ = z - int(z);   
  
   //wrap around
   int x1 = (int(x) + noiseWidth) % noiseWidth;
   int y1 = (int(y) + noiseHeight) % noiseHeight;
   int z1 = (int(z) + noiseDepth) % noiseDepth;
  
   //neighbor values
   int x2 = (x1 + noiseWidth - 1) % noiseWidth;
   int y2 = (y1 + noiseHeight - 1) % noiseHeight;
   int z2 = (z1 + noiseDepth - 1) % noiseDepth;

   //smooth the noise with bilinear interpolation
   float value = 0.0;
   value += fractX       * fractY       * fractZ       * noise[x1][y1][z1];
   value += fractX       * (1 - fractY) * fractZ       * noise[x1][y2][z1];
   value += (1 - fractX) * fractY       * fractZ       * noise[x2][y1][z1];
   value += (1 - fractX) * (1 - fractY) * fractZ       * noise[x2][y2][z1];

   value += fractX       * fractY       * (1 - fractZ) * noise[x1][y1][z2];
   value += fractX       * (1 - fractY) * (1 - fractZ) * noise[x1][y2][z2];
   value += (1 - fractX) * fractY       * (1 - fractZ) * noise[x2][y1][z2];
   value += (1 - fractX) * (1 - fractY) * (1 - fractZ) * noise[x2][y2][z2];

   return value;
}

float turbulence(float x, float y, float z, float size)
{
    float value = 0.0, initialSize = size;
   
    while(size >= 1)
    {
        value += smoothNoise(x / size, y / size, z / size) * size;
        size /= 2.0;
    }
   
    return(128.0 * value / initialSize);
}


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

	const float fadeinstart = 0.0f;
	const float fadeinstop = 0.1f;
	const float fadeoutstart = 0.90f;
	const float fadeoutstop = 1.0f;

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


	renderScene(pos, alpha);

}

void Vesi::renderScene(float pos, float alpha)
{
	
    glLoadIdentity();
	genMap(pos);

	for(int x=0; x<noiseWidth; x++)
		for(int y=0; y<noiseHeight; y++)
		{
			waterPlane->vertices[x*noiseWidth+y].y = smoothNoise(x, pos*50.0f, y)*0.0251f;
		}
	waterPlane->calculateNormals();

	/**/
	//Vector cam = Mathematics::sphereToCartesian(pos*12.0f, 1+pos*4.0f, -2.0f+pos*2.0f);
	Vector3 tgt = Vector3(sinf(pos*22.0f)*14.0f, 2, cosf(pos*22.0f)*10.10f);
    Vector3 upw = Vector3(0, 1, 0);
	/**/
    glLoadIdentity();
    //gluLookAt(cam.x, cam.y, cam.z, tgt.x, tgt.y, tgt.z, upw.x, upw.y, upw.z);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	cam->useCamera(3);

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

	glColor3f(1,1,1);
	glEnable(GL_TEXTURE_2D);
	//glDisable(GL_DEPTH_TEST);
	//glDepthMask(0);
	 texEnvCube(pos);
	//glDepthMask(1);
	//glEnable(GL_DEPTH_TEST);

	
	shaders.waterReflect->bind();

	glActiveTextureARB(GL_TEXTURE0_ARB);
	glEnable(GL_TEXTURE_CUBE_MAP);
	glBindTexture(GL_TEXTURE_CUBE_MAP, waterTextureID);
	shaders.waterReflect->setUniform1i("envMap", 0);

	shaders.waterReflect->setUniform3f("LightPos", 45,45,45);

		// Draw plane
		int i;
		glBegin(GL_TRIANGLES);
		for (i=0;i<waterPlane->facecount;i++)
		{
			Face &f = waterPlane->faces[i];
			Vector &v1 = waterPlane->vertices[waterPlane->faces[i].v1];
			Vector &v2 = waterPlane->vertices[waterPlane->faces[i].v2];
			Vector &v3 = waterPlane->vertices[waterPlane->faces[i].v3];

			TexCoord &uv1 = waterPlane->uv[waterPlane->faces[i].v1];
			TexCoord &uv2 = waterPlane->uv[waterPlane->faces[i].v2];
			TexCoord &uv3 = waterPlane->uv[waterPlane->faces[i].v3];

			Vector &n1 = waterPlane->normals[waterPlane->faces[i].v1];
			Vector &n2 = waterPlane->normals[waterPlane->faces[i].v2];
			Vector &n3 = waterPlane->normals[waterPlane->faces[i].v3];

				glTexCoord2fv((float *)&uv1);
				glNormal3fv((float *)&n1);
				glVertex3fv((float *)&v1);
            
				glTexCoord2fv((float *)&uv2);
				glNormal3fv((float *)&n2);
				glVertex3fv((float *)&v2);
            
				glTexCoord2fv((float *)&uv3);
				glNormal3fv((float *)&n3);
				glVertex3fv((float *)&v3);
		}
		glEnd();
		
	shaders.unbind();
	
	glDisable(GL_TEXTURE_CUBE_MAP);
	glDisable(GL_BLEND);
}




Vesi::Vesi()
{	

    int i;
    scount = 20;

    const int particles_per_stream = 80;

    srand(106919);

    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()*6;
        Vector v2 = Mathematics::randVector()*6;

        splines[i] = new StreamSpline(particles_per_stream, v1, v2);


    }


}

Vesi::~Vesi()
{
	delete waterPlane;

    int i;

    for (i = 0; i < scount; i++)
    {
        delete splines[i];
    }
    delete [] splines;
}


bool Vesi::init(unsigned long s, unsigned long e)
{
	waterPlane = new Mesh();
	waterPlane->buildPlane(60.0f, noiseWidth, noiseHeight, Vector());

    generateNoise();

    glEnable(GL_TEXTURE_CUBE_MAP_ARB);
    glGenTextures(1, &waterTextureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, waterTextureID);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
    for(int face=0; face<6; face++) {
        glTexImage2D(CubeMapTarget[face], 0, 4, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    }
    glDisable(GL_TEXTURE_CUBE_MAP_ARB);


	dmsLogStateGL();

	startTime = s;
	endTime = e;
	return true;
}
