/* -*- mia-c++ -*-
 *
 * This file is part of viewitgui - a library and program for the
 * visualization of 3D data sets. 
 *
 * Copyright (c) Leipzig, Madrid 1999-2013 Mirco Hellmann, Gert Wollny
 *
 * viewitgui is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * viewitgui is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with viewitgui; if not, see <http://www.gnu.org/licenses/>.
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define GL_GLEXT_PROTOTYPES 1

#include <mia.hh>
#include <iostream>
#include <viewit/texture.hh>
#include <viewit/drawable.hh>
#include <GL/glext.h>

using namespace mia; 

using namespace std; 

GLTextureBase::GLTextureBase()
{
}
GLTextureBase::~GLTextureBase()
{
}

void GLTextureBase::gl_attach()
{
	GLenum err = glGetError();
	if (err != GL_NO_ERROR) {
		cerr << "Possible BUG before glGenTextures "<<endl;
	}

	glGenTextures(1,&id);
	err = glGetError();
	if (err != GL_NO_ERROR) {
		throw gl_error("glGenTextures",err);
	}
	do_gl_attach();
}

void GLTextureBase::gl_detach()
{
	do_gl_detach();
	glDeleteTextures(1,&id);
}

void GLTextureBase::do_gl_detach()
{
}

void GL1DAlphaTexture::bind()const
{
	glBindTexture(GL_TEXTURE_1D,get_id());	
}

GL1DAlphaTexture::GL1DAlphaTexture()
{
}

void GL1DAlphaTexture::do_gl_attach()
{
	bind();
	glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
	glTexEnvi(GL_TEXTURE_1D,GL_TEXTURE_ENV_MODE,GL_MODULATE);
}


void GL1DAlphaTexture::set_texture_data(GLubyte *alpha, int size)
{
	bind();
	glTexImage1D(GL_TEXTURE_1D,0,1,size,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,alpha);	
}

GL2DHTexture::GL2DHTexture(int _steps,float _ambient, float _diffuse, float _specular,float _shine):
	steps(_steps),
	ambient(_ambient), 
	diffuse(_diffuse), 
	specular(_specular),
	shine(_shine)
{
	assert(steps);
		
	memset(tm,0,16 * sizeof(float));
	tm[3] = 0.5f;	tm[7] = 0.5f;  tm[15] = 2.0f; 
}

void GL2DHTexture::do_gl_attach()
{
	float *buff = new float[steps * steps];
	float *p = buff; 
	float delta = 1.8f / (steps - 1); 
	
	for (float y = -.9f,i=0; i < steps;i++,y += delta ) {
		for (float x = -.9f,j=0; j < steps;j++,x += delta,p++ ) {
			
			float slt = sqrt(1 -  x * x);
			*p = ambient + diffuse *  slt + specular * 
				pow((x * y - slt * sqrt(1 -  y * y)),shine);
		}
	}
	
	bind();
	
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

	glTexImage2D(GL_TEXTURE_2D,0,1,steps,steps,0, GL_LUMINANCE,GL_FLOAT,buff);
	GLenum err = glGetError();
	delete[] buff;
	if (err != GL_NO_ERROR) {
		throw gl_error("GL2DHTexture::do_gl_attach()",err);
	}
}

void GL2DHTexture::create_textur_matrix(const C3DFVector& light_pos,const C3DFVector& camera_pos,const C3DFVector& me)
{

	C3DFVector L = me - light_pos; 
	C3DFVector V = me - camera_pos;
	float norm = L.norm();
	if (norm > 0) 
		L /= norm;
	
	norm = V.norm();
	if (norm > 0) 
		V /= norm;
	
	tm[0] = 0.5f * L.x; 
	tm[1] = 0.5f * L.y; 
	tm[2] = 0.5f * L.z; 


	tm[4] = 0.5f * V.x; 
	tm[5] = 0.5f * V.y; 
	tm[6] = 0.5f * V.z; 

}

void GL2DHTexture::gl_texture_matrix_on()const
{
	glMatrixMode(GL_TEXTURE);
	glPushMatrix();
	glLoadMatrixf(tm);
}

void GL2DHTexture::gl_texture_matrix_off()const
{
	glMatrixMode(GL_TEXTURE);
	glPopMatrix();
}


void GL2DHTexture::bind()const
{
	glBindTexture(GL_TEXTURE_2D,get_id());	
}

struct TGL2DTextureData {
	const size_t inwidth;
	const size_t inheight;
	size_t width,height;
	float xend_coord;
	float yend_coord;
	
	TGL2DTextureData(C2DBounds size);
};

TGL2DTextureData::TGL2DTextureData(C2DBounds size):
	inwidth(size.x),
	inheight(size.y)
{
	width = height = 1;
	while (width < inwidth) width <<= 1;
	while (height < inheight) height <<= 1;
	
	xend_coord = float(inwidth-1)/float(width-1);
	yend_coord = float(inheight-1)/float(height-1);	
}


GL2DTexture::GL2DTexture(const C2DBounds& in_size):
	data(new TGL2DTextureData(in_size))
{
}

size_t GL2DTexture::get_inwidth() const
{
	return data->inwidth; 
}
	     
size_t GL2DTexture::get_inheight() const
{
	return data->inheight; 
}


float GL2DTexture::get_tex_xcoord(float x) const
{
	return (data->xend_coord * x);
}
float GL2DTexture::get_tex_ycoord(float y) const
{	
	return (data->yend_coord * y);
}

void GL2DTexture::set_data(const LuminData& InData)
{
	assert (get_inwidth() == InData.get_size().x && get_inheight() == InData.get_size().y);
	bind();
	GLenum err = glGetError();
	if (err != GL_NO_ERROR) {
		throw gl_error("GL2DTexture::set_data bind()",err);
	}
	
	if ( ! (get_inwidth() & 1) ) {
		glTexSubImage2D(GL_TEXTURE_2D,0,0,0,get_inwidth(),get_inheight(),
				GL_LUMINANCE,GL_UNSIGNED_BYTE,&InData(0,0));
	}else{
		int temp_width = get_inwidth() + 1; 
		int help_size = temp_width * get_inheight();
		vector<unsigned char> buffer(help_size);
		fill(buffer.begin(),buffer.end(), 0);
		
		auto b_out = buffer.begin();
		auto b_in = InData.begin(); 
		
		for (size_t i = 0; i < get_inheight(); i++, b_out += temp_width) {
			
			auto end = b_in; 
			advance(end, get_inwidth());
			copy(b_in, end, b_out);
			b_in = end; 
		}
		glTexSubImage2D(GL_TEXTURE_2D,0,0,0,temp_width,get_inheight(),
				GL_LUMINANCE,GL_UNSIGNED_BYTE, &buffer[0]);
		
	}
	err = glGetError();
	if (err != GL_NO_ERROR) {
		throw gl_error("GL2DTexture::set_data",err);
	}
}
GL2DTexture::~GL2DTexture() 
{
	delete data;
}

void GL2DTexture::bind()const
{
	glBindTexture(GL_TEXTURE_2D,get_id());
}

void GL2DTexture::do_gl_attach()
{
	GLenum err;
	bind();
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL2DTexture::do_gl_attach()-> glTexParameteri:",err);
	glTexImage2D(GL_TEXTURE_2D,0,1,get_data()->width,get_data()->height,0,GL_LUMINANCE,GL_UNSIGNED_BYTE,NULL);
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL2DTexture::do_gl_attach() -> glTexImage2D:",err);
}


struct TGL3DGrayTextureData {
	C3DBounds in_size; 
	C3DBounds size; 
	float _M_texture_matrix[16];
	
	TGL3DGrayTextureData(const C3DBounds& i_size);
};


TGL3DGrayTextureData::TGL3DGrayTextureData(const C3DBounds& i_size):
	in_size(i_size),
	size(1,1,1)
{
	while (size.x < in_size.x) size.x <<= 1;
	while (size.y < in_size.y) size.y <<= 1;
	while (size.z < in_size.z) size.z <<= 1;	
	
	// prepare translation of coordinates
	memset(_M_texture_matrix, 0, sizeof(_M_texture_matrix));
	_M_texture_matrix[ 0] = 1.0f / size.x; 
	_M_texture_matrix[ 5] = 1.0f / size.y; 
	_M_texture_matrix[10] = 1.0f / size.x; 
	_M_texture_matrix[15] = 1.0f; 
}

	
GL3DGrayTexture::GL3DGrayTexture(const C3DBounds& in_size):
	data(new TGL3DGrayTextureData(in_size))
{
	
}

GL3DGrayTexture::~GL3DGrayTexture()
{
	delete data;
}

void GL3DGrayTexture::set_data(const GrayData& in_data)
{
	assert (in_data.get_size() == data->in_size);

	bind();
	GLenum err = glGetError();
	if (err != GL_NO_ERROR) {
		throw gl_error("GL3DTexture::set_data bind()",err);
	}
	
	glTexSubImage3D(GL_TEXTURE_3D,0,
			0,0,0,
			data->in_size.x, data->in_size.y, data->in_size.z, 
			GL_LUMINANCE,GL_UNSIGNED_BYTE,&in_data(0,0,0));
	
	if (err != GL_NO_ERROR) {
		throw gl_error("GL3DTexture::glTexSubImage3D",err);
	}
	
}

// bind texture and enable the coordiate-translation
void GL3DGrayTexture::bind() const
{
	glBindTexture(GL_TEXTURE_3D,get_id());
	
	glMatrixMode(GL_TEXTURE_MATRIX);
	glLoadMatrixf(data->_M_texture_matrix);
	glMatrixMode(GL_MODELVIEW_MATRIX);
}

void GL3DGrayTexture::do_gl_attach()
{
	GLenum err;
	bind();
	glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_3D,GL_TEXTURE_WRAP_T,GL_CLAMP);
	
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL3DGrayTexture::do_gl_attach() -> glTexParameteri:",err);
	
	glTexImage3D(GL_TEXTURE_3D,0,1,data->size.x,data->size.y, data->size.z,
		     0, GL_LUMINANCE, GL_UNSIGNED_BYTE,NULL);
	
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL3DGrayTexture::do_gl_attach() -> glTexImage3D:",err);
}



GL2DLATexture::GL2DLATexture(const C2DBounds& in_size):
	GL2DTexture(in_size)
{
}

void GL2DLATexture::do_gl_attach()
{
	GLenum err;
	bind();
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL2DLATexture::do_gl_attach() -> glTexParameteri:",err);
	
	glTexImage2D(GL_TEXTURE_2D,0,2,get_data()->width,get_data()->height,0,
		     GL_LUMINANCE_ALPHA,GL_UNSIGNED_BYTE,NULL);
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("T2DAlphaSphere::do_gl_attach() -> glTexImage2D:",err);
}

void GL2DLATexture::set_data(const LuminData& InData)
{
	assert (get_data()->inwidth == InData.get_size().x && 
		get_data()->inheight == InData.get_size().y);

	int temp_width = get_data()->inwidth; 
	
	if ( temp_width & 1 ) {
		temp_width++;
	}
	
	vector<unsigned char> buffer(2 * temp_width * get_data()->inheight, 0);
	auto b_out = buffer.begin(); 
	auto b_in = InData.begin(); 
	
	for (size_t i = 0; i < get_data()->inheight; i++, 
		     b_out += 2 * temp_width) {
		
		int w = InData.get_size().x; 
		int k = 0; 
		//int j = 0; 
		
		while (w) {
			b_out[k++] = *b_in; 
			b_out[k++] = *b_in++; 
			--w; 
		}
	}
	
	bind();
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, temp_width, get_data()->inheight, 
			GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, &buffer[0]);

	GLenum err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("GL2DLATexture::set_data -> glTexImage2D:",err);
}

T2DAlphaSphere::T2DAlphaSphere(const C2DUBImage& _shape):
	shape(_shape),
	dithered_texture(true)
{
}

inline float sqr(float x) 
{
	return x * x; 
}

T2DAlphaSphere::T2DAlphaSphere(int radius):
	shape(C2DBounds(2 * radius ,2 * radius)),
	dithered_texture(false)
{
	int y = 0;
	float r2 = radius*radius;
	for (int i = -radius; i < radius; i++, y++) {
		int x = 0;
		for (int j = -radius; j < radius; j++, x++) {
			float h = 1.0  - (sqr(i) + sqr(j))/r2;
			if (h < 0.) {
				h = 0; 
			}
			unsigned char help = (unsigned char)(255 * h); 
			
			shape(x,y) =  help;
		}
	}
}

void T2DAlphaSphere::do_gl_attach()
{
	GLenum err;
	
	bind();
	
	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, shape.get_size().x, 
		     shape.get_size().y, 0, GL_ALPHA,
		     GL_UNSIGNED_BYTE, &shape(0,0));
	
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("T2DAlphaSphere::do_gl_attach()",err);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
	
	err = glGetError();
	if (err != GL_NO_ERROR)
		throw gl_error("T2DAlphaSphere::do_gl_attach()",err);
}


void T2DAlphaSphere::bind() const
{
	glBindTexture(GL_TEXTURE_2D,get_id());	
}
