#include <gtk/gtk.h>
#include <gtk/gtktable.h>

#include <gdk/gdkkeysyms.h>

#include <gtkglext-1.0/gtk/gtkgl.h>

#include <GL/gl.h>
#include <GL/glu.h>

#include <glib.h>
#include <glib-object.h>

#include "zft.h"

#define NUM_TABLE_ROWS 2
#define NUM_ORTH_TABLE_COLUMNS 6 

#define PRINT_SPOT printf("file: %s, line: %d\n", __FILE__, __LINE__)

#define ZFT_VIEWER_TYPE           	(zft_viewer_get_type ())
#define ZFT_VIEWER(obj)           	(G_TYPE_CHECK_INSTANCE_CAST ((obj), ZFT_VIEWER_TYPE, ZftViewer))
#define ZFT_VIEWER_CLASS(klass)   	(G_TYPE_CHECK_CLASS_CAST ((klass), ZFT_VIEWER_TYPE, ZftViewerClass))
#define IS_ZFT_VIEWER(obj)        	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ZFT_VIEWER_TYPE))
#define IS_ZFT_VIEWER_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((klass), ZFT_VIEWER_TYPE))

typedef struct _ZftViewer       ZftViewer;
typedef struct _ZftViewerClass  ZftViewerClass;

typedef enum{ZOOM_IN, ZOOM_OUT, TRANSLATE_LEFT, TRANSLATE_RIGHT, TRANSLATE_UP, TRANSLATE_DOWN, NOT_SET} CameraMove;

struct _ZftViewer
{
    GtkTable table;
    
    GtkWidget* drawing_area;
    
    GtkWidget* zoom_in;
    GtkWidget* zoom_out;
    
    GtkWidget* translate_left;
    GtkWidget* translate_right;
    GtkWidget* translate_up;
    GtkWidget* translate_down;
    
    CLvertex camera_eye;
    CLvertex camera_center;
    CLvertex camera_up;

    GLfloat* left_right_eye;
    GLfloat* up_down_eye;
    
    GLfloat* left_right_center;
    GLfloat* up_down_center;
    
    GLfloat zoom_width; /* height determined by aspect ratio */
    GLdouble aspect_ratio;
    
    guint idle_ID;
    CameraMove camera_move;
};

struct _ZftViewerClass
{
    GtkTableClass parent_class;
    
    /* this has to do with the classes signels, not actually needed at the moment */
    void (* zft_viewer) (ZftViewer *zft_v);
};

/* use these instead of glut/clut */
static GLfloat    view_rotx = 0.0;
static GLfloat    view_roty = 0.0;
static GLfloat    view_rotz = 0.0;
static GLfloat    view_transx = 0.0f;
static GLfloat    view_transy = -200.0f;
static GLfloat    view_transz = 0.0f;

/* mouse motion variables */
static float      begin_x = 0.0;
static float      begin_y = 0.0;

/* temp debug */
static CLvertex     last_point;
static CLnormal     last_normal;
static unsigned int last_x;
static unsigned int last_y;

/* lists for objects */
GList* droid_list = 0;
GList* leech_list = 0;
GList* eel_list = 0;
GList* drone_list = 0;
GList* fluxring_list = 0;
GList* hexfluxfield_list = 0;
GList* batteryring_list = 0;
GList* tierring_list = 0;

/* prototypes */
static void
zft_viewer_class_init(ZftViewerClass *klass);

static void
zft_viewer_init(ZftViewer* zft_viewer);

/**
 * This function tells Glib about the zft_viewer class and returns an ID that
 * uniquely identifies the class.
 **/
GType
zft_viewer_get_type(void)
{
    static GType zft_viewer_type = 0;
    
    if(!zft_viewer_type)
    {
	static const GTypeInfo zft_viewer_info = 
	    {
		sizeof(ZftViewerClass),
		NULL, /* base init */
		NULL, /* base finalize */
		(GClassInitFunc)zft_viewer_class_init,
		NULL, /* class finalize */
		NULL, /* class data */
		sizeof(ZftViewer),
		0, /* num preallocs */
		(GInstanceInitFunc)zft_viewer_init,
	    };
	
	zft_viewer_type = g_type_register_static(GTK_TYPE_TABLE,
						 "ZftViewer",
						 &zft_viewer_info,
						 0);
    }
    
    return zft_viewer_type;
}

/************************* Rays for the collision system *************************/

typedef struct 
{
    unsigned int ref_count;

    bool is_valid;

    CLvertex destination;
    
} ZftRemoveRay;

static ZfSmartPointer remove_ray_smart_pointer; /* interface for laser render*/
static ZfDynamicCollider remove_ray_dynamic_collider; /* interface for laser */

static bool
is_valid(ZftRemoveRay* remove_ray)
{
    return remove_ray->is_valid;
}

static void
reference(ZftRemoveRay* remove_ray)
{
    remove_ray->ref_count++;
}

static void
release(ZftRemoveRay* remove_ray)
{
    remove_ray->ref_count--;

    if (remove_ray->ref_count == 0)
	g_free(remove_ray);
}

static void
query_position(ZftRemoveRay* remove_ray,
	       CLvertex* position)
{
    clCopyVertex(position, &remove_ray->destination);
}

static void
collision_response(ZftRemoveRay* remove_ray,
		   const void* collider_data,
		   ZfType collider_type,
		   const CLvertex* collision_position,
		   const CLnormal* collision_force)
{
}

static void
zft_remove_ray_new(const CLvertex* destination)
{
    ZftRemoveRay* remove_ray;
    
    remove_ray = g_new(ZftRemoveRay, 1);
    
    clCopyVertex(&remove_ray->destination, destination);
    
    remove_ray->ref_count = 0;
    
    remove_ray->is_valid = true;
    
    zf_collision_system_add_dynamic(remove_ray,
				    &remove_ray_smart_pointer,
				    &remove_ray_dynamic_collider,
				    ZFT_REMOVE_RAY,
				    1.0f, /* mass */
				    0.4f); /* radius */
    
    /* step collision system */
    zf_collision_system_step();
    
    /* remove ray */
    remove_ray->is_valid = false;
}

/*******************************************************************************/


/*!
  \brief Dialog shown when a droid is placed in the level, the dialog asks
  the user for the type and start/end trigger times for the droid.
*/
void
show_droid_spawn_dialog(void)
{
    /* declare trigger time input widgets */
    GtkWidget* dialog;

    GtkWidget* type_combo_box;
    GtkWidget* type_combo_box_label;

    GtkWidget* start_time;
    GtkWidget* start_time_label;

    GtkWidget* nearest_flux;
    gchar      nearest_flux_data[80];

    GtkWidget* end_time;
    GtkWidget* end_time_label;

    float nearest_t;

    /* create widgets for the input dialog box */
    dialog = gtk_dialog_new_with_buttons("Droid Spawn",
                                         NULL,
                                         GTK_DIALOG_MODAL,
                                         GTK_STOCK_OK,
                                         GTK_RESPONSE_OK,
                                         GTK_STOCK_CANCEL,
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    /* create widgets */
    type_combo_box_label = gtk_label_new("Droid type:");
    type_combo_box = gtk_combo_box_new_text();
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Red");
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Green");
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Blue");
    gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo_box), 0);

    start_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    start_time_label = gtk_label_new("Start trigger time:");

    end_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    end_time_label = gtk_label_new("End trigger time:");

    /* calculate nearest flux time */
    nearest_t = zf_flux_nearest_repeater_t(&last_point);
    sprintf(nearest_flux_data, "(Incorrect) Nearest flux time: %f", nearest_t);
    nearest_flux = gtk_label_new(nearest_flux_data);

    /* set logical values for start and end time */
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(start_time), nearest_t - 1.0f);
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(end_time), nearest_t + 1.0f);

    /* add widgets to dialog */
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    nearest_flux);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    end_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    end_time);

    /* show widgets */
    gtk_widget_show(type_combo_box_label);
    gtk_widget_show(type_combo_box);
    gtk_widget_show(start_time_label);
    gtk_widget_show(start_time);
    gtk_widget_show(nearest_flux);
    gtk_widget_show(end_time_label);
    gtk_widget_show(end_time);

    /* run dialog and accept input on ok response */
    switch(gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        case GTK_RESPONSE_OK:
            {
		int type;
		float start_trig;
		float end_trig;

		type = gtk_combo_box_get_active(GTK_COMBO_BOX(type_combo_box));
		start_trig = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_time));
		end_trig   = gtk_spin_button_get_value(GTK_SPIN_BUTTON(end_time));
		
		droid_list = g_list_append(droid_list, zf_droid_new(&last_point, type, start_trig, end_trig));
		
		break;
	    }

        case GTK_RESPONSE_CANCEL:
	    break;
    }

    /* destory the widgets */
    gtk_widget_destroy(type_combo_box_label);
    gtk_widget_destroy(type_combo_box);
    gtk_widget_destroy(start_time_label);
    gtk_widget_destroy(start_time);
    gtk_widget_destroy(nearest_flux);
    gtk_widget_destroy(end_time_label);
    gtk_widget_destroy(end_time);
    gtk_widget_destroy(dialog);    
}

/*!
  \brief Dialog shown when a flux ring is placed in the level.
*/
void
show_hex_flux_field_spawn_dialog(void)
{
    /* declare trigger time input widgets */
    GtkWidget* dialog;

    GtkWidget* start_time;
    GtkWidget* start_time_label;

    GtkWidget* nearest_flux;
    gchar      nearest_flux_data[80];

    float nearest_t;

    /* create widgets for the input dialog box */
    dialog = gtk_dialog_new_with_buttons("Flux Ring Spawn",
                                         NULL,
                                         GTK_DIALOG_MODAL,
                                         GTK_STOCK_OK,
                                         GTK_RESPONSE_OK,
                                         GTK_STOCK_CANCEL,
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    /* start time spin button */
    start_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    start_time_label = gtk_label_new("Start trigger time:");

    /* calculate nearest flux time */
    nearest_t = zf_flux_nearest_repeater_t(&last_point);
    sprintf(nearest_flux_data, "(Incorrect) Nearest flux time: %f", nearest_t);
    nearest_flux = gtk_label_new(nearest_flux_data);

    /* set logical values for start time */
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(start_time), nearest_t);

    /* add widgets to dialog */
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    nearest_flux);

    /* show widgets */
    gtk_widget_show(start_time_label);
    gtk_widget_show(start_time);
    gtk_widget_show(nearest_flux);

    /* run dialog and accept input on ok response */
    switch(gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        case GTK_RESPONSE_OK:
            {
		float start_trig;
		
		start_trig = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_time));
		
		printf("%s() : adding new hex flux field (flux_t = %f)\n", __FUNCTION__, start_trig);
		hexfluxfield_list = g_list_append(hexfluxfield_list, zf_hexfluxfield_new(start_trig));
		
		break;
	    }

        case GTK_RESPONSE_CANCEL:
	    break;
    }

    /* destory the widgets */
    gtk_widget_destroy(start_time_label);
    gtk_widget_destroy(start_time);
    gtk_widget_destroy(nearest_flux);
    gtk_widget_destroy(dialog);    
}



/*!
  \brief Dialog shown when a flux ring is placed in the level.
*/
void
show_flux_ring_spawn_dialog(void)
{
    /* declare trigger time input widgets */
    GtkWidget* dialog;

    GtkWidget* type_combo_box;
    GtkWidget* type_combo_box_label;

    GtkWidget* start_time;
    GtkWidget* start_time_label;

    GtkWidget* nearest_flux;
    gchar      nearest_flux_data[80];

    GtkWidget* rotation;
    GtkWidget* rotation_label;

    float nearest_t;

    /* create widgets for the input dialog box */
    dialog = gtk_dialog_new_with_buttons("Flux Ring Spawn",
                                         NULL,
                                         GTK_DIALOG_MODAL,
                                         GTK_STOCK_OK,
                                         GTK_RESPONSE_OK,
                                         GTK_STOCK_CANCEL,
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    /* create widgets */
    type_combo_box_label = gtk_label_new("Flux Ring type:");
    type_combo_box = gtk_combo_box_new_text();
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Half Ring");
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Three Quarter Ring");
    gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo_box), 0);

    /* start time spin button */
    start_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    start_time_label = gtk_label_new("Start trigger time:");

    /* calculate nearest flux time */
    nearest_t = zf_flux_nearest_repeater_t(&last_point);
    sprintf(nearest_flux_data, "(Incorrect) Nearest flux time: %f", nearest_t);
    nearest_flux = gtk_label_new(nearest_flux_data);

    /* set logical values for start time */
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(start_time), nearest_t);

    /* rotation spin button */
    rotation = gtk_spin_button_new_with_range(0.0, 360.0, 45.0);
    rotation_label = gtk_label_new("Rotation (deg):");

    /* add widgets to dialog */
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    nearest_flux);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    rotation_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    rotation);

    /* show widgets */
    gtk_widget_show(type_combo_box_label);
    gtk_widget_show(type_combo_box);
    gtk_widget_show(start_time_label);
    gtk_widget_show(start_time);
    gtk_widget_show(nearest_flux);
    gtk_widget_show(rotation);
    gtk_widget_show(rotation_label);

    /* run dialog and accept input on ok response */
    switch(gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        case GTK_RESPONSE_OK:
            {
		int type;
		float start_trig;
		float rotation_angle;

		type = gtk_combo_box_get_active(GTK_COMBO_BOX(type_combo_box));
		start_trig = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_time));
		rotation_angle = gtk_spin_button_get_value(GTK_SPIN_BUTTON(rotation));

		printf("%s() : adding new flux_ring (start_t = %f, roll = %f)\n", __FUNCTION__, start_trig, rotation_angle);

		fluxring_list = g_list_append(fluxring_list, zf_flux_ring_new(!type, start_trig, rotation_angle));
		
		break;
	    }

        case GTK_RESPONSE_CANCEL:
	    break;
    }

    /* destory the widgets */
    gtk_widget_destroy(type_combo_box_label);
    gtk_widget_destroy(type_combo_box);
    gtk_widget_destroy(start_time_label);
    gtk_widget_destroy(start_time);
    gtk_widget_destroy(nearest_flux);
    gtk_widget_destroy(rotation);
    gtk_widget_destroy(rotation_label);
    gtk_widget_destroy(dialog);    
}

/*!
  \brief Dialog shown when a tier ring is placed in the level.
*/
void
show_tier_ring_spawn_dialog(void)
{
    /* declare trigger time input widgets */
    GtkWidget* dialog;

    GtkWidget* type_combo_box;
    GtkWidget* type_combo_box_label;

    GtkWidget* start_time;
    GtkWidget* start_time_label;

    GtkWidget* nearest_flux;
    gchar      nearest_flux_data[80];

    float nearest_t;

    /* create widgets for the input dialog box */
    dialog = gtk_dialog_new_with_buttons("Tier Ring Spawn",
                                         NULL,
                                         GTK_DIALOG_MODAL,
                                         GTK_STOCK_OK,
                                         GTK_RESPONSE_OK,
                                         GTK_STOCK_CANCEL,
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    /* create widgets */
    type_combo_box_label = gtk_label_new("Tier Ring type:");
    type_combo_box = gtk_combo_box_new_text();
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Red Tier");
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Green Tier");
    gtk_combo_box_append_text(GTK_COMBO_BOX(type_combo_box), "Blue Tier");
    gtk_combo_box_set_active(GTK_COMBO_BOX(type_combo_box), 0);

    start_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    start_time_label = gtk_label_new("Start trigger time:");

    /* calculate nearest flux time */
    nearest_t = zf_flux_nearest_repeater_t(&last_point);
    sprintf(nearest_flux_data, "(Incorrect) Nearest flux time: %f", nearest_t);
    nearest_flux = gtk_label_new(nearest_flux_data);

    /* set logical values for start and end time */
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(start_time), nearest_t);

    /* add widgets to dialog */
    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    type_combo_box);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    nearest_flux);

    /* show widgets */
    gtk_widget_show(type_combo_box_label);
    gtk_widget_show(type_combo_box);
    gtk_widget_show(start_time_label);
    gtk_widget_show(start_time);
    gtk_widget_show(nearest_flux);

    /* run dialog and accept input on ok response */
    switch(gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        case GTK_RESPONSE_OK:
            {
		int type;
		float start_trig;

		type = gtk_combo_box_get_active(GTK_COMBO_BOX(type_combo_box));
		start_trig = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_time));

		printf("%s() : adding new tier_ring (start_t = %f)\n", __FUNCTION__, start_trig);
		tierring_list = g_list_append(tierring_list, zf_tier_ring_new(start_trig, type));
		
		break;
	    }

        case GTK_RESPONSE_CANCEL:
	    break;
    }

    /* destory the widgets */
    gtk_widget_destroy(type_combo_box_label);
    gtk_widget_destroy(type_combo_box);
    gtk_widget_destroy(start_time_label);
    gtk_widget_destroy(start_time);
    gtk_widget_destroy(nearest_flux);
    gtk_widget_destroy(dialog);    
}

/*!
  \brief Dialog shown when a hud message is placed in the level.
*/
void
show_hud_message_spawn_dialog(void)
{
    /* declare trigger time input widgets */
    GtkWidget* dialog;

    GtkWidget* start_time;
    GtkWidget* start_time_label;

    GtkWidget* nearest_flux;
    gchar      nearest_flux_data[80];

    GtkWidget* message_label;
    GtkWidget* message_entry;

    float nearest_t;

    /* create widgets for the input dialog box */
    dialog = gtk_dialog_new_with_buttons("HUD Message Spawn",
                                         NULL,
                                         GTK_DIALOG_MODAL,
                                         GTK_STOCK_OK,
                                         GTK_RESPONSE_OK,
                                         GTK_STOCK_CANCEL,
                                         GTK_RESPONSE_CANCEL,
                                         NULL);

    /* create widgets */
    start_time = gtk_spin_button_new_with_range(0.0, zf_flux_query_max_t(), 0.1);
    start_time_label = gtk_label_new("Start trigger time:");

    message_entry = gtk_entry_new();
    gtk_entry_set_text(GTK_ENTRY(message_entry), "The wang fly hovers.");
    message_label = gtk_label_new("Message:");

    /* calculate nearest flux time */
    nearest_t = zf_flux_nearest_repeater_t(&last_point);
    sprintf(nearest_flux_data, "(Incorrect) Nearest flux time: %f", nearest_t);
    nearest_flux = gtk_label_new(nearest_flux_data);

    /* set logical values for start and end time */
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(start_time), nearest_t);

    /* add widgets to dialog */

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    nearest_flux);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    start_time);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    message_label);

    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
                                    message_entry);

    /* show widgets */
    gtk_widget_show(start_time_label);
    gtk_widget_show(start_time);
    gtk_widget_show(nearest_flux);
    gtk_widget_show(message_label);
    gtk_widget_show(message_entry);

    /* run dialog and accept input on ok response */
    switch(gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        case GTK_RESPONSE_OK:
            {
		float start_trig;
		
		start_trig = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_time));
		
		printf("%s() : adding new hud message (start_t = %f, msg = %s)\n",
		       __FUNCTION__,
		       start_trig,
		       gtk_entry_get_text(GTK_ENTRY(message_entry)));

		//zf_hud_message_new(start_trig, gtk_entry_get_text(GTK_ENTRY(message_entry)));
		
		break;
	    }

        case GTK_RESPONSE_CANCEL:
	    break;
    }

    /* destory the widgets */
    gtk_widget_destroy(start_time_label);
    gtk_widget_destroy(start_time);
    gtk_widget_destroy(nearest_flux);
    gtk_widget_destroy(message_label);
    gtk_widget_destroy(message_entry);
    gtk_widget_destroy(dialog);    
}

#if 0
static void
load_ortho(ZftViewer* zft_viewer)
{
    double height_offset;

    printf("%s(): START\n", __FUNCTION__);

    height_offset = zft_viewer->zoom_width * zft_viewer->aspect_ratio;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity ();
    glOrtho(-zft_viewer->zoom_width,
	    zft_viewer->zoom_width,
	    -height_offset, height_offset,
	    1, 2000);
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();

    printf("%s(): END\n", __FUNCTION__);
}
#endif

#if 0
static void
zft_viewer_print(ZftViewer* zft_viewer)
{
    printf("%s(): START\n", __FUNCTION__);

    printf("camera_eye = ");
    clPrintVertex(&zft_viewer->camera_eye);
    printf("camera_center = ");
    clPrintVertex(&zft_viewer->camera_center);
    printf("camera_up = ");
    clPrintVertex(&zft_viewer->camera_up);

    printf("left_right_eye = %f\n", *zft_viewer->left_right_eye);
    printf("up_down_eye = %f\n", *zft_viewer->up_down_eye);

    printf("left_right_center = %f\n", *zft_viewer->left_right_center);
    printf("up_down_center = %f\n", *zft_viewer->up_down_center);

    printf("zoom_width = %f\n", zft_viewer->zoom_width);
    printf("aspect_ratio = %f\n", zft_viewer->aspect_ratio);

    printf("idel_ID = %u\n", zft_viewer->idle_ID);

    printf("%s(): END\n", __FUNCTION__);
}
#endif

/**
 * This function, once set, will be called when the machine has spare time. It
 * is responsible for moving the camera in a direction specified by the the
 * ZftViewer struct (which needs to be casted from the data parameter). The
 * function then invalidates the screen causing a redraw.
 **/
gboolean
idle(gpointer data)
{	
    ZftViewer* zft_viewer = (ZftViewer*)data;
    
    switch(zft_viewer->camera_move)
    {
    case ZOOM_IN:
	//printf("%s(): ZOOM_IN\n", __FUNCTION__);
	//zft_viewer->zoom_width /= 1.01f;
	//load_ortho(zft_viewer);

	view_transy += 4.0f;

	break;
	
    case ZOOM_OUT:
	//printf("%s(): ZOOM_OUT\n", __FUNCTION__);
	//zft_viewer->zoom_width *= 1.01f;
	//load_ortho(zft_viewer);

	view_transy -= 4.0f;
	break;
	
    case TRANSLATE_LEFT:
	//printf("%s(): TRANSLATE_LEFT\n", __FUNCTION__);
	//*zft_viewer->left_right_eye -= 0.01 * zft_viewer->zoom_width;
	//*zft_viewer->left_right_center -= 0.01 * zft_viewer->zoom_width;

	view_transx += 4.0f;
	break;
	
    case TRANSLATE_RIGHT:
	//printf("%s(): TRANSLATE_RIGHT\n", __FUNCTION__);
	//*zft_viewer->left_right_eye += 0.01 * zft_viewer->zoom_width;
	//*zft_viewer->left_right_center += 0.01 * zft_viewer->zoom_width;

	view_transx -= 4.0f;
	break;
	
    case TRANSLATE_UP:
	//printf("%s(): TRANSLATE_UP\n", __FUNCTION__);
	//*zft_viewer->up_down_eye -= 0.01 * zft_viewer->zoom_width;
	//*zft_viewer->up_down_center -= 0.01 * zft_viewer->zoom_width;

	view_transz += 4.0f;
	break;
	
    case TRANSLATE_DOWN:
	//printf("%s(): TRANSLATE_DOWN\n", __FUNCTION__);
	//*zft_viewer->up_down_eye += 0.01 * zft_viewer->zoom_width;
	//*zft_viewer->up_down_center += 0.01 * zft_viewer->zoom_width;

	view_transz -= 4.0f;
	break;
	
    case NOT_SET:
	break;
    }	

#if 0
    /* print updated state of zft_viewer */
    zft_viewer_print(zft_viewer);
    printf("view_zoom = %f\n", view_zoom);
#endif

    /* Invalidate the whole window. */
    gdk_window_invalidate_rect(zft_viewer->drawing_area->window,
			       &zft_viewer->drawing_area->allocation,
			       TRUE);
    
    return TRUE;
}

static gboolean 
move_camera(GtkWidget *widget,
           	gpointer   data )
{
    ZftViewer* zft_viewer = (ZftViewer*)data;
    
    //printf("%s(): START\n", __FUNCTION__);

    if(widget == zft_viewer->zoom_in)
    {
	//printf("%s(): ZOOM_IN\n", __FUNCTION__);
	zft_viewer->camera_move = ZOOM_IN;
    }
    else if(widget == zft_viewer->zoom_out)
    {
	//printf("%s(): ZOOM_OUT\n", __FUNCTION__);
	zft_viewer->camera_move = ZOOM_OUT;
    }
    else if(widget == zft_viewer->translate_left)
	zft_viewer->camera_move = TRANSLATE_LEFT;
    else if(widget == zft_viewer->translate_right)
	zft_viewer->camera_move = TRANSLATE_RIGHT;
    else if(widget == zft_viewer->translate_up)
	zft_viewer->camera_move = TRANSLATE_UP;
    else if(widget == zft_viewer->translate_down)
	zft_viewer->camera_move = TRANSLATE_DOWN;
    else
	zft_viewer->camera_move = NOT_SET;
    
    zft_viewer->idle_ID = g_idle_add_full(GDK_PRIORITY_REDRAW, (GSourceFunc)idle, data, NULL); 

    //printf("%s(): END\n", __FUNCTION__);

    return TRUE;
}

static gboolean 
stop_moving_camera(GtkWidget *widget,
             	   gpointer   data )
{
    ZftViewer* zft_viewer = (ZftViewer*)data;
    
    if(zft_viewer->idle_ID)
    {
	g_source_remove(zft_viewer->idle_ID);
	zft_viewer->idle_ID = 0;
	zft_viewer->camera_move = NOT_SET;
    }
    
    return TRUE;
}

/**
 * Set the direction that the buttons will make the camera move. In this case,
 * we asssume that the camera is facing parallel to either the x, y or z axis.
 **/
static void
set_button_directions(ZftViewer* zft_viewer)
{
    printf("%s(): START\n", __FUNCTION__);

    /* haX0r - assume camera can only face in the direction of x, y, z axis - tone */
    if(zft_viewer->camera_up.x)
    {
	zft_viewer->up_down_eye = &zft_viewer->camera_eye.x;
	zft_viewer->up_down_center = &zft_viewer->camera_center.x;

	if(zft_viewer->camera_center.y - zft_viewer->camera_eye.y)
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.z;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.z;
        }
	else
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.y;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.y;
        }
    }
    else if(zft_viewer->camera_up.y)
    {
	zft_viewer->up_down_eye = &zft_viewer->camera_eye.y;
	zft_viewer->up_down_center = &zft_viewer->camera_center.y;
	
	if(zft_viewer->camera_center.x - zft_viewer->camera_eye.x)
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.z;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.z;
        }
	else
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.x;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.x;
        }
    }
    else if(zft_viewer->camera_up.z)
    {
	zft_viewer->up_down_eye = &zft_viewer->camera_eye.z;
	zft_viewer->up_down_center = &zft_viewer->camera_center.z;

	if(zft_viewer->camera_center.x - zft_viewer->camera_eye.x)
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.y;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.y;
        }
	else
        {
	    zft_viewer->left_right_eye = &zft_viewer->camera_eye.x;
	    zft_viewer->left_right_center = &zft_viewer->camera_center.x;
        }
    }
    printf("%s(): END\n", __FUNCTION__);
}	

/* new window size or exposure */
static gboolean
reshape (GtkWidget      	*widget,
	 GdkEventConfigure 	*event,
	 gpointer           data)
{
    GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
    GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
    
    //ZftViewer* zft_viewer = (ZftViewer *)data;
    
    /*** OpenGL BEGIN ***/
    if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
    	return FALSE;
    
    glViewport(0, 0, widget->allocation.width, widget->allocation.height);
    
    /* load a projection matrix that matches the window aspect ratio */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (double)widget->allocation.width/(double)widget->allocation.height, 0.1, 10000.0);

    /* reset the modelview matrix */
    glMatrixMode(GL_MODELVIEW);

    //zft_viewer->aspect_ratio = ((GLdouble)widget->allocation.height / (GLdouble)widget->allocation.width);
    //load_ortho(zft_viewer);
    


    gdk_gl_drawable_gl_end (gldrawable);
    /*** OpenGL END ***/
    
    return TRUE;
}

static void
draw_cursor(GtkWidget* widget)
{
    switch(zft_get_edit_state())
    {
    case(ADD_FLUX_REPEATER):
	{
	    double max_t;
	    CLvertex max_t_position;

	    /* find position of second last flux point */
	    max_t = zf_flux_query_max_t();
	    zf_flux_query_position(&max_t_position, max_t - 1.0f);
	    
	    /* draw line from last position to potential new position to approximate potential flux */
	    glEnable(GL_DEPTH_TEST);
	    glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
	    glBegin(GL_LINES);
	    glVertex3fv((GLfloat*) &last_point);
	    glVertex3fv((GLfloat*) &max_t_position);
	    glEnd();

	    /* draw a line representing last_normal */
	    glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
	    glBegin(GL_LINES);
	    glVertex3fv((GLfloat*) &last_point);
	    glVertex3f(last_point.x + last_normal.i * 32.0f,
		       last_point.y + last_normal.j * 32.0f,
		       last_point.z + last_normal.k * 32.0f);
	    glEnd();

	    break;
	}

    default:
	{
	    glDisable(GL_LIGHTING);
	    glPointSize(16.0f);
	    glBegin(GL_POINTS);
	    glColor3f(1.0f, 1.0f, 0.0f);
	    glVertex3f(last_point.x, last_point.y, last_point.z);
	    glEnd();
	    
	    glBegin(GL_LINES);
	    /* red x */
	    glColor3f(1.0f, 0.0f, 0.0f);
	    glVertex3f(last_point.x + 0.0f, last_point.y + 0.0f, last_point.z + 0.0f);
	    glVertex3f(last_point.x + 100.0f, last_point.y + 0.0f, last_point.z + 0.0f);
	    /* green y */
	    glColor3f(0.0f, 1.0f, 0.0f);
	    glVertex3f(last_point.x + 0.0f, last_point.y + 0.0f, last_point.z + 0.0f);
	    glVertex3f(last_point.x + 0.0f, last_point.y + 100.0f, last_point.z + 0.0f);
	    /* blue z */
	    glColor3f(0.0f, 0.0f, 1.0f);
	    glVertex3f(last_point.x + 0.0f, last_point.y + 0.0f, last_point.z + 0.0f);
	    glVertex3f(last_point.x + 0.0f, last_point.y + 0.0f, last_point.z + 100.0f);
	    glEnd();
	    
	    break;
	}
    }
}

static gboolean
draw(GtkWidget      *widget,
     GdkEventExpose *event,
     gpointer        data)
{
    GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
    GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);
    
    /*** OpenGL BEGIN ***/
    if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
    {
    	return FALSE;
    }
    
    glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glPushMatrix ();

#if 1
    /* rotate and translate to current view orientation */
    glLoadIdentity();
    glRotatef (CL_RAD2DEG(view_rotx), 1.0, 0.0, 0.0);
    glRotatef (CL_RAD2DEG(view_roty), 0.0, 1.0, 0.0);
    glRotatef (CL_RAD2DEG(view_rotz), 0.0, 0.0, 1.0);
    glTranslatef(view_transx, view_transy, view_transz);

#else
    /* use old gluLookAt() method */
    {
    	ZftViewer* zft_viewer = (ZftViewer *)data;
	
	gluLookAt(zft_viewer->camera_eye.x, zft_viewer->camera_eye.y, zft_viewer->camera_eye.z,
		  zft_viewer->camera_center.x,
		  zft_viewer->camera_center.y,
		  zft_viewer->camera_center.z,
		  zft_viewer->camera_up.x, zft_viewer->camera_up.y, zft_viewer->camera_up.z);
    }
#endif    


    /* render the level */
#if 1
    zf_render_system_step();
#endif
    
#if 1
    /* render axes at the origin */
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glBegin(GL_LINES);
    /* red x */
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(1000.0f, 0.0f, 0.0f);
    /* green y */
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 1000.0f, 0.0f);
    /* blue z */
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 1000.0f);
    glEnd();
#endif
 
    /* draw the last click point (debug) */   
#if 1
    draw_cursor(widget);
#endif

#if 0
    if(selected_repeater)
    {
	CLvertex vertex;
	zf_repeater_query_position(selected_repeater, &vertex);
	glPushMatrix();
	glTranslatef(vertex.x, vertex.y, vertex.z);
	glutSolidTeapot(3);
	glPopMatrix();
    }
#endif
    
    glPopMatrix ();
    
    /* if double buffered, swap buffers, otherwise, flush single buffer */
    if (gdk_gl_drawable_is_double_buffered (gldrawable))
    {
	gdk_gl_drawable_swap_buffers (gldrawable);
    }
    else
    {
	glFlush ();
    }
    
    /*** OpenGL END ***/
    gdk_gl_drawable_gl_end (gldrawable);
    
    return TRUE;
}

/*!
  \brief Works out opengl coordinates based on the mouse position
  
  \param x X coord of mouse (2D)
  
  \param y Y coord of mouse (2D)
 */
static void
update_last_point(int x, int y)
{
    GLdouble projection[16];   /* opengl projection matrix */
    GLdouble model_view[16];   /* opengl modelview matrix */
    GLint    view_port[4];     /* opengl viewport */
    CLvertex window_point;     /* location of button press in window coordinates */
    GLdouble point[3];         /* temp return value from gluUnProject() */
    
    int      unproject_result; /* true, if the call to gluUnProject was successful */
    
    /* get opengl info (matrices and viewport) */
    glPushMatrix();
    glLoadIdentity();
    glRotatef(CL_RAD2DEG(view_rotx), 1.0, 0.0, 0.0);
    glRotatef(CL_RAD2DEG(view_roty), 0.0, 1.0, 0.0);
    glRotatef(CL_RAD2DEG(view_rotz), 0.0, 0.0, 1.0);
    glTranslatef(view_transx, view_transy, view_transz);

    glGetDoublev(GL_PROJECTION_MATRIX, projection);
    glGetDoublev(GL_MODELVIEW_MATRIX, model_view);
    glGetIntegerv(GL_VIEWPORT, view_port);
    glPopMatrix();
    
#if 0
    /* print projection matrix */
    printf("projection matrix\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n",
	   projection[0], projection[1], projection[2], projection[3],
	   projection[4], projection[5], projection[6], projection[7],
	   projection[8], projection[9], projection[10], projection[11],
	   projection[12], projection[13], projection[14], projection[15]);
#endif
    
#if 0
    /* print modelview matrix */
    printf("model_view matrix\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n",
	   model_view[0], model_view[1], model_view[2], model_view[3],
	   model_view[4], model_view[5], model_view[6], model_view[7],
	   model_view[8], model_view[9], model_view[10], model_view[11],
	   model_view[12], model_view[13], model_view[14], model_view[15]);
#endif
    
#if 0
    /* print viewport */
    printf("view_port = %d %d %d %d\n",
	   view_port[0], view_port[1], view_port[2], view_port[3]);
#endif	

    /* update global record of 2d cursor position */
    last_x = x;
    last_y = y;
    
    /* get window coordinates of button press (origin is bottom left of window) */
    window_point.x = (float)x;
    window_point.y = (float)view_port[3] - (float)y;
    
    /* get window z-coordinate from depth buffer */
    glReadPixels((int)window_point.x,
		 (int)window_point.y,
		 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
		 &window_point.z);
#if 0
    /* print window coordinates */
    printf("%s() : window_point is (%f, %f, %f)\n",
	   __FUNCTION__,
	   window_point.x,
	   window_point.y,
	   window_point.z);
#endif
    
    unproject_result = gluUnProject(window_point.x,
				    window_point.y,
				    window_point.z,
				    model_view,
				    projection,
				    view_port,
				    &point[0],
				    &point[1],
				    &point[2]);
    //printf("%s() : UnProject = %d\n", __FUNCTION__, unproject_result);
    
    cluSetVertex(&last_point, point[0], point[1], point[2]);
    
#if 0
    /* print gl coordinate (object space) */
    printf("%s() : last_point is (%f, %f, %f)\n",
	   __FUNCTION__,
	   last_point.x,
	   last_point.y,
	   last_point.z);
#endif

    if (zf_heightmap_loaded())
    {
	/* update last_normal */
	//printf("update last normal\n");
	//clPrintVertex(&last_point);
	//clPrintNormal(&last_normal);
	zf_heightmap_query_normal(&last_normal, last_point.x, last_point.z);
	//clPrintNormal(&last_normal);
	//printf("done\n");
    }
    
    /* update status bar */
    /*! \todo */
}

/**
* This callback function is called when the mouse button is released within
* the drawing area.
*
* \todo Get rid of all the magic numbers in this function, define them
* somewhere.
*
* \todo Drone type! Needs a dialog and spawn dialog function.
*
**/
static gboolean
button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    //printf("%s() : START(%f, %f)\n", __FUNCTION__, event->x, event->y);

    /* button 1 (place objects) */
    if (event->button == 1)
    {
	/* get the opengl point the mouse points to */
	update_last_point(event->x, event->y);
	
	/* respond according to state of flux editing */
	switch(zft_get_edit_state())
	{
	case(ADD_FLUX_REPEATER):
	    {
		CLnormal heightmap_normal;

		/* copy last_normal to heightmap_normal */
		clCopyNormal(&heightmap_normal, &last_normal);
		
		/* scale normal by 10.0f */
		cluNormalScale(&heightmap_normal, 10.0f);

		/* add normal to last_point */
		cluVertexAdd(&last_point, &heightmap_normal);

		/* add repeater at modified last point */
		zf_flux_add_repeater(&last_point);
		break;
	    }
	    
	case(ADD_HEX_FLUX_FIELD):
	    show_hex_flux_field_spawn_dialog();
	    break;

	case(ADD_FLUX_RING):
	    show_flux_ring_spawn_dialog();
	    break;
	    
	case(ADD_BATTERY_RING):
	    {
		/* calculate nearest flux time */
		float nearest_t;
		
		nearest_t = zf_flux_nearest_repeater_t(&last_point);
		
		printf("%s() : add battery ring (%f)\n", __FUNCTION__, nearest_t);

		batteryring_list = g_list_append(batteryring_list,
						 zf_battery_ring_new(nearest_t));
		
		break;
	    }

	case(ADD_TIER_RING):
	    show_tier_ring_spawn_dialog();
	    break;

	case(ADD_HIVE):
	    {
		//last_point.y = zf_heightmap_query_height(last_point.x, last_point.z) + 5.0f;
	    
		zf_hive_new(&last_point);
		break;
	    }

	case(ADD_LANDSCAPE_OBJECT):
	    {
		//printf("%s() : adding landscape object\n", __FUNCTION__);
		
		switch(zft_get_LO_state())
		{
		case LO_TREE:
		    //printf("state = tree\n");
		    zf_landscape_object_new(&last_point, 0);
		    break;
		    
		case LO_DOME:
		    //printf("state = dome\n");
		    zf_landscape_object_new(&last_point, 1);
		    break;

		case LO_BUILDING:
		    //printf("state = building\n");
		    zf_landscape_object_new(&last_point, 2);
		    break;

		default:
		    printf("%s() : unknown landscape object type\n", __FUNCTION__);
		    exit(1);
		}

		//printf("%s() : done adding landscape object\n", __FUNCTION__);
		break;
	    }
	    
	case(ADD_TURRET):
	    {	    
		zf_turret_new(&last_point);
		break;
	    }

	case(ADD_DROID):
	    {
		show_droid_spawn_dialog();
		break;
	    }
	    
	case(ADD_EEL):
	    {
		/* calculate nearest flux time */
		float nearest_t;
		
		nearest_t = zf_flux_nearest_repeater_t(&last_point);
		
		eel_list = g_list_append(eel_list,
					 zf_eel_segment_new(CL_MAX(nearest_t - 10.0f, 0.0f), nearest_t, 0.0f, 10, NULL));
		
		break;
	    }

	case(ADD_LEECH):
	    {
		leech_list = g_list_append(leech_list,
					   zf_leech_new(&last_point));
		break;
	    }

	case(ADD_DRONE):
	    {
		/* drone type is always 1 - eek! */
		drone_list = g_list_append(drone_list,
					   zf_drone_new(1, &last_point));
		break;
	    }

	case(ADD_HUD_MESSAGE):
	    {
		show_hud_message_spawn_dialog();

		break;
	    }
	    
	    /*! \todo hax0r - creating ray of all possible heights of objects */	    
	case(REMOVE):
	    {
		CLUray ray;
		ZftViewer* zft_viewer = (ZftViewer *)data;

		/* create ray from camera eye to unprojected point */
		clCopyVertex(&ray.origin, &zft_viewer->camera_eye);
		cluNormalDifference(&ray.direction, &last_point, &ray.origin); 
		cluNormalNormalise(&ray.direction);
		
		last_point.y = zf_heightmap_query_height(last_point.x, last_point.z); /* ground height*/
		
		zft_remove_ray_new(&last_point);
		
		last_point.y += 5.0f; /* hive height */
		
		zft_remove_ray_new(&last_point);
		
		last_point.y += 4.2f; /* eel segment height */
		
		zft_remove_ray_new(&last_point); 
		
		last_point.y += 0.4f; /* eel segment height */
		
		zft_remove_ray_new(&last_point); 
		
		last_point.y += 0.4f; /* eel segmnent and control point height */
		
		zft_remove_ray_new(&last_point);
		
		last_point.y += 0.4f; /* eel segment height */
	    
		zft_remove_ray_new(&last_point);
		
		last_point.y += 0.4f; /* eel segment height */
		
		zft_remove_ray_new(&last_point);
		
		break;
	    }

	case NONE:
	    break;

	default:
	    {
		printf("%s() : ERROR : unknown operation type\n", __FUNCTION__);
		exit(1);
	    }
	} /* end switch */

	/* Invalidate the whole window. */
	gdk_window_invalidate_rect(widget->window,
				   &widget->allocation, TRUE);
    }

    /* button 2 and button 3 are rotations/translations so keep track of movement */
    if (event->button == 2 || event->button == 3)
    {
	//printf("%s() : BUTTON2 or BUTTON3\n", __FUNCTION__);
	begin_x = event->x;
	begin_y = event->y;
    }
    
    //printf("%s() : END\n", __FUNCTION__);
    return FALSE;
}

static gboolean motion_notify_event (GtkWidget      *widget,
				     GdkEventMotion *event,
				     gpointer        data)
{
    float w = widget->allocation.width;
    float h = widget->allocation.height;
    float x = event->x;
    float y = event->y;
    
    gboolean redraw = FALSE;
    
    //printf("%s() : START\n", __FUNCTION__);

    /* button 2 (zoom in and out) */
    if (event->state & GDK_BUTTON2_MASK)
    {
	//printf("%s() : BUTTON2\n", __FUNCTION__);
	view_transy -= ((GLfloat)(begin_y - y) / h) * 50.0;
	
	redraw = TRUE;
    }

    /* button 3 (rotate about the origin) */
    if (event->state & GDK_BUTTON3_MASK)
    {
	//printf("%s() : BUTTON3\n", __FUNCTION__);
	view_rotx += ((GLfloat)(begin_y - y) / h) * CL_PI;
	view_roty += ((GLfloat)(begin_x - x) / w) * CL_PI;
	
	redraw = TRUE;
    }
    
    /* reset values of begin x/y */
    begin_x = x;
    begin_y = y;

    /* figure out closest flux point */
    update_last_point(event->x, event->y);
    redraw = TRUE;
    
    /* redraw screen if necessary */
    if (redraw)
    {
	gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
    }
    
    //printf("%s() : END\n", __FUNCTION__);

    return TRUE;
}

/**
 * ZftViewer class initialisation function. This does nothing but may need to be implemented
 * if this widget is to emit signals.
 **/
static void
zft_viewer_class_init(ZftViewerClass *klass)
{
}
 
/**
* ZftViewer widget initialisation function.
**/
static void
zft_viewer_init(ZftViewer* zft_viewer)
{
    /* declare arrow that will be used for the four translation buttons */
    GtkWidget* arrow;
    
    /* declare gl configuration */
    GdkGLConfig* gl_config;

    /* create gl configuration with a double buffered visual */
    gl_config = gdk_gl_config_new_by_mode(GDK_GL_MODE_RGB   |
					  GDK_GL_MODE_DEPTH |
					  GDK_GL_MODE_DOUBLE);
    
    /* set viewer's (as a child of table) attributes */
    gtk_container_set_border_width(GTK_CONTAINER(zft_viewer), 2);
    
    gtk_table_resize(GTK_TABLE(zft_viewer), NUM_TABLE_ROWS, NUM_ORTH_TABLE_COLUMNS);
    gtk_table_set_homogeneous(GTK_TABLE(zft_viewer), FALSE);
    
    /* create viewer drawing area */
    zft_viewer->drawing_area = gtk_drawing_area_new();
    
    /* set OpenGl capability for the viewer drawing area */
    gtk_widget_set_gl_capability(zft_viewer->drawing_area,
				 gl_config,
				 NULL,
				 TRUE,
				 GDK_GL_RGBA_TYPE);

    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    gtk_widget_add_events(zft_viewer->drawing_area,
			  GDK_VISIBILITY_NOTIFY_MASK |
			  GDK_BUTTON_PRESS_MASK      |
			  GDK_BUTTON_RELEASE_MASK    |
			  GDK_POINTER_MOTION_MASK    |
			  GDK_BUTTON1_MOTION_MASK    |
			  GDK_BUTTON2_MOTION_MASK    |
			  GDK_BUTTON3_MOTION_MASK    );
    
    g_signal_connect(G_OBJECT(zft_viewer->drawing_area), "configure_event",
		     G_CALLBACK (reshape), zft_viewer);
    
    g_signal_connect(G_OBJECT(zft_viewer->drawing_area), "expose_event",
		     G_CALLBACK(draw), zft_viewer);
    
    g_signal_connect(G_OBJECT(zft_viewer->drawing_area), "button_press_event",
		     G_CALLBACK(button_press_event), zft_viewer);
    
    g_signal_connect(G_OBJECT (zft_viewer->drawing_area), "motion_notify_event",
		     G_CALLBACK (motion_notify_event), NULL);
    
    /* creates viewer buttons */
    zft_viewer->zoom_in = gtk_button_new_with_label("ZOOM IN");
    
    zft_viewer->zoom_out = gtk_button_new_with_label("ZOOM OUT");
    
    zft_viewer->translate_left = gtk_button_new();
    zft_viewer->translate_right = gtk_button_new();
    zft_viewer->translate_up = gtk_button_new();
    zft_viewer->translate_down = gtk_button_new();
    
    /* create arrows and assign to relevent translation buttons */
    arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_OUT); 
    gtk_container_add(GTK_CONTAINER(zft_viewer->translate_left), arrow);
    gtk_widget_show(arrow);
    arrow = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_OUT); 
    gtk_container_add(GTK_CONTAINER(zft_viewer->translate_right), arrow);
    gtk_widget_show(arrow);
    arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_OUT); 
    gtk_container_add(GTK_CONTAINER(zft_viewer->translate_up), arrow);
    gtk_widget_show(arrow);
    arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT); 
    gtk_container_add(GTK_CONTAINER(zft_viewer->translate_down), arrow);
    gtk_widget_show(arrow);
    
    /* assign button callbacks */
    g_signal_connect(G_OBJECT(zft_viewer->zoom_in), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->zoom_in), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->zoom_out), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->zoom_out), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_left), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_left), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_right), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_right), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_up), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_up), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_down), "pressed",
		     G_CALLBACK(move_camera), zft_viewer);
    g_signal_connect(G_OBJECT(zft_viewer->translate_down), "released",
		     G_CALLBACK(stop_moving_camera), zft_viewer);
    
    /* attach viewer drawing area to viewer table */
    gtk_table_attach_defaults(GTK_TABLE(zft_viewer), zft_viewer->drawing_area, 0, 6, 0, 1);
    
    /* attach zoom buttons to viewer table */
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->zoom_in, 
		     0, 1, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->zoom_out, 
		     1, 2, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    
    /* attach tranlation buttons to viewer table */
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->translate_left, 
		     2, 3, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->translate_up, 
		     3, 4, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->translate_down, 
		     4, 5, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    gtk_table_attach(GTK_TABLE(zft_viewer), zft_viewer->translate_right, 
		     5, 6, 1, 2, 
		     GTK_FILL | GTK_EXPAND, GTK_SHRINK,
		     0, 0);
    
    /* show drawing area */
    gtk_widget_show(zft_viewer->drawing_area);
    
    /* show zoom buttons */
    gtk_widget_show(zft_viewer->zoom_in);
    gtk_widget_show(zft_viewer->zoom_out);
    
    /* show translation buttons */
    gtk_widget_show(zft_viewer->translate_left);
    gtk_widget_show(zft_viewer->translate_up);
    gtk_widget_show(zft_viewer->translate_down);
    gtk_widget_show(zft_viewer->translate_right);
    
    /* set ray callback functions */
    remove_ray_smart_pointer.is_valid = (ZfIsValid*) is_valid;
    remove_ray_smart_pointer.reference = (ZfReference*) reference;
    remove_ray_smart_pointer.release = (ZfRelease*) release;

    remove_ray_dynamic_collider.query_position = (ZfQueryPosition*) query_position;
    remove_ray_dynamic_collider.collision_response = (ZfCollisionResponse*) collision_response;

}

/**
* Creates a new viewer.
**/
GtkWidget*
zft_viewer_new(CLvertex camera_eye, CLvertex camera_center, CLvertex camera_up)
{
    ZftViewer* zft_viewer;
    
    zft_viewer = g_object_new(zft_viewer_get_type(), NULL);
    
    clCopyVertex(&zft_viewer->camera_eye, &camera_eye);
    clCopyVertex(&zft_viewer->camera_center, &camera_center);
    clCopyVertex(&zft_viewer->camera_up, &camera_up);
    
    set_button_directions(zft_viewer);
    
    zft_viewer->camera_move = NOT_SET;
    
    zft_viewer->zoom_width = 30.0;
    
    return GTK_WIDGET(zft_viewer);
}

GList*
zft_viewer_get_droid_list(void)
{
    return droid_list;
}

GList*
zft_viewer_get_leech_list(void)
{
    return leech_list;
}

GList*
zft_viewer_get_eel_list(void)
{
    return eel_list;
}

GList*
zft_viewer_get_drone_list(void)
{
    return drone_list;
}

GList*
zft_viewer_get_fluxring_list(void)
{
    return fluxring_list;
}

GList*
zft_viewer_get_hexfluxfield_list(void)
{
    return hexfluxfield_list;
}

GList*
zft_viewer_get_batteryring_list(void)
{
    return batteryring_list;
}

GList*
zft_viewer_get_tierring_list(void)
{
    return tierring_list;
}
