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


#include <viewit/drawable.hh>
#include <viewit/gltext.hh>
//#include "status.hh"

#include <GL/glu.h>
#include <iostream>
#include <unistd.h>
#include <sstream>

using namespace std; 


const std::string OBJ_POS("pos");
const std::string OBJ_ROT("rot");
const std::string OBJ_VISIBLE("visible");
const std::string OBJ_TRANSPARENT("transparent");
const std::string OBJ_LIT("lit");
const std::string OBJ_SHINY("shiny");
const std::string OBJ_CULL("cull");
const std::string OBJ_BACK_CULL("back_cull");
const std::string OBJ_COLOR1("color1");
const std::string OBJ_COLOR2("color2");
const std::string OBJ_COLOR3("color3");
const std::string OBJ_AMBIENT_LIGHT_COLOR("ambient_light_color");
const std::string OBJ_DIFFUSE_FRONT_COLOR("diffuse_front_color");
const std::string OBJ_DIFFUSE_BACK_COLOR("diffuse_back_color");
const std::string OBJ_LIGHT_MODEL("light_model");
const std::string OBJ_ALPHA("alpha");


TDrawable::TDrawable(const string& _name):
	name(_name), animated(false)
{
	drawable_state.flags = 0;
	drawable_state.position = C3DFVector(0.0f,0.0f,0.0f);
	drawable_state.rotation = C3DFVector(0.0f,0.0f,0.0f);
	drawable_state.color1 = TColor(1.0f,1.0f,1.0f,0.5f);
	drawable_state.color2 = TColor(1.0f,0.0f,0.0f,0.5f);
	drawable_state.color3 = TColor(0.0f,1.0f,0.0f,0.5f);
	drawable_state.light_model = 0;
	drawable_state.ambient_light_color = TColor(0.3,0.3,0.3,1.0);
	drawable_state.diffuse_front_color = TColor(1.0,1.0,1.0,1.0);
	drawable_state.diffuse_back_color = TColor(.0,.7,0.7,1.0);
	drawable_state.is_selected = false;
	drawable_state.alpha = 1.0f; 
}

TDrawable::TDrawable(const string& _name,const TDrawable& org):
	name(_name),
	drawable_state(org.drawable_state)
{
}

TDrawable::~TDrawable()
{
}

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


void TDrawable::turn_on_light()const
{
	glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);

}

void TDrawable::turn_off_light()const
{
	glDisable(GL_COLOR_MATERIAL);	
	glDisable(GL_LIGHTING);
}

void TDrawable::pre_transformation()const
{
	if (drawable_state.flags & DRAW_HAS_TRANFORMATION) {
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		
		// should go into a display list for performance reasons
		glTranslatef(drawable_state.position.x,drawable_state.position.y,drawable_state.position.z);
		glRotatef(drawable_state.rotation.x,1.0f,0.0f,0.0f);
		glRotatef(drawable_state.rotation.y,0.0f,1.0f,0.0f);
		glRotatef(drawable_state.rotation.z,0.0f,0.0f,1.0f);	
	}
}


void TDrawable::set_material()const 
{
	glLightModeliv(GL_LIGHT_MODEL_TWO_SIDE,&drawable_state.light_model);
	glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,&drawable_state.ambient_light_color.x);
	glMaterialfv(GL_FRONT,GL_DIFFUSE,&drawable_state.diffuse_front_color.x);
	glMaterialfv(GL_BACK,GL_DIFFUSE,&drawable_state.diffuse_back_color.x);

	if (draw_shiny()) {
		cvdebug() << "TDrawable: 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 0
void TDrawable::get_state(TStatusNode* status)
{
	status->add_child_with_content("visible", is_visible());
	status->add_child_with_content("transparent", is_transparent());
	status->add_child_with_content("lit",need_light());
	status->add_child_with_content("shiny",draw_shiny());
	status->add_child_with_content("culling",culling_is_enabled());
	status->add_child_with_content("back_culling",do_cull_back());
	status->add_child_with_content("position",drawable_state.position);
	status->add_child_with_content("rotation",drawable_state.rotation);
	status->add_child_with_content("color1",drawable_state.color1);
	status->add_child_with_content("color2",drawable_state.color2);
	status->add_child_with_content("color3",drawable_state.color3);
	status->add_child_with_content("ambient_light_color",drawable_state.ambient_light_color);
	status->add_child_with_content("diffuse_front_color",drawable_state.diffuse_front_color);
	status->add_child_with_content("diffuse_back_color",drawable_state.diffuse_back_color);	
	status->add_child_with_content("light_model",drawable_state.light_model);
	status->add_child_with_content("alpha",drawable_state.alpha);
}


bool TDrawable::set_attribute(const std::string& name, const std::string& value)
{

	std::stringstream s(value);
	int flag; 
	if (name == OBJ_POS) {
		s >> drawable_state.position;
	}else if (name == OBJ_ROT) {
		s >> drawable_state.rotation;
	}else if (name == OBJ_VISIBLE) {
		s >> flag;
		if (flag) 
			show(); 
		else 
			hide();
	}else if (name == OBJ_TRANSPARENT) {
		s >> flag;
		if (flag) 
			transparent_on();  
		else 
			transparent_off(); 
	}else if (name == OBJ_LIT) {
		s >> flag;
		if (flag) 
			light_on(); 
		else 
			light_off();
	}else if (name == OBJ_SHINY) {
		s >> flag;
		if (flag) 
			shiny_on(); 
		else 
			shiny_off();
	}else if (name == OBJ_CULL){
		s >> flag;
		if (flag) 
			culling_on();
		else culling_off();
	}else if (name == OBJ_BACK_CULL) {
		s >> flag;
		if (flag) 
			cull_back();
		else 
			cull_front();
	}
	else if (name == OBJ_COLOR1)
		s >> drawable_state.color1;
	else if (name == OBJ_COLOR2)
		s >> drawable_state.color2;
	else if (name == OBJ_COLOR3)
		s >> drawable_state.color3;
	else if (name == OBJ_AMBIENT_LIGHT_COLOR)
		s >> drawable_state.ambient_light_color;
	else if (name == OBJ_DIFFUSE_FRONT_COLOR)
		s >> drawable_state.diffuse_front_color;
	else if (name == OBJ_DIFFUSE_BACK_COLOR)
		s >> drawable_state.diffuse_back_color;
	else if (name == OBJ_LIGHT_MODEL)
		s >> drawable_state.light_model;
	else if (name == OBJ_ALPHA)
		s >> drawable_state.alpha;
	else 
		return false; 
	return true; 
}
#endif

void TDrawable::set_transparency(float alpha) 
{

	if (alpha <= 0.0) {
		drawable_state.alpha = 
			drawable_state.diffuse_front_color.a = 
			drawable_state.diffuse_back_color.a = 
			drawable_state.color1.a = 
			drawable_state.color2.a = 
			drawable_state.color3.a = 0.0;
		hide();
	} else if (alpha >= 1.0) {
		drawable_state.alpha = 
			drawable_state.diffuse_front_color.a = 
			drawable_state.diffuse_back_color.a = 
			drawable_state.color1.a = 
			drawable_state.color2.a = 
			drawable_state.color3.a = 1.0;
		transparent_off();
		show();
	} else {
		drawable_state.alpha = 
			drawable_state.diffuse_front_color.a = 
			drawable_state.diffuse_back_color.a = 
			drawable_state.color1.a = 
			drawable_state.color2.a = 
			drawable_state.color3.a = alpha;
		transparent_on();
		show();
	}
}


void TDrawable::gl_draw(const TCamera& c) const
{
	glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
	
	pre_transformation();
	
	if (need_light()) {
		turn_on_light();
		set_material();
	}
	if (culling_is_enabled()) {
		glEnable(GL_CULL_FACE);
		if (do_cull_front()) {
			glCullFace(GL_FRONT);
		}else
			glCullFace(GL_BACK);
	}

		
	do_gl_draw(c);
	
	if (need_light()) {
		turn_off_light();
	}
	
	if (culling_is_enabled()) {
		glDisable(GL_CULL_FACE);
	}
	
	end_transformation();
	glPopClientAttrib();
}

void TDrawable::gl_attach()
{
	//	DEBUG_LOG(cerr << name << " gl_attach()" << endl);
}

void TDrawable::gl_detach()
{
	//	DEBUG_LOG(cerr << name << " gl_release()" << endl);
}

bool TDrawable::animate()
{
	return false;
}

void TDrawable::set_color1(const TColor& _color)
{
	drawable_state.color1 = _color; 
	drawable_state.alpha = _color.a; 
}

void TDrawable::set_color2(const TColor& _color)
{
	drawable_state.color2 = _color; 
}

void TDrawable::set_color3(const TColor& _color)
{
	drawable_state.color3 = _color; 
}

void TDrawable::export_object() const 
{
	string filename = name + string(".obj");
	FILE *f = fopen(filename.c_str(),"w");
	if (f) {
		if (!do_export(f))
			unlink(filename.c_str()); 
		fclose(f);
	}else{
		perror(filename.c_str());
	}
}
void TDrawable::select()
{
	drawable_state.is_selected = true;
	do_select();
}

void TDrawable::unselect()
{
	drawable_state.is_selected = false;
	do_unselect();
}


void TDrawable::do_select()
{
}

void TDrawable::do_unselect()
{
}


bool  TDrawable::do_export(FILE *f)const
{
	fprintf(f,"no export implemented\n");
	return true; 
}

const string& TDrawable::get_type() const
{
	return type_TDrawable;
}


void TDrawable::draw_decoration(const C2DBounds& viewport,const TGLText& writer)
{
	
}

bool TDrawable::do_handle_command_event(PEventInfo info)
{
	switch (info->get_command()) {
	case  EV_DRAWABLE_GET_STATE: {
		TDrawableEventInfo& ev = dynamic_cast<TDrawableEventInfo&>(*info);
		ev.set_clipxy((drawable_state.flags & DRAW_USE_XY_CLIP_PLANE)   == DRAW_USE_XY_CLIP_PLANE);
		ev.set_clipxz((drawable_state.flags & DRAW_USE_XZ_CLIP_PLANE)   == DRAW_USE_XZ_CLIP_PLANE);
		ev.set_clipyz((drawable_state.flags & DRAW_USE_YZ_CLIP_PLANE)   == DRAW_USE_YZ_CLIP_PLANE);
		ev.set_clipyz((drawable_state.flags & DRAW_USE_FREE_CLIP_PLANE) == DRAW_USE_FREE_CLIP_PLANE);
		ev.set_visibility((drawable_state.flags & DRAW_VISIBLE) == DRAW_VISIBLE);
		ev.set_transparency(drawable_state.alpha);
		return true;
	} break;
	case  EV_DRAWABLE_SHOW:			show(); return true; 
	case  EV_DRAWABLE_HIDE:			hide(); return true; 
	case  EV_DRAWABLE_SET_TRANSPARENCY: {
		TDrawableEventInfo& ev = dynamic_cast<TDrawableEventInfo&>(*info);
		set_transparency(ev.get_transparency());
	} break;
	case  EV_DRAWABLE_XYCLIP_ON:		drawable_state.flags |= DRAW_USE_XY_CLIP_PLANE;return true;
	case  EV_DRAWABLE_XYCLIP_OFF:		drawable_state.flags &=~DRAW_USE_XY_CLIP_PLANE;return true;
	case  EV_DRAWABLE_XZCLIP_ON:		drawable_state.flags |= DRAW_USE_XZ_CLIP_PLANE;return true;
	case  EV_DRAWABLE_XZCLIP_OFF:		drawable_state.flags &=~DRAW_USE_XZ_CLIP_PLANE;return true;
	case  EV_DRAWABLE_YZCLIP_ON:		drawable_state.flags |= DRAW_USE_YZ_CLIP_PLANE;return true;
	case  EV_DRAWABLE_YZCLIP_OFF:		drawable_state.flags &=~DRAW_USE_YZ_CLIP_PLANE;return true;
	case  EV_DRAWABLE_FREECLIP_ON:		drawable_state.flags |= DRAW_USE_FREE_CLIP_PLANE;return true;
	case  EV_DRAWABLE_FREECLIP_OFF:		drawable_state.flags &=~DRAW_USE_FREE_CLIP_PLANE;return true;

	}
	return TEventHandler::do_handle_command_event(info);
}

TDummyDrawable::TDummyDrawable():TDrawable("            ")
{
}

void TDrawable::povray_output_object(FILE *f)const
{
	
	
	cerr << name << ":" << get_type() << " povray_output_object not implemented" << endl;
}

void TDrawable::povray_output_description(FILE *f)const
{
	fprintf(f,"%s\n",get_name().c_str());
}

void TDrawable::povray_output_attributes(FILE *f)const
{
	if (!is_visible()) {
		return; 
	}
	
	if (is_transparent()) {
		fprintf(f,"#declare %s_color1 = rgbf < %f, %f, %f, %f>\n",get_name().c_str(),
			get_color1().x,get_color1().y,get_color1().z,1.0f - get_color1().a);
		fprintf(f,"#declare %s_color2 = rgbf < %f, %f, %f, %f>\n",get_name().c_str(),
			get_color2().x,get_color2().y,get_color2().z,1.0f - get_color2().a);
		fprintf(f,"#declare %s_color3 = rgbf < %f, %f, %f, %f>\n",get_name().c_str(),
			get_color3().x,get_color3().y,get_color3().z,1.0f - get_color1().a);
	}else{
		fprintf(f,"#declare %s_color1 = rgb < %f, %f, %f>\n",get_name().c_str(),
			get_color1().x,get_color1().y,get_color1().z);
		fprintf(f,"#declare %s_color2 = rgb < %f, %f, %f>\n",get_name().c_str(),
			get_color2().x,get_color2().y,get_color2().z);
		fprintf(f,"#declare %s_color3 = rgb < %f, %f, %f>\n",get_name().c_str(),
			get_color3().x,get_color3().y,get_color3().z);
	}
}

bool TDrawable::do_handle_key_event(int key)
{
	switch (key) {
	case 'v':toggle_visible(); break;
	case 't':
		if (drawable_state.alpha > 0.0) {
			transparent_on();
			drawable_state.alpha -= 0.05; 
			drawable_state.diffuse_front_color.a = 
				drawable_state.diffuse_back_color.a = 
				drawable_state.color1.a = 
				drawable_state.color2.a = 
				drawable_state.color3.a = drawable_state.alpha;
			if (drawable_state.alpha <= 0.0)
				hide();
				
		}
		break;
	case 'T':
		if (drawable_state.alpha < 1.0) {
			transparent_on();
			show();
			drawable_state.alpha += 0.05; 
			drawable_state.diffuse_front_color.a = 
				drawable_state.diffuse_back_color.a = 
				drawable_state.color1.a = 
				drawable_state.color2.a = 
				drawable_state.color3.a = drawable_state.alpha;
			if (drawable_state.alpha >= 1.0)
				transparent_off();
				
		}
		break; 
	case 'e':export_object(); break; 
	case '1':toggle_enable_clip_plane(DRAW_USE_XY_CLIP_PLANE); break;
	case '2':toggle_enable_clip_plane(DRAW_USE_XZ_CLIP_PLANE); break;		
	case '3':toggle_enable_clip_plane(DRAW_USE_YZ_CLIP_PLANE); break;		
	case '4':toggle_enable_clip_plane(DRAW_USE_FREE_CLIP_PLANE); break;		

	default:return false; 

	}
	return true; 
}


const C3DFVector TDrawable::zero(0,0,0);
