#define NO_SDL_GLEXT

#include <GL/glew.h>
#include <SDL/SDL.h>
#include <SDL/SDL_OpenGL.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include <math.h>
#include <stdio.h>

#include "project_volumes.h"
#include "project_shaders.h"
#include "project_screen.h"
#include "math_vector3D.h"

#include "part_volume1.h"

int programm_vrnd1 = PR_VOLUME1;

GLint v1_width;
GLint v1_isodepth;

GLint v1_campos;
GLint v1_camdir;
GLint v1_camup;
GLint v1_camright;

GLint v1_alphon;

GLint v1_tex_volume;
GLint v1_tex_pal;


vpal vpal1[256];

Cvolume::Cvolume(int width, int height, int depth)
{
	this->width = width;
	this->height = height;
	this->depth = depth;

	this->iso_depth = default_iso_depth;

	this->data = new unsigned int[width * height * depth];
}

Cvolume::~Cvolume()
{
}

int Cvolume::GetWidth()
{
	return width;
}

int Cvolume::GetHeight()
{
	return height;
}

int Cvolume::GetDepth()
{
	return depth;
}

unsigned int* Cvolume::GetDataPointer()
{
	return data;
}

GLuint Cvolume::GetVolumeID()
{
	return vol_id;
}

float Cvolume::GetIsoDepth()
{
	return iso_depth;
}

void Cvolume::SetIsoDepth(float iso_depth)
{
	this->iso_depth = iso_depth;
}

void Cvolume::UploadVolumeTexture()
{
	glActiveTextureARB(GL_TEXTURE0);
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_TEXTURE_3D);
	glBindTexture(GL_TEXTURE_3D, vol_id);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP);
	glTexImage3D(GL_TEXTURE_3D, 0, 4, width, height, depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}

GLuint Cvolume::InitVolumeTexture()
{
	glGenTextures(1, &vol_id);

	UploadVolumeTexture();

	return vol_id;
}



// Volume Generation Algorithms
// ----------------------------

#define fsin_size 65536
bool pvolume_init = false;
int fsin1[fsin_size], fsin2[fsin_size], fsin3[fsin_size];

void init_pvolume()
{
	for (int i=0; i<fsin_size; i++)
	{
		//fsin1[i] = (int)(sin(i / 45.0) * 123.0);
		//fsin2[i] = (int)(sin(i / 63.0) * 97.0);
		//fsin3[i] = (int)(sin(i / 200.0) * 248.0);

		fsin1[i] = (int)(sin(i / 45.0) * 73.0);
		fsin2[i] = (int)(sin(i / 113.0) * 397.0);
		fsin3[i] = (int)(sin(i / 150.0) * 248.0);
	}

	for (int i=0;i<64;i++)
	{
		vpal1[i].r = i << 2; vpal1[i].g = i << 1; vpal1[i].b = 0;
	    vpal1[64+i].r = 255 - (i<<1); vpal1[64+i].g = (i<<1)+128; vpal1[64+i].b = i << 2;
		vpal1[128+i].r = (63 - i)<<1; vpal1[128+i].g = 255 - (i<<1); vpal1[128+i].b = 255;
		vpal1[192+i].r = 0; vpal1[192+i].g = 128 - (i<<1); vpal1[192+i].b = (63-i) << 2;
	}

	pvolume_init = true;
}

int EvalSines(int x, int y, int z, int octave)
{
	return ((fsin1[x << octave] >> octave) + (fsin2[y << octave] >> octave) + (fsin3[z << octave] >> octave) + (fsin3[(x + y + 64) << octave] >> octave));
}

void ClearBorders(int width, int height, int depth, unsigned int *voldata)
{
	int wh = width * height;

	for (int y=0; y<height; y++)
	{
		for (int x=0; x<width; x++)
		{
			voldata[y * width + x] = 0;
			voldata[(depth-1) * wh + y * width + x] = 0;
		}
	}

	for (int z=0; z<depth; z++)
	{
		for (int y=0; y<height; y++)
		{
			voldata[z * wh + y * width] = 0;
			voldata[z * wh + y * width + (width-1)] = 0;
		}
	}

	for (int z=0; z<depth; z++)
	{
		for (int x=0; x<width; x++)
		{
			voldata[z * wh + x] = 0;
			voldata[z * wh + (height-1) * width + x] = 0;
		}
	}
}

void Cvolume::GenVolumePlasma()
{
	if (!pvolume_init) init_pvolume();

	int i = 0;
	for (int z=0; z<depth; z++)
	{
		float zz = (float)(depth - z) / (float)depth;
		for (int y=0; y<height; y++)
		{
			for (int x=0; x<width; x++)
			{
				int ii = 0;
				for (int j=1; j<=8; j++)
				{
					ii +=  EvalSines(x,y,z,j);
				}
				int cc = ii & 511;

				if (x==0 || x==width-1 || y==0 || y==height-1 || z==0 || z==depth-1) cc = 0;
				if (cc > 255) cc = 511 - cc;
				int aa = 255;
				if (cc < 192) aa = 0;
				aa >>= 4;

				data[i++] = (aa << 24) | (cc << 16) | (cc << 8) | cc;
			}
		}
		printf("%d / %d\r",z,depth);
	}
}

void Cvolume::GenVolumeSphere()
{
	printf("\n");

	const int nm = 48;

	int pos_x[nm];
	int pos_y[nm];
	int pos_z[nm];

	for (int i=0; i<nm; i++)
	{
		float ii = (float)i;
		pos_x[i] = (int)(sin(ii*(ii+1)) * width/2.5 + width/2);
		pos_y[i] = (int)((sin(ii*(ii+1)) + sin(ii * (ii+1) * (ii+2))) * height/2.5 + height/2);
		pos_z[i] = (int)(sin(ii*(ii+1)*sin(ii*(ii+4))) * depth/2.5 + depth/2);
	}

	int i = 0;
	for (int z=0; z<depth; z++)
	{
		for (int y=0; y<height; y++)
		{
			for (int x=0; x<width; x++)
			{
				float c = 0.0f;
				float min = 1000000000.0f;
				for (int n=0; n<nm; n++)
				{
					float dist = sqrt((float)((x - pos_x[n]) * (x - pos_x[n]) + (y - pos_y[n]) * (y - pos_y[n]) + (z - pos_z[n]) * (z - pos_z[n])));
					if (dist < min)
						min = dist;
				}
				c = 2.0 * min;

				if (c < 64) c /= 2.0f;
				if (c > width) c = (float)width;

				int cc = (int)((c / (float)(width)) * 255) & 255;
				int aa = cc >> 4;

				unsigned int ci = (aa << 24) | ((cc >> 0) << 16) | ((cc >> 1) << 8) | (cc >> 3);;

				data[i++] = ci;
			}
		}
		printf("%d / %d\r",z,depth);
	}
}

/*void Cvolume::GenVolumeSphere()
{
	printf("\n");

	const int nm = 16;

	int pos_x[nm];
	int pos_y[nm];
	int pos_z[nm];

	for (int i=0; i<nm; i++)
	{
		float ii = (float)i;
		pos_x[i] = (int)(sin(ii*(ii+1)) * width/2.5 + width/2);
		pos_y[i] = (int)((sin(ii*(ii+1)) + sin(ii * (ii+1) * (ii+2))) * height/2.5 + height/2);
		pos_z[i] = (int)(sin(ii*(ii+1)*sin(ii*(ii+4))) * depth/2.5 + depth/2);
	}

	int i = 0;
	for (int z=0; z<depth; z++)
	{
		for (int y=0; y<height; y++)
		{
			for (int x=0; x<width; x++)
			{
				float c = 0.0f;
				float min = 1000000000.0f;
				for (int n=0; n<nm; n++)
				{
					float dist = sqrt((float)((x - pos_x[n]) * (x - pos_x[n]) + (y - pos_y[n]) * (y - pos_y[n]) + (z - pos_z[n]) * (z - pos_z[n])));
					if (dist!=0)
						c += 16384.0f / (dist * dist);
				}

				if (c < 128) c = 0;
				if (c > width) c = (float)width;

				int cc = (int)((c / (float)(width)) * 255) & 255;
				int aa = cc >> 2;

				unsigned int ci = (aa << 24) | ((cc >> 0) << 16) | ((cc >> 0) << 8) | (cc >> 0);;

				data[i++] = ci;
			}
		}
		printf("%d / %d\r",z,depth);
	}
}*/

void Cvolume::FadeByDistanceFromCenter(float radius)
{
	printf("\n");

	int i = 0;
	for (int z=0; z<depth; z++)
	{
		float z_center = z - depth / 2.0f;
		for (int y=0; y<height; y++)
		{
			float y_center = y - height / 2.0f;
			for (int x=0; x<width; x++)
			{
				float x_center = x - width / 2.0f;
				float c = radius * (width / 2.0f - sqrt(x_center * x_center + y_center * y_center + z_center * z_center));
				if (c < 0) c = 0;
				if (c > width) c = (float)width;

				float cc = c / (float)width;

				unsigned int ci = data[i];
				unsigned int a = (unsigned int)(((ci >> 24) & 255) * cc);
				unsigned int r = (unsigned int)(((ci >> 16) & 255) * cc);
				unsigned int g = (unsigned int)(((ci >> 8) & 255) * cc);
				unsigned int b = (unsigned int)((ci & 255) * cc);

				data[i++] = (a << 24) | (r << 16) | (g << 8) | b;
			}
		}
		printf("%d / %d\r",z,depth);
	}
}

void Cvolume::GenVolumeEmpty()
{
	for (int i = 0; i < width * height * depth; i++)
	{
		data[i] = 0;
	}
}

void Cvolume::GenVolume(int way)
{
	switch(way)
	{
		case VGEN_EMPTY:
			GenVolumeEmpty();
		break;

		case VGEN_PLASMA:
			GenVolumePlasma();
			FadeByDistanceFromCenter(16);
		break;

		case VGEN_SPHERE:
			GenVolumeSphere();
			FadeByDistanceFromCenter(16);
		break;

		default:
			GenVolumeEmpty();
		break;
	}
}

// Volume Loading Functions
// ------------------------

void Cvolume::LoadVolume(char *filename)
{
	// load file

	unsigned short* fdata;
	FILE *fvol;
	fvol = fopen(filename,"rt");
	int count = 0;

	fseek(fvol, 0, SEEK_END);
    count = ftell(fvol) / 2;
    rewind(fvol);

	fdata = new unsigned short[count];
	fread(fdata, sizeof(char), count, fvol);
	fclose(fvol);


	// file loaded

	for (int i=0; i<count; i++)
	{
		int c = fdata[i] >> 6;
		int a = c >> 8;
		//if (a < 128) a = 0;
		unsigned int cc = (a<<24) | (c<<16) | (c<<8) | c;
		data[i] = cc;
	}

	delete [] fdata;
}

// Volume Rendering Functions
// --------------------------

void fuck_matrix(float rot_x, float rot_y, float rot_z)
{
	float c_rx = cos(rot_x);
	float c_ry = cos(rot_y);
	float c_rz = cos(rot_z);
	float s_rx = sin(rot_x);
	float s_ry = sin(rot_y);
	float s_rz = sin(rot_z);

	Cmatrix rot_matrix_X(	1,	0,		0, 
							0,	c_rx,	-s_rx, 
							0,	s_rx,	c_rx);

	Cmatrix rot_matrix_Y(	c_ry,	0,		s_ry, 
							0,		1,		0, 
							-s_ry,	0,		c_ry);

	Cmatrix rot_matrix_Z(	c_rz,	-s_rz,	0, 
							s_rz,	c_rz,	0, 
							0,		0,		1);

	rot_matrix = rot_matrix_X * rot_matrix_Y * rot_matrix_Z;
}

void Cvolume::DrawSlice(int num, Cvector pos, Cvector spc)
{
	num %= depth;
	float z = pos.z - (num - depth / 2) * spc.z;
	float l_x = (width * spc.x) / 2;
	float l_y = (height * spc.y) / 2;
	float r = (float)num / depth;

	rot_vecs.y = SDL_GetTicks() / 2048.0f;
	fuck_matrix(rot_vecs.x, rot_vecs.y, rot_vecs.z);

	Cvector p1 = Cvector(0,0,r);
	Cvector p2 = Cvector(1,0,r);
	Cvector p3 = Cvector(1,1,r);
	Cvector p4 = Cvector(0,1,r);

	p1 = p1 - Cvector(0.5f, 0.5f, 0.5f);
	p2 = p2 - Cvector(0.5f, 0.5f, 0.5f);
	p3 = p3 - Cvector(0.5f, 0.5f, 0.5f);
	p4 = p4 - Cvector(0.5f, 0.5f, 0.5f);

	p1 = rot_matrix.mul_vec(p1);
	p2 = rot_matrix.mul_vec(p2);
	p3 = rot_matrix.mul_vec(p3);
	p4 = rot_matrix.mul_vec(p4);

	p1 = p1 + Cvector(0.5f, 0.5f, 0.5f);
	p2 = p2 + Cvector(0.5f, 0.5f, 0.5f);
	p3 = p3 + Cvector(0.5f, 0.5f, 0.5f);
	p4 = p4 + Cvector(0.5f, 0.5f, 0.5f);

	glBegin(GL_QUADS);
		glTexCoord3f(p1.x, p1.y, p1.z); glVertex3f(-l_x, -l_y, z);
		glTexCoord3f(p2.x, p2.y, p2.z); glVertex3f(l_x, -l_y, z);
		glTexCoord3f(p3.x, p3.y, p3.z); glVertex3f(l_x, l_y, z);
		glTexCoord3f(p4.x, p4.y, p4.z); glVertex3f(-l_x, l_y, z);
	glEnd();
}

void Cvolume::RenderVolumeSlices(bool alpha, Cvector pos, Cvector dir, Cvector up, Cvector right)
{
	glLoadIdentity();
	glTranslatef(pos.x, pos.y, cam_zoom);
	Cvector spc = Cvector(right.x, up.y, dir.z);
	for (int i=depth-1; i>=0; i--)
	{
		DrawSlice(i, pos, spc);
	}
}

void Cvolume::RenderVolumeShaders(bool alpha, Cvector pos, Cvector dir, Cvector up, Cvector right)
{
	MyUseProgram(programm_vrnd1);

	v1_width = MyGetUniformLocation(programm_vrnd1, "width");
	MyUniform1f(v1_width, (float)width);
	v1_isodepth = MyGetUniformLocation(programm_vrnd1, "iso_depth");
	MyUniform1f(v1_isodepth, iso_depth);

	v1_campos = MyGetUniformLocation(programm_vrnd1, "cam_pos");
	glUniform3f(v1_campos, pos.x, pos.y, pos.z);
	v1_camdir = MyGetUniformLocation(programm_vrnd1, "cam_dir");
	glUniform3f(v1_camdir, dir.x, dir.y, dir.z);
	v1_camup = MyGetUniformLocation(programm_vrnd1, "cam_up");
	glUniform3f(v1_camup, up.x, up.y, up.z);
	v1_camright = MyGetUniformLocation(programm_vrnd1, "cam_right");
	glUniform3f(v1_camright, right.x, right.y, right.z);

	v1_alphon = MyGetUniformLocation(programm_vrnd1, "alph_on");
	glUniform1f(v1_alphon, alpha);

	v1_tex_volume = MyGetUniformLocation(programm_vrnd1, "volume");
	MyUniform1i(v1_tex_volume, 0);
	v1_tex_pal = MyGetUniformLocation(programm_vrnd1, "pal");
	MyUniform1i(v1_tex_pal, 1);

	DrawScreenQuad();
}

void Cvolume::RenderVolumeOpenCL(bool alpha, Cvector pos, Cvector dir, Cvector up, Cvector right)
{
}

void Cvolume::RenderVolume(int way, bool alpha, Cvector pos, Cvector dir, Cvector up, Cvector right)
{
	switch(way)
	{
		case VRND_SLICES:
			RenderVolumeSlices(alpha, pos, dir, up, right);
		break;

		case VRND_SHADERS:
			RenderVolumeShaders(alpha, pos, dir, up, right);
		break;

		case VRND_OPENCL:
			RenderVolumeOpenCL(alpha, pos, dir, up, right);
		break;

		default:
			RenderVolumeSlices(alpha, pos, dir, up, right);
		break;
	}
}
