/*
	**
	** psfe.c 
	**
	** Postscript display frontend to network traffic visualiser
	**
	** Copyright 1998 Damien Miller <dmiller@ilogic.com.au>
	**
	** This software is licensed under the terms of the GNU General 
	** Public License (GPL). Please see the file COPYING for details.
	** 
	** $Id: psfe.c,v 1.7 1998/12/02 11:40:51 dmiller Exp $
	**
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <signal.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "psfe.h"
#include "util.h"
#include "packet-summary.h"
#include "summary-sort.h"

#define WIDTH					((float)595)
#define HEIGHT					((float)842)

#define HEADING_Y_INDENT	((float)120)
#define TIME_Y_INDENT		((float)27 + HEADING_Y_INDENT)
#define LABEL_SEPERATION	(0.8)
#define RADIUS					(WIDTH * 0.25)
#define LABEL_RADIUS			(RADIUS + LABEL_SEPERATION)

/* Used for drawing of peer lines */
typedef struct 
{
	host_t	*host;
	float		x_pos;
	float		y_pos;
	float		rotation_percent;
} visible_host_t;

/* Prototypes */
static void draw_peers(host_t *host, GHashTable *peers, GHashTable *visible_hosts);
static void format_hostname(host_t *host, char *s, size_t s_len);
void format_time(time_t t, char *buf, size_t len);
GHashTable *build_visible_hosts(float x_pos, float y_pos, float radius, host_list_t *hl, unsigned long n_hosts);
static void label_hosts_polygon(float x_pos, float y_pos, float radius, const char *font, float size, host_list_t *hl, unsigned long n_hosts);
static void draw_hosts_polygon(float x_pos, float y_pos, float radius, unsigned long n_hosts);
static void draw_string(const char *font, float size, float x_pos, float y_pos, const char *string);
static void print_header(void);
host_list_t *randomise_host_list(host_list_t *hl, unsigned long n_hosts);
void get_extents(const char *string);
void set_font(const char *font, float size);
void center_string(const char *string);
void draw_cross(float x_pos, float y_pos, float size);
void left_angle_string(const char *string, float angle);
void right_angle_string(const char *string, float angle);

/* Displays summary of network traffic */
void ps_display_summary(summary_t *p, unsigned int max_hosts, int host_sort_mode, int host_reverse, unsigned int max_peers, int peer_sort_mode, int peer_reverse)
{
	host_list_t		*sorted_hosts;
	host_list_t		*hl;
	host_list_t		*last_hl;
	char				start_time[80];
	char				finish_time[80];
	char				time_message[256];
	GHashTable		*visible_hosts;
	unsigned long	n_hosts;

	setlinebuf(stdout);

	/* Figure out how many sides out polygon is going to have */
	if (max_hosts != 0)
	{
		if (p->active_hosts < max_hosts)
			n_hosts = p->active_hosts;
		else 
			n_hosts = max_hosts;
	} else
	{
		n_hosts = p->active_hosts;
	}
	
	/* Sort hosts list */
	sorted_hosts = sort_hosts(p->hosts, max_hosts, host_sort_mode, host_reverse);

	/* Randomise order */
	sorted_hosts = randomise_host_list(sorted_hosts, n_hosts);

	/* Print postscript headers */
	print_header();

	/* Draw page heading */
	draw_string("Helvetica-Bold", 30, WIDTH / 2.0, 
					HEIGHT - HEADING_Y_INDENT, "traffic-vis report");

	/* Draw start and finish times */
	format_time(p->summary_start, start_time, sizeof(start_time));
	format_time(p->summary_finish, finish_time, sizeof(finish_time));
	snprintf(time_message, sizeof(time_message), "%s to %s", start_time, finish_time);
	draw_string("Helvetica", 8, WIDTH / 2.0, 
					HEIGHT - TIME_Y_INDENT, time_message);

	/* Label hosts polygon */
	label_hosts_polygon((WIDTH / 2.0), (HEIGHT / 2.0), LABEL_RADIUS, 
							  "Helvetica", 7.0, sorted_hosts, n_hosts);

	/* Build table of visible hosts (used in peer drawing */
	visible_hosts = build_visible_hosts((WIDTH / 2.0), (HEIGHT / 2.0), 
													RADIUS, sorted_hosts, n_hosts);

	/* Draw peer relationships */
	hl = sorted_hosts;
	while(hl != NULL)
	{
		/* Draw peer ralationships */
		draw_peers(hl->host, p->peers, visible_hosts);
		
		/* Move on to next peer entry, destroying current one as we go */
		last_hl = hl;
		hl = hl->next;
		free(last_hl);
	}

	/* Draw hosts polygon */
	/* We do this last so it is drawn on top of peer lines */
	/* draw_hosts_polygon((WIDTH / 2.0), (HEIGHT / 2.0), RADIUS, n_hosts); */

	/* Terminate postscript page */
	printf("showpage\n");
	printf("%%%%EndPage\n");
}

static void draw_peers(host_t *host, GHashTable *peers, GHashTable *visible_hosts)
{
	peer_list_t		*peer_list;
	peer_list_t		*last_pl;
	visible_host_t	*src;
	visible_host_t	*dst;
	
	peer_list = sort_peers(host->peer_keys, host->n_peers, peers, 0, 0, 0);

	/* Save graphics state */
	printf("gsave\n");

	/* Set line width to be as fine as possible */
	printf("0 setlinewidth\n");

	while(peer_list != NULL)
	{
		src = g_hash_table_lookup(visible_hosts, 
					(gpointer)&(peer_list->peer->host1->ip_addr));
		dst = g_hash_table_lookup(visible_hosts, 
					(gpointer)&(peer_list->peer->host2->ip_addr));

		if ((src != NULL) && (dst != NULL))
		{
			printf("newpath\n");
			printf("%f %f moveto\n", src->x_pos, src->y_pos);
			printf("%f %f lineto\n", dst->x_pos, dst->y_pos);
			printf("stroke\n");
		}

		last_pl = peer_list;
		peer_list = peer_list->next;
		free(last_pl->peer);
		free(last_pl);
	}

	/* Restore graphics state */
	printf("grestore\n");
}

static void format_hostname(host_t *host, char *s, size_t s_len)
{
	static struct	in_addr i;

	i.s_addr = htonl(host->ip_addr);
	
	if (host->hostname != NULL)
		snprintf(s, s_len, "%s", host->hostname);
	else	
		snprintf(s, s_len, "Unknown (%s)", inet_ntoa(i));
}

void format_time(time_t t, char *buf, size_t len)
{
	struct tm	*tm;
	
	tm = localtime(&t);
	
	strftime(buf, len, "%d-%b-%Y %H:%M:%S %Z", tm);
}

static void print_header(void)
{
	printf("%%!PS-Adobe-2.0\n");
	printf("%%%%Creator: traffic-vis 0.22\n");
	printf("%%%%Orientation: Portrait\n");
	printf("%%%%EndComments\n");
	printf("%%%%PageTrailer\n");
	printf("%%%%Page: 0 0\n");
}

static void draw_string(const char *font, float size, float x_pos, float y_pos, const char *string)
{
	/* Set up font */
	set_font(font, size);
	
	/* Start a new path */
	printf("newpath\n");

	/* Move to position */
	printf("%f %f moveto\n", x_pos, y_pos);
	
	/* Convert string extents to offset */
	center_string(string);

	/* Draw string */
	printf("(%s) show\n", string);
}

static void draw_hosts_polygon(float x_pos, float y_pos, float radius, unsigned long n_hosts)
{
	unsigned long 	c;
	float				face_theta;
	
	/* Calculate rotation per side */
	face_theta = (2.0 * M_PI) / (float)n_hosts;

	/* Start drawing and move into position */
	printf("newpath\n");
	printf("%f %f moveto\n", 
		x_pos + radius * sin(-0.5 * face_theta),
		y_pos + radius * cos(-0.5 * face_theta));
	
	/* Draw a line segment connecting each vertex */
	for(c = 0; c < n_hosts; c++)
	{
		printf("%f %f lineto\n",
				  x_pos + radius * sin(((float)c + 0.5) * face_theta), 
				  y_pos + radius * cos(((float)c + 0.5) * face_theta));
	}
	
	/* Color in polygon edge */
	printf("stroke\n");
}

static void label_hosts_polygon(float x_pos, float y_pos, float radius, const char *font, float size, host_list_t *hl, unsigned long n_hosts)
{
	unsigned long 	c;
	float				face_theta;
	
	/* Calculate rotation per side */
	face_theta = (2.0 * M_PI) / (float)n_hosts;

	/* Set up font */
	set_font(font, size);
	
	for(c = 0; c < n_hosts; c++)
	{
		char					hostname[80];

		format_hostname(hl->host, hostname, sizeof(hostname));
		
		/* Set up drawing and move to position */
		printf("newpath\n");
		printf("gsave\n");
		printf("%f %f moveto\n", 
			x_pos + radius * sin((float)c * face_theta), 
			y_pos + radius * cos((float)c * face_theta));

		/* Reverse direction for names on the left hand side */
		if (c > (n_hosts / 2))
			right_angle_string(hostname, 270.0 - ((360.0 / (float)n_hosts) * (float)c));
		else
			left_angle_string(hostname, 90.0 - ((360.0 / (float)n_hosts) * (float)c));

		printf("grestore\n");
		
		hl = hl->next;
	}
}

GHashTable *build_visible_hosts(float x_pos, float y_pos, float radius, host_list_t *hl, unsigned long n_hosts)
{
	unsigned long 	c;
	float				face_theta;
	GHashTable		*visible_hosts;
	visible_host_t	*vh;
	
	/* Calculate rotation per side */
	face_theta = (2.0 * M_PI) / (float)n_hosts;

	/* Allocate new hash table */
	visible_hosts = g_hash_table_new((GHashFunc)host_hash, (GCompareFunc)host_compare);
	
	for(c = 0; c < n_hosts; c++)
	{
		/* Create new table entry */
		vh = util_zmalloc(sizeof(*vh));
		vh->host = hl->host;
		
		/* Calculate X position of host on edge of polygon */
		/* by interpolation between adjacent polygon vertices */
		vh->x_pos = radius * sin((float)(c - 0.5) * face_theta);
		vh->x_pos += radius * sin((float)(c + 0.5) * face_theta);
		vh->x_pos /= 2;
		vh->x_pos += x_pos;

		/* Calculate Y position of host on edge of polygon */
		vh->y_pos = radius * cos((float)(c - 0.5) * face_theta);
		vh->y_pos += radius * cos((float)(c + 0.5) * face_theta);
		vh->y_pos /= 2;
		vh->y_pos += y_pos;
		
		/* Calculate rotation of host (used to draw arrow heads */
		vh->rotation_percent = (100.0 / (float)n_hosts) * (float)c;

		/* Add info to hash table */
		g_hash_table_insert(visible_hosts, (gpointer)&(vh->host->ip_addr), (gpointer)vh);
		
		hl = hl->next;
	}

	return(visible_hosts);
}

host_list_t *randomise_host_list(host_list_t *hl, unsigned long n_hosts)
{
	host_list_t		root;
	host_list_t		*last_hl;
	host_list_t		*new_hl;
	host_t			**host_array;
	unsigned long	c;
	unsigned long	d;

	/* Make array out of linked-list */
	host_array = util_zmalloc(sizeof(*host_array) * n_hosts);
	c = 0;
	root.next = NULL;
	while(hl != NULL)	
	{
		/* Find a random empty position in array */
		d = random() % n_hosts;
		while (host_array[d] != NULL)
		{
			d++;
			d %= n_hosts;
		}
		
		/* Copy host there */
		host_array[d] = hl->host;
		
		/* Move on to next host */
		last_hl = hl;
		hl = hl->next;
		
		/* Dispose of old list entry */
		free(last_hl);
	}

	/* Make linked list out of array */
	for(c = 0; c < n_hosts; c++)
	{
		new_hl = malloc(sizeof(*new_hl));
		new_hl->next = root.next;
		new_hl->host = host_array[c];
		root.next = new_hl;
	}

	/* Dispose of host array */
	free(host_array);
	
	return(root.next);
}

void get_extents(const char *string)
{
	printf("gsave\n");
	printf("newpath\n");
	printf("0 0 moveto\n");
	printf("(%s) false charpath flattenpath pathbbox\n", string);
	printf("grestore\n");
}

void set_font(const char *font, float size)
{
	printf("/%s findfont\n", font);
	printf("%f scalefont\n", size);
	printf("setfont\n");
}

void center_string(const char *string)
{
	/* Get string extents */
	get_extents(string);

	/* PS stack: llx lly urx ury (from get_extents) */
	printf("3 2 roll\n");		/* PS Stack: llx urx ury lly */
	printf("sub 2 div neg\n");	/* PS Stack: llx urx (-height / 2) */
	printf("3 1 roll\n");		/* PS Stack: (-height / 2) llx urx */
	printf("exch sub\n");		/* PS Stack: (-height / 2) width */
	printf("2 div neg\n");		/* PS Stack: (-height / 2) (-width / 2) */
	printf("exch\n");				/* PS Stack: (-width / 2) (-height / 2) */
	printf("rmoveto\n");
}

void left_angle_string(const char *string, float angle)
{
	/* Get string extents */
	get_extents(string);

	/* PS stack: llx lly urx ury (from get_extents) */
	printf("3 2 roll\n");			/* PS Stack: llx urx ury lly */
	printf("sub 2 div neg\n");		/* PS Stack: llx urx (-height / 2) */
	printf("3 1 roll\n");			/* PS Stack: (-height / 2) llx urx */
	printf("pop pop\n");				/* PS Stack: (-height / 2) */
	printf("0 exch\n");				/* PS Stack: 0 (-height / 2) */
	printf("%f rotate\n", angle);	/* PS Stack: 0 (-height / 2) */
	printf("rmoveto\n");
	printf("(%s) show\n", string);
}

void right_angle_string(const char *string, float angle)
{
	/* Get string extents */
	get_extents(string);

	/* PS stack: llx lly urx ury (from get_extents) */
	printf("3 2 roll\n");			/* PS Stack: llx urx ury lly */
	printf("sub 2 div neg\n");		/* PS Stack: llx urx (-height / 2) */
	printf("3 1 roll\n");			/* PS Stack: (-height / 2) llx urx */
	printf("exch sub neg\n");		/* PS Stack: (-height / 2) (-width) */
	printf("exch\n");					/* PS Stack: (-width) (-height / 2) */
	printf("%f rotate\n", angle);	/* PS Stack: 0 (-height / 2) */
	printf("rmoveto\n");
	printf("(%s) show\n", string);
}

