/* This file contains functions needed to draw three dimensional graphs. There
 * are currently two functions accessible from outside (look for them at the end
 * of the file), one for creating data from an okno_s structure, the other one
 * for getting the data from a file. */
#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <gtk/gtk.h>
#include <sys/time.h>
#include "main.h"

#define PI 3.141593
#define MAXNAME 8

typedef double points3d_t[3] ;	/* 3d point */
typedef double points_t[2] ;	/* 2d point */
typedef char name_t[MAXNAME] ;


/* the z buffer contains the information about the z coordinate of the points drawn */
typedef struct {
	int width ;
	int height ;
	int *z ;	/* z coordinate of the pixel */
	int *set ; /* tells whether pixel is set or empty */
} buffer3d_s ;


/* all graph-related data are stored in this structure */
typedef struct {
	GtkWidget *picture ; /* gdk_drawable containing the pixmap */
	GdkPixmap *pixmap ;	/* pixmap we are drawing on */
	buffer3d_s *buffer ; /* the z buffer */
	int npoints ;	/* how many data points */
	points3d_t *coord ; /* data points coordinates */
	points3d_t axes[3] ;	/* coordinates of the data points coordinate system */
	points3d_t *castx ;	/* coordinates of the data points as cast on the graph axes */
	points3d_t *casty ;
	points3d_t *castxy ;
	name_t *labels ;	/* optional point labels */
	int *class ;	/* optional class info to use different colors */
	double zoom ;	/* guess what */
	double maxvect ;	/* longest vector in the graph */
	gboolean draw_labels ;	/* different options */
	gboolean draw_projections ;
	gboolean draw_coordinates ;
	gboolean draw_perspective ;
	gboolean turn_around_local ;
	gboolean big_points ;
	char *title ; /* graph title */
} graph3d_s ;
	

/* frees memory allocated for a graph */
int graph3d_free(graph3d_s *g) {
	if(g->coord) free(g->coord) ;
	if(g->castx) free(g->castx) ;
	if(g->casty) free(g->casty) ;
	if(g->castxy) free(g->castxy) ;
	if(g->labels) free(g->labels) ;
	if(g->class) free(g->class) ;
	if(g->title) free(g->title) ;
	if(g) free(g) ;
	return EXIT_SUCCESS ;
}


/* destroys the graph and frees the allocated memory */
void graph3d_destroy_window(GtkWidget *w, gpointer dane) {
	GtkWidget *okno ;
	data_s * d ;
	graph3d_s * g;

	d = (data_s *) dane ;
	g = (graph3d_s *) d->data1 ;
	okno = (GtkWidget *) d->data5 ;

	graph3d_free(g) ;
	free(d) ;
}


/* destroys the graph and frees the allocated memory */
void graph3d_quit(GtkWidget *w, gpointer dane) {
	GtkWidget *okno ;
	data_s * d ;
	graph3d_s * g;

	d = (data_s *) dane ;
	g = (graph3d_s *) d->data1 ;
	okno = (GtkWidget *) d->data5 ;
	gtk_widget_destroy(okno) ;

}

/* used to measure the execution times of the code */
void checkpoint(struct timeval *t0, char *description) {
	long usec ;
	struct timeval t1 ;

	gettimeofday(&t1, NULL) ;
	usec = 1000000 * (t1.tv_sec - t0->tv_sec) + t1.tv_usec - t0->tv_usec ;
	printf("%s %li ", description, usec) ;
	t0->tv_sec = t1.tv_sec ;
	t0->tv_usec = t1.tv_usec ;

}



/* -------------------------- handling z-buffer -------------------- */

/* create a new z buffer for storing the pixmap z axis informations */
buffer3d_s * graph3d_buffer3d_new(int width, int height) {
	buffer3d_s *res ;
	int i ;

	res = calloc(1, sizeof(*res)) ;
	res->width = width + 1 ;
	res->height = height + 1 ;
	i = width * height ;
	res->z = calloc(i + 1, sizeof(*res->z)) ;
	res->set = calloc(i + 1, sizeof(*res->set)) ;
	return res ;
}


/* free the allocated z buffer */
void graph3d_buffer3d_free(buffer3d_s * b) {

	free(b->z) ;
	free(b->set) ;
	free(b) ;
}


/* -------------------------- drawing primitives --------------------------- */

/* same as gdk_draw_string, but checks the parameters */
void graph3d_draw_string(GdkDrawable *drawable, GdkFont *font, GdkGC *gc,
	gint x, gint y, const gchar *string, gint maxw, gint maxh) {

	gint ascent, descent, width, lbearing, rbearing ;
	
	gdk_string_extents(font, string, &lbearing, &rbearing, &width,
		&ascent, &descent) ;

	if(x < lbearing || x > (maxw - rbearing - 1) || 
		y < ascent || y > (maxh - descent - 1)) return ;

	gdk_draw_string(drawable, font, gc, x, y, string) ;
}


/* draw rectangle on a pixmap, respecting the z information in the buffer */
void graph3d_draw_rectangle(GdkDrawable *pic, buffer3d_s *b, points3d_t pos,
	int width, int height, GdkGC *pen) {

	int x0, y0, xt, yt, j ;
	double z0 ; 

	if(pos[0] < 0 || pos[0] > b->width - width  - 1
		|| pos[1] < 0 || pos[1] > b->height - height - 1) 
		return ;

	x0 = pos[0], y0 = pos[1], z0 = pos[2] ;
	
	for(xt = x0 ; xt < x0 + width ; xt++) 
		for(yt = y0 ; yt < y0 + width ; yt++) {

			j = xt * b->height + yt ;

			if( b->set[j] == 0 || b->z[j] < z0) {
				gdk_draw_point(pic, pen, xt, yt) ;
				b->set[j] = 1 ;
				b->z[j] = z0 ;
			}
		}
}


/* draw a line on the buffer, respecting the 3d pixmap inforamtion */
static void graph3d_draw_line(GdkDrawable *pic, buffer3d_s *b, points3d_t start, 
	points3d_t end, GdkGC *pen) {

	int *pp_set0, *pp_set, *pp_z0, *pp_z, 
		v, i, j, xv, yv, x0, y0, xt, yt, x1, y1, z0, zv, z1, zt ;
	double vd ;

	if(!b) {
		gdk_beep() ;
		printf("fatal error in graph3d_draw_line: buffer is NULL\n") ;
		exit(1) ;
	}


	x0 = start[0], y0 = start[1], z0 = start[2] ;
	x1 = end[0], y1 = end[1], z1 = end[2] ;

	pp_set0 = b->set ;
	pp_z0 = b->z ;
	xv = x1 - x0 , yv = y1 - y0 , zv = z1 - z0 ;

	xt = 1.1 * abs(xv) , yt = 1.1 * abs(yv) ;

	if(xt > yt) v = xt ; else v = yt ;

	for(i = 0 ; i < v ; i++) {

		vd = 1.0 * i / v ;
		xt = x0 + xv * vd , yt = y0 + yv * vd , zt = z0 + zv * vd ;

		if(xt < 0 || xt >= (b->width - 5) || 
		 yt < 0 || yt >= (b->height - 5)) {
			continue ;
		}

		j = xt * b->height + yt ;

		pp_z = pp_z0 + j ;
		pp_set = pp_set0 + j ;

		if( !*(pp_set) || *(pp_z) < zt) {
			gdk_draw_point(pic, pen, xt, yt) ;
			++(*(pp_set)) ;
			 *(pp_z) = zt ;
		}
	}
}


/* ------------------------------ vector operations ---------------------- */

/* scales x and y positions of a point according to the current perspective */
static void graph3d_points_perspective(points3d_t p, double perspective, double maxvect) {
	double factor ;

	factor = perspective / (perspective + maxvect - p[2]) ;
	p[0] *= factor ;
	p[1] *= factor ;
	p[2] *= factor ;
}


void graph3d_points_copy(points3d_t to, points3d_t from) {
	to[0] = from[0] ;
	to[1] = from[1] ;
	to[2] = from[2] ;
}


static void graph3d_points_negate(points3d_t p) {
	p[0] = -p[0] ;
	p[1] = -p[1] ;
	p[2] = -p[2] ;
}


static void graph3d_points_move(points3d_t p, int width, int height) {
	p[0] = width + p[0] ;
	p[1] = height - p[1] ;
}


/* computate the maximal distance from the middle point of the coordinate system */
double graph3d_maxvect(points3d_t *p, int npoints) {
	double tmp, res = 0 ;
	int i, j ;

	for(i = 0 ; i < npoints ; i++) {
		tmp = 0 ;
		for(j = 0 ; j < 3 ; j++) tmp += p[i][j] * p[i][j] ;
		tmp = sqrt(tmp) ;
		if(tmp > res) res = tmp ;
	}

	return res ;
}


/* each coordinate in the graph gets multiplied by scale */
static void graph3d_graph_scale(graph3d_s *graph, double scale) {
	int i ;

	for(i = 0 ; i < 3 ; i++) {
		graph->axes[i][0] *= scale ;
		graph->axes[i][1] *= scale ;
		graph->axes[i][2] *= scale ;
	}

	for(i = 0 ; i < graph->npoints ; i++) {
		graph->coord[i][0] *= scale ;
		graph->coord[i][1] *= scale ;
		graph->coord[i][2] *= scale ;

		graph->castxy[i][0] *= scale ;
		graph->castxy[i][1] *= scale ;
		graph->castxy[i][2] *= scale ;

		graph->castx[i][0] *= scale ;
		graph->castx[i][1] *= scale ;
		graph->castx[i][2] *= scale ;

		graph->casty[i][0] *= scale ;
		graph->casty[i][1] *= scale ;
		graph->casty[i][2] *= scale ;
	}

	graph->maxvect = graph3d_maxvect(graph->coord, graph->npoints) ;

}


/* ------------------------- drawing the graph ----------------------- */

/* this function does the actual drawing of the sequence gc graph */
void graph3d_draw_graph(GtkWidget *picture, graph3d_s *graph, opt_s *o) {
	GdkDrawable *gdkpic ;
	GdkColor grey = { 0, 54000, 54000, 54000 } ;
	GdkGC *pen, *black, *white ;
	GtkStyle *style ;
	char scale_name[3][2] = { "X\0", "Y\0", "Z\0" } ;
	double perspective ;
	points3d_t p0, p1 ;
	int i, wid0, wid1, dheight, dwidth, color ;
	buffer3d_s *buf ;

	graph->buffer = graph3d_buffer3d_new(picture->allocation.width, 
		picture->allocation.height) ;
	buf = graph->buffer ;

	style = gtk_widget_get_style(picture) ;
	pen = gdk_gc_new(picture->window) ;
	gdkpic = graph->pixmap ;

	black = picture->style->black_gc ;
	white = picture->style->white_gc ;

	gdk_color_parse("lightgrey", &grey) ;
	gdk_colormap_alloc_color(gdk_colormap_get_system(), &grey, FALSE, TRUE) ;

	dheight = picture->allocation.height / 2 ;
	dwidth = picture->allocation.width / 2 ;

	perspective = 3 * graph->maxvect ;

	gdk_gc_set_foreground(pen, &grey) ;
	gdk_draw_rectangle(gdkpic, pen, TRUE, 0, 0, 
		picture->allocation.width, picture->allocation.height) ;

	gdk_draw_string(gdkpic, o->czcionka, black, 0.1 * dwidth, 
		gdk_string_height(o->czcionka, graph->title), graph->title) ;

	/* drawing the coordinate system */
	if(graph->draw_coordinates) 
	for( i = 0 ; i < 3 ; i++) {

		wid0 = -3.0 * graph->axes[i][2] / graph->maxvect + 3.0 ;
		wid1 = 3.0 * graph->axes[i][2] / graph->maxvect + 3.0 ;

		graph3d_points_copy(p0, graph->axes[i]) ;
		graph3d_points_negate(p0) ;
		graph3d_points_copy(p1, graph->axes[i]) ;

		if(graph->draw_perspective) {
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
			graph3d_points_perspective(p1, perspective, graph->maxvect) ;
		}

		graph3d_points_move(p0, dwidth, dheight) ;
		graph3d_points_move(p1, dwidth, dheight) ;
	
		graph3d_draw_line(gdkpic, buf, p0, p1, black ) ;

		p0[0] += wid0 ;
		p1[0] += wid1 ;
		graph3d_draw_line(gdkpic, buf, p0, p1, black ) ;

		graph3d_draw_string(gdkpic, o->czcionka, black, p1[0], p1[1], 
			scale_name[i], picture->allocation.width, picture->allocation.height) ;
	}

	/* drawing graph points */
	for(i = 0 ; i < graph->npoints ; i++) {
		color = (1 + graph->class[i]) % o->ncolors ;
		gdk_gc_set_foreground(pen, &o->colors[color]) ;
		
		graph3d_points_copy(p0, graph->coord[i]) ;

		if(graph->big_points) wid1 = 3.0 * graph->coord[i][2] / graph->maxvect + 4.0 ;
		else wid1 = 2 ;

		if(graph->draw_perspective) 
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
		graph3d_points_move(p0, dwidth, dheight) ;

		graph3d_draw_rectangle(gdkpic, buf, p0, wid1, wid1, pen) ;
		if(graph->draw_labels)
			graph3d_draw_string(gdkpic, o->czcionka, black, p0[0], p0[1],
				graph->labels[i], picture->allocation.width, picture->allocation.height) ;
	}

	/* draw projections of all points on the XY plane */
	if(graph->draw_projections == 1)
	for(i = 0 ; i < graph->npoints ; i++) {
		graph3d_points_copy(p0, graph->coord[i]) ;
		graph3d_points_copy(p1, graph->castxy[i]) ;

		if(graph->draw_perspective) {
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
			graph3d_points_perspective(p1, perspective, graph->maxvect) ;
		}

		graph3d_points_move(p0, dwidth, dheight) ;
		graph3d_points_move(p1, dwidth, dheight) ;

		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;
		graph3d_points_copy(p0, graph->castx[i]) ;

		if(graph->draw_perspective) 
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
		graph3d_points_move(p0, dwidth, dheight) ;

		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;
		graph3d_points_copy(p0, graph->casty[i]) ;
	
		if(graph->draw_perspective) 
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
		graph3d_points_move(p0, dwidth, dheight) ;
		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;
	} 

	/* drawing a projection of the last point on the x/y plane */
	if(graph->draw_projections == 2) {
		i-- ;
		graph3d_points_copy(p1, graph->castxy[i]) ;
		if(graph->draw_perspective) 
			graph3d_points_perspective(p1, perspective, graph->maxvect) ;
		graph3d_points_move(p1, dwidth, dheight) ;
		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;
	
		graph3d_points_copy(p0, graph->castx[i]) ;
		if(graph->draw_perspective) 
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
		graph3d_points_move(p0, dwidth, dheight) ;
		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;

		graph3d_points_copy(p0, graph->casty[i]) ;
		if(graph->draw_perspective) 
			graph3d_points_perspective(p0, perspective, graph->maxvect) ;
		graph3d_points_move(p0, dwidth, dheight) ;
		graph3d_draw_line(gdkpic, buf, p0, p1, white ) ;

	}

	/* finally, updating the window */
	gdk_draw_pixmap(picture->window, 
		picture->style->fg_gc[GTK_WIDGET_STATE(picture)],
		graph->pixmap,
		0, 0, 0, 0, -1, -1) ;

	graph3d_buffer3d_free(graph->buffer) ;
}


/* function called when the window size changes (configure event) */
void graph3d_configure(GtkWidget *picture, GdkEventExpose *event, gpointer data) {
	graph3d_s *graph ;
	opt_s *o ;
	data_s *d ;
	int height, width, dwidth, dheight, marginx, marginy ;
	double scalex, scaley, scale ;

	d = (data_s *) data ;
	o = (opt_s *) d->data ;
	graph = (graph3d_s *) d->data1 ;

	/* allocating new pixmap */
	if(graph->pixmap) gdk_pixmap_unref(graph->pixmap) ;
	graph->pixmap = gdk_pixmap_new(picture->window, picture->allocation.width, 
		picture->allocation.height, -1) ;

	height = picture->allocation.height ;
	width = picture->allocation.width ;

	marginx = 0.05 * width ;
	marginy = 0.05 * height ;

	dwidth = width/2 - marginx ;
	dheight = height/2 - marginy ;

	/* scaling the coordinates to fit the window */
	scalex = dwidth / graph->maxvect ;
	scaley = dheight / graph->maxvect ;

	if(scalex > scaley) scale = scaley ;
	else scale = scalex ;

	graph3d_graph_scale(graph, scale * graph->zoom) ;

	/* drawing the graph */
	graph3d_draw_graph(picture, graph, o) ;

}


/* function called when the window is revealed (expose event) */
void graph3d_expose(GtkWidget *picture, GdkEventExpose *event, gpointer data) {
	graph3d_s *graph ;
	opt_s *o ;
	data_s *d ;

	d = (data_s *) data ;
	o = (opt_s *) d->data ;
	graph = (graph3d_s *) d->data1 ;

	gdk_draw_pixmap(picture->window, 
		picture->style->fg_gc[GTK_WIDGET_STATE(picture)],
		graph->pixmap,
		0, 0, 0, 0, -1, -1) ;
}


/* ------------------------ recalculating the whole graph ----------------- */

/* recalculates the coordinates of the vectors */
void graph3d_turn_degree(graph3d_s * graph, int degree, int axis) {
	int i, a, b ;
	double ad, bd, teta, sin_t, cos_t  ;

	teta = degree * PI / 180 ;

	sin_t = sin(teta) ;
	cos_t = cos(teta) ;

	a = (axis + 1) % 3 ;
	b = (axis + 2) % 3 ;

	/* recalculating point and projection coordinates */
	for(i = 0 ; i < graph->npoints ; i++) {
		ad = graph->coord[i][a] ;
		bd = graph->coord[i][b] ;
		graph->coord[i][a] = ad * cos_t + bd * sin_t ;
		graph->coord[i][b] = -ad * sin_t + bd * cos_t ;

		ad = graph->castx[i][a] ;
		bd = graph->castx[i][b] ;
		graph->castx[i][a] = ad*cos_t + bd*sin_t ;
		graph->castx[i][b] = -ad * sin_t + bd * cos_t ;

		ad = graph->casty[i][a] ;
		bd = graph->casty[i][b] ;
		graph->casty[i][a] = ad*cos_t + bd*sin_t ;
		graph->casty[i][b] = -ad * sin_t + bd * cos_t ;

		ad = graph->castxy[i][a] ;
		bd = graph->castxy[i][b] ;
		graph->castxy[i][a] = ad*cos_t + bd*sin_t ;
		graph->castxy[i][b] = -ad * sin_t + bd * cos_t ;
	}

	/* recalculating axes coordinates */
	for(i = 0 ; i < 3 ; i++) {
		ad = graph->axes[i][a] ;
		bd = graph->axes[i][b] ;
		graph->axes[i][a] = ad*cos_t + bd*sin_t ;
		graph->axes[i][b] = -ad * sin_t + bd * cos_t ;
	}

}


/* computates the directional cosinii of an axe */
int graph3d_dircos(points3d_t ax, points3d_t cos) {
	double d ;
	
	d = sqrt(ax[0]*ax[0] + ax[1]*ax[1] + ax[2]*ax[2]) ;
	cos[0] = ax[0] / d ;
	cos[1] = ax[1] / d ;
	cos[2] = ax[2] / d ;
	return EXIT_SUCCESS ;
}


/* recalculates the coords of a point using the cosini of the new coord system */
int graph3d_points_cosini_r(points3d_t p, 
	points3d_t cx,
	points3d_t cy,
	points3d_t cz) {

	points3d_t t ;

	t[0] = cx[0]*p[0] + cy[0]*p[1] + cz[0]*p[2] ;
	t[1] = cx[1]*p[0] + cy[1]*p[1] + cz[1]*p[2] ;
	t[2] = cx[2]*p[0] + cy[2]*p[1] + cz[2]*p[2] ;

	graph3d_points_copy(p, t) ;
	return EXIT_SUCCESS ;
}


/* recalculates the coords of a point using the cosini of the new coord system */
int graph3d_points_cosini(points3d_t p, 
	points3d_t cx,
	points3d_t cy,
	points3d_t cz) {

	points3d_t t ;

	t[0] = cx[0]*p[0] + cx[1]*p[1] + cx[2]*p[2] ;
	t[1] = cy[0]*p[0] + cy[1]*p[1] + cy[2]*p[2] ;
	t[2] = cz[0]*p[0] + cz[1]*p[1] + cz[2]*p[2] ;

	graph3d_points_copy(p, t) ;
	return EXIT_SUCCESS ;
}


/* recalculates the coordinates of the vectors */
void graph3d_turn_cosini_r (graph3d_s* graph, 
	points3d_t cx,
	points3d_t cy,
	points3d_t cz) {

	int i ;

	/* recalculating point and projection coordinates */
	for(i = 0 ; i < graph->npoints ; i++) {
		graph3d_points_cosini_r(graph->coord[i], cx, cy, cz) ;
		graph3d_points_cosini_r(graph->castx[i], cx, cy, cz) ;
		graph3d_points_cosini_r(graph->casty[i], cx, cy, cz) ;
		graph3d_points_cosini_r(graph->castxy[i], cx, cy, cz) ;
	}

	/* recalculating axes coordinates */
	for(i = 0 ; i < 3 ; i++) {
		graph3d_points_cosini_r(graph->axes[i], cx, cy, cz) ;
	}

}


/* recalculates the coordinates of the vectors */
void graph3d_turn_cosini (graph3d_s* graph, 
	points3d_t cx,
	points3d_t cy,
	points3d_t cz) {

	int i ;

	/* recalculating point and projection coordinates */
	for(i = 0 ; i < graph->npoints ; i++) {
		graph3d_points_cosini(graph->coord[i], cx, cy, cz) ;
		graph3d_points_cosini(graph->castx[i], cx, cy, cz) ;
		graph3d_points_cosini(graph->casty[i], cx, cy, cz) ;
		graph3d_points_cosini(graph->castxy[i], cx, cy, cz) ;
	}

	/* recalculating axes coordinates */
	for(i = 0 ; i < 3 ; i++) {
		graph3d_points_cosini(graph->axes[i], cx, cy, cz) ;
	}

}


/* it seems that there is some problem with precision. This is to precisely
 * align the axes. */
void graph3d_axes_zero(graph3d_s *graph) {

	graph->axes[0][0] = graph->maxvect ;
	graph->axes[0][1] = 0 ;
	graph->axes[0][2] = 0 ;

	graph->axes[1][0] = 0 ;
	graph->axes[1][1] = graph->maxvect ;
	graph->axes[1][2] = 0 ;

	graph->axes[2][0] = 0 ;
	graph->axes[2][1] = 0 ;
	graph->axes[2][2] = graph->maxvect ;

}


/* ------------------------- interface functions ----------------------- */

/* this function is called repeatedly when an arrow is pressed */
void graph3d_turn(gpointer dane) {
	GtkWidget *picture ;
	data_s * d ;
	opt_s *o ;
	int *dir, axis, teta ;
	graph3d_s *graph ;
	points3d_t cx, cy, cz ;

	d = (data_s *) dane ;
	o = (opt_s *) d->data ;
	graph = (graph3d_s *) d->data1 ;
	dir = (int *) d->data3 ;
	picture = (GtkWidget *) d->data2 ;

	teta = 10 ; /* default turn in degrees */
	axis = *dir / 2 ; /* around which axis are we turning */
	teta *= (*dir % 2) * 2 - 1 ; /* plus or minus direction */

	if(graph->turn_around_local) {
		/* computate the directional cosini of the axes */
		graph3d_dircos(graph->axes[0], cx) ;
		graph3d_dircos(graph->axes[1], cy) ;
		graph3d_dircos(graph->axes[2], cz) ;
		/* reposition the graph so the axes co-align with the primary coord. sys.*/
		graph3d_turn_cosini(graph, cx, cy, cz) ;
		graph3d_axes_zero(graph) ;
		/* turn the graph */
		graph3d_turn_degree(graph, teta, axis) ;
		/* back to the initial coordinate system */
		graph3d_turn_cosini_r(graph, cx, cy, cz) ;
		/* draw the graph */
	} else {
		graph3d_turn_degree(graph, teta, axis) ;
	}

	graph3d_draw_graph(picture, graph, o) ;
}


/* starts turning the graph when an arrow gets pressed */
void graph3d_test(GtkWidget *w, gpointer dane) {
	GtkWidget *picture ;
	graph3d_s *graph ;
	data_s *d ;
	opt_s *o ;
	points3d_t cx, cy, cz ;

	d = (data_s *) dane ;
	o = (opt_s *) d->data ;
	graph = (graph3d_s *) d->data1 ;
	picture = (GtkWidget *) d->data2 ;

	graph3d_dircos(graph->axes[0], cx) ;
	graph3d_dircos(graph->axes[1], cy) ;
	graph3d_dircos(graph->axes[2], cz) ;
	graph3d_turn_cosini(graph, cx, cy, cz) ;
	graph3d_axes_zero(graph) ;
	graph->zoom = 1.0 ;
	graph3d_configure(graph->picture, NULL, dane) ;

}


/* turns the graph once if an arrow is clicked */
void graph3d_zoom_increase(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->zoom *= 1.1 ;
	graph3d_configure(graph->picture, NULL, dane) ;
	
}


/* turns the graph once if an arrow is clicked */
void graph3d_zoom_decrease(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->zoom /= 1.1 ;
	graph3d_configure(graph->picture, NULL, dane) ;
}


/* turns the graph once if an arrow is clicked */
void graph3d_turning_once(GtkWidget *w, gpointer dane) {
	data_s *d ;

	d = (data_s *) dane ;
	graph3d_turn(d) ;
}

/* starts turning the graph when an arrow gets pressed */
void graph3d_turning_start(GtkWidget *w, gpointer dane) {
	data_s *d ;
	guint *id ;

	d = (data_s *) dane ;
	id = malloc(sizeof(*id)) ;
	*id = gtk_timeout_add(100, (GtkFunction) graph3d_turn, d) ;
	d->data4 = id ;
	graph3d_turn(d) ;
}


/* stops turning the graph after an arrow is released */
void graph3d_turning_stop(GtkWidget *w, gpointer dane) {
	data_s *d ;
	guint *id ;

	d = (data_s *) dane ;
	id= (guint *) d->data4  ;
	gtk_timeout_remove(*id) ;
	free(id) ;
}


/* toggles big / small points */
void graph3d_toggle_points(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->big_points = !graph->big_points ;
	graph3d_configure(graph->picture, NULL, dane) ;
}


/* toggles the mode the arrow buttons behave */
void graph3d_toggle_turning(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->turn_around_local = !graph->turn_around_local ;
	graph3d_configure(graph->picture, NULL, dane) ;
}


/* switches labels on and off */
void graph3d_show_coordinates(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->draw_coordinates = ! graph->draw_coordinates ;

	graph3d_configure(graph->picture, NULL, dane) ;
}


/* switches perspective on and off */
void graph3d_show_perspective(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->draw_perspective = ! graph->draw_perspective ;

	graph3d_configure(graph->picture, NULL, dane) ;
}


/* switches labels on and off */
void graph3d_show_labels(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->draw_labels = ! graph->draw_labels ;

	graph3d_configure(graph->picture, NULL, dane) ;
}


/* switches projections on and off */
void graph3d_show_projections(GtkWidget *w, gpointer dane) {
	graph3d_s *graph ;
	data_s *d ;

	d = (data_s *) dane ;
	graph = (graph3d_s *) d->data1 ;
	graph->draw_projections++ ;
	graph->draw_projections %= 3 ;

	graph3d_configure(graph->picture, NULL, dane) ;
}


/* create buttons with tooltips, shortcut keys and connect the apr. functions */
void graph3d_create_buttons(
	GtkWidget *okno, 
	GtkWidget *ypole, 
	GtkWidget *picture, 
	graph3d_s *graph, 
	data_s *data,
	opt_s *o) {
	
	GtkWidget **butt, *habo, *test, *hpole, *labels, 
		*big, *proj, *coord, *persp, *quit, *zoomp, *zoomm ;
	GtkAccelGroup *accel ;
	GtkTooltips *tips ;
	char tmp[300], keys[] = "xXyYzZ", 
		label_names[6][3] = { "<X", "X>", "<Y", "Y>", "<Z", "Z>" },
		axe_names[] = {'X', 'Y', 'Z'} ;

	int i, *dir ;
	data_s **d, *dd ;

	d = malloc(sizeof(*d)*6) ;
	dd = malloc(sizeof(*dd)) ;
	dir = malloc(sizeof(*dir)*6) ;

	dd->data = o ;
	dd->data1 = graph ;
	dd->data2 = picture ;

	for(i = 0 ; i < 6 ; i++) {
		d[i] = malloc(sizeof(*d[i])) ;
		dir[i] = i ;
		d[i]->data = o ;
		d[i]->data1 = graph ;
		d[i]->data2 = picture ;
		d[i]->data3 = &dir[i]  ;
	}

	tips = gtk_tooltips_new() ;

	habo = gtk_handle_box_new() ;
	gtk_box_pack_start(GTK_BOX(ypole), habo, FALSE, FALSE, 0) ;
	hpole = gtk_hbox_new(FALSE, 0) ;
	gtk_widget_set_usize(hpole, -1, 40) ;
	gtk_container_add(GTK_CONTAINER(habo), hpole) ;
	gtk_widget_show(habo) ;
	gtk_widget_show(hpole) ;

	butt = malloc(sizeof(*butt) * 6) ;

	/* making arrows */
	for(i = 0 ; i < 6 ; i++) 

	accel = gtk_accel_group_new() ;
	gtk_accel_group_attach(accel, GTK_OBJECT(okno)) ;
	
	/* arrow buttons */
	for(i = 0 ; i < 6 ; i++) {
		strcpy(tmp, label_names[i]) ;
		butt[i] = gtk_button_new_with_label(tmp) ;
		gtk_box_pack_start(GTK_BOX(hpole), butt[i], FALSE, FALSE, 0) ;
		gtk_signal_connect(GTK_OBJECT(butt[i]), "pressed",
		GTK_SIGNAL_FUNC(graph3d_turning_start), d[i]) ;
		gtk_signal_connect(GTK_OBJECT(butt[i]), "released",
		GTK_SIGNAL_FUNC(graph3d_turning_stop), d[i]) ;
		gtk_signal_connect(GTK_OBJECT(butt[i]), "clicked",
		GTK_SIGNAL_FUNC(graph3d_turning_once), d[i]) ;
		sprintf(tmp, "Use this button to turn the 3D plot around the %c axis." 
			" The keyboard accelerator for this action is %c", axe_names[i/2], keys[i]) ;
		gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), butt[i], tmp, "Just try it") ;
		gtk_widget_add_accelerator(butt[i], "clicked", accel, keys[i], 
			GDK_SHIFT_MASK * (i % 2), 0) ;
	}

	/* other buttons */
	zoomp = gtk_button_new_with_label("+") ;
	gtk_box_pack_start(GTK_BOX(hpole), zoomp, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(zoomp), "clicked",
		GTK_SIGNAL_FUNC(graph3d_zoom_increase), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), zoomp,
		"Zoom in. Shortcut is '+'", NULL) ;
	gtk_widget_add_accelerator(zoomp, "clicked", accel, '+', 0, 0) ;

	/* other buttons */
	zoomm = gtk_button_new_with_label("-") ;
	gtk_box_pack_start(GTK_BOX(hpole), zoomm, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(zoomm), "clicked",
		GTK_SIGNAL_FUNC(graph3d_zoom_decrease), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), zoomm,
		"Zoom out. Shortcut is '-'", NULL) ;
	gtk_widget_add_accelerator(zoomm, "clicked", accel, '-', 0, 0) ;

	/* other buttons */
	labels = gtk_toggle_button_new_with_label("Turn") ;
	gtk_box_pack_start(GTK_BOX(hpole), labels, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(labels), "toggled",
		GTK_SIGNAL_FUNC(graph3d_toggle_turning), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), labels,
		"Toggle between turning in the graph's coordinate system and the "
		"viewers coordinate system (if this is not clear, just click it and "
		"try what the arrow buttons do in this mode). Shortcut is 't'", NULL) ;
	gtk_widget_add_accelerator(labels, "toggled", accel, 't', 0, 0) ;

	labels = gtk_toggle_button_new_with_label("Labels") ;
	gtk_box_pack_start(GTK_BOX(hpole), labels, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(labels), "toggled",
		GTK_SIGNAL_FUNC(graph3d_show_labels), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), labels,
		"Turn the labels of the data points on / off. Shortcut is 'l'", NULL) ;
	gtk_widget_add_accelerator(labels, "toggled", accel, 'l', 0, 0) ;

	test = gtk_button_new_with_label("Zero") ;
	gtk_box_pack_start(GTK_BOX(hpole), test, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(test), "clicked",
		GTK_SIGNAL_FUNC(graph3d_test), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), test,
		"Rotate the graph back to the initial position. Shortcut is '0'", NULL) ;
	gtk_widget_add_accelerator(test, "clicked", accel, '0', 0, 0) ;

	big = gtk_toggle_button_new_with_label("Points") ;
	gtk_box_pack_start(GTK_BOX(hpole), big, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(big), "toggled",
		GTK_SIGNAL_FUNC(graph3d_toggle_points), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), big,
		"Change the size of the drawn data points. Shortcut is 's'", NULL) ;
	gtk_widget_add_accelerator(big, "toggled", accel, 's', 0, 0) ;

	proj = gtk_button_new_with_label("Proj.") ;
	gtk_box_pack_start(GTK_BOX(hpole), proj, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(proj), "clicked",
		GTK_SIGNAL_FUNC(graph3d_show_projections), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), proj,
		"Show a projection of the data points on the x-y plane. This can"
		" help you to orientate in their respective positions. If you click it"
		" twice, you will see only the projection of the rast data point."
		" Shortcut is 'p'", NULL) ;
	gtk_widget_add_accelerator(proj, "clicked", accel, 'p', 0, 0) ;

	coord = gtk_toggle_button_new_with_label("XYZ") ;
	gtk_box_pack_start(GTK_BOX(hpole), coord, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(coord), "toggled",
		GTK_SIGNAL_FUNC(graph3d_show_coordinates), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), coord,
		"Show / hide the coordinate system. Shortcut is 'c'", NULL) ;
	gtk_widget_add_accelerator(coord, "toggled", accel, 'c', 0, 0) ;

	persp = gtk_toggle_button_new_with_label("Perspective") ;
	gtk_box_pack_start(GTK_BOX(hpole), persp, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(persp), "toggled",
		GTK_SIGNAL_FUNC(graph3d_show_perspective), dd) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), persp,
		"Use / don't use perspective. Shortcut is 'P'", NULL) ;
	gtk_widget_add_accelerator(persp, "toggled", accel, 'p', GDK_SHIFT_MASK, 0) ;

	quit = gtk_button_new_with_label("Close") ;
	gtk_box_pack_start(GTK_BOX(hpole), quit, FALSE, FALSE, 0) ;
	gtk_signal_connect(GTK_OBJECT(quit), "clicked",
		GTK_SIGNAL_FUNC(graph3d_quit), data) ;
	gtk_tooltips_set_tip(GTK_TOOLTIPS(tips), quit,
		"Close this window. Shortcut q / Q / ctrl+q", NULL) ;
	gtk_widget_add_accelerator(quit, "clicked", accel, 'q', 0, 0) ;
	gtk_widget_add_accelerator(quit, "clicked", accel, 'q', GDK_SHIFT_MASK, 0) ;
	gtk_widget_add_accelerator(quit, "clicked", accel, 'q', GDK_CONTROL_MASK, 0) ;

	if(!o->show_tooltips) gtk_tooltips_disable(tips) ;
}


/* ------------------------------- data retrieval ------------------------- */

/* reads graph data from a file, allocates space etc. */
graph3d_s * graph3d_read_file(opt_s *o, char *filenam) {
	int alloc = 500, i, j, tmp, class ;
	double x, y, z ;
	char bufor[BUFSIZ], label[MAXNAME] ;
	graph3d_s *graph ;
	FILE *fp ;

	if( (fp = fopen(filenam, "r")) == NULL) {
		gdk_beep() ;
		printf("could not open file %s for reading\n", filenam) ;
		return NULL ;
	}

	graph = calloc(1, sizeof(*graph)) ;
	
	/* reserving memory */
	graph->coord =  malloc(sizeof(*graph->coord)  * alloc) ;
	graph->castx =  malloc(sizeof(*graph->coord)  * alloc) ;
	graph->casty =  malloc(sizeof(*graph->coord)  * alloc) ;
	graph->castxy = malloc(sizeof(*graph->coord)  * alloc) ;
	graph->class =  malloc(sizeof(*graph->class)  * alloc) ;
	graph->labels = malloc(sizeof(*graph->labels) * alloc) ;

	/* defaults */
	graph->npoints = 0 ;
	graph->draw_labels = TRUE ;
	graph->draw_projections = FALSE ;
	graph->draw_coordinates = TRUE ;
	graph->turn_around_local = TRUE ;
	graph->big_points = TRUE ;
	graph->draw_perspective = FALSE ;
	graph->zoom = 1.0 ;
	graph->pixmap = NULL ;
	graph->buffer = NULL ;
	graph->title = NULL ;

	if(!graph->coord || !graph->castx || !graph->casty || !graph->castxy) {
		gdk_beep() ;
		printf("Could not allocate enough memory\n") ;
		graph3d_free(graph) ;
		return NULL ;
	}

	/* reading the file with the data */
	for(i = 0, j = 0 ; fgets(bufor, BUFSIZ, fp) ; i++) {

		if(*bufor == '#' || *bufor == '\n') continue ;

		class = 0 ;
		*label = '\0' ;
		tmp = sscanf(bufor, " %lf %lf %lf %i %8s", &x, &y, &z, &class, label) ;

		if(tmp < 3) {
			printf("could not read line %i\n", i) ;
			continue ;
		}

		if(tmp == 3) sscanf(bufor, " %lf %lf %lf %8s", &x, &y, &z, label) ;

		/* reserving more memory if necessary */
		if(j >= alloc) {
			alloc += 500 ;
			graph->coord = realloc(graph->coord, sizeof(*graph->coord)*alloc) ;
			graph->castx = realloc(graph->castx, sizeof(*graph->coord)*alloc) ;
			graph->casty = realloc(graph->casty, sizeof(*graph->coord)*alloc) ;
			graph->castxy = realloc(graph->castxy, sizeof(*graph->coord)*alloc) ;
			graph->labels = realloc(graph->labels, sizeof(*graph->labels) * alloc) ;
			graph->class =  realloc(graph->class, sizeof(*graph->class)  * alloc) ;

			if(!graph->coord || !graph->castx || !graph->casty || !graph->castxy) {
				printf("Could not allocate enough memory\n") ;
				graph3d_free(graph) ;
				return NULL ;
			}
		}

		graph->class[j] = class ;
		strcpy(graph->labels[j], label) ;

		/* store the coordinates of the XY plane projections */
		graph->coord[j][0] = x ;
		graph->coord[j][1] = y ;
		graph->coord[j][2] = z ;

		graph->castx[j][0] = x ;
		graph->castx[j][1] = 0 ;
		graph->castx[j][2] = 0 ;

		graph->casty[j][0] = 0 ;
		graph->casty[j][1] = y ;
		graph->casty[j][2] = 0 ;

		graph->castxy[j][0] = x ;
		graph->castxy[j][1] = y ;
		graph->castxy[j][2] = 0 ;

		j++ ;
	}

	if(j < 1) {
		printf("no data points read\n") ;
		graph3d_free(graph) ;
		return NULL ;
	}

	graph->maxvect = graph3d_maxvect(graph->coord, j) ;
	graph3d_axes_zero(graph) ;
	graph->npoints = j ;

	return graph ;

}


/* --------------------------- create graph ---------------------------- */

/* creates a graph of the given values */
int graph3d_create(opt_s *o, char *filenam, char *title) {

	GtkWidget *okno, *ypole, *picture ;
	graph3d_s *graph ;
	data_s *d ;

	/* getting the graph */
	if( (graph = graph3d_read_file(o, filenam)) == NULL) {
		gdk_beep() ;
		komunikat("No data found or file\n %s \ncannot be opened", filenam) ;
		return EXIT_FAILURE;
	}

	if(graph->npoints == 0) {
		gdk_beep() ;
		komunikat("No data points read") ;
		graph3d_free(graph) ;
		return EXIT_FAILURE ;
	}

	/* main graph window */
	okno = gtk_window_new(GTK_WINDOW_TOPLEVEL) ;
	gtk_widget_set_usize(okno, 
	 gdk_screen_width() * 0.5, gdk_screen_height() * 0.5) ;
	graph->title = title ;
	gtk_window_set_title(GTK_WINDOW(okno), graph->title) ;

	d = malloc(sizeof(*d)) ;
	d->data = o ;
	d->data1 = graph ;
	d->data5 = okno ;

	gtk_signal_connect(GTK_OBJECT(okno), "destroy",
		GTK_SIGNAL_FUNC(graph3d_destroy_window), d) ;

	ypole = gtk_vbox_new(FALSE, 0) ;
	gtk_container_add(GTK_CONTAINER(okno), ypole) ;

	/* drawing area for the graph */
	picture = gtk_drawing_area_new() ;
	gtk_drawing_area_size(GTK_DRAWING_AREA(picture), 400, 150) ;

	graph3d_create_buttons(okno, ypole, picture, graph, d, o) ;
	gtk_box_pack_start(GTK_BOX(ypole), picture, TRUE, TRUE, 0) ;

	graph->picture = picture ;

	/* connect expose event to graph drawing */
	gtk_signal_connect(GTK_OBJECT(picture), "expose_event",
		GTK_SIGNAL_FUNC(graph3d_expose), d) ;
	gtk_signal_connect(GTK_OBJECT(picture), "configure_event",
		GTK_SIGNAL_FUNC(graph3d_configure), d) ;

	gtk_widget_show_all(okno) ;

	return EXIT_SUCCESS ;
}


/* create graph from a user selected file */
void graph3d_create_from_file(gpointer dane, guint signal, GtkWidget *kontrolka) {

	opt_s *o ;
	char * filenam, *title, *cp ;

	o = (opt_s *) dane ;

	filenam = file_get_name(o, NULL) ;
	if(!filenam || strlen(filenam) == 0) return ;

	if( !(cp = strrchr(filenam, '/')) ) title = strdup(filenam) ;
	else title = strdup( (cp + 1) ) ;

	graph3d_create(o, filenam, title) ;
	free(filenam) ;

}


/* creates a graph from a text window stored in a okno_s structure */
void graph3d_create_from_input(gpointer dane, guint signal, GtkWidget *kontrolka) {
	opt_s *o ;
	char *title ;

	o = (opt_s *) dane ;

	window_tofile(&o->panels[0], o) ;
	title = strdup(o->panels[0].title) ;
	graph3d_create(o, o->panels[0].tmpfil, title) ;
}
