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

#ifndef __drawable_hh
#define __drawable_hh

#include <GL/gl.h>
#include <mia.hh>

using namespace mia; 

#include <map>
//#include <sigc++/sigc++.h>

#define DRAW_VISIBLE               0x01
#define DRAW_TRANSPARENT           0x02
#define DRAW_HAS_ROTATION          0x04
#define DRAW_HAS_TRANSLATION       0x08
#define DRAW_HAS_TRANFORMATION     (DRAW_HAS_TRANSLATION | DRAW_HAS_ROTATION)
#define DRAW_NEED_LIGHT            0x10
#define DRAW_CULL_ENABLE           0x20
#define DRAW_CULL_FRONT_FACE       0x40
#define DRAW_CULL_BACK_FACE        0x80

#define DRAW_USE_XY_CLIP_PLANE       0x100
#define DRAW_USE_XZ_CLIP_PLANE       0x200
#define DRAW_USE_YZ_CLIP_PLANE       0x400
#define DRAW_USE_FREE_CLIP_PLANE     0x800
#define DRAW_USE_CLIP_PLANE_MASK     (DRAW_USE_XY_CLIP_PLANE | DRAW_USE_XZ_CLIP_PLANE | DRAW_USE_YZ_CLIP_PLANE | DRAW_USE_FREE_CLIP_PLANE)

#define DRAW_SHINY                 0x2000


#define DRAWABLE_CLASSNAME "TDrawable"

#define DEBUG_LOG(s) s

#include <list>
using namespace std;

//#include "status.hh"

#include <viewit/event.hh>
#include <viewit/glerror.hh>

#include <viewit/camera.hh>
#include <stdexcept>

class TGLText; 

/// typedef to store some vertices
typedef list<C3DFVector> TVertexList; 


const string type_TDrawable("TDrawable");

/// the basic class for all objects, then can appear in a GL-scene. 
/**
   This is the basic class for all objects , that can appear in an OpenGL-scene. 
   It can be visible, transparent, individually moved, supports three colors (think this over)
   can be illuminated and enable/ diabel each available clip plane.
   
   \todo improve the interface to save the state of the drawable
   \todo create a cleaner minimal interface that can be used as a basis for drawable plugins 
 */
class TDrawable : public TEventHandler {
private:	
	/** a zero vector for comparison reasons. 
	    \remark where is this used?
	*/
	const static C3DFVector zero; 
	
	/// the name of the drawable. 
	string name;
	
	/// 
	bool animated;

	/** the current status of the drawable
	    \remark this should be replaced by something better
	*/
	struct {
		int flags; 
		C3DFVector position; 
		C3DFVector rotation; 
		TColor color1; 
		TColor color2; 	
		TColor color3;	
		TColor ambient_light_color; 
		TColor diffuse_front_color;
		TColor diffuse_back_color;
		int light_model;
		bool is_selected;
		float alpha; 
	} drawable_state ; 
	
	
public:
	/// construct a drawable with the given name
	TDrawable(const string& _name);
	
	/// copy a drawable, but give a new name
 	TDrawable(const string& _name,const TDrawable& org);
	
	/// destruct drawable -< ensures a virtual destructor
	virtual ~TDrawable();
	
	/** gives a string literal for the type of the drawable
	    \remark obsolete
	*/
	virtual const string& get_type() const;
	
	/// \returns a string literal for the classname of the object
	virtual const char *get_classname() const;
	
	/** fills the list with the names of the base classes recursively
	    \param pointer to an allocated stringlist 
	*/
	virtual void get_classname_list(list<string> *classlist) const;

	
	/// \returns  the name of the drawable
	const string& get_name() const;
	
	/// toggles the visible status of the drawable
	void toggle_visible();
	
	/// turns the visible status of the drawable on
	void show();
	
	/// turns the visible status of the drawable off
	void hide();
	
	/// returns the visible status of the drawable
	bool is_visible() const;
	
	/// toggles the transparency status of the drawable
	void toggle_transparent();
	
	/// turns on the transparency status of the drawable
	void transparent_on();
	
	/// turns off the transparency status of the drawable	
	void transparent_off();
	
	/// returns the transparency status of the drawable
	bool is_transparent() const; 
	
	/// returns the transparency value of the drawable
	float get_transparency() const;
	
	/// sets the transparency value of the drawable
	virtual void set_transparency(float alpha);

	/// returns the select status of this drawable
	bool is_selected()const; 
	
	/// query whether shiny drawing is on
	bool draw_shiny() const;
	
	/// turn the shiny flag on
	void shiny_on();
	
	/// turn the shiny flag off
	void shiny_off();
	
	/// switch between the lightning models 
	void toggle_lightmodel();
	
	/// toggle the shiny-flag 
	void toggle_draw_shiny() {
		drawable_state.flags ^= DRAW_SHINY;
	}
	
	/// togle the culling flag 
	void toggle_culling_enable();
	
	///  turn culling on 
	void culling_on();
	
	/// \returns the whether the drawable needs animation or not
	bool is_animated() const;
#if 0
	/// set the value of a attribute 
	void set_attribute(const std::string& name, const std::string& value);
	
	/// restore the state of the object from an XML tree
	virtual void set_state(const TStatusNode& root);
#endif	
	/// turn off culling 
	void culling_off();
	
	/// \retuns whether culling is enabled
	bool culling_is_enabled() const; 
	
	/// cull front faces
	void cull_front();
	
	/// \returns true if the front faces are culled
	bool do_cull_front()const;
	
	/// cull back-faces
	void cull_back();
	
	/// \returns true if the back faces are culled
	bool do_cull_back()const;
	
	/// toggle lightning for this drawable
	void toggle_light();
	
	/// turn on lightning  for this drawable
	void light_on();
	
	/** set a specific lightning model
	    \param lm 0 = on-sided, 1 = two-sided
	*/
	void set_light_model(int lm);
	
	/// turn off lightning  for this drawable
	void light_off();
	
	/// tells whether lightning is wanted/needed for this drawable
	bool need_light() const;
	
	/// toggle the usage of clip planes for this drawable
	void toggle_enable_clip_plane(int flags);
	
	/// returns the clip plane usage flags
	int use_clip_plane();
	
	/** set an object specific rotation
	    \param new_rot the rotation around the x,y,z-axis (in that order applied)
	*/
	void set_rotation(const C3DFVector& new_rot);
	/** add an object specific rotation
	    \param add_rot  rotation to add 
	    \remark this is an addition of components and no true concatation
	*/
	void add_rotation(const C3DFVector& add_rot);
	
	/** set a new object specific position in the object coordinate system
	    \param pos new position
	*/
	void set_position(const C3DFVector& pos);
	
	/** move the object in the object coordinate system
	    \param shift incremental position change 
	*/
	void move(const C3DFVector& shift);

	/** \returns the drawable color 1
	    \todo replace this with some more general approach 
	*/
	const TColor& get_color1()const;

	/** set the drawable color 1
	    \todo replace this with some more general approach 
	*/
	
	void set_color1(const TColor& color);

	/** \returns the drawable color 1
	    \todo replace this with some more general approach 
	*/
	const TColor& get_color2()const;
	
	/** set the drawable color 2
	    \todo replaca this with some more general approach 
	*/

	void set_color2(const TColor& _color);	

	/** \returns the drawable color 1
	    \todo replace this with some more general approach 
	*/
	const TColor& get_color3()const;
	
	/** set the drawable color 3
	    \param color new color no 3
	    \todo replaca this with some more general approach 
	*/
	void set_color3(const TColor& color);
	

	/** \returns the ambient color
	    \todo replace this with some more general approach 
	*/
	const TColor& get_ambient_light_color()const;
	
	
	void set_ambient_light_color(const TColor& color);
	
	const TColor& get_diffuse_front_color()const;
	void set_diffuse_front_color(const TColor& _color);	

	const TColor& get_diffuse_back_color()const;
	void set_diffuse_back_color(const TColor& _color);

	
	virtual void povray_output_object(FILE *f)const;
	void povray_output_attributes(FILE *f)const;
	virtual void povray_output_description(FILE *f)const;
	
	/** 
	    Draw the drawable using the given camera.
	    It checks whether the drawable is visible, applys additional transforations and 
	    turns on lightning if neccessary.
	    Then it calls the virtual method {\em do_gl_draw} which does the actual drawing.
	*/
	void gl_draw(const TCamera& c) const;
	
	/** Draw decoration (scales, text, ...) for this drawable
	    \param viewport area where we may write
	    \param writer the text writer object
	*/
	virtual void draw_decoration(const C2DBounds& viewport,const TGLText& writer);
	
	/** set this drawable as selected */
	void select();

	/** set this drawable as unselected */
	void unselect();

	/** apply initializations which are only possible 
	    with a valid OPenGL rendering context 
	*/
	virtual void gl_attach();
	
	/** cleanups which are necessary before the OpenGL rendering context 
	    becomes invalid.
	*/
	virtual void gl_detach();
	
	/** Animate the drawable if nesseccary
	 */
	virtual bool animate();
	
	/// export the object to a text file called {\em name}{\tt + .obj}
	void export_object() const;
private:	
	/** special action if the drawable is selected 
	    The standart implementation does nothing.
	 */
	virtual void do_select();
	
	/** special action if the drawable is un-selected 
	    The standart implementation does nothing.
	 */
	virtual void do_unselect();
	
	/** The actual display method of the drawable. This one has to be overwritten 
	    \param camera the actual camera defining viewport, projecttion et al. 
	 */
	virtual void do_gl_draw(const TCamera& camera) const = 0; 
	
	/** The actual object export method,
	    \param f is the file to write to
	    \returns true if the object was succesfully exported
	 */
	virtual bool do_export(FILE *f)const;
	
protected:	
	/** 
	    The Key-event handler for a drawable. It obeys the following keys:
	    \begin{itemize}
	    \item 'v' - toggle visible
	    \item 't' - toggle transparency
	    \end{itemize}
	*/
	bool do_handle_key_event(int key);
	bool do_handle_command_event(PEventInfo info);
	void pre_transformation()const;
	void set_material()const;
	void turn_on_light()const;
	void turn_off_light()const;
	/** set the animation capability for this drawable
	    \param animate enables or disables the animation
	*/
	void set_animated(bool animate);
	void end_transformation()const;
	

};


/// Type for a  map where all the drawable of a scene go in.
typedef map<string, TDrawable *> TDrawMap; 


// Dummy class will see if we need it.
class TDummyDrawable: public TDrawable {
public:	
	TDummyDrawable();
private:		
	virtual void do_gl_draw(const TCamera& ) const{};
	virtual void povray_output_object(FILE *)const{};
	virtual void povray_output_attributes(FILE *)const{};
	virtual void povray_output_description(FILE *)const{};
};

// inline implementation

inline const char* TDrawable::get_classname() const
{
	return DRAWABLE_CLASSNAME;
}

inline void TDrawable::toggle_visible()
{
	drawable_state.flags ^= DRAW_VISIBLE; 
}

inline const string& TDrawable::get_name() const
{
	return name; 
}

inline void TDrawable::toggle_lightmodel()
{
	drawable_state.light_model ^= 1; 
}
	
inline void TDrawable::show()
{
	drawable_state.flags |=  DRAW_VISIBLE; 
}

inline void TDrawable::hide()
{
	drawable_state.flags &= ~DRAW_VISIBLE; 
}

inline bool TDrawable::is_visible() const
{
	return drawable_state.flags & DRAW_VISIBLE; 
}

inline void TDrawable::toggle_light()
{
	drawable_state.flags ^= DRAW_NEED_LIGHT; 
}

inline void TDrawable::light_on()
{
	drawable_state.flags |= DRAW_NEED_LIGHT;
}
inline void TDrawable::light_off()
{
	drawable_state.flags &= ~DRAW_NEED_LIGHT;
}
inline bool TDrawable::need_light() const
{
	return drawable_state.flags & DRAW_NEED_LIGHT;
}

inline void TDrawable::set_rotation(const C3DFVector& new_rot)
{
	drawable_state.rotation = new_rot; 
	if (drawable_state.rotation  == zero)
		drawable_state.flags &= ~DRAW_HAS_ROTATION; 
	else
		drawable_state.flags |= DRAW_HAS_ROTATION; 
}
inline void TDrawable::add_rotation(const C3DFVector& add_rot)
{
	drawable_state.rotation += add_rot; 
	if (drawable_state.rotation == zero)
		drawable_state.flags &= ~DRAW_HAS_ROTATION; 
	else
		drawable_state.flags |= DRAW_HAS_ROTATION; 
}

inline void TDrawable::set_position(const C3DFVector& pos)
{
	drawable_state.position = pos;
	if (drawable_state.position == zero)
		drawable_state.flags &= ~DRAW_HAS_TRANSLATION; 
	else
		drawable_state.flags |= DRAW_HAS_TRANSLATION; 
}
inline void TDrawable::move(const C3DFVector& shift)
{
	drawable_state.position += shift;
	if (drawable_state.position == zero)
		drawable_state.flags &= ~DRAW_HAS_TRANSLATION; 
	else
		drawable_state.flags |= DRAW_HAS_TRANSLATION; 
}

inline void TDrawable::toggle_transparent()
{
	drawable_state.flags ^= DRAW_TRANSPARENT; 
}

inline void TDrawable::transparent_on()
{
	drawable_state.flags |=  DRAW_TRANSPARENT; 
}

inline void TDrawable::transparent_off()
{
	drawable_state.flags &= ~DRAW_TRANSPARENT; 
}

inline bool TDrawable::is_transparent() const
{
	return drawable_state.flags & DRAW_TRANSPARENT; 
}

inline float TDrawable::get_transparency() const
{
	return drawable_state.alpha;
}

inline void TDrawable::toggle_culling_enable()
{
	drawable_state.flags ^= DRAW_CULL_ENABLE; 
}

inline void TDrawable::culling_on()
{
	drawable_state.flags |= DRAW_CULL_ENABLE; 	
}	

inline void TDrawable::culling_off()
{
	drawable_state.flags &= ~DRAW_CULL_ENABLE;
}	

inline bool TDrawable::culling_is_enabled() const
{
	return drawable_state.flags & DRAW_CULL_ENABLE; 
}	

inline void TDrawable::cull_front()
{
	drawable_state.flags |= DRAW_CULL_FRONT_FACE; 
}	

inline bool TDrawable::do_cull_front()const
{
	return drawable_state.flags & DRAW_CULL_FRONT_FACE; 
}	

inline void TDrawable::cull_back()
{
	drawable_state.flags &= ~DRAW_CULL_FRONT_FACE; 
}	

inline bool TDrawable::do_cull_back()const
{
	return ! (drawable_state.flags & DRAW_CULL_FRONT_FACE);
}	

inline bool TDrawable::is_animated() const
{
	return animated;
}

inline void TDrawable::set_animated(bool animate)
{
	animated = animate;
}

inline const TColor& TDrawable::get_color1()const
{
	return drawable_state.color1; 
}

inline const TColor& TDrawable::get_color2()const
{
	return drawable_state.color2; 
}

inline const TColor& TDrawable::get_color3()const
{
	return drawable_state.color3; 
}

inline const TColor& TDrawable::get_ambient_light_color()const
{
	return drawable_state.ambient_light_color;
}

inline void TDrawable::set_ambient_light_color(const TColor& color)
{
	drawable_state.ambient_light_color = color;
}

inline const TColor& TDrawable::get_diffuse_front_color()const
{
	return drawable_state.diffuse_front_color; 
}
	
inline void TDrawable::set_diffuse_front_color(const TColor& _color)
{
	drawable_state.diffuse_front_color = _color; 
}

inline const TColor& TDrawable::get_diffuse_back_color()const
{
	return drawable_state.diffuse_back_color; 
}
	
inline void TDrawable::set_diffuse_back_color(const TColor& _color)
{
	drawable_state.diffuse_back_color = _color; 
}

inline void TDrawable::end_transformation()const
{
	if (drawable_state.flags & DRAW_HAS_TRANFORMATION)
		glPopMatrix();
}

inline void TDrawable::toggle_enable_clip_plane(int flags)
{
	drawable_state.flags ^= (flags & DRAW_USE_CLIP_PLANE_MASK);
}


inline int TDrawable::use_clip_plane()
{
	return (drawable_state.flags & DRAW_USE_CLIP_PLANE_MASK);
}

inline bool TDrawable::is_selected()const
{
	return (drawable_state.is_selected);
}

inline void TDrawable::set_light_model(int model)
{
	drawable_state.light_model = model; 
}

inline void TDrawable::shiny_on()
{
	drawable_state.flags |= DRAW_SHINY;
}

inline void TDrawable::shiny_off()
{
	drawable_state.flags &= ~DRAW_SHINY;
}

inline bool TDrawable::draw_shiny() const 
{
	return drawable_state.flags & DRAW_SHINY;
};

#endif
