/*****************************************************************************
 *
 * STATUSWRL.C - Nagios 3-D (VRML) Network Status View
 *
 * Copyright (c) 1999-2002 Ethan Galstad (nagios@nagios.org)
 * Last Modified:   10-22-2002
 *
 * Description:
 *
 * This CGI will dynamically create a 3-D VRML model of all hosts that are
 * being monitored on your network.
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

#include "../common/config.h"
#include "../common/locations.h"
#include "../common/common.h"
#include "../common/objects.h"
#include "../common/statusdata.h"

#include "cgiutils.h"
#include "getcgi.h"
#include "auth.h"
#include "edata.h"

extern char main_config_file[MAX_FILENAME_LENGTH];
extern char url_html_path[MAX_FILENAME_LENGTH];
extern char url_images_path[MAX_FILENAME_LENGTH];

extern char *statuswrl_include;

extern hostextinfo *hostextinfo_list;
extern host        *host_list;

extern int default_statuswrl_layout_method;


#define NAGIOS_VRML_IMAGE               "nagiosvrml.png"

#define DEFAULT_NODE_WIDTH		0.5
#define DEFAULT_HORIZONTAL_SPACING	1.0
#define DEFAULT_VERTICAL_SPACING	1.0

/* needed for auto-layout modes */
#define DEFAULT_NODE_HEIGHT             0.5
#define DEFAULT_NODE_HSPACING           1.0
#define DEFAULT_NODE_VSPACING           1.0
#define CIRCULAR_DRAWING_RADIUS         5.0

#define LAYOUT_USER_SUPPLIED            0
#define LAYOUT_COLLAPSED_TREE           2
#define LAYOUT_BALANCED_TREE            3
#define LAYOUT_CIRCULAR                 4


void calculate_host_coords(void);
void calculate_world_bounds(void);
void display_world(void);
void write_global_vrml_data(void);
void draw_process_icon(void);
void draw_host(hostextinfo *);
void draw_host_links(void);
void draw_host_link(host *,double,double,double,double,double,double);
void document_header(void);
int process_cgivars(void);

int number_of_host_layer_members(host *,int);
int max_child_host_layer_members(host *);
int host_child_depth_separation(host *, host *);
int max_child_host_drawing_width(host *);

void calculate_balanced_tree_coords(host *,int,int);
void calculate_circular_coords(void);
void calculate_circular_layer_coords(host *,double,double,int,int);

char url_logo_images_path[MAX_FILENAME_LENGTH];

authdata current_authdata;

float link_radius=0.016;

float floor_width=0.0;
float floor_depth=0.0;

double min_z_coord=0.0;
double min_x_coord=0.0;
double min_y_coord=0.0;
double max_z_coord=0.0;
double max_x_coord=0.0;
double max_y_coord=0.0;

double max_world_size=0.0;

double nagios_icon_x=0.0;
double nagios_icon_y=0.0;
int draw_nagios_icon=FALSE;

double custom_viewpoint_x=0.0;
double custom_viewpoint_y=0.0;
double custom_viewpoint_z=0.0;
int custom_viewpoint=FALSE;

float vertical_spacing=DEFAULT_VERTICAL_SPACING;
float horizontal_spacing=DEFAULT_HORIZONTAL_SPACING;
float node_width=DEFAULT_NODE_WIDTH;
float node_height=DEFAULT_NODE_WIDTH;	/* should be the same as the node width */

char *host_name="all";
int show_all_hosts=TRUE;

int use_textures=TRUE;
int use_text=TRUE;
int use_links=TRUE;

int layout_method=LAYOUT_USER_SUPPLIED;

int coordinates_were_specified=FALSE;   /* were drawing coordinates specified with extended host info entries? */





int main(int argc, char **argv){
	int result;

	/* reset internal variables */
	reset_cgi_vars();

	/* read the CGI configuration file */
	result=read_cgi_config_file(DEFAULT_CGI_CONFIG_FILE);
	if(result==ERROR){
		document_header();
		return ERROR;
	        }

	/* defaults from CGI config file */
	layout_method=default_statuswrl_layout_method;

	/* get the arguments passed in the URL */
	process_cgivars();

	document_header();

	/* read the main configuration file */
	result=read_main_config_file(main_config_file);
	if(result==ERROR)
		return ERROR;

	/* read all object configuration data */
	result=read_all_object_configuration_data(main_config_file,READ_ALL_OBJECT_DATA);
	if(result==ERROR)
		return ERROR;

	/* read all status data */
	result=read_all_status_data(DEFAULT_CGI_CONFIG_FILE,READ_ALL_STATUS_DATA);
	if(result==ERROR){
		free_memory();
		return ERROR;
                }

	/* get authentication information */
	get_authentication_information(&current_authdata);

	/* read in extended host information */
	read_extended_object_config_data(DEFAULT_CGI_CONFIG_FILE,READ_EXTENDED_HOST_INFO);

	/* display the 3-D VRML world... */
	display_world();

	/* free all allocated memory */
	free_memory();
	free_extended_data();

	return OK;
        }



void document_header(void){
	char date_time[MAX_DATETIME_LENGTH];
	time_t current_time;
	time_t expire_time;


	printf("Cache-Control: no-store\n");
	printf("Pragma: no-cache\n");

	time(&current_time);
	get_time_string(&current_time,date_time,sizeof(date_time),HTTP_DATE_TIME);
	printf("Last-Modified: %s\n",date_time);

	expire_time=0L;
	get_time_string(&expire_time,date_time,sizeof(date_time),HTTP_DATE_TIME);
	printf("Expires: %s\n",date_time);

	printf("Content-Type: x-world/x-vrml\n\n");

	return;
        }



int process_cgivars(void){
	char **variables;
	int error=FALSE;
	int x;

	variables=getcgivars();

	for(x=0;variables[x]!=NULL;x++){

		/* do some basic length checking on the variable identifier to prevent buffer overflows */
		if(strlen(variables[x])>=MAX_INPUT_BUFFER-1){
			x++;
			continue;
		        }


		/* we found the host argument */
		else if(!strcmp(variables[x],"host")){
			x++;
			if(variables[x]==NULL){
				error=TRUE;
				break;
			        }

			host_name=(char *)malloc(strlen(variables[x])+1);
			if(host_name==NULL)
				host_name="all";
			else
				strcpy(host_name,variables[x]);

			if(!strcmp(host_name,"all"))
				show_all_hosts=TRUE;
			else
				show_all_hosts=FALSE;
		        }

		/* we found the no textures argument*/
		else if(!strcmp(variables[x],"notextures"))
			use_textures=FALSE;

		/* we found the no text argument*/
		else if(!strcmp(variables[x],"notext"))
			use_text=FALSE;

		/* we found the no links argument*/
		else if(!strcmp(variables[x],"nolinks"))
			use_links=FALSE;

		/* we found the layout method option */
		else if(!strcmp(variables[x],"layout")){
			x++;
			if(variables[x]==NULL){
				error=TRUE;
				break;
			        }
			layout_method=atoi(variables[x]);
		        }

		/* we found custom viewpoint coord */
		else if(!strcmp(variables[x],"viewx")){
			x++;
			if(variables[x]==NULL){
				error=TRUE;
				break;
			        }
			custom_viewpoint_x=strtod(variables[x],NULL);
			custom_viewpoint=TRUE;
		        }
		else if(!strcmp(variables[x],"viewy")){
			x++;
			if(variables[x]==NULL){
				error=TRUE;
				break;
			        }
			custom_viewpoint_y=strtod(variables[x],NULL);
			custom_viewpoint=TRUE;
		        }
		else if(!strcmp(variables[x],"viewz")){
			x++;
			if(variables[x]==NULL){
				error=TRUE;
				break;
			        }
			custom_viewpoint_z=strtod(variables[x],NULL);
			custom_viewpoint=TRUE;
		        }

	        }

	/* free memory allocated to the CGI variables */
	free_cgivars(variables);

	return error;
        }



/* top-level VRML world generation... */
void display_world(void){
	hostextinfo *temp_hostextinfo;

	/* get the url we will use to grab the logo images... */
	snprintf(url_logo_images_path,sizeof(url_logo_images_path),"%slogos/",url_images_path);
	url_logo_images_path[sizeof(url_logo_images_path)-1]='\x0';

	/* calculate host drawing coordinates */
	calculate_host_coords();

	/* calculate world bounds */
	calculate_world_bounds();

	/* get the floor dimensions */
	if(max_x_coord>0)
		floor_width=(float)(max_x_coord-min_x_coord)+(node_width*2);
	else
		floor_width=(float)(max_x_coord+min_x_coord)+(node_width*2);
	if(max_z_coord>0)
		floor_depth=(float)(max_z_coord-min_z_coord)+(node_height*2);
	else
		floor_depth=(float)(max_z_coord+min_z_coord)+(node_height*2);

	/* write global VRML data */
	write_global_vrml_data();

	/* no coordinates were specified, so display warning message */
	if(coordinates_were_specified==FALSE){

		printf("\n");
		printf("Transform{\n");
		printf("translation 0.0 0.0 0.0\n");
		printf("children[\n");

		printf("Billboard{\n");
		printf("children[\n");
		printf("Shape{\n");
		printf("appearance Appearance {\n");
		printf("material Material {\n");
		printf("diffuseColor 1 0 0\n");
		printf("}\n");
		printf("}\n");
		printf("geometry Text {\n");
		printf("string [ \"Error: You have not supplied any 3-D drawing coordinates.\", \"Read the documentation for more information on supplying\", \"3-D drawing coordinates by defining\", \"extended host information entries in your config files.\" ]\n");
		printf("fontStyle FontStyle {\n");
		printf("family \"TYPEWRITER\"\n");
		printf("size 0.3\n");
		printf("justify \"MIDDLE\"\n");
		printf("}\n");
		printf("}\n");
		printf("}\n");
		printf("]\n");
		printf("}\n");

		printf("]\n");
		printf("}\n");
	        }

	/* coordinates were specified... */
	else{

		/* draw Nagios icon */
		if(layout_method!=LAYOUT_USER_SUPPLIED)
			draw_process_icon();

		/* draw all hosts */
		for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next)
			draw_host(temp_hostextinfo);

		/* draw host links */
		draw_host_links();
	        }

	return;
        }




/******************************************************************/
/************************ UTILITY FUNCTIONS ***********************/
/******************************************************************/

/* calculates how many "layers" separate parent and child - used by collapsed tree layout method */
int host_child_depth_separation(host *parent, host *child){
	int this_depth=0;
	int min_depth=0;
	int have_min_depth=FALSE;
	host *temp_host;

	if(child==NULL)
		return -1;

	if(parent==child)
		return 0;

	if(is_host_immediate_child_of_host(parent,child)==TRUE)
		return 1;

	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){

		if(is_host_immediate_child_of_host(parent,temp_host)==TRUE){

			this_depth=host_child_depth_separation(temp_host,child);

			if(this_depth>=0 && (have_min_depth==FALSE || (have_min_depth==TRUE && (this_depth<min_depth)))){
				have_min_depth=TRUE;
				min_depth=this_depth;
			        }
		        }
	        }

	if(have_min_depth==FALSE)
		return -1;
	else
		return min_depth+1;
        }



/* calculates how many hosts reside on a specific "layer" - used by collapsed tree layout method */
int number_of_host_layer_members(host *parent, int layer){
	int current_layer;
	int layer_members=0;
	host *temp_host;

	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){
		
		current_layer=host_child_depth_separation(parent,temp_host);

		if(current_layer==layer)
			layer_members++;
	        }

	return layer_members;
        }



/* calculate max number of members on all "layers" beneath and including parent host - used by collapsed tree layout method */
int max_child_host_layer_members(host *parent){
	int current_layer;
	int max_members=1;
	int current_members=0;

	for(current_layer=1;;current_layer++){

		current_members=number_of_host_layer_members(parent,current_layer);

		if(current_members<=0)
			break;

		if(current_members>max_members)
			max_members=current_members;
	        }

	return max_members;
        }



/* calculate max drawing width for host and children - used by balanced tree layout method */
int max_child_host_drawing_width(host *parent){
	host *temp_host;
	int child_width=0;

	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){
		
		if(is_host_immediate_child_of_host(parent,temp_host)==TRUE)
			child_width+=max_child_host_drawing_width(temp_host);
	        }

	/* no children, so set width to 1 for this host */
	if(child_width==0)
		return 1;

	else
		return child_width;
        }
	



/******************************************************************/
/********************* CALCULATION FUNCTIONS **********************/
/******************************************************************/

/* calculates host drawing coordinates */
void calculate_host_coords(void){
	hostextinfo *temp_hostextinfo;
	host *this_host;
	host *temp_host;
	int child_hosts=0;
	int parent_hosts=0;
	int max_layer_width=1;
	int current_child_host=0;
	int current_parent_host=0;
	int center_x=0;
	int offset_x=DEFAULT_NODE_WIDTH/2;
	int offset_y=DEFAULT_NODE_WIDTH/2;
	int current_layer=0;
	int layer_members=0;
	int current_layer_member=0;
	int max_drawing_width=0;
	int x=0;
  

	/******************************/
	/***** MANUAL LAYOUT MODE *****/
	/******************************/

	/* user-supplied coords */
	if(layout_method==LAYOUT_USER_SUPPLIED){

		/* see which hosts we should draw (only those with 3-D coords) */
		for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next){

			if(temp_hostextinfo->have_3d_coords==TRUE)
				temp_hostextinfo->should_be_drawn=TRUE;
			else
				temp_hostextinfo->should_be_drawn=FALSE;
		        }

		return;
		}

	/*****************************/
	/***** AUTO-LAYOUT MODES *****/
	/*****************************/

	/* add empty extended host info entries for all hosts that don't have any */
	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){

		/* find the corresponding hostextinfo definition */
		temp_hostextinfo=find_hostextinfo(temp_host->name);

		/* none was found, so add a blank one */
		if(temp_hostextinfo==NULL)
			add_extended_host_info(temp_host->name,NULL,NULL,NULL,NULL,NULL,0,0,0.0,0.0,0.0,0,0);

		/* default z coord should 0 for auto-layout modes unless overridden later */
		else
			temp_hostextinfo->z_3d=0.0;
	        }


	/***** COLLAPSED TREE MODE *****/
	if(layout_method==LAYOUT_COLLAPSED_TREE){

		/* always use NULL as the "main" host, screen coords/dimensions are adjusted automatically */
		this_host=NULL;

		/* find total number of immediate parents for this host */
		parent_hosts=number_of_immediate_parent_hosts(this_host);

		/* find the max layer width we have... */
		max_layer_width=max_child_host_layer_members(this_host);
		if(parent_hosts>max_layer_width)
			max_layer_width=parent_hosts;

		/* calculate center x coord */
		center_x=(((DEFAULT_NODE_WIDTH*max_layer_width)+(DEFAULT_NODE_HSPACING*(max_layer_width-1)))/2)+offset_x;

		/* coords for Nagios icon if necessary */
		if(this_host==NULL || this_host->parent_hosts==NULL){
			nagios_icon_x=center_x;
			nagios_icon_y=offset_y;
			draw_nagios_icon=TRUE;
		        }

		/* do we need to draw a link to parent(s)? */
		if(this_host!=NULL && is_host_immediate_child_of_host(NULL,this_host)==FALSE)
			offset_y+=DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING;

		/* see which hosts we should draw and calculate drawing coords */
		for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next){

			/* find the host that matches this entry */
			temp_host=find_host(temp_hostextinfo->host_name,NULL);

			if(temp_host==NULL)
				continue;
			
			/* this is an immediate parent of the "main" host we're drawing */
			else if(is_host_immediate_parent_of_host(this_host,temp_host)==TRUE){
				temp_hostextinfo->should_be_drawn=TRUE;
				temp_hostextinfo->have_3d_coords=TRUE;
				temp_hostextinfo->x_3d=center_x-(((parent_hosts*DEFAULT_NODE_WIDTH)+((parent_hosts-1)*DEFAULT_NODE_HSPACING))/2)+(current_parent_host*(DEFAULT_NODE_WIDTH+DEFAULT_NODE_HSPACING))+(DEFAULT_NODE_WIDTH/2);
				temp_hostextinfo->y_3d=offset_y;
				current_parent_host++;
			        }
			
			/* this is the "main" host we're drawing */
			else if(this_host==temp_host){
				temp_hostextinfo->should_be_drawn=TRUE;
				temp_hostextinfo->have_3d_coords=TRUE;
				temp_hostextinfo->x_3d=center_x;
				temp_hostextinfo->y_3d=DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING+offset_y;
			        }

			/* else do not draw this host (we might if its a child - see below, but assume no for now) */
			else{
				temp_hostextinfo->should_be_drawn=FALSE;
				temp_hostextinfo->have_3d_coords=FALSE;
			        }
		        }


		/* TODO: REORDER CHILD LAYER MEMBERS SO THAT WE MINIMIZE LINK CROSSOVERS FROM PARENT HOSTS */

		/* draw hosts in child "layers" */
		for(current_layer=1;;current_layer++){
			
			/* how many members in this layer? */
			layer_members=number_of_host_layer_members(this_host,current_layer);

			if(layer_members==0)
				break;

			current_layer_member=0;

			/* see which hosts are members of this layer and calculate drawing coords */
			for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next){

				/* find the host that matches this entry */
				temp_host=find_host(temp_hostextinfo->host_name,NULL);

				if(temp_host==NULL)
					continue;

				/* is this host a member of the current child layer? */
				if(host_child_depth_separation(this_host,temp_host)==current_layer){
					temp_hostextinfo->should_be_drawn=TRUE;
					temp_hostextinfo->have_3d_coords=TRUE;
					temp_hostextinfo->x_3d=center_x-(((layer_members*DEFAULT_NODE_WIDTH)+((layer_members-1)*DEFAULT_NODE_HSPACING))/2)+(current_layer_member*(DEFAULT_NODE_WIDTH+DEFAULT_NODE_HSPACING))+(DEFAULT_NODE_WIDTH/2);
					if(this_host==NULL)
						temp_hostextinfo->y_3d=((DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING)*current_layer)+offset_y;
					else
						temp_hostextinfo->y_3d=((DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING)*(current_layer+1))+offset_y;
					current_layer_member++;
				        }
			        }
		        }

	        }


	/***** "BALANCED" TREE MODE *****/
	else if(layout_method==LAYOUT_BALANCED_TREE){

		/* always use NULL as the "main" host, screen coords/dimensions are adjusted automatically */
		this_host=NULL;

		/* find total number of immediate parents for this host */
		parent_hosts=number_of_immediate_parent_hosts(this_host);

		/* find the max drawing width we have... */
		max_drawing_width=max_child_host_drawing_width(this_host);
		if(parent_hosts>max_drawing_width)
			max_drawing_width=parent_hosts;

		/* calculate center x coord */
		center_x=(((DEFAULT_NODE_WIDTH*max_drawing_width)+(DEFAULT_NODE_HSPACING*(max_drawing_width-1)))/2)+offset_x;

		/* coords for Nagios icon if necessary */
		if(this_host==NULL || this_host->parent_hosts==NULL){
			nagios_icon_x=center_x;
			nagios_icon_y=offset_y;
			draw_nagios_icon=TRUE;
		        }

		/* do we need to draw a link to parent(s)? */
		if(this_host!=NULL && is_host_immediate_child_of_host(NULL,this_host)==FALSE)
			offset_y+=DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING;

		/* see which hosts we should draw and calculate drawing coords */
		for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next){

			/* find the host that matches this entry */
			temp_host=find_host(temp_hostextinfo->host_name,NULL);

			if(temp_host==NULL)
				continue;
			
			/* this is an immediate parent of the "main" host we're drawing */
			else if(is_host_immediate_parent_of_host(this_host,temp_host)==TRUE){
				temp_hostextinfo->should_be_drawn=TRUE;
				temp_hostextinfo->have_3d_coords=TRUE;
				temp_hostextinfo->x_3d=center_x-(((parent_hosts*DEFAULT_NODE_WIDTH)+((parent_hosts-1)*DEFAULT_NODE_HSPACING))/2)+(current_parent_host*(DEFAULT_NODE_WIDTH+DEFAULT_NODE_HSPACING))+(DEFAULT_NODE_WIDTH/2);
				temp_hostextinfo->y_3d=offset_y;
				current_parent_host++;
			        }
			
			/* this is the "main" host we're drawing */
			else if(this_host==temp_host){
				temp_hostextinfo->should_be_drawn=TRUE;
				temp_hostextinfo->have_3d_coords=TRUE;
				temp_hostextinfo->x_3d=center_x;
				temp_hostextinfo->y_3d=DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING+offset_y;
			        }

			/* else do not draw this host (we might if its a child - see below, but assume no for now) */
			else{
				temp_hostextinfo->should_be_drawn=FALSE;
				temp_hostextinfo->have_3d_coords=FALSE;
			        }
		        }

		/* draw all children hosts */
		calculate_balanced_tree_coords(this_host,center_x,DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING+offset_y);

	        }


	/***** CIRCULAR LAYOUT MODE *****/
	else if(layout_method==LAYOUT_CIRCULAR){

		/* draw process icon */
		nagios_icon_x=0;
		nagios_icon_y=0;
		draw_nagios_icon=TRUE;

		/* calculate coordinates for all hosts */
		calculate_circular_coords();
	        }

	return;
        }



/* calculate world dimensions */
void calculate_world_bounds(void){
	hostextinfo *temp_hostextinfo;

	min_x_coord=0.0;
	min_y_coord=0.0;
	min_z_coord=0.0;
	max_x_coord=0.0;
	max_y_coord=0.0;
	max_z_coord=0.0;

	/* check all extended host entries */
	for(temp_hostextinfo=hostextinfo_list;temp_hostextinfo!=NULL;temp_hostextinfo=temp_hostextinfo->next){

		if(temp_hostextinfo->have_3d_coords==FALSE){
			temp_hostextinfo->should_be_drawn=FALSE;
			continue;
		        }

		if(temp_hostextinfo->should_be_drawn==FALSE)
			continue;

		if(temp_hostextinfo->x_3d < min_x_coord)
			min_x_coord=temp_hostextinfo->x_3d;
		else if(temp_hostextinfo->x_3d > max_x_coord)
			max_x_coord=temp_hostextinfo->x_3d;
		if(temp_hostextinfo->y_3d < min_y_coord)
			min_y_coord=temp_hostextinfo->y_3d;
		else if(temp_hostextinfo->y_3d > max_y_coord)
			max_y_coord=temp_hostextinfo->y_3d;
		if(temp_hostextinfo->z_3d < min_z_coord)
			min_x_coord=temp_hostextinfo->z_3d;
		else if(temp_hostextinfo->z_3d > max_z_coord)
			max_z_coord=temp_hostextinfo->z_3d;

		coordinates_were_specified=TRUE;
	        }

	/* no drawing coordinates were specified */
	if(coordinates_were_specified==FALSE){
		min_x_coord=0.0;
		max_x_coord=0.0;
		min_y_coord=0.0;
		max_y_coord=0.0;
		min_z_coord=0.0;
		max_z_coord=6.0;
	        }

	max_world_size=max_x_coord-min_x_coord;
	if(max_world_size<(max_y_coord-min_y_coord))
		max_world_size=max_y_coord-min_y_coord;
	if(max_world_size<(max_z_coord-min_z_coord))
		max_world_size=max_z_coord-min_z_coord;

	return;
        }


/******************************************************************/
/*********************** DRAWING FUNCTIONS ************************/
/******************************************************************/


/* write global VRML data */
void write_global_vrml_data(void){
	hostextinfo *temp_hostextinfo;
	float visibility_range=0.0;
	float viewpoint_z=0.0;

	/* write VRML code header */
	printf("#VRML V2.0 utf8\n");

	/* write world information */
	printf("\n");
	printf("WorldInfo{\n");
	printf("title \"Nagios 3-D Network Status View\"\n");
	printf("info [\"Copyright (c) 1999-2002 Ethan Galstad\"\n");
        printf("\"nagios@nagios.org\"]\n");
	printf("}\n");

	/* background color */
	printf("\n");
	printf("Background{\n");
	printf("skyColor 0.1 0.1 0.15\n");
	printf("}\n");

	/* calculate visibility range - don't let it get too low */
	visibility_range=(max_world_size*2.0);
	if(visibility_range<25.0)
		visibility_range=25.0;

	/* write fog information */
	printf("\n");
	printf("Fog{\n");
	printf("color 0.1 0.1 0.15\n");
	printf("fogType \"EXPONENTIAL\"\n");
	printf("visibilityRange %2.2f\n",visibility_range);
	printf("}\n");

	/* custom viewpoint */
	if(custom_viewpoint==TRUE){
		printf("\n");
		printf("Viewpoint{\n");
		printf("position %2.2f %2.2f %2.2f\n",custom_viewpoint_x,custom_viewpoint_y,custom_viewpoint_z);
		printf("fieldOfView 0.78\n");
		printf("description \"Entry Viewpoint\"\n");
		printf("}\n");
	        }

	/* host close-up viewpoint */
	if(show_all_hosts==FALSE){

		temp_hostextinfo=find_hostextinfo(host_name);
		if(temp_hostextinfo!=NULL && temp_hostextinfo->have_3d_coords==TRUE){
			printf("\n");
			printf("Viewpoint{\n");
			printf("position %2.3f %2.3f %2.3f\n",temp_hostextinfo->x_3d,temp_hostextinfo->y_3d,temp_hostextinfo->z_3d+5.0);
			printf("fieldOfView 0.78\n");
			printf("description \"Host Close-Up Viewpoint\"\n");
			printf("}\n");
	                }
	        }

	/* calculate z coord for default viewpoint - don't get too close */
	viewpoint_z=max_world_size;
	if(viewpoint_z<10.0)
		viewpoint_z=10.0;

	/* default viewpoint */
	printf("\n");
	printf("Viewpoint{\n");
	printf("position %2.2f %2.2f %2.2f\n",min_x_coord+((max_x_coord-min_x_coord)/2.0),min_y_coord+((max_y_coord-min_y_coord)/2.0),viewpoint_z);
	printf("fieldOfView 0.78\n");
	printf("description \"Default Viewpoint\"\n");
	printf("}\n");

	/* problem timer */
	printf("DEF ProblemTimer TimeSensor{\n");
	printf("loop TRUE\n");
	printf("cycleInterval 5\n");
	printf("}\n");

	/* host text prototype */
	printf("PROTO HostText[\n");
	printf("field MFString the_text [\"\"]\n");
	printf("field SFColor font_color 0.6 0.6 0.6");
	printf("]\n");
	printf("{\n");
	printf("Billboard{\n");
	printf("children[\n");
	printf("Shape{\n");
	printf("appearance Appearance {\n");
	printf("material Material {\n");
	printf("diffuseColor IS font_color\n");
	printf("}\n");
	printf("}\n");
	printf("geometry Text {\n");
	printf("string IS the_text\n");
	printf("fontStyle FontStyle {\n");
	printf("family \"TYPEWRITER\"\n");
	printf("size 0.1\n");
	printf("justify \"MIDDLE\"\n");
	printf("}\n");
	printf("}\n");
	printf("}\n");
	printf("]\n");
	printf("}\n");
	printf("}\n");

	/* include user-defined world */
	if(statuswrl_include!=NULL && coordinates_were_specified==TRUE && layout_method==LAYOUT_USER_SUPPLIED){
		printf("\n");
		printf("Inline{\n");
		printf("url \"%s%s\"\n",url_html_path,statuswrl_include);
		printf("}\n");
	        }

	return;
	}



/* draws a host */
void draw_host(hostextinfo *temp_hostextinfo){
	host *temp_host;
	hoststatus *temp_hoststatus=NULL;
	char state_string[16]="";
	double x, y, z;
	char *vrml_safe_hostname=NULL;
	int a, ch;

	if(temp_hostextinfo==NULL)
		return;

	/* make sure we have the coordinates */
	if(temp_hostextinfo->have_3d_coords==FALSE)
		return;
	else{
		x=temp_hostextinfo->x_3d;
		y=temp_hostextinfo->y_3d;
		z=temp_hostextinfo->z_3d;
	        }

	/* find the config entry for this host */
	temp_host=find_host(temp_hostextinfo->host_name,NULL);
	if(temp_host==NULL)
		return;

	/* make the host name safe for embedding in VRML */
	vrml_safe_hostname=(char *)strdup(temp_host->name);
	if(vrml_safe_hostname==NULL)
		return;
	for(a=0;vrml_safe_hostname[a]!='\x0';a++){
		ch=vrml_safe_hostname[a];
		if((ch<'a' || ch>'z') && (ch<'A' || ch>'Z') && (ch<'0' || ch>'9'))
			vrml_safe_hostname[a]='_';
	        }

	/* see if user is authorized to view this host  */
	if(is_authorized_for_host(temp_host,&current_authdata)==FALSE)
		return;

	/* get the status of the host */
	temp_hoststatus=find_hoststatus(temp_host->name);

	printf("\n");


	/* host object */
	printf("Anchor{\n");
	printf("children[\n");

	printf("Transform {\n");
	printf("translation %2.2f %2.2f %2.2f\n",x,y,z);
	printf("children [\n");

	printf("DEF Host%s Shape{\n",vrml_safe_hostname);
	printf("appearance Appearance{\n");
	printf("material DEF HostMat%s Material{\n",vrml_safe_hostname);
	if(temp_hoststatus==NULL)
		printf("emissiveColor 0.2 0.2 0.2\ndiffuseColor 0.2 0.2 0.2\n");
	else if(temp_hoststatus->status==HOST_UP)
		printf("emissiveColor 0.2 1.0 0.2\ndiffuseColor 0.2 1.0 0.2\n");
	else
		printf("emissiveColor 1.0 0.2 0.2\ndiffuseColor 1.0 0.2 0.2\n");
	printf("transparency 0.4\n");
	printf("}\n");
	if(use_textures==TRUE && temp_hostextinfo->vrml_image!=NULL){
		printf("texture ImageTexture{\n");
		printf("url \"%s%s\"\n",url_logo_images_path,temp_hostextinfo->vrml_image);
		printf("}\n");
		}
	printf("}\n");
	printf("geometry Box{\n");
	printf("size %2.2f %2.2f %2.2f\n",node_width,node_width,node_width);
	printf("}\n");
	printf("}\n");

	printf("]\n");
	printf("}\n");

	printf("]\n");
	printf("description \"View status details for host '%s' (%s)\"\n",temp_host->name,temp_host->alias);
	printf("url \"%s?host=%s\"\n",STATUS_CGI,temp_host->name);
	printf("}\n");


	/* draw status text */
	if(use_text==TRUE){

		printf("\n");
		printf("Transform{\n");
		printf("translation %2.3f %2.3f %2.3f\n",x,y+DEFAULT_NODE_WIDTH,z);
		printf("children[\n");
		printf("HostText{\n");

		if(temp_hoststatus!=NULL){
			if(temp_hoststatus->status==HOST_UP)
				printf("font_color 0 1 0\n");
			else if(temp_hoststatus->status==HOST_DOWN || temp_hoststatus->status==HOST_UNREACHABLE)
				printf("font_color 1 0 0\n");
	                }
		printf("the_text [\"%s\", \"%s\", ",temp_host->name,temp_host->alias);
		if(temp_hoststatus==NULL)
			strcpy(state_string,"UNKNOWN");
		else{
			if(temp_hoststatus->status==HOST_DOWN)
				strcpy(state_string,"DOWN");
			else if(temp_hoststatus->status==HOST_UNREACHABLE)
				strcpy(state_string,"UNREACHABLE");
			else if(temp_hoststatus->status==HOST_PENDING)
				strcpy(state_string,"PENDING");
			else
				strcpy(state_string,"UP");
	                }
		printf("\"%s\"]\n",state_string);

		printf("}\n");
		printf("]\n");
		printf("}\n");
	        }

	/* host is down or unreachable, so make it fade in and out */
	if(temp_hoststatus!=NULL && (temp_hoststatus->status==HOST_DOWN || temp_hoststatus->status==HOST_UNREACHABLE))
		printf("ROUTE ProblemTimer.fraction_changed TO HostMat%s.set_transparency\n",vrml_safe_hostname);

	free(vrml_safe_hostname);

	return;
	}



/* draw links between hosts */
void draw_host_links(void){
	hostextinfo *child_hostextinfo;
	hostextinfo *parent_hostextinfo;
	host *parent_host;
	host *child_host;

	if(use_links==FALSE)
		return;

	for(child_hostextinfo=hostextinfo_list;child_hostextinfo!=NULL;child_hostextinfo=child_hostextinfo->next){

		if(child_hostextinfo->have_3d_coords==FALSE)
			continue;

		child_host=find_host(child_hostextinfo->host_name,NULL);
		if(child_host==NULL)
			continue;

		/* check authorization */
		if(is_authorized_for_host(child_host,&current_authdata)==FALSE)
			continue;

		/* draw a link from this host to all of its parent hosts */
		for(parent_host=host_list;parent_host!=NULL;parent_host=parent_host->next){

			if(is_host_immediate_child_of_host(child_host,parent_host)==TRUE){

				parent_hostextinfo=find_hostextinfo(parent_host->name);

				if(parent_hostextinfo==NULL)
					continue;

				if(parent_hostextinfo->have_3d_coords==FALSE)
					continue;
				
				/* check authorization */
				if(is_authorized_for_host(parent_host,&current_authdata)==FALSE)
					continue;

				/* draw the link between the child and parent hosts */
				draw_host_link(parent_host,parent_hostextinfo->x_3d,parent_hostextinfo->y_3d,parent_hostextinfo->z_3d,child_hostextinfo->x_3d,child_hostextinfo->y_3d,child_hostextinfo->z_3d);
			        }
		        }
	        }

	
	return;
        }




/* draws a link from a parent host to a child host */
void draw_host_link(host *hst,double x0, double y0, double z0, double x1, double y1, double z1){

	printf("\n");

	if(hst!=NULL)
		printf("# Host '%s' LINK\n",hst->name);

	printf("Shape{\n");

	printf("appearance DEF MATslategrey_0_ Appearance {\n");
	printf("material Material {\n");
	printf("diffuseColor 0.6 0.6 0.6\n");
	printf("ambientIntensity 0.5\n");
	printf("emissiveColor 0.6 0.6 0.6\n");
        printf("}\n");
	printf("}\n");

	printf("geometry IndexedLineSet{\n");
	printf("coord Coordinate{\n");
	printf("point [ %2.3f %2.3f %2.3f, %2.3f %2.3f %2.3f ]\n",x0,y0,z0,x1,y1,z1);
	printf("}\n");
	printf("coordIndex [ 0,1,-1 ]\n");
	printf("}\n");

	printf("}\n");

	return;
	}



/* draw process icon */
void draw_process_icon(void){
	hostextinfo *child_hostextinfo;
	hostextinfo *parent_hostextinfo;
	host *parent_host;
	host *child_host;

	if(draw_nagios_icon==FALSE)
		return;

	/* draw process icon */
	printf("\n");


	printf("Anchor{\n");
	printf("children[\n");

	printf("Transform {\n");
	printf("translation %2.2f %2.2f %2.2f\n",nagios_icon_x,nagios_icon_y,0.0);
	printf("children [\n");

	printf("DEF ProcessNode Shape{\n");
	printf("appearance Appearance{\n");
	printf("material Material{\n");
	printf("emissiveColor 0.5 0.5 0.5\n");
	printf("diffuseColor 0.5 0.5 0.5\n");
	printf("transparency 0.2\n");
	printf("}\n");
	if(use_textures==TRUE){
		printf("texture ImageTexture{\n");
		printf("url \"%s%s\"\n",url_logo_images_path,NAGIOS_VRML_IMAGE);
		printf("}\n");
		}
	printf("}\n");
	printf("geometry Box{\n");
	printf("size %2.2f %2.2f %2.2f\n",node_width*3.0,node_width*3.0,node_width*3.0);
	printf("}\n");
	printf("}\n");

	printf("]\n");
	printf("}\n");

	printf("]\n");
	printf("description \"View Nagios Process Information\"\n");
	printf("url \"%s?type=%d\"\n",EXTINFO_CGI,DISPLAY_PROCESS_INFO);
	printf("}\n");


	if(use_links==FALSE)
		return;

	/* draw links to immediate child hosts */
	for(child_hostextinfo=hostextinfo_list;child_hostextinfo!=NULL;child_hostextinfo=child_hostextinfo->next){

		if(child_hostextinfo->have_3d_coords==FALSE)
			continue;

		child_host=find_host(child_hostextinfo->host_name,NULL);
		if(child_host==NULL)
			continue;

		/* check authorization */
		if(is_authorized_for_host(child_host,&current_authdata)==FALSE)
			continue;

		/* draw a link to the host */
		if(is_host_immediate_child_of_host(NULL,child_host)==TRUE)
			draw_host_link(NULL,nagios_icon_x,nagios_icon_y,0.0,child_hostextinfo->x_3d,child_hostextinfo->y_3d,child_hostextinfo->z_3d);
	        }

	return;
        }




/******************************************************************/
/***************** COORDINATE CALCULATION FUNCTIONS ***************/
/******************************************************************/

/* calculates coords of a host's children - used by balanced tree layout method */
void calculate_balanced_tree_coords(host *parent, int x, int y){
	int parent_drawing_width;
	int start_drawing_x;
	int current_drawing_x;
	int this_drawing_width;
	host *temp_host;
	hostextinfo *temp_hostextinfo;

	/* calculate total drawing width of parent host */
	parent_drawing_width=max_child_host_drawing_width(parent);

	/* calculate starting x coord */
	start_drawing_x=x-(((DEFAULT_NODE_WIDTH*parent_drawing_width)+(DEFAULT_NODE_HSPACING*(parent_drawing_width-1)))/2);
	current_drawing_x=start_drawing_x;


	/* calculate coords for children */
	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){

		temp_hostextinfo=find_hostextinfo(temp_host->name);
		if(temp_hostextinfo==NULL)
			continue;

		if(is_host_immediate_child_of_host(parent,temp_host)==TRUE){

			/* get drawing width of child host */
			this_drawing_width=max_child_host_drawing_width(temp_host);

			temp_hostextinfo->x_3d=current_drawing_x+(((DEFAULT_NODE_WIDTH*this_drawing_width)+(DEFAULT_NODE_HSPACING*(this_drawing_width-1)))/2);
			temp_hostextinfo->y_3d=y+DEFAULT_NODE_HEIGHT+DEFAULT_NODE_VSPACING;
			temp_hostextinfo->have_3d_coords=TRUE;
			temp_hostextinfo->should_be_drawn=TRUE;
			
			current_drawing_x+=(this_drawing_width*DEFAULT_NODE_WIDTH)+((this_drawing_width-1)*DEFAULT_NODE_HSPACING)+DEFAULT_NODE_HSPACING;

			/* recurse into child host ... */
			calculate_balanced_tree_coords(temp_host,temp_hostextinfo->x_3d,temp_hostextinfo->y_3d);
		        }

	        }

	return;
        }


/* calculate coords of all hosts in circular layout method */
void calculate_circular_coords(void){

	/* calculate all host coords, starting with first layer */
	calculate_circular_layer_coords(NULL,0.0,360.0,1,CIRCULAR_DRAWING_RADIUS);

	return;
        }
	

/* calculates coords of all hosts in a particular "layer" in circular layout method */
void calculate_circular_layer_coords(host *parent, double start_angle, double useable_angle, int layer, int radius){
	int parent_drawing_width=0;
	int this_drawing_width=0;
	int immediate_children=0;
	double current_drawing_angle=0.0;
	double this_drawing_angle=0.0;
	double available_angle=0.0;
	double clipped_available_angle=0.0;
	double average_child_angle=0.0;
	double x_coord=0.0;
	double y_coord=0.0;
	host *temp_host;
	hostextinfo *temp_hostextinfo;


	/* get the total number of immediate children to this host */
	immediate_children=number_of_immediate_child_hosts(parent);

	/* bail out if we're done */
	if(immediate_children==0)
		return;

	/* calculate total drawing "width" of parent host */
	parent_drawing_width=max_child_host_drawing_width(parent);

	/* calculate average angle given to each child host */
	average_child_angle=(double)(useable_angle/(double)immediate_children);

	/* calculate initial drawing angle */
	current_drawing_angle=start_angle;


	/* calculate coords for children */
	for(temp_host=host_list;temp_host!=NULL;temp_host=temp_host->next){

		temp_hostextinfo=find_hostextinfo(temp_host->name);
		if(temp_hostextinfo==NULL)
			continue;

		if(is_host_immediate_child_of_host(parent,temp_host)==TRUE){

			/* get drawing width of child host */
			this_drawing_width=max_child_host_drawing_width(temp_host);

			/* calculate angle this host gets for drawing */
			available_angle=useable_angle*((double)this_drawing_width/(double)parent_drawing_width);

			/* clip available angle if necessary */
			/* this isn't really necessary, but helps keep things looking a bit more sane with less potential connection crossover */
			clipped_available_angle=360.0/layer;
			if(available_angle<clipped_available_angle)
				clipped_available_angle=available_angle;

			/* calculate the exact angle at which we should draw this child */
			this_drawing_angle=current_drawing_angle+(available_angle/2.0);

			/* compensate for angle overflow */
			while(this_drawing_angle>=360.0)
				this_drawing_angle-=360.0;
			while(this_drawing_angle<0.0)
				this_drawing_angle+=360.0;

			/* calculate drawing coords of this host using good ol' geometry... */
			x_coord=-(sin(-this_drawing_angle*(M_PI/180.0))*radius);
			y_coord=-(sin((90+this_drawing_angle)*(M_PI/180.0))*radius);

			temp_hostextinfo->x_3d=(int)x_coord;
			temp_hostextinfo->y_3d=(int)y_coord;
			temp_hostextinfo->have_3d_coords=TRUE;
			temp_hostextinfo->should_be_drawn=TRUE;

			/* recurse into child host ... */
			calculate_circular_layer_coords(temp_host,current_drawing_angle+((available_angle-clipped_available_angle)/2),clipped_available_angle,layer+1,radius+CIRCULAR_DRAWING_RADIUS);

			/* increment current drawing angle */
			current_drawing_angle+=available_angle;
		        }
	        }

	return;
        }


