/*
systym: the NIH-syndrome javascript library
copyright (c) 2009 Chris Rose-Mathew
*/

//===================================================
// class: Sprite
//===================================================
// A Sprite is a rectangular screen area used to
// display graphics that can move and resize.
// A Sprite can be made of existing elements,
// or created outside of the document tree and
// inserted somewhere later.
// Once in the document tree, a sprite can be
// moved around and resized, shown or hidden.
// You can dynamically change the image while
// it is displaying simply by changing it's 
// image URL.
// 
// properties:
// * Node element  (ro)
// * Node container  (ro)
// * Boolean logging
// * Boolean deferGeometryChanges
// * Boolean positionContainer
// * Number x  (ro)
// * Number y  (ro)
// * Number z  (ro)
// * Number width  (ro)
// * Number height  (ro)
// * Number opacity (ro)
// * Boolean useFilterAlpha

// functions:
// * Sprite(containingScene);
// * absolute();
// * fixed();
// * relative();
// * position(x,y,z);
// * position(x,y);
// * position(Point);
// * position2D(x,y);
// * position3D(x,y,z);
// * size(x,y);
// * size(Point)
// * show();
// * hide();
// * visible();
// * invisible();
// * applyGeometryChanges();
// * setOpacity(opacityValue);
// * color(cssValue);
// * color(r,g,b)
// * color(r,g,b,a);
// * backgroundColor(cssValue);
// * backgroundColor(r,g,b)
// * useBackgroundImage(Text url);
// * Text backgroundImageUrl();
// * useImage(Text url);
// * Text imageUrl();
// * useEventHandler(Text eventType, function eventHandler);
// * onload()
// * onclick()
// * onmouseover()
// * onmouseout()
// * onmousedown()
// * onmouseup()
// * onresize()
// * onunload()
//
// Create a Sprite by calling the Sprite()
// constructor function. When called with
// no parameters the sprite will create an
// extra-document element for itself and
// apply it's default properties.
// You can pass a javascript object containing
// a reference to a DOM node that will act as
// the containing scene for the Sprite - typically
// this is a large block region that fills the
// browser window.
//
//   var sceneDiv = document.getElementById("myscenediv");
//   var mysprite = new Sprite(sceneDiv);
//   mysprite.size(32,32);
//   mysprite.position(100,180);
//   mysprite.useImage("images/particles/sparkle1.png");
//   mysprite.useEventHandler("click", clickParticle);
//
//   ... where clickParticle is defined like this:
//
//   var scene.clickParticle = function(event)
//   {
//      alert("Poof");
//   }
//
// Sprites generally behave as
// absolutely-positioned block elements with
// explicit heights and widths, that move
// relative to their containing "scene" element.
// Absolute positioning means they act outside
// of the general flow of other child elements
// of the containing scene element.
//
// You can use a background image or a proper
// img element to display the sprite graphics.
// You can also use both at the same time.
// Browsers that support alpha transparency will
// correctly use this property of the source images.
// Note that you cannot resize a background image
// with your sprite element. If you need to
// dynamically scale your sprite, use an
// image instead.
//
// If you require images or background images with
// alpha-transparency that work in IE6, do not use
// the functions useImage() or useBackgroundImage() to
// do the actual application of the image to the Sprite,
// but rather create the Sprite object and set it's
// element id to one that has predefined CSS rules for
// background-image and the hackish DX filters.
// See Sprite::useImage() for more details
// 
// You can also use animated gif images for amazing
// effects with moving Sprites in browsers
// that support them.
//
// Sprite objects will attempt to use what structures
// are made available to them.
//===================================================
// TODO
// containerNode could be a DOM node or a text ID
//    the constructor should disambiguate and deal with it
//===================================================
function Sprite(containerNode, useContainerNode)
{
	// ------- Properties --------

	this.logging = false;
	this.deferGeometryChanges = false;
	this.positionContainer = false;
	this.x = 0;
	this.y = 0;
	this.z = 0;
	this.width = 0;
	this.height = 0;
	this.opacity = 1.0;
	this.useFilterAlpha = false;
	this.imageUrl = null;

	if (useContainerNode)
	{
		this.element = containerNode;
		this.container = this.element.parentNode;
	}
	else
	{
		this.element = document.createElement("div");	
		this.container = containerNode;
		if (!this.container)
		{
			log.addItem("Sprite created with no initial container. Creating extra-tree node...");
		}
		else
		{
			this.container.appendChild(this.element);
		}
	}
}

// -------- Visibility Via display attribute ---------

Sprite.prototype.show = function()
{
	if (!this.element)
		return;

	this.element.style.display = "block";
}

Sprite.prototype.hide = function()
{
	if (!this.element)
		return;

	this.element.style.display = "none";
}

// -------- Visibility Via visible attribute---------

Sprite.prototype.visible = function()
{
	if (!this.element)
		return;

	if (this.positionContainer)
		this.container.style.visibility = "visible";
	else
		this.element.style.visibility = "visible";
}

Sprite.prototype.invisible = function()
{
	if (!this.element)
		return;

	if (this.positionContainer)
		this.container.style.visibility = "hidden";
	else
		this.element.style.visibility = "hidden";
}

// -------- Colour ---------

//=======================================
// Sprite::color(cssValue, opacity)
//=======================================
// Set the foreground color of the Sprite contents.
// The cssValue argument is presumed to be a text string containing
// a complete CSS colour specification.
// The opacity parameter is optional and if given, must be a
// normalized floating point value (range 0.0 - 1.0)
// Examples:
// half-transparent red:
//       s.color("#FF0000", 0.5);
// fully-opaque green:
//       s.color("#00FF00", 1.0);
// blue (opacity left as previous to call):
//       s.color("#0000FF");
// green-grey  (opacity left as previous to call):
//       s.color("#AABBAA");
//=======================================
Sprite.prototype.color = function(cssValue, opacity)
{
	if (!this.element)
		return;

	if (cssValue)
	{
		this.element.style.color = cssValue;
		if (opacity)
		{
			this.opacity = opacity;
			this.element.style.opacity = this.opacity;
			this.element.style.filter = "alpha(opacity=" + (this.opacity * 100) + ")"
		}
	}
}

//=======================================
// Sprite::colorRGBA(r,g,b,a)
//=======================================
// Set the foreground color of the Sprite contents using
// integer RGB values (0-255 byte range for red, green, and blue),
// and a normalized floating-point opacity value.
// If the final opacity term is left out, opacity is left as it was
// pre-call.
// Examples:
// bright red, half-transparent:
//       s.colorRGBA(255, 0, 0, 0.5);
// dark-green, fully-opaque:
//       s.colorRGBA(0, 160, 0, 1.0);
// blue (opacity left as previous to call):
//       s.colorRGBA(0,0,255);
//=======================================
Sprite.prototype.colorRGBA = function(r,g,b,a)
{
	if (!this.element)
		return;

	if (r && g && b)
	{
		if (a)
		{
			this.opacity = a;
			if (this.opacity < 0)
				this.opacity = 0;
				
			this.element.style.opacity = this.opacity;
			this.element.style.filter = "alpha(opacity=" + (this.opacity * 100) + ")"
		}
		
		// Set colour using rgb() syntax
		this.element.style.color = "rgb(" + r + "," + g + "," + b + ")";
	}
	else
	{
		alert("No r,g or b givent to Sprite::colorRGBA()");
	}
}

//=======================================
// Sprite::colorRGBAFloat(r,g,b,a)
//=======================================
// Set the foreground color of the Sprite contents using normalized
// floating-point numeric values (0-1.0 range for red, green, and blue,
// and opacity).
// If the final opacity term is left out, opacity is left as it was
// pre-call.
// Examples:
// half-transparent red:
//       s.colorRGBAFloat(1.0, 0, 0, 0.5);
// fully-opaque dark-green:
//       s.colorRGBAFloat(0.0, 0.6, 0.0, 1.0);
// blue (opacity left as previous to call):
//       s.colorRGBAFloat(0,0,1);
//=======================================
Sprite.prototype.colorRGBAFloat = function(r,g,b,a)
{
	if (!this.element)
		return;

	if (r && g && b)
	{
		if (a)
		{
			this.opacity = a;
			if (this.opacity < 0)
				this.opacity = 0;
				
			this.element.style.opacity = this.opacity;
			this.element.style.filter = "alpha(opacity=" + (this.opacity * 100) + ")"
		}
		
		// Set colour using rgb() syntax
		this.element.style.color = "rgb(" + (r*255).toFixed(0) + "," + (g*255).toFixed(0) + "," + (b*255).toFixed(0) + ")";		
	}
	else
	{
		alert("No r,g or b givent to Sprite::colorRGBAFloat()");
	}
}

//=======================================
// Sprite::backgroundColor(cssValue)
//=======================================
// Set the background color of the Sprite drawing area.
// The cssValue argument is presumed to be a text string containing
// a complete CSS colour specification.
// Examples:
// bright red:
//       s.backgroundColor("#FF0000");
// bright green:
//       s.backgroundColor("#00FF00");
// dark blue:
//       s.backgroundColor("#000066");
// green-grey:
//       s.backgroundColor("#AABBAA");
//=======================================
Sprite.prototype.backgroundColor = function(cssValue)
{
	if (!this.element)
		return;
		
	if (cssValue)
	{
		this.element.style.backgroundColor = cssValue;
	}
}

//=======================================
// Sprite::backgroundColorRGB(r,g,b)
//=======================================
// Set the background color of the Sprite contents using
// integer RGB values (0-255 byte range for red, green, and blue).
// Examples:
// bright red:
//       s.backgroundColorRGB(255, 0, 0);
// dark-green:
//       s.backgroundColorRGB(0, 160, 0);
// blue:
//       s.backgroundColorRGB(0,0,255);
//=======================================
Sprite.prototype.backgroundColorRGB = function(r,g,b)
{
	if (!this.element)
		return;
		
	if (r && g && b)
	{
		this.element.style.backgroundColor = "rgb(" + r + "" + g + "" + b + ")";
	}
}

//=======================================
// Sprite::setOpacity(opacityValue)
//=======================================
// Set the opacity Sprite content area and contents using
// a normalized floating-point value (ranging from 0.0 to 1.0)
// Examples:
// half-tansparent:
//       s.setOpacity(0.5);
// full opaque (not see-through):
//       s.setOpacity(1.0);
// TODO: Set to display:none if full-transparent etc.?
//=======================================
Sprite.prototype.setOpacity = function(opacityValue)
{
	if (!this.element)
		return;

	if (opacityValue != null)
	{
		this.opacity = opacityValue;
		if (this.opacity < 0)
			this.opacity = 0;
			
		if (this.positionContainer)
		{
			this.container.style.opacity = this.opacity;
			this.container.style.filter = "alpha(opacity=" + (this.opacity * 100) + ")";
		}
		else
		{
			this.element.style.opacity = this.opacity;
			this.element.style.filter = "alpha(opacity=" + (this.opacity * 100) + ")";
		}
	}
}

// -------- Imagery ---------

Sprite.prototype.useBackgroundImage = function(imageUrl)
{
	if (!this.element)
		return;
		
//	if (this.element.filters && this.element.filters.length>0)
//	{
//		// Can use filters - must be IE? (Presumptions...)
//		this.element.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, src='" + imageUrl + "' sizingMethod='crop')";
//	}
//	else
//	{
		this.element.style.backgroundImage = "url(" + imageUrl + ")";
//	}
}

Sprite.prototype.backgroundImageUrl = function()
{
	if (this.element)
		return this.element.style.backgroundImage;
}

//=======================================
// Sprite::useImage(imageUrl, width, height, altText)
//=======================================
// Use a given image for the sprite as an image element, rather
// than a style-based background image. The width, height and
// altText are optional parameters.
//
// Limitations:
// If the image contains transparency (PNG-style) and you need
// the effect to be visible on IE6, you need:
// * to set the Sprite,useFilterAlpha property to true before calling
//    this function.
// * you should create the Sprite from an existing document element,
//    rather than from it's container element (See the Sprite() constructor).
// * The sprite element needs to have CSS rules applying a filter to it.
//    This is best achieved by having these rules existing in your
//    conditionally-included "IE-hacks" stylesheet as a static rule,
//    which can then be overridden later by Javascript.
//    If the rules are not specified statically in the stylesheet, this
//    function will fail to detect filters on the element and the
//    usual image loading functionality will happen instead.
//=======================================
Sprite.prototype.useImage = function(imageUrl, width, height, altText)
{
	if (!this.element)
		return;
	
	if (this.useFilterAlpha && this.element.filters != null) // OH MY, MICROSOFT!!!!!
	{
		// To set use node.style.filter = text
		// To see if exists, use node.filters
		// Probably have to have the filter originally set in CSS before this
		this.element.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, src='" + imageUrl + "' sizingMethod='crop')";
		this.imageUrl = imageUrl;
	}
	else
	{
		if (this.image == null || this.image == undefined)
		{
			// Create img element if not using AlphaImageLoader
			this.image = document.createElement("img");
			this.element.appendChild(this.image);
		}
		// Load the image from the given URL and save the URL
		this.image.setAttribute("src", imageUrl);
		this.imageUrl = imageUrl;
		
		// Set up image properties
		if (width)
			this.image.setAttribute("width", width + "px");
		if (height)
			this.image.setAttribute("height", height + "px");
		if (altText)
			this.image.setAttribute("alt", altText);
	}
}

Sprite.prototype.imageUrl = function()
{
	return this.imageUrl;
}


// -------- Positioning ---------

Sprite.prototype.relative = function()
{
	if (this.element)
	{
		this.element.style.position = "relative";
	}
}

Sprite.prototype.absolute = function()
{
	if (this.element)
	{
		this.element.style.position = "absolute";
	}
}

Sprite.prototype.fixed = function()
{
	if (this.element)
	{
		this.element.style.position = "fixed";
	}
}

Sprite.prototype.position = function(x, y, z)
{
	if (z == null)
	{
		if (y == null)
		{
			if (x == null)
			{
				return new Point(this.x, this.y, this.z);
			}
			
			if (x instanceof Point)
			{
				// Only x given - and it's a Point
				this.x = x.x;
				this.y = x.y;
				this.z = x.z;
			}
		}
		else
		{
			//x and y given
			this.x = x;
			this.y = y;
			if (this.positionContainer == false)
			{
				// Normal positioning (position Sprite.element)
				if (!this.deferGeometryChanges && this.element)
				{
					this.element.style.position = "absolute";
					this.element.style.left = this.x.toFixed(0) + "px";
					this.element.style.top = this.y.toFixed(0) + "px";
				}
			}
			else
			{
				// Position container instead of element
				if (!this.deferGeometryChanges && this.container)
				{
					this.container.style.position = "absolute";
					this.container.style.left = this.x.toFixed(0) + "px";
					this.container.style.top = this.y.toFixed(0) + "px";
				}
			}
			return;
		}
	}
	else
	{
		// all coordinates given
		this.x = x;
		this.y = y;
		this.z = z;
	}

	// apply internal geometry to css
	if (this.deferGeometryChanges == false)
		this.applyGeometryChanges();
}

Sprite.prototype.position2D = function(x, y)
{
	this.x = x;
	this.y = y;
	if (this.deferGeometryChanges == false)
		this.applyGeometryChanges();
}

Sprite.prototype.position3D = function(x, y, z)
{
	this.x = x;
	this.y = y;
	this.z = z;
	if (this.deferGeometryChanges == false)
		this.applyGeometryChanges();
}

Sprite.prototype.applyGeometryChanges = function()
{
	if (this.positionContainer == false)
	{
		// Default positioning (position Sprite.element)
		if (this.element)
		{
			this.element.style.position = "absolute";
			this.element.style.left = this.x.toFixed(0) + "px";
			this.element.style.top = this.y.toFixed(0) + "px";
			this.element.style.zIndex = this.z.toFixed(0) + " ";
		}
	}
	else
	{
		// Position container node instead of Sprite.element
		if (this.container)
		{
			this.container.style.position = "absolute";
			this.container.style.left = this.x.toFixed(0) + "px";
			this.container.style.top = this.y.toFixed(0) + "px";
			this.container.style.zIndex = this.z.toFixed(0) + " ";
		}
	}
}

// -------- Sizing ---------

//=================================================
// size()
//=================================================
// Resize the sprite
//=================================================
Sprite.prototype.size = function(width, height)
{
	if (height == null && width instanceof Point)
	{
		if (width.x == NaN || width.y == NaN)
		{
			alert("NaN width or height on Sprite::size(Point)");
			return;
		}

		if (width.x == Math.Infinity || width.y == Math.Infininty)
		{
			alert("Inifinite width or height on Sprite::size()");
			return;
		}

		// Get sizes from point
		this.width = width.x;
		this.height = width.y;
			
		if (this.width < 0)
			this.width = 0;
		if (this.height < 0)
			this.height = 0;

		// Modify element style to current
		if (this.element)
		{
			this.element.style.width = this.width + "px";
			this.element.style.height = this.height + "px";
		}
		
		// Modify any image element
		if (this.image)
		{
			this.image.setAttribute("width", this.width);
			this.image.setAttribute("height", this.height);
		}
		
		// TODO modify image size if alpha image loader
	}
	else
	{
		if (width == NaN || height == NaN)
		{
			alert("NaN width of height on Sprite::size()");
			return;
		}

		if (width == Math.Infinity || height == Math.Infiinty)
		{
			alert("NaN width of height on Sprite::size()");
			return;
		}

		// Store given values
		this.width = width;
		this.height = height;
		
		// Validate values
		if (this.width < 0)
			this.width = 0;
		if (this.height < 0)
			this.height = 0;
			
		// Modify element style
		if (this.element)
		{
			this.element.style.width = this.width + "px";
			this.element.style.height = this.height + "px";
		}
		
		// Modify any image element
		if (this.image)
		{
			this.image.setAttribute("width", this.width);
			this.image.setAttribute("height", this.height);
		}
	}
	if (this.logging)
	{
		log.addItem("Sprite resized to (" + this.width + ", " + this.height + ")");
	}
}

// -------- Events ---------

Sprite.prototype.useEventHandler = function(eventName, eventHandler)
{
	if (this.element)
	{
		if (eventName == "click")
			this.element.onclick = eventHandler;
		else if (eventName == "mouseover")
			this.element.onmouseover = eventHandler;
		else if (eventName == "mouseout")
			this.element.onmouseout = eventHandler;
		else if (eventName == "mousedown")
			this.element.onmousedown = eventHandler;
		else if (eventName == "mouseup")
			this.element.onmouseup = eventHandler;
		else if (eventName == "mouseover")
			this.element.onmouseover = eventHandler;
		else if (eventName == "resize")
			this.element.onresize = eventHandler;
		else if (eventName == "unload" || eventName == "quit" || eventName == "exit")
			this.element.onunload = eventHandler;
	}
}

Sprite.prototype.onclick = function(event)
{
}

Sprite.prototype.onmouseover = function(event)
{
}

Sprite.prototype.onmouseout = function(event)
{
}

Sprite.prototype.onmouseup= function(event)
{
}

Sprite.prototype.onmousedown = function(event)
{
}

Sprite.prototype.onresize = function(event)
{
}

Sprite.prototype.onunload = function(event)
{
}

Sprite.prototype.makeInteractive = function()
{
	if (this.element == null)
		return;

	this.element.onclick = this.onclick;
	this.element.onmouseover = this.onmouseover;
	this.element.onmouseout = this.onmouseout;
	this.element.onmouseup = this.onmouseup;
	this.element.onmousedown = this.onmousedown;
	this.element.onresize = this.onresize;
	this.element.onunload = this.onunload;
}

	// -------- XXX ---------


