<?php
/*
 +-------------------------------------------------------------------------+
 | Copyright (C) 2022-2025 The Cacti Group, Inc.                           |
 |                                                                         |
 | Based on the Original Plugin developed by Howard Jones                  |
 |                                                                         |
 | Copyright (C) 2005-2022 Howard Jones and contributors                   |
 |                                                                         |
 | Permission is hereby granted, free of charge, to any person obtaining   |
 | a copy of this software and associated documentation files              |
 | (the "Software"), to deal in the Software without restriction,          |
 | including without limitation the rights to use, copy, modify, merge,    |
 | publish, distribute, sublicense, and/or sell copies of the Software,    |
 | and to permit persons to whom the Software is furnished to do so,       |
 | subject to the following conditions:                                    |
 |                                                                         |
 | The above copyright notice and this permission notice shall be          |
 | included in all copies or substantial portions of the Software.         |
 |                                                                         |
 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,         |
 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES         |
 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                |
 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS     |
 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN      |
 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN       |
 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE        |
 | SOFTWARE.                                                               |
 +-------------------------------------------------------------------------+
 | Cacti: The Complete RRDtool-based Graphing Solution                     |
 +-------------------------------------------------------------------------+
 | Extensions to Howard Jones' original work are designed, written, and    |
 | maintained by the Cacti Group.                                          |
 |                                                                         |
 | Howard Jones was the original author of Weathermap.  You can reach      |
 | him at: howie@thingy.com                                                |
 +-------------------------------------------------------------------------+
 | http://www.network-weathermap.com/                                      |
 | http://www.cacti.net/                                                   |
 +-------------------------------------------------------------------------+
*/

require_once('HTML_ImageMap.class.php');

class WeatherMapNode extends WeatherMapItem {
	var $owner;
	var $id;
	var $x,	$y;
	var $original_x, $original_y,$relative_resolved;
	var $width, $height;
	var $label, $proclabel, $labelfont;
	var $labelangle;
	var $name;
	var $infourl = array();
	var $notes;
	var $colours = array();
	var $overliburl;
	var $overlibwidth, $overlibheight;
	var $overlibcaption = array();
	var $maphtml;
	var $selected = 0;
	var $iconfile, $iconscalew, $iconscaleh;
	var $targets = array();
	var $bandwidth_in, $bandwidth_out;
	var $inpercent, $outpercent;
	var $max_bandwidth_in, $max_bandwidth_out;
	var $max_bandwidth_in_cfg, $max_bandwidth_out_cfg;
	var $labeloffset, $labeloffsetx, $labeloffsety;

	var $inherit_fieldlist;

	var $labelbgcolour;
	var $labeloutlinecolour;
	var $labelfontcolour;
	var $labelfontshadowcolour;

	var $cachefile;
	var $usescale;
	var $useiconscale;
	var $scaletype, $iconscaletype;
	var $inscalekey,$outscalekey;
	var $inscaletag, $outscaletag;

	# var $incolour,$outcolour;
	var $scalevar, $iconscalevar;
	var $notestext = array();
	var $image;
	var $centre_x, $centre_y;
	var $relative_to;
	var $zorder;
	var $template;
	var $polar;
	var $boundingboxes=array();

	// PHP 8.1. QA
	var $aiconfillcolour;
	var $aiconoutlinecolour;

	function __construct() {
		$this->inherit_fieldlist = array(
			'boundingboxes'         => array(),
			'my_default'            => null,
			'label'                 => '',
			'proclabel'             => '',
			'usescale'              => 'DEFAULT',
			'scaletype'             => 'percent',
			'iconscaletype'         => 'percent',
			'useiconscale'          => 'none',
			'scalevar'              => 'in',
			'template'              => ':: DEFAULT ::',
			'iconscalevar'          => 'in',
			'labelfont'             => 3,
			'relative_to'           => '',
			'relative_resolved'     => false,
			'x'                     => null,
			'y'                     => null,
			'inscalekey'            => '',
			'outscalekey'           => '',
			#'incolour'             => -1,
			#'outcolour'            => -1,
			'original_x'            => 0,
			'original_y'            => 0,
			'inpercent'             => 0,
			'outpercent'            => 0,
			'labelangle'            => 0,
			'iconfile'              => '',
			'iconscalew'            => 0,
			'iconscaleh'            => 0,
			'targets'               => array(),
			'infourl'               => array(IN => '', OUT => ''),
			'notestext'             => array(IN => '', OUT => ''),
			'notes'                 => array(),
			'hints'                 => array(),
			'overliburl'            => array(IN => array(), OUT => array()),
			'overlibwidth'          => 700,
			'overlibheight'         => 200,
			'overlibcaption'        => array(IN => '', OUT => ''),
			'labeloutlinecolour'    => array(0, 0, 0),
			'labelbgcolour'         => array(255, 255, 255),
			'labelfontcolour'       => array(0, 0, 0),
			'labelfontshadowcolour' => array(-1, -1, -1),
			'aiconoutlinecolour'    => array(0,0,0),
			'aiconfillcolour'       => array(-2,-2,-2), // copy from the node label
			'labeloffset'           => '',
			'labeloffsetx'          => 0,
			'labeloffsety'          => 0,
			'zorder'                => 600,
			'max_bandwidth_in'      => 100,
			'max_bandwidth_out'     => 100,
			'max_bandwidth_in_cfg'  => '100',
			'max_bandwidth_out_cfg' => '100'
		);

		$this->width    = 0;
		$this->height   = 0;
		$this->centre_x = 0;
		$this->centre_y = 0;
		$this->polar    = false;
		$this->image    = null;
	}

	function my_type() {
		return 'NODE';
	}

	// make a mini-image, containing this node and nothing else
	// figure out where the real NODE centre is, relative to the top-left corner.
	function pre_render($image, &$map) {
		// don't bother drawing if there's no position - it's a template
		if (is_null($this->x)) {
			return;
		}

		if (is_null($this->y)) {
			return;
		}

		// apparently, some versions of the gd extension will crash
		// if we continue...
		if ($this->label == '' && $this->iconfile=='') {
			return;
		}

		// start these off with sensible values, so that bbox
		// calculations are easier.
		$icon_x1   = $this->x;
		$icon_x2   = $this->x;
		$icon_y1   = $this->y;
		$icon_y2   = $this->y;
		$label_x1  = $this->x;
		$label_x2  = $this->x;
		$label_y1  = $this->y;
		$label_y2  = $this->y;
		$boxwidth  = 0;
		$boxheight = 0;
		$icon_w    = 0;
		$icon_h    = 0;

		$col = new Colour(-1, -1, -1);

		# print $col->as_string();

		// if a target is specified, and you haven't forced no background, then the background will
		// come from the SCALE in USESCALE
		if (!empty($this->targets) && $this->usescale != 'none') {
			$pc = 0;

			if ($this->scalevar == 'in') {
				$pc  = $this->inpercent;
				$col = $this->colours[IN];
			}

			if ($this->scalevar == 'out') {
				$pc  = $this->outpercent;
				$col = $this->colours[OUT];
			}
		} elseif ($this->labelbgcolour != array(-1,-1,-1)) {
			// $col=myimagecolorallocate($node_im, $this->labelbgcolour[0], $this->labelbgcolour[1], $this->labelbgcolour[2]);
			$col = new Colour($this->labelbgcolour);
		}

		$colicon = null;
		if (!empty($this->targets) && $this->useiconscale != 'none') {
			wm_debug('Colorising the icon');

			$pc  = 0;
			$val = 0;

			if ($this->iconscalevar == 'in') {
				$pc  = $this->inpercent;
				//$col = $this->colours[IN];
				$val = $this->bandwidth_in;
			}

			if ($this->iconscalevar == 'out') {
				$pc  = $this->outpercent;
				//$col = $this->colours[OUT];
				$val = $this->bandwidth_out;
			}

			if ($this->iconscaletype == 'percent') {
				list($colicon,$node_iconscalekey,$icontag) =
					$map->NewColourFromPercent($pc, $this->useiconscale,$this->name );
			} else {
				// use the absolute value if we aren't doing percentage scales.
				list($colicon,$node_iconscalekey,$icontag) = $map->NewColourFromPercent($val, $this->useiconscale, $this->name, false);
			}
		}

		// figure out a bounding rectangle for the label
		if ($this->label != '') {
			$padding   = 4.0;
			$padfactor = 1.0;

			$this->proclabel = $map->ProcessString($this->label, $this, true, true);

			// if screenshot_mode is enabled, wipe any letters to X and wipe any IP address to 127.0.0.1
			// hopefully that will preserve enough information to show cool stuff without leaking info
			if ($map->get_hint('screenshot_mode') == 1) {
				$this->proclabel = screenshotify($this->proclabel);
			}

			list($strwidth, $strheight) = $map->myimagestringsize($this->labelfont, $this->proclabel);

			if ($this->labelangle == 90 || $this->labelangle == 270) {
				$boxwidth  = ($strheight * $padfactor) + $padding;
				$boxheight = ($strwidth  * $padfactor) + $padding;

				wm_debug('Node->pre_render: ' . $this->name . " Label Metrics are: $strwidth x $strheight -> $boxwidth x $boxheight");

				$label_x1 = $this->x - ($boxwidth / 2);
				$label_y1 = $this->y - ($boxheight / 2);

				$label_x2 = $this->x + ($boxwidth / 2);
				$label_y2 = $this->y + ($boxheight / 2);

				if ($this->labelangle==90) {
					$txt_x = $this->x + ($strheight / 2);
					$txt_y = $this->y + ($strwidth / 2);
				}

				if ($this->labelangle==270) {
					$txt_x = $this->x - ($strheight / 2);
					$txt_y = $this->y - ($strwidth / 2);
				}
			}

			if ($this->labelangle == 0 || $this->labelangle == 180) {
				$boxwidth  = ($strwidth  * $padfactor) + $padding;
				$boxheight = ($strheight * $padfactor) + $padding;

				wm_debug("Node->pre_render: ".$this->name." Label Metrics are: $strwidth x $strheight -> $boxwidth x $boxheight");

				$label_x1 = $this->x - ($boxwidth / 2);
				$label_y1 = $this->y - ($boxheight / 2);

				$label_x2 = $this->x + ($boxwidth / 2);
				$label_y2 = $this->y + ($boxheight / 2);

				$txt_x = $this->x - ($strwidth / 2);
				$txt_y = $this->y + ($strheight / 2);

				if ($this->labelangle == 180) {
					$txt_x = $this->x + ($strwidth / 2);
					$txt_y = $this->y - ($strheight / 2);
				}

				# $this->width = $boxwidth;
				# $this->height = $boxheight;
			}

			$map->nodes[$this->name]->width = $boxwidth;
			$map->nodes[$this->name]->height = $boxheight;

			# print "TEXT at $txt_x , $txt_y\n";
		}

		// figure out a bounding rectangle for the icon
		if ($this->iconfile != '') {
			$icon_im = null;
			$icon_w  = 0;
			$icon_h  = 0;

			if ($this->iconfile == 'rbox' ||
				$this->iconfile == 'box' ||
				$this->iconfile == 'round' ||
				$this->iconfile == 'inpie' ||
				$this->iconfile == 'outpie' ||
				$this->iconfile == 'gauge' ||
				$this->iconfile == 'nink') {
				wm_debug("Artificial Icon type " . $this->iconfile . " for $this->name");

				// this is an artificial icon - we don't load a file for it

				$icon_im = imagecreatetruecolor(intval(round($this->iconscalew)), intval(round($this->iconscaleh)));

				imageSaveAlpha($icon_im, true);

				$nothing = imagecolorallocatealpha($icon_im, 128, 0, 0, 127);

				imagefill($icon_im, 0, 0, $nothing);

				$fill = null;
				$ink  = null;

				$aifill = new Colour($this->aiconfillcolour);
				$aiink  = new Colour($this->aiconoutlinecolour);

				// if useiconscale isn't set, then use the static
				// colour defined, or copy the colour from the label
				if ($this->useiconscale == 'none') {
					if ($aifill->is_copy() && !$col->is_none()) {
						$fill = $col;
					} elseif ($aifill->is_real()) {
						$fill = $aifill;
					}
				} else {
					// if useiconscale IS defined, use that to figure out
					// the fill colour
					$pc  = 0;
					$val = 0;

					if ($this->iconscalevar == 'in') {
						$pc  = $this->inpercent;
						$val = $this->bandwidth_in;
					}

					if ($this->iconscalevar == 'out') {
						$pc  = $this->outpercent;
						$val = $this->bandwidth_out;
					}

					if ($this->iconscaletype == 'percent') {
						list($fill, $junk, $junk) =
						$map->NewColourFromPercent($val, $this->useiconscale,$this->name, false );
					} else {
						// use the absolute value if we aren't doing percentage scales.
						list($fill,  $junk, $junk) = $map->NewColourFromPercent($val, $this->useiconscale,$this->name, false );
					}
				}

				if ($this->aiconoutlinecolour != array(-1, -1, -1)) {
					$ink = $aiink;
				}

				if ($this->iconfile == 'box') {
					if ($fill !== null && !$fill->is_none()) {
						imagefilledrectangle($icon_im, 0, 0, $this->iconscalew - 1, $this->iconscaleh - 1, $fill->gdallocate($icon_im) );
					}

					if ($ink !== null && !$ink->is_none()) {
						imagerectangle($icon_im, 0, 0, $this->iconscalew - 1, $this->iconscaleh - 1, $ink->gdallocate($icon_im) );
					}
				} elseif ($this->iconfile == 'rbox') {
					if ($fill !== null && !$fill->is_none()) {
						imagefilledroundedrectangle($icon_im, 0, 0, $this->iconscalew - 1, $this->iconscaleh - 1, 4, $fill->gdallocate($icon_im));
					}

					if ($ink !== null && !$ink->is_none()) {
						imageroundedrectangle($icon_im, 0, 0, $this->iconscalew - 1, $this->iconscaleh - 1, 4, $ink->gdallocate($icon_im));
					}
				} elseif ($this->iconfile == 'round') {
					$rx = $this->iconscalew / 2 - 1;
					$ry = $this->iconscaleh / 2 - 1;

					if ($fill !== null && !$fill->is_none()) {
						imagefilledellipse($icon_im, $rx, $ry, $rx*2, $ry*2, $fill->gdallocate($icon_im));
					}

					if ($ink !== null && !$ink->is_none()) {
						imageellipse($icon_im, $rx, $ry, $rx*2, $ry*2, $ink->gdallocate($icon_im));
					}
				} elseif ($this->iconfile == 'nink') {
					// print "NINK **************************************************************";
					$rx = $this->iconscalew / 2 - 1;
					$ry = $this->iconscaleh / 2 - 1;

					$size    = $this->iconscalew;
					$quarter = $size / 4;

					$col1 = $this->colours[OUT];
					$col2 = $this->colours[IN];

					// assert('!is_null($col1)');
					// assert('!is_null($col2)');

					imagefilledarc($icon_im, $rx - 1, $ry, $size, $size, 270, 90, $col1->gdallocate($icon_im), IMG_ARC_PIE);
					imagefilledarc($icon_im, $rx + 1, $ry, $size, $size, 90, 270, $col2->gdallocate($icon_im), IMG_ARC_PIE);

					imagefilledarc($icon_im, $rx - 1, $ry + $quarter, $quarter * 2, $quarter *2, 0, 360, $col1->gdallocate($icon_im), IMG_ARC_PIE);
					imagefilledarc($icon_im, $rx + 1, $ry - $quarter, $quarter * 2, $quarter *2, 0, 360, $col2->gdallocate($icon_im), IMG_ARC_PIE);

					if ($ink !== null && !$ink->is_none()) {
						// XXX - need a font definition from somewhere for NINK text
						$font = 1;

						$instr  = $map->ProcessString("{node:this:bandwidth_in:%.1k}", $this);
						$outstr = $map->ProcessString("{node:this:bandwidth_out:%.1k}", $this);

						list($twid, $thgt) = $map->myimagestringsize($font, $instr);

						$map->myimagestring($icon_im, $font, $rx - $twid / 2, $ry - $quarter + ($thgt / 2), $instr, $ink->gdallocate($icon_im));

						list($twid, $thgt) = $map->myimagestringsize($font, $outstr);

						$map->myimagestring($icon_im, $font, $rx - $twid / 2,  $ry + $quarter + ($thgt / 2), $outstr, $ink->gdallocate($icon_im));

						imageellipse($icon_im, $rx, $ry, $rx * 2, $ry * 2, $ink->gdallocate($icon_im) );

						// imagearc($icon_im, $rx,$ry,$quarter*4,$quarter*4, 0,360, $ink->gdallocate($icon_im));
					}

					// print "NINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n";
				} elseif ($this->iconfile == 'inpie' || $this->iconfile == 'outpie') {
					// XXX - needs proper colours
					# list($colpie,$node_iconscalekey,$icontag) = $map->NewColourFromPercent($pc, $this->useiconscale,$this->name);

					if ($this->iconfile == 'inpie') {
						$segment_angle = (($this->inpercent) / 100) * 360;
					}

					if ($this->iconfile == 'outpie') {
						$segment_angle = (($this->outpercent) / 100) * 360;
					}

					$rx = $this->iconscalew / 2 - 1;
					$ry = $this->iconscaleh / 2 - 1;

					if ($fill !== null && !$fill->is_none()) {
						imagefilledellipse($icon_im, $rx, $ry, $rx*2, $ry*2, $fill->gdallocate($icon_im));
					}

					if ($ink !== null && !$ink->is_none()) {
						// imagefilledarc  ( resource $image  , int $cx  , int $cy  , int $width  , int $height  , int $start  , int $end  , int $color  , int $style  )
						imagefilledarc($icon_im, $rx, $ry, $rx * 2, $ry * 2, 0, $segment_angle, $ink->gdallocate($icon_im), IMG_ARC_PIE);
					}

					if ($fill !== null && !$fill->is_none()) {
						imageellipse($icon_im, $rx, $ry, $rx * 2, $ry * 2, $fill->gdallocate($icon_im));
					}

					// warn('inpie AICON not implemented yet [WMWARN99]');
				} elseif ($this->iconfile == 'gauge') {
					// if ($this->iconfile=='outpie') { warn('outpie AICON not implemented yet [WMWARN99]'); }
					wm_warn('gauge AICON not implemented yet [WMWARN99]');
				}
			} else {
				$this->iconfile = $map->ProcessString($this->iconfile, $this);

				if (is_readable($this->iconfile)) {
					imagealphablending($image, true);
					// draw the supplied icon, instead of the labelled box

					$icon_im = imagecreatefromfile($this->iconfile);
					# $icon_im = imagecreatefrompng($this->iconfile);

					if (function_exists('imagefilter') && isset($colicon) && $this->get_hint('use_imagefilter') == 1) {
						imagefilter($icon_im, IMG_FILTER_COLORIZE, $colicon->r, $colicon->g, $colicon->b);
					} elseif (isset($colicon)) {
						// debug("Skipping unavailable imagefilter() call.\n");
						imagecolorize($icon_im, $colicon->r, $colicon->g, $colicon->b);
					}

					wm_debug('If this is the last thing in your logs, you probably have a buggy GD library. Get > 2.0.33 or use PHP builtin.');

					if ($icon_im) {
						$icon_w = imagesx($icon_im);
						$icon_h = imagesy($icon_im);

						if (($this->iconscalew * $this->iconscaleh) > 0) {
							imagealphablending($icon_im, true);

							wm_debug('SCALING ICON here');

							if ($icon_w > $icon_h) {
								$scalefactor = $icon_w / $this->iconscalew;
							} else {
								$scalefactor = $icon_h / $this->iconscaleh;
							}

							$new_width  = ceil($icon_w / $scalefactor);
							$new_height = ceil($icon_h / $scalefactor);
							$scaled     = imagecreatetruecolor($new_width, $new_height);

							imagealphablending($scaled, false);
							imagecopyresampled($scaled, $icon_im, 0, 0, 0, 0, $new_width, $new_height, intval(round($icon_w)), intval(round($icon_h)));
							imagedestroy($icon_im);

							$icon_im = $scaled;
						}
					} else {
						wm_warn ('Couldn\'t open ICON: \'' . $this->iconfile . '\' - is it a PNG, JPEG or GIF? [WMWARN37]');
					}
				} elseif ($this->iconfile != 'none') {
					wm_warn ('ICON \'' . $this->iconfile . '\' does not exist, or is not readable. Check path and permissions. [WMWARN38]');
				}
			}

			if ($icon_im) {
				$icon_w = imagesx($icon_im);
				$icon_h = imagesy($icon_im);

				$icon_x1 = $this->x - $icon_w / 2;
				$icon_y1 = $this->y - $icon_h / 2;
				$icon_x2 = $this->x + $icon_w / 2;
				$icon_y2 = $this->y + $icon_h / 2;

				$map->nodes[$this->name]->width = imagesx($icon_im);
				$map->nodes[$this->name]->height = imagesy($icon_im);

				// $map->imap->addArea("Rectangle", "NODE:" . $this->name . ':0', '', array($icon_x1, $icon_y1, $icon_x2, $icon_y2));
				$map->nodes[$this->name]->boundingboxes[] = array($icon_x1, $icon_y1, $icon_x2, $icon_y2);
			}
		}

		// do any offset calculations
		$dx = 0;
		$dy = 0;

		if ($this->labeloffset != '' && $this->iconfile != '') {
			$this->labeloffsetx = 0;
			$this->labeloffsety = 0;

			list($dx, $dy) = calc_offset($this->labeloffset,
				($icon_w + $boxwidth -1),
				($icon_h + $boxheight)
			);

			#$this->labeloffsetx = $dx;
			#$this->labeloffsety = $dy;
		}

		$label_x1 += ($this->labeloffsetx + $dx);
		$label_x2 += ($this->labeloffsetx + $dx);
		$label_y1 += ($this->labeloffsety + $dy);
		$label_y2 += ($this->labeloffsety + $dy);

		if ($this->label != '') {
			// $map->imap->addArea("Rectangle", "NODE:" . $this->name .':1', '', array($label_x1, $label_y1, $label_x2, $label_y2));
			$map->nodes[$this->name]->boundingboxes[] = array($label_x1, $label_y1, $label_x2, $label_y2);
		}

		// work out the bounding box of the whole thing
		$bbox_x1 = min($label_x1, $icon_x1);
		$bbox_x2 = max($label_x2, $icon_x2) + 1;
		$bbox_y1 = min($label_y1, $icon_y1);
		$bbox_y2 = max($label_y2, $icon_y2) + 1;

		// create TWO imagemap entries - one for the label and one for the icon
		// (so we can have close-spaced icons better)
		$temp_width  = intval(round($bbox_x2 - $bbox_x1));
		$temp_height = intval(round($bbox_y2 - $bbox_y1));

		// create an image of that size and draw into it
		$node_im = imagecreatetruecolor($temp_width, $temp_height);

		// ImageAlphaBlending($node_im, false);
		imageSaveAlpha($node_im, true);

		$nothing = imagecolorallocatealpha($node_im, 128, 0, 0, 127);
		imagefill($node_im, 0, 0, $nothing);

		$label_x1 -= $bbox_x1;
		$label_x2 -= $bbox_x1;
		$label_y1 -= $bbox_y1;
		$label_y2 -= $bbox_y1;

		$icon_x1  -= $bbox_x1;
		$icon_x2  -= $bbox_x1;
		$icon_y1  -= $bbox_y1;
		$icon_y2  -= $bbox_y1;

		$icon_x1   = intval(round($icon_x1));
		$icon_x2   = intval(round($icon_x2));
		$icon_y1   = intval(round($icon_y1));
		$icon_y2   = intval(round($icon_y2));

		$bbox_x1   = intval(round($bbox_x1));
		$bbox_x2   = intval(round($bbox_x2));
		$bbox_y1   = intval(round($bbox_y1));
		$bbox_y2   = intval(round($bbox_y2));

		// Draw the icon, if any
		if (isset($icon_im)) {
			imagecopy($node_im, $icon_im, $icon_x1, $icon_y1, 0, 0, imagesx($icon_im), imagesy($icon_im));
			imagedestroy($icon_im);
		}

		// Draw the label, if any
		if ($this->label != '') {
			$txt_x -= $bbox_x1;
			$txt_x += ($this->labeloffsetx + $dx);
			$txt_y -= $bbox_y1;
			$txt_y += ($this->labeloffsety + $dy);

			#       print "FINAL TEXT at $txt_x , $txt_y\n";

			$label_x1 = ceil($label_x1);
			$label_y1 = ceil($label_y1);
			$label_x2 = ceil($label_x2);
			$label_y2 = ceil($label_y2);

			// if there's an icon, then you can choose to have no background
			if (!$col->is_none()) {
			    imagefilledrectangle($node_im, $label_x1, $label_y1, $label_x2, $label_y2, $col->gdallocate($node_im));
			}

			if ($this->selected) {
				imagerectangle($node_im, $label_x1, $label_y1, $label_x2, $label_y2, $map->selected);

				// would be nice if it was thicker, too...
				wimagerectangle($node_im, $label_x1 + 1, $label_y1 + 1, $label_x2 - 1, $label_y2 - 1, $map->selected);
			} else {
				$olcol = new Colour($this->labeloutlinecolour);

				if ($olcol->is_real()) {
					imagerectangle($node_im, $label_x1, $label_y1, $label_x2, $label_y2, $olcol->gdallocate($node_im));
				}
			}

			$shcol = new Colour($this->labelfontshadowcolour);

			if ($shcol->is_real()) {
				$map->myimagestring($node_im, $this->labelfont, $txt_x + 1, $txt_y + 1, $this->proclabel, $shcol->gdallocate($node_im),$this->labelangle);
			}

			$txcol = new Colour($this->labelfontcolour[0],$this->labelfontcolour[1],$this->labelfontcolour[2]);

			#$col=myimagecolorallocate($node_im, $this->labelfontcolour[0], $this->labelfontcolour[1],
			#	$this->labelfontcolour[2]);
			if ($txcol->is_contrast()) {
				if ($col->is_real()) {
					$txcol = $col->contrast();
				} else {
					wm_warn("You can't make a contrast with 'none'. [WMWARN43]");

					$txcol = new Colour(0, 0, 0);
				}
			}

			$map->myimagestring($node_im, $this->labelfont, $txt_x, $txt_y, $this->proclabel, $txcol->gdallocate($node_im),$this->labelangle);
			//$map->myimagestring($node_im, $this->labelfont, $txt_x, $txt_y, $this->proclabel, $txcol->gdallocate($node_im),90);
		}

		# imagerectangle($node_im,$label_x1,$label_y1,$label_x2,$label_y2,$map->black);
		# imagerectangle($node_im,$icon_x1,$icon_y1,$icon_x2,$icon_y2,$map->black);

		$map->nodes[$this->name]->centre_x = $this->x - $bbox_x1;
		$map->nodes[$this->name]->centre_y = $this->y - $bbox_y1;

		if (1 == 0) {
			imageellipse($node_im, $this->centre_x, $this->centre_y, 8, 8, $map->selected);

			foreach (array('N', 'S', 'E', 'W', 'NE', 'NW', 'SE', 'SW') as $corner) {
				list($dx, $dy) = calc_offset($corner, $this->width, $this->height);

				imageellipse($node_im, $this->centre_x + $dx, $this->centre_y + $dy, 5, 5, $map->selected);
			}
		}

		# $this->image = $node_im;
		$map->nodes[$this->name]->image = $node_im;
	}

	function update_cache($cachedir,$mapname) {
		$cachename = $cachedir . '/node_' . md5($mapname . '/' . $this->name) . '.png';

		// save this image to a cache, for the editor
		imagepng($this->image,$cachename);
	}

	// draw the node, using the pre_render() output
	function NewDraw($image, &$map) {
		// take the offset we figured out earlier, and just blit
		// the image on. Who says "blit" anymore?

		// it's possible that there is no image, so better check.
		if (isset($this->image)) {
			imagealphablending($image, true);

			$dst_x = $this->x - $this->centre_x;
			$dst_y = $this->y - $this->centre_y;
			$src_x = imagesx($this->image);
			$src_y = imagesy($this->image);

			imagecopy($image, $this->image, (int) $dst_x, (int) $dst_y, 0, 0, $src_x, $src_y);
		}
	}

	// take the pre-rendered node and write it to a file so that
	// the editor can get at it.
	function WriteToCache() {
	}

	function Reset(&$newowner) {
		$this->owner=$newowner;
		$template = $this->template;

		if ($template == '') {
			$template = 'DEFAULT';
		}

		wm_debug("Resetting $this->name with $template");

		// the internal default-default gets it's values from inherit_fieldlist
		// everything else comes from a node object - the template.
		if ($this->name==':: DEFAULT ::') {
			foreach (array_keys($this->inherit_fieldlist) as $fld) {
				$this->$fld=$this->inherit_fieldlist[$fld];
			}
		} else {
			$this->CopyFrom($this->owner->nodes[$template]);
		}

		$this->template = $template;

		// to stop the editor tanking, now that colours are decided earlier in ReadData
		$this->colours[IN]  = new Colour(192,192,192);
		$this->colours[OUT] = new Colour(192,192,192);

		$this->id = $newowner->next_id++;
	}

	function CopyFrom(&$source) {
		wm_debug("Initialising NODE $this->name from $source->name");
		// assert('is_object($source)');

		foreach (array_keys($this->inherit_fieldlist)as $fld) {
			if ($fld != 'template') $this->$fld=$source->$fld;
		}
	}

	function WriteConfig() {
		$output='';

		if (!defined('TAB')) {
			define('TAB', "\t");
		}

		if (!defined('EOL')) {
			define('EOL', PHP_EOL);
		}

		// This allows the editor to wholesale-replace a single node's configuration
		// at write-time - it should include the leading NODE xyz line (to allow for renaming)
		if ($this->config_override != '') {
			$output  = $this->config_override . EOL;
		} else {
			# $defdef = $this->owner->defaultnode;
			$dd = $this->owner->nodes[$this->template];

			wm_debug("Writing config for NODE $this->name against $this->template");

			# $field = 'zorder'; $keyword = 'ZORDER';
			$basic_params = array(
				# array('template','TEMPLATE',CONFIG_TYPE_LITERAL),
				array('label',                 'LABEL',                CONFIG_TYPE_LITERAL),
				array('zorder',                'ZORDER',               CONFIG_TYPE_LITERAL),
				array('labeloffset',           'LABELOFFSET',          CONFIG_TYPE_LITERAL),
				array('labelfont',             'LABELFONT',            CONFIG_TYPE_LITERAL),
				array('labelangle',            'LABELANGLE',           CONFIG_TYPE_LITERAL),
				array('overlibwidth',          'OVERLIBWIDTH',         CONFIG_TYPE_LITERAL),
				array('overlibheight',         'OVERLIBHEIGHT',        CONFIG_TYPE_LITERAL),

				array('aiconoutlinecolour',    'AICONOUTLINECOLOR',    CONFIG_TYPE_COLOR),
				array('aiconfillcolour',       'AICONFILLCOLOR',       CONFIG_TYPE_COLOR),
				array('labeloutlinecolour',    'LABELOUTLINECOLOR',    CONFIG_TYPE_COLOR),
				array('labelfontshadowcolour', 'LABELFONTSHADOWCOLOR', CONFIG_TYPE_COLOR),
				array('labelbgcolour',         'LABELBGCOLOR',         CONFIG_TYPE_COLOR),
				array('labelfontcolour',       'LABELFONTCOLOR',       CONFIG_TYPE_COLOR)
			);

			# TEMPLATE must come first. DEFAULT
			if ($this->template != 'DEFAULT' && $this->template != ':: DEFAULT ::') {
				$output .= TAB . 'TEMPLATE ' . $this->template . EOL;
			}

			foreach ($basic_params as $param) {
				$field   = $param[0];
				$keyword = $param[1];

			#	$comparison=($this->name == 'DEFAULT' ? $this->inherit_fieldlist[$field] : $defdef->$field);
				if ($this->$field != $dd->$field) {
					if ($param[2] == CONFIG_TYPE_COLOR) {
						$output .= TAB . "$keyword " . render_colour($this->$field) . EOL;
					}

					if ($param[2] == CONFIG_TYPE_LITERAL) {
						$output .= TAB . "$keyword " . $this->$field . EOL;
					}
				}
			}

			// IN/OUT are the same, so we can use the simpler form here
#			print_r($this->infourl);
			#$comparison=($this->name == 'DEFAULT'
			#? $this->inherit_fieldlist['infourl'][IN] : $defdef->infourl[IN]);
			if ($this->infourl[IN] != $dd->infourl[IN]) {
				$output .= TAB . 'INFOURL ' . $this->infourl[IN] . EOL;
			}

			#$comparison=($this->name == 'DEFAULT'
			#? $this->inherit_fieldlist['overlibcaption'][IN] : $defdef->overlibcaption[IN]);
			if ($this->overlibcaption[IN] != $dd->overlibcaption[IN]) {
				$output .= TAB . 'OVERLIBCAPTION ' . $this->overlibcaption[IN] . EOL;
			}

			// IN/OUT are the same, so we can use the simpler form here
			# $comparison=($this->name == 'DEFAULT'
			# ? $this->inherit_fieldlist['notestext'][IN] : $defdef->notestext[IN]);
			if ($this->notestext[IN] != $dd->notestext[IN]) {
				$output .= TAB . 'NOTES ' . $this->notestext[IN] . EOL;
			}

			# $comparison=($this->name == 'DEFAULT'
			# ? $this->inherit_fieldlist['overliburl'][IN] : $defdef->overliburl[IN]);
			if ($this->overliburl[IN] != $dd->overliburl[IN]) {
				$output .= TAB . 'OVERLIBGRAPH ' . join(' ', $this->overliburl[IN]) . EOL;
			}

			$val = $this->iconscalew . ' ' . $this->iconscaleh . ' ' .$this->iconfile;

			$comparison = $dd->iconscalew . ' ' . $dd->iconscaleh . ' ' . $dd->iconfile;

			if ($val != $comparison) {
				$output .= TAB . 'ICON ';

				if ($this->iconscalew > 0) {
					$output .= $this->iconscalew . ' ' . $this->iconscaleh . ' ';
				}

				$output .= ($this->iconfile == '' ?  'none':$this->iconfile) . EOL;
			}

			# $comparison=($this->name == 'DEFAULT'
			# ? $this->inherit_fieldlist['targets'] : $defdef->targets);

			if ($this->targets != $dd->targets) {
				$output .= TAB . 'TARGET';

				foreach ($this->targets as $target) {
					if (strpos($target[4], ' ') == false) {
						$output .= ' ' . $target[4];
					} else {
						$output .= ' "' . $target[4] . '"';
					}
				}

				$output .= EOL;
			}

		#	$comparison = ($this->name == 'DEFAULT' ? $this->inherit_fieldlist['usescale'] : $defdef->usescale) . " " .
		#		($this->name == 'DEFAULT' ? $this->inherit_fieldlist['scalevar'] : $defdef->scalevar);
			$val = $this->usescale . ' ' . $this->scalevar . ' ' . $this->scaletype;
			$comparison = $dd->usescale . ' ' . $dd->scalevar . ' ' . $dd->scaletype;

			if (($val != $comparison)) {
				$output .= TAB . 'USESCALE ' . $val . EOL;
			}

#			$comparison = ($this->name == 'DEFAULT'
#				? $this->inherit_fieldlist['useiconscale'] : $defdef->useiconscale) . " " .
#				($this->name == 'DEFAULT' ? $this->inherit_fieldlist['iconscalevar'] : $defdef->iconscalevar);
			$val = $this->useiconscale . ' ' . $this->iconscalevar;
			$comparison= $dd->useiconscale . ' ' . $dd->iconscalevar;

			if ($val != $comparison) {
				$output .= TAB . 'USEICONSCALE ' . $val . EOL;
			}

			#$comparison = ($this->name == 'DEFAULT'
			#? $this->inherit_fieldlist['labeloffsetx'] : $defdef->labeloffsetx) . " " . ($this->name == 'DEFAULT'
		#		? $this->inherit_fieldlist['labeloffsety'] : $defdef->labeloffsety);
			$val = $this->labeloffsetx . ' ' . $this->labeloffsety;
			$comparison = $dd->labeloffsetx . ' ' . $dd->labeloffsety;

			if ($comparison != $val ) {
				$output .= TAB . 'LABELOFFSET ' . $val . EOL;
			}

			#$comparison=($this->name == 'DEFAULT' ? $this->inherit_fieldlist['x'] : $defdef->x) . " " .
			#			($this->name == 'DEFAULT' ? $this->inherit_fieldlist['y'] : $defdef->y);
			$val = $this->x . ' ' . $this->y;
			$comparison = $dd->x . ' ' . $dd->y;

			if ($val != $comparison) {
				if ($this->relative_to == '') {
					$output .= TAB . 'POSITION ' . $val . EOL;
				} else {
					if ($this->polar) {
						$output .= TAB . 'POSITION ' . $this->relative_to . ' ' .  $this->original_x . 'r' . $this->original_y . EOL;
					} else {
						$output .= TAB . 'POSITION ' . $this->relative_to . ' ' .  $this->original_x . ' ' . $this->original_y . EOL;
					}
				}
			}

			if ($this->max_bandwidth_in != $dd->max_bandwidth_in || $this->max_bandwidth_out != $dd->max_bandwidth_out || $this->name == 'DEFAULT') {
				if ($this->max_bandwidth_in == $this->max_bandwidth_out) {
					$output .= TAB . 'MAXVALUE ' . $this->max_bandwidth_in_cfg . EOL;
				} else {
					$output .= TAB . 'MAXVALUE ' . $this->max_bandwidth_in_cfg . ' ' . $this->max_bandwidth_out_cfg . EOL;
				}
			}

			foreach ($this->hints as $hintname=>$hint) {
				// all hints for DEFAULT node are for writing
				// only changed ones, or unique ones, otherwise
				if (($this->name == 'DEFAULT') || (isset($dd->hints[$hintname]) && $dd->hints[$hintname] != $hint) || (!isset($dd->hints[$hintname]))) {
					$output .= TAB . "SET $hintname $hint" . EOL;
				}
			}

			if ($output != '') {
				$output = 'NODE ' . $this->name . EOL . "$output" . EOL;
			}
		}

		return ($output);
	}

	function asJS() {
		$js  = "\t\t\t";
		$js .= 'Nodes[' . js_escape($this->name) . '] = {';
		$js .= 'x:' . (is_null($this->x)? "'null'" : $this->x) . ', ';
		$js .= 'y:' . (is_null($this->y)? "'null'" : $this->y) . ', ';
		$js .= '"id":' . $this->id. ', ';
		// $js.="y:" . $this->y . ", ";
		$js .= 'ox:' . $this->original_x . ', ';
		$js .= 'oy:' . $this->original_y . ', ';
		$js .= 'relative_to:' . js_escape($this->relative_to) . ', ';
		$js .= 'label:' . js_escape($this->label) . ', ';
		$js .= 'name:' . js_escape($this->name) . ', ';
		$js .= 'infourl:' . js_escape($this->infourl[IN]) . ', ';
		$js .= 'overlibcaption:' . js_escape($this->overlibcaption[IN]) . ', ';
		$js .= 'overliburl:' . js_escape(join(' ',$this->overliburl[IN])) . ', ';
		$js .= 'overlibwidth:' . $this->overlibheight . ', ';
		$js .= 'overlibheight:' . $this->overlibwidth . ', ';

		if (preg_match('/^(none|nink|inpie|outpie|box|rbox|gauge|round)$/', $this->iconfile)) {
			$js .= 'iconfile:' . js_escape('::' . $this->iconfile);
		} else {
			$js .= 'iconfile:' . js_escape($this->iconfile);
		}

		$js .= "};\n";
		$js .= "\t\t\tNodeIDs['N" . $this->id . "'] = " . js_escape($this->name) . ";\n";

		return $js;
	}

	function asJSON($complete = true) {
		$js  = "\t\t\t";
		$js .= js_escape($this->name) . ': {';
		$js .= '"id":' . $this->id. ', ';
		$js .= '"x":' . ($this->x - $this->centre_x). ', ';
		$js .= '"y":' . ($this->y - $this->centre_y) . ', ';
		$js .= '"cx":' . $this->centre_x. ', ';
		$js .= '"cy":' . $this->centre_y . ', ';
		$js .= '"ox":' . $this->original_x . ', ';
		$js .= '"oy":' . $this->original_y . ', ';
		$js .= '"relative_to":' . js_escape($this->relative_to) . ', ';
		$js .= '"name":' . js_escape($this->name) . ', ';

		if ($complete) {
			$js .= '"label":' . js_escape($this->label) . ', ';
			$js .= '"infourl":' . js_escape($this->infourl) . ', ';
			$js .= '"overliburl":' . js_escape($this->overliburl) . ', ';
			$js .= '"overlibcaption":' . js_escape($this->overlibcaption) . ', ';

			$js .= '"overlibwidth":' . $this->overlibheight . ', ';
			$js .= '"overlibheight":' . $this->overlibwidth . ', ';
			$js .= '"iconfile":' . js_escape($this->iconfile). ', ';
		}

		$js .= '"iconcachefile":' . js_escape($this->cachefile);
		$js .= "},\n";

		return $js;
	}
}

