#include <math.h>
#include <gtk/gtk.h>
#include <gtkgl/gtkglarea.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "entity.h"
#include "rendgtkgl.h"

static void
rendgtkgl_area_redraw (ENode *glarea);

static gint
rendgtkgl_area_realize (GtkWidget *widget, gpointer data)
{
    ENode *node = data;

   /* OpenGL functions can be called only if make_current returns true */
    if (gtk_gl_area_make_current(GTK_GL_AREA(widget)))
    {
       	glViewport(0,0, widget->allocation.width, widget->allocation.height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0,100, 100,0, -1,1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
    }
    EDEBUG (("rendgtkgl", "GTK GL realize called"));

    rendgtkgl_area_redraw (node);

    return TRUE;
}

/* When widget is exposed it's contents are redrawn. */
static gint
rendgtkgl_area_expose (GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
    ENode *node = data;

    if (event->count > 0) 
    {
	rendgtkgl_area_redraw (node);
    }
    return TRUE;
}

/* When glarea widget size changes, viewport size is set to match the new size */
static gint
rendgtkgl_area_configure (GtkWidget *widget, GdkEventConfigure *event)
{
  /* OpenGL functions can be called only if make_current returns true */
  if (gtk_gl_area_make_current(GTK_GL_AREA(widget)))
    {
      glViewport(0,0, widget->allocation.width, widget->allocation.height);
    }
  return TRUE;
}



static void
rendgtkgl_render (ENode * node)
{
    GtkWidget *glarea;
    EBuf *value;

    /* Attribute list for gtkglarea widget. Specifies a
       list of Boolean attributes and enum/integer
       attribute/value pairs. The last attribute must be
       GDK_GL_NONE. See glXChooseVisual manpage for further
       explanation.
     */
    int attrlist[] = {
	GDK_GL_RGBA,
	GDK_GL_RED_SIZE,1,
	GDK_GL_GREEN_SIZE,1,
	GDK_GL_BLUE_SIZE,1,
	GDK_GL_DOUBLEBUFFER,
	GDK_GL_NONE
    };
    
    /* Check if OpenGL is supported. */
    if (gdk_gl_query() == FALSE) {
	g_warning("OpenGL not known to be supported on this system\n");
	return;
    }

#if 0
    /* This only works in 1.2.2 and greater, so I'll just leave it out. */
    /* vendor dependent version info string */
    info_str = gdk_gl_get_info();
    g_print(info_str);
    g_free(info_str);
#endif
    
    /* Create new OpenGL widget. */
    glarea = GTK_WIDGET(gtk_gl_area_new(attrlist));
    /* Events for widget must be set before X Window is created */
    gtk_widget_set_events(GTK_WIDGET(glarea),
			  GDK_EXPOSURE_MASK|
			  GDK_BUTTON_PRESS_MASK);
    
    /* Redraw image when exposed. */
    gtk_signal_connect(GTK_OBJECT(glarea), "expose_event",
	GTK_SIGNAL_FUNC(rendgtkgl_area_expose), node);
    /* When window is resized viewport needs to be resized also. */
    gtk_signal_connect(GTK_OBJECT(glarea), "configure_event",
	GTK_SIGNAL_FUNC(rendgtkgl_area_configure), node);
    /* Do initialization when widget has been realized. */
    gtk_signal_connect(GTK_OBJECT(glarea), "realize",
	GTK_SIGNAL_FUNC(rendgtkgl_area_realize), node);

  
    /* gtk_widget_set_usize(GTK_WIDGET(glarea), 100,100); */
  
    value = enode_attrib (node, "visible", NULL);

    if ((ebuf_empty (value)) || erend_value_is_true (value))
	gtk_widget_show (glarea);
   
    enode_set_kv (node, "top-widget", glarea);

    EDEBUG (("rendgtkgl", "GTK GL render called"));
}


static int
rendgtkgl_redraw_attr_set (ENode * node, EBuf * attr, EBuf * value)
{
    rendgtkgl_area_redraw (node);
    return TRUE;
}


static void
rendgtkgl_parent (ENode *parent_node, ENode *child_node)
{
    EDEBUG (("rendgtkgl", "rendgtkgl_parent called, looking for parent"));
}


static void
rendgtkgl_destroy (ENode * node)
{
    GtkWidget *top = enode_get_kv (node, "top-widget");

    if (top)
	gtk_widget_destroy (top);
}

void
rendgtkgl_line_render (ENode *node)
{
    EBuf *val;
    float x1, y1, x2, y2;
    float width;
    
    EDEBUG (("rendgtkgl", "rendering line"));
    
    val = enode_attrib (node, "x1", NULL);
    x1 = erend_get_float (val);

    val = enode_attrib (node, "y1", NULL);
    y1 = erend_get_float (val);

    val = enode_attrib (node, "x2", NULL);
    x2 = erend_get_float (val);

    val = enode_attrib (node, "y2", NULL);
    y2 = erend_get_float (val);


    val = enode_attrib (node, "width", NULL);
    if (ebuf_not_empty (val))
    {
	width = erend_get_float (val);
	EDEBUG (("rendgtkgl", "setting line width to %f", width));
	glLineWidth(width);
    }
    else
    {
	glLineWidth(1.0);
    }

    glBegin(GL_LINES);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glEnd();  
}

void
rendgtkgl_point_render (ENode *node)
{
    EBuf *val;
    float x, y;
    
    EDEBUG (("rendgtkgl", "rendering point"));
    
    val = enode_attrib (node, "x", NULL);
    x = erend_get_float (val);

    val = enode_attrib (node, "y", NULL);
    y = erend_get_float (val);

    glBegin(GL_POINTS);
    glVertex2f(x, y);
    glEnd();  
}

void
rendgtkgl_matrix_render (ENode *node)
{
    EBuf *val;
    float x, y, z;
    float angle;

    EDEBUG (("rendgtkgl", "rendering matrix"));
    
    glPushMatrix();
    
    val = enode_attrib (node, "translate-x", NULL);
    if (ebuf_not_empty (val))
    {
	x = erend_get_float (val);
	
    	val = enode_attrib (node, "translate-y", NULL);
	y = erend_get_float (val);
	
	val = enode_attrib (node, "translate-z", NULL);
	z = erend_get_float (val);

	glTranslatef (x, y, z);
    }

    val = enode_attrib (node, "rotate-angle", NULL);
    if (ebuf_not_empty (val))
    {
	angle = erend_get_float (val);
	
	val = enode_attrib (node, "rotate-x", NULL);
	x = erend_get_float (val);
	
    	val = enode_attrib (node, "rotate-y", NULL);
	y = erend_get_float (val);
	
	val = enode_attrib (node, "rotate-z", NULL);
	z = erend_get_float (val);

	EDEBUG (("rendgtkgl", "doing gl-matrix rotation, %f, %f, %f, %f",
		angle, x, y, z));
	glRotatef (angle, x, y, z);
    }

    val = enode_attrib (node, "scale-x", NULL);
    if (ebuf_not_empty (val))
    {
	x = erend_get_float (val);
	
    	val = enode_attrib (node, "scale-y", NULL);
	y = erend_get_float (val);
	
	val = enode_attrib (node, "scale-z", NULL);
	z = erend_get_float (val);

	glScalef (x, y, z);
    }
}

void
rendgtkgl_text_render (ENode *node)
{
   EBuf *val;
   float x, y;  
   char *p, *text = NULL;
   
   text = enode_attrib_str (node, "text", NULL);
   
   EDEBUG (("rendgtkgl", "rendering text"));
    
   val = enode_attrib (node, "x", NULL);
   x = erend_get_float (val);
   
   val = enode_attrib (node, "y", NULL);
   y = erend_get_float (val);

   glRasterPos2f(x, y);
      
   for ( p = text; *p; p++ ) {
      glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *p);
   }
   
}

static void
rendgtkgl_rectangle_render (ENode *node)
{
   /* change values when you split this up okay? */
   EBuf *val;
   float x1, y1, x2, y2;
   
   EDEBUG (("rendgtkgl", "rendering a rectangle" ));
   
   val = enode_attrib (node, "x1", NULL);
   x1 = erend_get_float (val);
   
   val = enode_attrib (node, "y1", NULL);
   y1 = erend_get_float (val);
   
   val = enode_attrib (node, "x2", NULL);
   x2 = erend_get_float (val);
     
   val = enode_attrib (node, "y2", NULL);
   y2 = erend_get_float (val);
   
   EDEBUG (("rendgtkgl", "rendering a rectangle %f, %f, %f, %f",
	    x1, y1, x2, y2));
   
   glRectf (x1, y1, x2, y2);
}


static void
rendgtkgl_cube_render (ENode *node)
{
   EBuf *val;
   float size;

   EDEBUG (("rendgtkgl", "rendering cube"));

   val  = enode_attrib (node, "size", NULL);
   size = erend_get_float (val);   
   glutSolidCube(size);
}

static void
rendgtkgl_sphere_render (ENode *node)
{
   EBuf *val;
   float radius, slices, stacks;
   
   val = enode_attrib (node, "radius", NULL);
   radius = erend_get_float (val);
   
   val = enode_attrib (node, "slices", NULL);
   slices = erend_get_float (val);
   
   val = enode_attrib (node, "stacks", NULL);
   stacks = erend_get_float (val);
   
   glutSolidSphere(radius, slices, stacks);
}

static gint
rendgtkgl_object_render (ENodeTreeWalk *walker)
{
    EBufConst *type;
    EBufConst *val;
    GdkColor color;
    ENode *node = walker->curnode;

    type = enode_type (node);

    EDEBUG (("rendgtkgl", "rendering %s", walker->curnode->element->str));

    /* Common stuff */
    val = enode_attrib (node, "color", NULL);
    if (ebuf_not_empty (val)) 
    {
	if (gdk_color_parse (val->str, &color) )
	{
	    glColor3us (color.red, color.green, color.blue);
        }
	else
	{
	    glColor3us (255, 255, 255);
	}
    }
    
    if (ebuf_equal_str (type, "gl-point")) 
    {
	rendgtkgl_point_render (node);
    } 
    else if (ebuf_equal_str (type, "gl-line")) 
    {
	rendgtkgl_line_render (node);
    } 
    else if (ebuf_equal_str (type, "gl-matrix")) 
    {
	rendgtkgl_matrix_render (node);
    }
    else if (ebuf_equal_str (type, "gl-rectangle"))
    {
        rendgtkgl_rectangle_render (node);
    }
    else if (ebuf_equal_str (type, "glut-cube"))
    { 
        rendgtkgl_cube_render (node);  
    } 
    else if (ebuf_equal_str (type, "glut-sphere"))
    {
        rendgtkgl_sphere_render (node);	
    }
    else if (ebuf_equal_str (type, "glut-text"))
    {
        rendgtkgl_text_render (node);
    }
      
    return (TRUE);
}

static gint
rendgtkgl_object_parent (ENodeTreeWalk *walker)
{
    EBuf *type;
    
    EDEBUG (("rendgtkgl", "Parenting %s to parent %s", walker->curnode->element->str,
	     walker->parentnode->element->str));
    type = enode_type (walker->curnode);
    
    if (ebuf_equal_str(type, "gl-matrix"))
    {
    	EDEBUG (("rendgtkgl", "Popping matrix"));
	glPopMatrix();
    }

    return (TRUE);
}
		
static void
rendgtkgl_area_redraw (ENode *glarea)
{
    GtkWidget *widget;

    widget = enode_get_kv (glarea, "top-widget");
    if (!widget)
	return;
    
    /* OpenGL functions can be called only if make_current returns true */
    if (gtk_gl_area_make_current(GTK_GL_AREA(widget)))
    {
    	ENodeTreeWalk *walker;
 	glClearColor(0,0,0,1);
    	glClear(GL_COLOR_BUFFER_BIT);
	
	walker = enode_treewalk_new (glarea);
	enode_treewalk (walker, rendgtkgl_object_render, rendgtkgl_object_parent);
	enode_treewalk_free (walker);

	/* Swap backbuffer to front */
	gtk_gl_area_swapbuffers(GTK_GL_AREA(widget));
    }

}


void
renderer_init (RendererFlags flags)
{
    Element *element;
    ElementAttr *e_attr;

    EDEBUG (("rendgtkgl", "registering rendgtkgl"));
    
    if (flags & RENDERER_REGISTER)
    {
      	EDEBUG (("rendgtkgl", "Registering glarea element"));
     	element = g_new0 (Element, 1);
	element->render_func = rendgtkgl_render;
	element->parent_func = rendgtkgl_parent;
	element->destroy_func = rendgtkgl_destroy;
	element->description = "Create a gtk GL area for doing GL based graphics.";
	element->tag = "glarea";
	element_register (element);
	
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "redraw";
	e_attr->description = "Cause a redraw of the GL Area.";
	e_attr->value_desc = "*";
	e_attr->set_attr_func = rendgtkgl_redraw_attr_set;
	element_register_attrib (element, e_attr);


	/* GtkGLArea Point Renderer */
	element = g_new0 (Element, 1);
	element->tag = "gl-point";
	element->description = "Create a single point on the gl area.";
	element_register (element);
	
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "x";
	e_attr->description = "Horizontal point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);
    
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "y";
	e_attr->description = "Vertical point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);

        /* GtkGLArea Rectangle Renderer, change atribs when you split this up please */
        element = g_new0 (Element, 1);
        element->tag = "gl-rectangle";
	element->description = "Create a rectangle in the gl area.";
        element_register (element);
       
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "x1";
        e_attr->description = "First horizontal point location.";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
        
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "y1";
        e_attr->description = "First vertical point location.";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);   

        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "x2";
        e_attr->description = "Second horizontal point location.";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
       
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "y2";
        e_attr->description = "Second vertical point location.";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr); 
       
        /* GtkGLArea Line Renderer */
	element = g_new0 (Element, 1);
	element->tag = "gl-line";
	element->description = "Create a line in the gl area.";
	element_register (element);
	
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "x1";
	e_attr->description = "First horizontal point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);
    
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "y1";
	e_attr->description = "First vertical point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);
   
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "x2";
	e_attr->description = "Second horizontal point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);
    
	e_attr = g_new0 (ElementAttr, 1);
	e_attr->attribute = "y2";
	e_attr->description = "Second vertical point location.";
	e_attr->value_desc = "0,*";
	element_register_attrib (element, e_attr);
    
        /* GtkGLArea Cube Renderer */
        element = g_new0 (Element, 1);
        element->tag = "glut-cube";
	element->description = "Draw a cube in the gl area.";
        element_register (element);

        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "size";
        e_attr->description = "Size of cube.";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
        element = g_new0 (Element, 1);

        /* GtkGLArea Sphere Renderer */
        element->tag = "glut-sphere";
	element->description = "Create a sphere in the gl area.";
        element_register (element);

        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "radius";
        e_attr->description = "radius of sphere";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);

        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "slices";
        e_attr->description = "number of subdivisions around X axis";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);

        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "stacks";
        e_attr->description = "number of subdivisions along Z axis";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
       
        /* font text render */
        element = g_new0 (Element, 1);
        element->tag = "glut-text";
	element->description = "Draw text in a gl area.";
        element_register (element);
               
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "text";
        e_attr->description = "text in widget";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
              
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "x";
        e_attr->description = "x co-ordinate";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);
       
        e_attr = g_new0 (ElementAttr, 1);
        e_attr->attribute = "y";
        e_attr->description = "y coordinate";
        e_attr->value_desc = "0,*";
        element_register_attrib (element, e_attr);       
	
    }
}




syntax highlighted by Code2HTML, v. 0.9.1