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

#pragma warning(disable: 4244)

#include <math.h>

#include "Meta.hpp"
#include "../mathematics.hpp"
#include "../primitives.hpp"
#include "../lightsystem.hpp"

void Meta::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 Meta::renderScene(float pos, float alpha)
{

	//glRotatef(pos*500, 1,1,1);
	
	const float timenow = (float)dmsGetModulePosition();
	
	for(int i=0; i<15; i++)
	{
		float movement = pos*15.0f;//*(1+fmod(i, 4.0f));
		float movement_factor = 0.01f;
		mb->move_ball(i, 
				(i%2==0 ? -1 : 1)     *(fmodf(2+i,5.0f)+0.7f)*(float)cos(i*0.4f+timenow/400.0f) - (0.4f+fmodf(3+i,4.0f))*(float)sin(i*0.4f+timenow/600.0f),
				(i*i*i%2==0 ? -1 : 1)*(fmodf(2+i,5.0f)+0.7f)*(float)sin(i*0.4f+timenow/500.0f) - (0.4f+fmodf(3+i,4.0f))*(float)sin(i*0.4f+timenow/400.0f),
				(i*i%2==0 ? -1 : 1)*(fmodf(2+i,5.0f)+0.7f)*(float)sin(i*0.4f+timenow/500.0f) - (0.4f+fmodf(3+i,4.0f))*(float)sin(i*0.4f+timenow/400.0f)
			);
	}

	mc->computeMetaBalls();
	
	// lighting crap
	static float lightModelAmbient[] = {0.2f, 0.2f, 0.2f, 1.0f};
	static float ambient[] = {0.4f, 0.4f, 0.4f, 1.0f};
	static float diffuse[] = {0.9f, 0.9f, 0.9f, 0.0f};
	static float light_position[] = {20.0f, 20.0f, 20.0f, 0.0f};

	if(!shaderLight)
	{
		  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightModelAmbient);
		  glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
		  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
		  glEnable(GL_LIGHTING);
		  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
		  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
		  glEnable(GL_LIGHT0);
		  glShadeModel(GL_SMOOTH);
		  glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	} else { 
		  Light::startLight(Vector(4,4,4), Vector(1,1,1), Vector(1,1,1), Vector(1,1,1), 0.10f, 0.005f, 0.169f);
	}

	cam->useCamera(0);

	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

    glEnable(GL_TEXTURE_2D);    
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_DEPTH_TEST);

    glDisable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D, t->getID());
	glScalef(0.3473f,0.3473f,0.3473f);
	glColor3f(1,1,1);

	mc->draw();

	if(!shaderLight)
	{
		glDisable(GL_LIGHTING);
		glDisable(GL_LIGHT0);
	} else {
		Light::endLight();
	}

	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
    glDisable(GL_TEXTURE_GEN_S); 
    glDisable(GL_TEXTURE_GEN_T);



}




Meta::Meta()
{	
}

Meta::~Meta()
{
	delete mb;
	delete mc;
}


bool Meta::init(unsigned long s, unsigned long e)
{
	
	//shaderLight = (rand()%2==0 ? true : false);
	shaderLight = false;

    t = dmsGetTexture("49.jpg");
      
    
	mb = new MetaBalls();
	mc = new MarchCube(-10, 10, 10, 10, -10, -10, 0.5, -0.5f, -0.5);
	mc->setMetaBalls(mb, 0.3f);

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

// ----------
// Cubes
// ----------


MarchCube::MarchCube(	float start_x, float start_y, float start_z, float end_x, float end_y, float end_z, float step_x, float step_y, float step_z)
{
	int idx, x, y, z;
	this->start_x = start_x;
	this->start_y = start_y;
	this->start_z = start_z;

	this->end_x = end_x;
	this->end_y = end_y;
	this->end_z = end_z;

	this->step_x = step_x;
	this->step_y = step_y;
	this->step_z = step_z;

	this->size_x = (int) ((this->end_x - this->start_x) / this->step_x);
	this->size_y = (int) ((this->end_y - this->start_y) / this->step_y);
	this->size_z = (int) ((this->end_z - this->start_z) / this->step_z);

	this->vertices = new MetaVertex[this->size_x * this->size_y * this->size_z];

	for (z = 0; z < this->size_z; z++)
	{
		for (y = 0; y < this->size_y; y++)
		{
			for (x = 0; x < this->size_x; x++)
			{
				idx = x + y*this->size_y + z * this->size_y * this->size_z;

				this->vertices[idx].x_pos = this->start_x + this->step_x * x;
				this->vertices[idx].y_pos = this->start_y + this->step_y * y;
				this->vertices[idx].z_pos = this->start_z + this->step_z * z;
				this->vertices[idx].flux = 0.0;
				this->vertices[idx].inside = false;
				this->vertices[idx].normal_x = 0.0;
				this->vertices[idx].normal_y = 0.0;
				this->vertices[idx].normal_z = 0.0;
			}
		}
	}
}


MarchCube::~MarchCube()
{
	delete [] vertices;
}

void MarchCube::computeMetaBalls()
{
	int idx, x, y, z;
	for (z = 1; z < (this->size_z - 1); z++)
	{
		for (y = 1; y < (this->size_y - 1); y++)
		{
			for (x = 1; x < (this->size_x - 1); x++)
			{
				idx = x + y*this->size_y + z * this->size_y * this->size_z;

				this->vertices[idx].flux = this->mb->get_vertex_value(this->vertices[idx]);
				
				if (this->vertices[idx].flux > this->MetaBallsIsoVal)
					this->vertices[idx].inside = true;
				else
					this->vertices[idx].inside = false;


				this->vertices[idx].normal_x = this->vertices[idx - 1].flux - this->vertices[idx+1].flux;
				this->vertices[idx].normal_y = this->vertices[idx - this->size_y].flux - this->vertices[idx + this->size_y].flux;
				this->vertices[idx].normal_z = this->vertices[idx - (this->size_y * this->size_z)].flux - this->vertices[idx + (this->size_y * this->size_z)].flux; 
			}
		}
	}
}

void MarchCube::draw()
{
	int idx, x, y, z;
	short lookup = 0;

	for (z = 0; z < (this->size_z - 1); z++)
	{
		for (y = 0; y < (this->size_y - 1); y++)
		{
			for (x = 0; x < (this->size_x - 1); x++)
			{
				idx = x + y*this->size_y + z * this->size_y * this->size_z;
				if (this->vertices[idx].inside) lookup |= 128;
				if (this->vertices[idx+1].inside) lookup |= 64;
				if (this->vertices[idx+1+this->size_y].inside) lookup |= 4;
				if (this->vertices[idx + this->size_y].inside) lookup |= 8;
				if (this->vertices[idx + (this->size_y * this->size_z)].inside) lookup |= 16;
				if (this->vertices[idx + 1 + (this->size_y * this->size_z)].inside) lookup |= 32;
				if (this->vertices[idx + 1 + this->size_y + (this->size_y * this->size_z)].inside) lookup |= 2;
				if (this->vertices[idx + this->size_y + (this->size_y * this->size_z)].inside) lookup |= 1;

				if ((lookup != 0) && (lookup != 255))
				{
					// 0 - 1
					if (edgeTable[lookup] & 1) 
						this->verts[0] = this->mb->interpolate(	this->vertices[idx + this->size_y + (this->size_y * this->size_z)],
											this->vertices[idx + 1 + this->size_y + (this->size_y * this->size_z)]);

					// 1 - 2
					if (edgeTable[lookup] & 2) 
						this->verts[1] = this->mb->interpolate(	this->vertices[idx + 1 + this->size_y + (this->size_y * this->size_z)],
											this->vertices[idx + 1 + this->size_y]);

					// 2 - 3
					if (edgeTable[lookup] & 4) 
						this->verts[2] = this->mb->interpolate(	this->vertices[idx + 1 + this->size_y],
											this->vertices[idx + this->size_y]);

					// 3 - 0
					if (edgeTable[lookup] & 8) 
						this->verts[3] = this->mb->interpolate(	this->vertices[idx + this->size_y],
											this->vertices[idx + this->size_y + (this->size_y * this->size_z)]);
			
					// 4 - 5
					if (edgeTable[lookup] & 16) 
						this->verts[4] = this->mb->interpolate(	this->vertices[idx + (this->size_y * this->size_z)],
											this->vertices[idx + 1 + (this->size_y * this->size_z)]);

					// 5 - 6
					if (edgeTable[lookup] & 32) 
						this->verts[5] = this->mb->interpolate(	this->vertices[idx + 1 + (this->size_y * this->size_z)],
											this->vertices[idx + 1]);

					// 6 - 7
					if (edgeTable[lookup] & 64) 
						this->verts[6] = this->mb->interpolate(	this->vertices[idx + 1],
											this->vertices[idx]);

					// 7 - 4
					if (edgeTable[lookup] & 128) 
						this->verts[7] = this->mb->interpolate(	this->vertices[idx],
											this->vertices[idx + (this->size_y * this->size_z)]);

					// 0 - 4
					if (edgeTable[lookup] & 256)
						this->verts[8] = this->mb->interpolate(	this->vertices[idx + this->size_y + (this->size_y * this->size_z)],
											this->vertices[idx + (this->size_y * this->size_z)]);

					// 1 - 5
					if (edgeTable[lookup] & 512) 
						this->verts[9] = this->mb->interpolate(	this->vertices[idx + 1 + this->size_y + (this->size_y * this->size_z)],
											this->vertices[idx + 1 + (this->size_y * this->size_z)]);

					// 2 - 6
					if (edgeTable[lookup] & 1024) 
						this->verts[10] = this->mb->interpolate(	this->vertices[idx + 1 + this->size_y],
											this->vertices[idx + 1]);

					// 3 - 7
					if (edgeTable[lookup] & 2048) 
						this->verts[11] = this->mb->interpolate(	this->vertices[idx + this->size_y],
											this->vertices[idx]);

					glColor3f(1.0f, 1.0f, 1.0f);
						
					int i, j;
					for (i = 0; triTable[lookup][i] != -1; i+=3)
					{
						glBegin(GL_TRIANGLES);
						{
							for (j = i; j < (i+3); j++)
							{
								glNormal3f( (float) this->verts[triTable[lookup][j]].normal_x, 
											(float) this->verts[triTable[lookup][j]].normal_y, 
											(float) this->verts[triTable[lookup][j]].normal_z);

								glVertex3f(	(float) this->verts[triTable[lookup][j]].x_pos,
											(float) this->verts[triTable[lookup][j]].y_pos,
											(float) this->verts[triTable[lookup][j]].z_pos
											);
										
							}
						}
						glEnd();
					}
				}
				lookup = 0;
			}
		}
	}
}

void MarchCube::setMetaBalls(MetaBalls *mb, float iso_value)
{
	this->mb = mb;
	this->mb->set_iso_value(iso_value);
	this->MetaBallsIsoVal = iso_value;
}

// ----------
// Balls
// ----------

MetaBalls::MetaBalls()
{
	numBalls = META_BALLS;
	meta_points = new MetaBall[numBalls];
	for(int i=0; i<META_BALLS; i++)
	{
		meta_points[i].power = 0.15+(rand()%10)*0.1f;
		meta_points[i].x_pos = -8.0+rand()%16;
		meta_points[i].y_pos = -8.0+rand()%16;
		meta_points[i].z_pos = -8.0+rand()%16;
		meta_points[i].move_mod = -4.0+(rand()%100)*0.08f;
		meta_points[i].actpos = Vector();//Vector(meta_points[i].x_pos, meta_points[i].y_pos, meta_points[i].z_pos);
	}
}

MetaBalls::~MetaBalls()
{
	if(meta_points!=0)
		delete [] meta_points;
}

void MetaBalls::set_iso_value(float iv) 
{
	this->iso_value = iv;
}

void MetaBalls::move_ball(int idx, float x, float y, float z)
{
	meta_points[idx].x_pos = meta_points[idx].actpos.x+x;
	meta_points[idx].y_pos = meta_points[idx].actpos.y+y;
	meta_points[idx].z_pos = meta_points[idx].actpos.z+z;
}

MetaVertex MetaBalls::interpolate(MetaVertex v1, MetaVertex v2)
{
	MetaVertex v;
	float diff;

	diff = (this->iso_value - v1.flux) / (v2.flux - v1.flux);

	v.x_pos = v1.x_pos + (v2.x_pos - v1.x_pos) * diff;
	v.y_pos = v1.y_pos + (v2.y_pos - v1.y_pos) * diff;
	v.z_pos = v1.z_pos + (v2.z_pos - v1.z_pos) * diff;
	v.flux = (v1.flux + v2.flux) * 0.5f;

	v.normal_x = v1.normal_x + (v2.normal_x - v1.normal_x) * diff;
	v.normal_y = v1.normal_y + (v2.normal_y - v1.normal_y) * diff;
	v.normal_z = v1.normal_z + (v2.normal_z - v1.normal_z) * diff;

	return v;
}

float MetaBalls::get_vertex_value(MetaVertex v)
{
	float flux = 0.0f;

	for (i = 0; i < numBalls; i++)
	{
		length_x = meta_points[i].x_pos - v.x_pos;
		length_y = meta_points[i].y_pos - v.y_pos;
		length_z = meta_points[i].z_pos - v.z_pos;
	
		flux += fabs(meta_points[i].power) * meta_points[i].power / (
					length_x * length_x + 
					length_y * length_y +
					length_z * length_z + 1.0f);
	}

	return flux;
}