/* -*- 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/>.
 */


#define GL_GLEXT_PROTOTYPES 1


#include <GL/gl.h>
#include <GL/glext.h>
#include <vector>
#include <queue>


#include <viewit/glmesh.hh>
#include <viewit/texture.hh>
#include <viewit/gltext.hh>

using namespace std;
using namespace mia; 

typedef struct {
	C3DBounds t; 
	float z_dist; 
} STriangle; 


#if 0
template<> struct __type_traits<STriangle> {
	typedef __true_type    has_trivial_default_constructor;
	typedef __true_type    has_trivial_copy_constructor;
	typedef __true_type    has_trivial_assignment_operator;
	typedef __true_type    has_trivial_destructor;
	typedef __true_type    is_POD_type;
};
#endif

inline bool operator < (const STriangle& a, const STriangle& b) 
{
	return a.z_dist < b.z_dist; 
}

struct TGLMeshData {
	PTriangleMesh _M_mesh; 
	mutable std::vector<C3DFVector> _M_triangle_midpoints; 
	GL3DGrayTexture *_M_texture; 
	float _M_max_grow_log;
	float _M_max_shrink_log;
	float _M_max_grow;
	float _M_max_shrink;
	float _M_inv_grow_log;
	float _M_inv_shrink_log;
	float _M_inv_grow;
	float _M_inv_shrink;
	
	float _M_max_scale; 
	int  _M_scale_type; 
	const TDrawable& _M_owner;
	bool do_draw_decoration; 
	
	TGLMeshData(PTriangleMesh mesh,const TDrawable& owner);

	bool do_handle_key_event(int key);
	void do_gl_draw(const TCamera& c,float alpha) const;
	void init_texturing()const;
	void deinit_texturing()const; 
	void sort_triangles(std::priority_queue<STriangle> *st, const TCamera& c)const;
	void draw_decoration(const C2DBounds& viewport,const TGLText& writer);
	void evaluate_triangle_midpoints()const;
	void apply_color_change();
	void toggle_scale_type();
	void inside_out();
	void gl_attach();
};

TGLMeshData::TGLMeshData(PTriangleMesh mesh, const TDrawable& owner):
	_M_mesh(mesh),
	_M_triangle_midpoints(mesh->triangle_size()),
	_M_texture(NULL), 
	_M_max_grow_log(0),
	_M_max_shrink_log(0),
	_M_max_grow(0),
	_M_max_shrink(0),
	_M_max_scale(0),
	_M_scale_type(md_scale_2mm),
	_M_owner(owner),
	do_draw_decoration(false)
{
	//	inside_out();
	if (! (_M_mesh->get_available_data() & CTriangleMesh::ed_normal))
		_M_mesh->evaluate_normals();
	
	evaluate_triangle_midpoints();

	if (_M_mesh->get_available_data() & CTriangleMesh::ed_scale) {
		
		CTriangleMesh::const_scale_iterator sb = mesh->scale_begin();
		CTriangleMesh::const_scale_iterator se = mesh->scale_end();
		
		while (sb != se) {
			if (_M_max_grow < *sb) 
				_M_max_grow = *sb; 
			if (_M_max_shrink > *sb)
				_M_max_shrink = *sb; 
			++sb; 
		}
		_M_max_grow_log = log(_M_max_grow);
		_M_max_shrink   = - _M_max_shrink; 
		_M_max_shrink_log = _M_max_shrink > 0 ?  log(_M_max_shrink) : 0;
		cerr << "scale: " << _M_max_shrink << ":" <<_M_max_grow << endl; 
		
		_M_inv_grow_log = _M_max_grow_log != 0 ? 1.0 / _M_max_grow_log : 0.0; 
		_M_inv_shrink_log = _M_inv_shrink_log != 0 ? 1.0 / _M_inv_shrink_log : 0.0; 
		_M_inv_grow = _M_max_grow != 0 ? 1.0 / _M_max_grow : 0.0; 
		_M_inv_shrink = _M_inv_shrink != 0 ? 1.0 / _M_inv_shrink : 0.0; 
		do_draw_decoration = true; 
	}
}

void TGLMeshData::toggle_scale_type() 
{
	++_M_scale_type;
	if (_M_scale_type == md_last) {
		_M_scale_type = md_standart; 
	}
	apply_color_change();
}

void TGLMeshData::gl_attach()
{
	apply_color_change();
}

void TGLMeshData::inside_out()
{
	CTriangleMesh::triangle_iterator tb = _M_mesh->triangles_begin();
	CTriangleMesh::triangle_iterator te = _M_mesh->triangles_end();
	
	while (tb != te) {
		unsigned int tmp = (*tb).z; 
		(*tb).z = (*tb).y; 
		(*tb).y = tmp;
		++tb;
	}
}


void TGLMeshData::sort_triangles(std::priority_queue<STriangle> *stq, const TCamera& c)const
{
	CTriangleMesh::const_triangle_iterator tb = _M_mesh->triangles_begin();
	CTriangleMesh::const_triangle_iterator te = _M_mesh->triangles_end();
	
	std::vector<C3DFVector>::const_iterator tmb =  _M_triangle_midpoints.begin(); 

	
	while( tb != te) {
		STriangle st; 
		st.t = *tb;
		st.z_dist = - c.get_z_dist(*tmb);
		stq->push(st);
		++tb;
		++tmb;
	}
}

void TGLMeshData::evaluate_triangle_midpoints()const
{
	CTriangleMesh::const_triangle_iterator tb = _M_mesh->triangles_begin();
	CTriangleMesh::const_triangle_iterator te = _M_mesh->triangles_end();
	
	std::vector<C3DFVector>::iterator tmb =  _M_triangle_midpoints.begin(); 
	
	while (tb != te) {
		*tmb = (_M_mesh->vertex_at((*tb).x) +
			_M_mesh->vertex_at((*tb).y) +
			_M_mesh->vertex_at((*tb).z)) / 3.0f;
		++tb;
		++tmb; 
	}
}

void TGLMeshData::draw_decoration(const C2DBounds& viewport,const TGLText& writer)
{
	if (!do_draw_decoration) 
		return;  
	
	int y = viewport.y; 
	
	glColor4f(0.0f,.5f,0.0f,0.3f);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glBegin(GL_QUADS);
	glVertex2f(5.0f,y - 10.0f);
	glVertex2f(90.0f,y - 10.0f);
	glVertex2f(90.0f,y - 110.0f);
	glVertex2f(5.0f,y - 110.0f);
	glEnd();
	glDisable(GL_BLEND);
	
	
	glBegin(GL_QUAD_STRIP);
	glColor3fv(&_M_owner.get_color2().x);
	glVertex2f(10.0f,y - 20.0f);
 	glVertex2f(20.0f,y - 20.0f);
	
	// in this case we do not mark zero 
	if (_M_scale_type != md_standart) {
		glColor3fv(&_M_owner.get_color1().x);
		glVertex2f(10.0f,y - 60.0f);
		glVertex2f(20.0f,y - 60.0f);
	}
	glColor3fv(&_M_owner.get_color3().x);
	glVertex2f(10.0f,y - 100.0f);
	glVertex2f(20.0f,y - 100.0f);
	glEnd();

	
	char value[20]; 
	
	switch (_M_scale_type) {
	case md_log:
		snprintf(value,20," e^%3.1f",_M_max_grow_log);
		glColor3fv(&_M_owner.get_color2().x);
		writer.write(25, int(y - 34), value);
		
		glColor3fv(&_M_owner.get_color1().x);
		writer.write(25, int(y - 67), "  0.0");
		
		snprintf(value,20,"-e^%3.1f",_M_max_shrink_log);
		glColor3fv(&_M_owner.get_color3().x);
		writer.write(25 , int(y - 100), value);
		break; 
	case md_standart:
		snprintf(value,20,"%5.1f",_M_max_grow);
		glColor3fv(&_M_owner.get_color2().x);
		writer.write(25, int(y - 34), value);
		
		snprintf(value,20,"%5.1f",-_M_max_shrink);
		glColor3fv(&_M_owner.get_color3().x);
		writer.write(25 , int(y - 100), value);
		break; 
	default:
		if (_M_max_scale > _M_max_grow)
			snprintf(value,20,"%5.1f",_M_max_scale);
		else
			snprintf(value,20,"%5.1f+",_M_max_scale);
		
		glColor3fv(&_M_owner.get_color2().x);
		writer.write(25, int(y - 34), value);
		
		glColor3fv(&_M_owner.get_color1().x);
		writer.write(25, int(y - 67), "  0.0");
		
		if (_M_max_scale > _M_max_shrink)
			snprintf(value,20,"%5.1f",-_M_max_scale);
		else
			snprintf(value,20,"%5.1f-",-_M_max_scale);
		glColor3fv(&_M_owner.get_color3().x);
		writer.write(25 , int(y - 100), value);
	}
}

void TGLMeshData::apply_color_change()
{
	_M_max_scale = 2.0f;
	
	if (! (_M_mesh->get_available_data() & CTriangleMesh::ed_scale)) {
		return;
	}
	
	float inv_max_scale = 0.5f; 
	
	const CTriangleMesh& m = *_M_mesh; 
	CTriangleMesh::const_scale_iterator sb = m.scale_begin();
	CTriangleMesh::const_scale_iterator se = m.scale_end();
	CTriangleMesh::color_iterator       cb = _M_mesh->color_begin();
	
	switch (_M_scale_type) {
	case md_log:
		while (sb != se) {
			float scale = *sb;
			TColor color; 
			if (scale < 0) { // if max_shrink == 0 then never true; 
				scale = log( 1.0f - scale);
				color = ((_M_max_shrink_log - scale ) * _M_owner.get_color1() + 
					 scale * _M_owner.get_color3()) * _M_inv_shrink_log; 
			}else if (scale > 0){
				scale = log(scale + 1.0f);
				color = ((_M_max_grow_log - scale) * _M_owner.get_color1() + 
					 scale * _M_owner.get_color2()) * _M_inv_grow_log; 
			}else{
				color = _M_owner.get_color1();
			}
			(*cb).x = color.x; 
			(*cb).y = color.y; 
			(*cb).z = color.z; 	
			++cb; 
			++sb;
		}
		break; 
	case md_divide:
		while (sb != se) {
			float scale = *sb;
			TColor color; 
			if (scale < 0) { // if max_shrink == 0 then never true; 
				color = _M_owner.get_color3(); 
			}else if (scale > 0){
				color = _M_owner.get_color2(); 
			}else{
				color = _M_owner.get_color1();
			}
			(*cb).x = color.x; 
			(*cb).y = color.y; 
			(*cb).z = color.z; 	
			
			++cb; 
			++sb;
		}
		break; 		
	case md_standart:
		while (sb != se) {
			float scale = *sb; 
			TColor color; 
			float s = (scale + _M_max_shrink)/ (_M_max_grow + _M_max_shrink); 
			color =  s * _M_owner.get_color2() + (1.0f - s) * _M_owner.get_color3(); 
			(*cb).x = color.x; 
			(*cb).y = color.y; 
			(*cb).z = color.z; 	
			
			++cb; 
			++sb;
		}
		break; 
	case md_scale_32mm:
		_M_max_scale *= 2.0;
		inv_max_scale *= 0.5; 
	case md_scale_16mm:		
		_M_max_scale *= 2.0;
		inv_max_scale *= 0.5; 
	case md_scale_8mm:		
		_M_max_scale *= 2.0;
		inv_max_scale *= 0.5; 
	case md_scale_4mm:		
		_M_max_scale *= 2.0;
		inv_max_scale *= 0.5; 
	case md_scale_2mm:
		while (sb != se) {
			float scale = *sb; 
			TColor color; 
			if (scale < 0) { // if max_shrink == 0 then never true; 
				if (scale > -_M_max_scale)
					color = ((scale + _M_max_scale) * _M_owner.get_color1() - 
						 scale * _M_owner.get_color3()) * inv_max_scale; 
				else
					color = _M_owner.get_color3(); 
			}else if (scale > 0){
				if (scale < _M_max_scale)
					color = ((_M_max_scale - scale) * _M_owner.get_color1() + 
						 scale * _M_owner.get_color2()) * inv_max_scale; 
				else
					color = _M_owner.get_color2(); 
			}else{
				color = _M_owner.get_color1();
			}
			(*cb).x = color.x; 
			(*cb).y = color.y; 
			(*cb).z = color.z; 	
			
			++cb; 
			++sb;
		}
		break; 
	default:
		std::cerr << "Warning: unknown scale type" << std::endl; 
	}
}

bool TGLMeshData::do_handle_key_event(int key)
{
	switch (key) {
	case 's':toggle_scale_type();
		break;
	case 'd':do_draw_decoration = !do_draw_decoration; 
		break; 
		
	default:
		return false;
	}
	return true; 
}

void TGLMeshData::init_texturing()const
{
	if (!_M_texture) 
		return; 
	
	_M_texture->bind();
	glEnable(GL_TEXTURE);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(3, GL_FLOAT, 0, _M_mesh->get_vertex_pointer());
}

void TGLMeshData::deinit_texturing()const
{
	if (!_M_texture) 
		return; 
	glDisable(GL_TEXTURE);
}

void TGLMeshData::do_gl_draw(const TCamera& c, float alpha) const
{
	glColorMaterial(GL_FRONT_AND_BACK,  GL_AMBIENT_AND_DIFFUSE); 
	if (_M_mesh->get_color_pointer()) {
		glColorPointer(3, GL_FLOAT, 0, _M_mesh->get_color_pointer());
		glEnableClientState(GL_COLOR_ARRAY);

	}


	if (_M_mesh->get_normal_pointer()) {
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT, 0, _M_mesh->get_normal_pointer());
	}

	init_texturing();
	// draw the mesh
	
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, _M_mesh->get_vertex_pointer());

	/*
	if (_M_owner.draw_shiny()) {
		cvdebug() << "TGLMeshData: draw shiny\n"; 
		TColor specular_color(.5,.5,0.5,1.0);
		glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,&specular_color.x);
		glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,5.0f);
	}else{
		cvdebug() << "TGLMeshData: draw normal\n"; 
		TColor black(.0,.0,0.0,0.0);
		glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,&black.x);
		glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,&black.x);
	}
	*/

	if (!_M_owner.is_transparent()) {
		glDrawElements(GL_TRIANGLES,3 * _M_mesh->triangle_size(),GL_UNSIGNED_INT, 
			       _M_mesh->get_triangle_pointer());
	}else{

		if (_M_mesh->get_color_pointer()) {
			glBlendColor(0.0,0.0,0.0,alpha);
			glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
		}
		
		std::priority_queue<STriangle> __st;
		sort_triangles(&__st,c);
		
		cvdebug() << "mesh drawn transparent" << endl; 
		while ( ! __st.empty() ) {
			//			cerr << __st.top().z_dist << endl; 
			glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, &__st.top().t.x);
			__st.pop();
		}
	}
	// (un)set client state
	deinit_texturing();
	
	if (_M_mesh->get_normal_pointer())
		glDisableClientState(GL_NORMAL_ARRAY);
	
	if (_M_mesh->get_color_pointer()) {
		glDisableClientState(GL_COLOR_ARRAY);
	}
	glDisableClientState(GL_VERTEX_ARRAY);
}

TGLMesh::TGLMesh(const string& name, PTriangleMesh mesh):
	TDrawable(name),
	data(new TGLMeshData(mesh,*this))
{
	light_on();
	culling_on();
	cull_back();
}

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


bool TGLMesh::do_handle_key_event(int key)
{
	switch (key) {
	case 'g':toggle_draw_shiny();   break;
	case 'l':toggle_lightmodel();   break; // light on both sides or not
	case 'k':toggle_culling_enable();break; 
	case 'p':
		if (do_cull_front())
			cull_back();
		else
			cull_front();
		break; 
	default:
		if (! data->do_handle_key_event(key)) 
			return TDrawable::do_handle_key_event(key);
	}
	return true; 
}

void TGLMesh::do_gl_draw(const TCamera& c) const
{
	cvdebug() << "Mesh draw with color " << get_color1() << "\n"; 
	glColor4fv(&get_color1().x);
	data->do_gl_draw(c, get_color1().a); 
}
	
void TGLMesh::set_texture(GL3DGrayTexture *texture)
{
	data->_M_texture = texture; 
}

void TGLMesh::draw_decoration(const C2DBounds& viewport,const TGLText& writer)
{
	data->draw_decoration(viewport,writer);
}

void TGLMesh::gl_attach()
{
	data->gl_attach();
}

bool TGLMesh::do_handle_command_event(PEventInfo info) {
	
	switch (info->get_command()) {
	case  EV_GLMESH_GET_STATE: {
		TGLMeshEventInfo& ev = dynamic_cast<TGLMeshEventInfo&>(*info);
		
		ev.set_draw_shiny(draw_shiny());
		ev.set_light_model(need_light());
		ev.set_culling(culling_is_enabled());
		ev.set_front_face_culling(do_cull_front());
		ev.set_color1(get_color1());
		ev.set_color2(get_color2());
		ev.set_color3(get_color3());
		ev.set_has_color(true);
	} break;
		
	case EV_GLMESH_TOGGLE_SHINY:
		toggle_draw_shiny();
		break;
		
	case EV_GLMESH_TOGGLE_LIGHTMODEL:
		toggle_lightmodel();
		break;
		
	case EV_GLMESH_TOGGLE_CULLING:
		toggle_culling_enable();
		break;
		
	case EV_GLMESH_TOGGLE_FRONTFACECULLING:
		if (do_cull_front())
			cull_back();
		else
			cull_front();
		break;
	
	case EV_GLMESH_SET_COLOR1: {
		TGLMeshEventInfo& ev = dynamic_cast<TGLMeshEventInfo&>(*info);
		set_color1(ev.get_color1());
		
		if (data->_M_mesh->get_available_data() & CTriangleMesh::ed_scale)
			apply_color_change();
		else
			set_diffuse_front_color(ev.get_color1());
	} break;
	
	case EV_GLMESH_SET_COLOR2: {
		TGLMeshEventInfo& ev = dynamic_cast<TGLMeshEventInfo&>(*info);
		set_color2(ev.get_color2());
		if (data->_M_mesh->get_available_data() & CTriangleMesh::ed_scale)
			apply_color_change();
		else
			set_diffuse_front_color(ev.get_color2());
		apply_color_change();
	} break;
	
	case EV_GLMESH_SET_COLOR3: {
		TGLMeshEventInfo& ev = dynamic_cast<TGLMeshEventInfo&>(*info);
		set_color3(ev.get_color3());
		apply_color_change();
	} break;

	case EV_GLMESH_TOGGLE_COLORSCALE:
		data->toggle_scale_type();
		break;
		
	case EV_GLMESH_SHOW_DECORATION:
		data->do_draw_decoration = !data->do_draw_decoration; 
		break;
	
	default: return TDrawable::do_handle_command_event(info);
	}
	return true;
}
	

const char* TGLMesh::get_classname() const 
{
	return GLMESH_CLASSNAME;
}

void TGLMesh::get_classname_list(list<string> *classlist) const
{
	if (classlist) {
		classlist->push_front(GLMESH_CLASSNAME);
		TDrawable::get_classname_list(classlist);
	}
}

CTriangleMesh* TGLMesh::get_mesh()
{
	return data->_M_mesh.get();
}
	

void TGLMesh::apply_color_change()
{
	data->apply_color_change();
}
