var CANVAS_ID = 0;
var DEBUG = 0;
var HAVE_PRINTFIRE = 0;

/* ************************************************** BROWSER DETECTION ************************************************** */
var isIE;
var isGecko;
var isSafari;
var isKonqueror;
var isMac;

var agt = navigator.userAgent.toLowerCase();
var is_major = parseInt(navigator.appVersion);

var is_nav = ((agt.indexOf('ozilla') != -1) && (agt.indexOf('spoofer') == -1) && (agt.indexOf('compatible') == -1) && (agt.indexOf('opera') == -1) && (agt.indexOf('webtv') == -1) && (agt.indexOf('hotjava') == -1));
var is_nav4up = (is_nav && (is_major >= 4));

var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
var is_ie5 = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.0")!=-1) );
var is_ie5_5 = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.5") != -1));
var is_ie6 = (is_ie && (is_major == 4) && (agt.indexOf("msie 6.0") != -1));
var is_ie7 = (is_ie && (is_major == 4) && (agt.indexOf("msie 7.0") != -1));
var is_ie5up = (is_ie && (is_major == 4) && ( (agt.indexOf("msie 5.0") != -1) || (agt.indexOf("msie 5.5") != -1) || (agt.indexOf("msie 6.0") != -1) || (agt.indexOf("msie 7.0") != -1) ) );

isIE = is_ie; 
isMac = (agt.indexOf("macintosh") != -1);
isGecko = (agt.indexOf("gecko") != -1);
isSafari = (agt.indexOf("safari") != -1);
isKonqueror = (agt.indexOf("konqueror") != -1);
var pluginDetected = false;
var pluginVer = 0;
var activeXDisabled = false;
// we can check for plugin existence only when browser is 'is_ie5up' or 'is_nav4up'
if (is_nav4up) {
	// Refresh 'navigator.plugins' to get newly installed plugins.
	// Use 'navigator.plugins.refresh(false)' to refresh plugins
	// without refreshing open documents (browser windows)
	if(navigator.plugins) {
		navigator.plugins.refresh(false);
	}

	// check for Java plugin in installed plugins
	if(navigator.mimeTypes) {
		for (i=0; i < navigator.mimeTypes.length; i++) {
			if (navigator.mimeTypes[i].type != null && navigator.mimeTypes[i].type.indexOf("application/x-java-applet")) {
				pluginDetected = true;
			}
			
			if ((navigator.mimeTypes[i].type != null) && (navigator.mimeTypes[i].type.indexOf("version=1.3") != -1) ) {
		        pluginVer      = "1.3";
		        break;
			}
			if ((navigator.mimeTypes[i].type != null) && (navigator.mimeTypes[i].type.indexOf("version=1.4") != -1) ) {
		        pluginVer      = "1.4";
		        break;
			}
			if ((navigator.mimeTypes[i].type != null) && (navigator.mimeTypes[i].type.indexOf("version=1.5") != -1) ) {
		        pluginVer      = "1.5";
		        break;
			}
		}
	}
} 
else if (is_ie5up || isKonqueror) {
	var javaVersion;
	var shell;
	pluginDetected = navigator.javaEnabled();
}

/* ************************************************** PROTOTYPE LIKE FUNCTIONS ******************************************** */

// these three provided so you don't have to rely on prototype being installed
function $e(elementId)
{
	if (typeof(elementId) == "object") { return elementId; }		
	return document.getElementById(elementId);
}

function removeEl(elementId)
{
	if (typeof(elementId) != "object") { elementId = $e(elementId); }
	elementId.parentNode.removeChild(elementId);			
}

// unlike prototype's library function, this won't work with display:none elements, so careful (you'll have to manually
// unhide/hide)
function getDimensions(elementId)
{
	if (typeof(elementId) != "object") { elementId = $e(elementId); }
	return { 'width': elementId.offsetWidth, 'height': elementId.offsetHeight };
}

function getPosition(elementId)
{
	if (typeof(elementId) != "object") { elementId = $e(elementId); }
	var l = parseInt(elementId.offsetLeft);
	var t = parseInt(elementId.offsetTop);
	return { 'left': l, 'top': t };
}

/* *************************************************** DRAWING LIBRARY **************************************************** */
/* Wrappers for working with Zorn's wz_jsgraphics.js http://www.walterzorn.com/jsgraphics/jsgraphics_e.htm#docu */

function JsSprite(optoriginx, optoriginy, optrotation)
{
	this.instructions = new Array();
	this.origin_x = parseInt(optoriginx) || 0;
	this.origin_y = parseInt(optoriginy) || 0;
	this.rotation_degrees = parseInt(optrotation) || 0;
}

function _js_setOrigin(x, y)
{
	// sprites are rendered and rotated relative to the origin
	this.origin_x = parseInt(x);
	this.origin_y = parseInt(y);
}

function _js_addInstruction()
{
	/*
	 * Arg0: function reference (from jsGraphics)
	 * Arg1-n: arguments to arg0
	 */
	 
	 /* Future development: sprite rotation */
	 
	 /* because of the limitation inherent in rotating some things (because no current support in 
	  * the underlying jsGraphics library), the only functions supported in a sprite are:
	  * * * drawLine, drawPolyline, drawPolygon, fillPolygon
	  */
	 var instruction = new Object();
	 for (var i = 0; i < arguments.length; i++)
	 {
	 	if (i == 0)
	 	{
	 		instruction.funcRef = arguments[0];
	 	}
	 	else
	 	{
	 		if (!instruction.argsList)
	 		{
	 			instruction.argsList = new Array();
	 		}
	 		instruction.argsList.push(arguments[i]);
	 	}
	 }
	 this.instructions.push(instruction);
}

function _js_setRotation(rotation)
{
	// placeholder for now; future: sprite rotation
	this.rotation_degrees = parseInt(rotation);
}

function _js_paintAtLocation(graphics_context, x, y)
{
	this.renderAtLocation(graphics_context, x, y);
	this.graphics_context.paint();
}

function _js_renderAtLocation(graphics_context, x, y)
{
	try
	{
		this.graphics_context = graphics_context; // a jsGraphics
		for (var i = 0; i < this.instructions.length; i++)
		{
			var funcRef = this.instructions[i].funcRef;
			var argsList = this.instructions[i].argsList;
			var thisArgsx = null;
			var thisArgsy = null;
			var thisArgsz = null;
			var thisArgsa = null;
			// reset XY context:
			if (this.rotation_degrees > 0)
			{
				// UNIMPLEMENTED XX TODO XX
			}
			else
			{
				if ((typeof(argsList[0]) == 'array') || (typeof(argsList[0]) == 'object'))
				{
					thisArgsx = new Array();
					thisArgsy = new Array();
					// funcRef(xarr, yarr) (drawPolyline, drawPolygon, fillPolygon)
					for (var j = 0; j < argsList[0].length; j++)
					{
						thisArgsx[j] = parseInt(argsList[0][j]) + parseInt(x);
					}

					for (var k = 0; k < argsList[1].length; k++)
					{
						thisArgsy[k] = parseInt(argsList[1][k]) + parseInt(y);					
					}
					argsList = [thisArgsx, thisArgsy];
				}
				else
				{
					// funcRef(x, y, x2, y2) (drawLine)
					thisArgsx = parseInt(argsList[0]) + parseInt(x);
					thisArgsy = parseInt(argsList[1]) + parseInt(y);
					thisArgsz = parseInt(argsList[2]) + parseInt(x);
					thisArgsa = parseInt(argsList[3]) + parseInt(y);
					argsList = [thisArgsx, thisArgsy, thisArgsz, thisArgsa];
				}
			}
			funcRef.apply(this.graphics_context, argsList);
		}
	}
	catch (e)
	{
		if (DEBUG)
		{
			if (HAVE_PRINTFIRE)
			{
				printfire("Exception: " + e.message);
			}
			else
			{
				alert("Exception: " + e.message);				
			}
		}
	}
}

JsSprite.prototype.setOrigin = _js_setOrigin;
JsSprite.prototype.addInstruction = _js_addInstruction;
JsSprite.prototype.setRotation = _js_setRotation;
JsSprite.prototype.renderAtLocation = _js_renderAtLocation;
JsSprite.prototype.paintAtLocation = _js_paintAtLocation;

function CanvasSet()
{
	try
	{
		/*
		   arguments 0,2 are passed without remark to the Canvas creator (stacking colors is not smart, but I don't prevent it)
		   argument 1 is replaced here intelligently with a canvas holder
		   argument 3: an array of opacities: 
		   argument 4: zindex, modified when making a stack of Canvases
		*/
		this.canvasses = new Object();
		// Unfortunately, you can't assume two canvas objects in a common parent will work.  This is because of a quirk with
		// the createRange() DOM function, or its implementation in some browsers.  To work around this issue, every opacity
		// canvas has its own unique parent, which we therefore provide.  This is not a serious performance issue, because 
		// we're talking about 2x canvasses with probable canvas lists on the order of <10.
		this.parents = new Object();
		this.base_canvas_num = 0;
	
		var arg3 = arguments[3] || [1];
		arg3.sort().reverse();
		var arg4 = ((arguments[4] >= 0) || (arguments[4] == "auto")) ? parseInt(arguments[4]) : "auto";
		var arg4_int = arg4;
	
		var parentEle = (arguments[1] ? $e(arguments[1]) : $e(arguments[0]).parentNode);
		if (!parentEle) { parentEle = null; } // arguments[0] was a positional spec, arguments[1] was null, use body and geometry spec for canvasses
		for (var i = 0; i < arg3.length; i++)
		{
			this.parents[arg3[i]] = new Canvas(arguments[0], parentEle, "", 1.0, arg4_int); // parents do not have a background color (opacity is for inheritance of child ie don't double apply low opacity)
			this.parents[arg3[i]].render();
			if (arg4_int != "auto") { arg4_int++; }
			this.base_canvas_num++;
			if (i == 0)
			{
				this.default_canvas = this.canvas = this.parents[arg3[i]];
			}
		}	

		arg4_int = arg4;
		var d = getDimensions(arguments[0]); // the mock element
		var childSpec = "0,0+" + d.width + "+" + d.height;
	
		for (var i = 0; i < arg3.length; i++)
		{
			// use the parents we just created:
			this.canvasses[arg3[i]] = new Canvas(childSpec, this.parents[arg3[i]].id, arguments[2], arg3[i], arg4_int);
			this.canvasses[arg3[i]].render();
			if (arg4_int != "auto") { arg4_int++; }
			// set base context
			if (i == 0)
			{
				this.default_graphics = this.graphics = this.canvasses[arg3[i]].graphics;
			}
		}
	}
	catch (e)
	{
		if (DEBUG)
		{
			if (HAVE_PRINTFIRE)
			{
				printfire("Exception: " + e.message);
			}
			else
			{
				alert(e.message);
			}
		}
	}
}

function _canvasset_setContext(opacity)
{
	if (this.canvasses[opacity])
	{
		this.graphics = this.canvasses[opacity].graphics;
	}
	else
	{
		// reset to initial
		this.graphics = this.default_graphics;
	}
	return this.graphics;
}

function _canvasset_clearAll()
{
	for (var i in this.parents)
	{
		this.parents[i].clear();
	}
	
	for (var i in this.canvasses)
	{
		this.canvasses[i].clear();
	}
}

function _canvasset_hideAll()
{
	for (var i in this.parents)
	{
		this.parents[i].hide();
	}
	
	for (var i in this.canvasses)
	{
		this.canvasses[i].hide();		
	}
}

function _canvasset_paintAll()
{
	// parents can't be painted (this is what would throw the createRange exception)
	for (var i in this.canvasses)
	{
		this.canvasses[i].paint();
	}
}

function _canvasset_showAll()
{
	for (var i in this.parents)
	{
		this.parents[i].show();
	}
	
	for (var i in this.canvasses)
	{
		this.canvasses[i].show();		
	}
}

function _canvasset_removeAll()
{
	for (var i in this.canvasses)
	{
		this.canvasses[i].remove();		
	}

	for (var i in this.parents)
	{
		this.parents[i].remove();
	}
}

function _canvasset_getHeight()
{
	return this.canvas.height;
}

function _canvasset_getWidth()
{
	return this.canvas.width;
}

function _canvasset_setStroke(stroke)
{
	for (var i in this.canvasses)
	{
		this.canvasses[i].setStroke(stroke);
	}

	for (var i in this.parents)
	{
		this.parents[i].setStroke(stroke);
	}
}

function _canvasset_setColor(color)
{
	for (var i in this.canvasses)
	{
		this.canvasses[i].setColor(color);
	}

	for (var i in this.parents)
	{
		this.parents[i].setColor(color);
	}
}

function _canvasset_setPrintable(printable)
{
	for (var i in this.canvasses)
	{
		this.canvasses[i].setPrintable(printable);
	}

	for (var i in this.parents)
	{
		this.parents[i].setPrintable(printable);
	}
}

CanvasSet.prototype.setContext = _canvasset_setContext;
CanvasSet.prototype.clear = _canvasset_clearAll;
CanvasSet.prototype.hide = _canvasset_hideAll;
CanvasSet.prototype.show = _canvasset_showAll;
CanvasSet.prototype.remove = _canvasset_removeAll;
CanvasSet.prototype.paint = _canvasset_paintAll;
CanvasSet.prototype.getHeight = _canvasset_getHeight;
CanvasSet.prototype.getWidth = _canvasset_getWidth;
CanvasSet.prototype.setStroke = _canvasset_setStroke;
CanvasSet.prototype.setColor = _canvasset_setColor;
CanvasSet.prototype.setPrintable = _canvasset_setPrintable;

function Canvas()
{
	/* Argument 0 can be either:
	 * 		* an element reference to 'mock', or a position specification in the format of:
	 * 		* XPOS,YPOS+WIDTH+HEIGHT (similar to an ImageMagick geometry spec)
	 */
	try
	{
		var arg0 = arguments[0];
		if (arg0.indexOf('+') == -1)
		{
			this.type = 'mock';
			this.mockElement = $e(arg0);
			/* Argument 1: optionally, you pick to mock the mock element, but you also pick the element you want your result to go inside (innerHTML) */
			var arg1 = arguments[1];
			var pe = $e(arg1);
			if (pe)
			{
				this.parentElement = pe;				
			}
			else
			{
				this.parentElement = $e(this.mockElement.parentNode.id);
			}
			var dimensions = getDimensions(this.mockElement);
			this.width = dimensions.width;
			this.height = dimensions.height;
			var position = getPosition(this.mockElement);
			this.posx = position.left || 0;
			this.posy = position.top || 0;
		}
		else
		{
			this.type = 'positional';
			var geoms = arg0.split('+');
			var poses = geoms[0].split(',');
			// The position of this canvas is specified
			this.posx = parseInt(poses[0]);
			this.posy = parseInt(poses[1]);
			this.width = parseInt(geoms[1]);
			this.height = parseInt(geoms[2]);
			this.mockElement = null;
			/* Argument 1: optionally, you pick the element you want your result to go inside (innerHTML) */
			var arg1 = arguments[1];
			this.parentElement = $e(arg1) ? $e(arg1) : document.body;
		}

		/* Argument 2 is a color specified as none, "#nnnnnn"; default: transparent (none) */
		this.color = arguments[2] || "";

		/* Argument 3 is an opacity specified as a percent [0-1]; default: 1 */
		this.opacity = ((arguments[3] >= 0) && (arguments[3] <= 1)) ? arguments[3] : 1;

		/* Argument 4 is a z-index specified as auto, 0-n; default: auto */
		this.zindex = ((arguments[4] >= 0) || (arguments[4] == "auto")) ? arguments[4] : "auto";

		this.id = "canvas_" + CANVAS_ID++;
	}
	catch (e)
	{
		if (DEBUG)
		{
			if (HAVE_PRINTFIRE)
			{
				printfire("Exception: " + e.message);
			}
			else
			{
				alert(e.message);
			}
		}
	}
}

function _canvas_render(makeInvisible)
{
	var html_add = '<div id="' + this.id + '" style="';
	// +1 to show end borders if you're drawing a graph
	html_add += 'position:absolute;overflow:hidden;display:none;left:' + this.posx + 'px;top:' + this.posy + 'px;width:' + (parseInt(this.width)+1) + ';height:' + (parseInt(this.height)+1);
	if (this.color)
	{
		html_add += ';background-color:' + this.color;
	}
	html_add += ';z-index:' + this.zindex + ';opacity:' + this.opacity + ';';
	html_add += '"></div>';
	this.parentElement.innerHTML += html_add;
	var myself = $e(this.id);
	
	if (!makeInvisible)
	{
		this.show();
	}
	
	this.graphics = new jsGraphics(this.id);
	// set meaningful defaults
	this.graphics.setStroke(1);
	this.graphics.setColor("#000000");
}

function _canvas_remove()
{
	// no memory leaks (ideally, client library will remove all canvases before leaving a page context; however,
	// because there are no closures defined on these elements, there should not be memory leaks anyway)
	this.mockElement = null;
	this.parentElement = null;
	removeEl($e(this.id));
}

function _canvas_hide()
{
	$e(this.id).style.display = "none";
}

function _canvas_show()
{
	$e(this.id).style.display = "block";
	this.setOpacity(); // glitchy if display:none?
}

function _canvas_setOpacity() 
{
	var opacity = parseFloat(this.opacity); // safety
	if ((this != null) && (opacity >= 0) && (opacity <= 1))
	{
		if (isIE)
		{
			$e(this.id).style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + parseInt(opacity * 100) + ")";
		}
		else if (isSafari)
		{
			$e(this.id).style.opacity = opacity;
		}
		else
		{
			$e(this.id).style.MozOpacity = opacity;
		}
	}
}

function _canvas_initialize()
{
	this.render(true);
}

function _canvas_transplant(newparent)
{
	// attempt to move this canvas to a new parent (useful when dynamically reloading elements of a page)
	newparent.appendChild($e(this.id));
}

function _canvas_clear()
{
	this.graphics.clear();
}

function _canvas_paint()
{
	this.graphics.paint();
}

function _canvas_setStroke(stroke)
{
	this.graphics.setStroke(stroke);
}

function _canvas_setColor(color)
{
	this.graphics.setColor(color);
}

function _canvas_setPrintable(printable)
{
	this.graphics.setPrintable(printable);
}

Canvas.prototype.setStroke = _canvas_setStroke;
Canvas.prototype.setColor = _canvas_setColor;
Canvas.prototype.setPrintable = _canvas_setPrintable;
Canvas.prototype.paint = _canvas_paint;
Canvas.prototype.clear = _canvas_clear;
Canvas.prototype.render = _canvas_render;
Canvas.prototype.remove = _canvas_remove;
Canvas.prototype.setOpacity = _canvas_setOpacity;
Canvas.prototype.hide = _canvas_hide;
Canvas.prototype.show = _canvas_show;
Canvas.prototype.initialize = _canvas_initialize;
Canvas.prototype.transplant = _canvas_transplant;
