/**
SetupNamespace
sets up a "namespace" by checking that the given collection (to be 
used as a namespace) exists within the parent collection (parent 
namespace), and creating it if it does not
namespace: the name of the child namespace to check or create (as a string)
parent: (optional) the parent that the new namespace should be contained within, or null if this is a root namespace
returns: returns a reference to the child namespace that we checked/created
**/
function SetupNamespace(namespace, parent) {
	if (!parent) parent = window;
	if (!parent[namespace]) parent[namespace] = [];
	return parent[namespace];
}

/**
SetupNamespaces
sets up the namespace given in the parameter
namespaceStr: the namespace to create, represented as a string (e.g. "Namespace1.InnerNameSpace")
**/
function SetupNamespaces(namespaceStr) {  
	var namespaces = namespaceStr.split(".");
	var last = null;
	for (var n=0; n < namespaces.length; n++)
		last = SetupNamespace(namespaces[n], last);
}

/**
Get
gets an element from the DOM using it's id attribute.  An alias for 
document.getElementById
elemId: the value of the id attribute of the element to retrieve
returns: returns the element in the DOM with the given id
**/
function Get(elemId) { 
	return document.getElementById(elemId);
}

/**
Elem
creates a DOM element (the DOM equivalent of an HTML tag).  An alias for 
document.createElement, with an added hack to optionally allow setting the name 
attribute for the tag during creation.  This is needed because IE does not allow 
the name attribute to be changed for an element, so setting the value during 
creation is the only way to have a custom name attribute on a tag.  The name 
attribute cannot be changed once created, and a non-standard element creation 
method is used to accomplish creation in IE if a name attribute is given.
nodeType: the type of element to create (the type of HTML tag to create, for example 
    "div" for a div tag or "input" for an input tag)
nodeName: optional.  if provided, this value is set as the initial (and immutable) 
    value of the element's "name" attribute.  if not provided, the element may not 
    have a name attribute
returns: returns a reference to the newly created element
**/
function Elem(nodeType, nodeName) { 
	if (nodeName && nodeName != undefined && document.all) { 
		return document.createElement("<" + nodeType + " name=\"" + nodeName + "\">");
	}
	else { 
		var elem = document.createElement(nodeType);
		if (nodeName && nodeName != undefined) elem.name = nodeName;
		return elem;
	}
}

/**
Text
creates a DOM entity that contains text (as opposed to an element, which contains 
an HTML tag).  By default, text entities are interpreted literally, and character 
entities and non-ASCII characters are not parsed.  In this mode, it is not necessary 
or possible to escape any characters (like &, <, >) in the text (this mode is just an 
alias for document.createTextNode).  Optionally, include the parse flag to request 
parsing of the text, which allows the inclusion of unicode characters and character 
entities (at a performance cost due to hackery).
text: the text to place in the text node
parse: optional, defaults to false if omitted. if true, the text will be parsed for 
    unicode and character entities, and all reserved ASCII characters must be escaped. 
    if false, the text is interpreted literally and character entities and 
    unicode are prohibited, but escaping characters is not necessary (and performance 
    is faster).
returns: returns the created text node
**/
function Text(text, parse) { 
	if (parse) { 
		var parser = Elem("span");
		parser.innerHTML = text;
		return parser;
	}
	
	return document.createTextNode(text);
}

/**
ClearElem
removes the child nodes of the given node, so that it is empty.  Use this 
to clear a portion of the screen
node: the node whose children will be removed from the DOM
**/
function ClearElem(node) { 
    if (node == null || node == undefined) return;
    while (node.hasChildNodes()) node.removeChild(node.firstChild);
}

/**
AddEventHandler
adds an event handler to the current event without overwriting 
the existing event handlers, and returns the new setting for the 
event.  ex: window.onload = AddEventHandler(window.onload, function() { alert("loaded!"); });
event: the event to add the handler to
newHandler: the reference to a function/closure to add to the event's handlers
returns: returns the function to assign to the event to cause the combined existing 
    and new event handlers to be called when the event is launched
**/
function AddEventHandler(event, newHandler) { 
	if (typeof(newHandler) != "function") return;
	return function() { if(event) event.apply(this, arguments); newHandler.apply(this, arguments); };
}

SetupNamespaces("Pliner.Util");

Pliner.Util.Display = {
    /**
    FindXPosition
    gets the absolute X-coordinate position of a DOM object on the page.
    obj: the DOM object we want to locate the X-coordinate of
    returns: returns the X-coordinate of the DOM object, or 0 if there is an error
    **/
    FindXPosition: function(obj) {
        var curleft = 0;
        if(obj.offsetParent) {
            while(1) {
                curleft += obj.offsetLeft;
                if(!obj.offsetParent)
                    break;
                obj = obj.offsetParent;
            }
        }
        else if(obj.x) {
            curleft += obj.x;
        }
        
        return curleft;
    }, 
    
    /**
    FindYPosition
    gets the absolute Y-coordinate position of a DOM object on the page.
    obj: the DOM object we want to locate the Y-coordinate of
    returns: returns the Y-coordinate of the DOM object, or 0 if there is an error
    **/
    FindYPosition: function(obj) {
        var curtop = 0;
        if(obj.offsetParent) {
            while(1) {
                curtop += obj.offsetTop;
                if(!obj.offsetParent)
                    break;
                obj = obj.offsetParent;
            }
        }
        else if(obj.y) {
            curtop += obj.y;
        }
        return curtop;
    }, 
    
    /**
    FindScreenCenteredX
    helps in centering DOM objects on the page.  Given the element's width, this 
    function tells what its X-coordinate should be for it to be centered on the page.
    itemWidth: the width of the DOM element that is to be centered on the page
    returns: returns the X-coordinate to give the DOM element for it to be centered on the page
    **/
    FindScreenCenteredX: function(itemWidth) {
        if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf("iphone") >= 0) return 10;
        
        var screenWidth  = document.body.clientWidth
        var scrollOffset = document.all ? ((document.compatMode && document.compatMode != "BackCompat")? document.documentElement : document.body).scrollLeft : pageXOffset;
        return Math.max(Math.floor((screenWidth - itemWidth) / 2 + scrollOffset), 0);
    }, 

    /**
    FindScreenCenteredY
    helps in centering DOM objects on the page.  Given the element's width, this function 
    tells what its Y-coordinate should be for it to be centered on the page.
    itemHeight: the height of the DOM element that is to be centered on the page
    returns: returns the Y-coordinate to give the DOM element for it to be centered on the page
    **/
    FindScreenCenteredY: function(itemHeight) {
        if (navigator.userAgent && navigator.userAgent.toLowerCase().indexOf("iphone") >= 0) return 10;

        var screenHeight = document.documentElement.clientHeight;
        var scrollOffset = document.all ? ((document.compatMode && document.compatMode != "BackCompat")? document.documentElement : document.body).scrollTop : pageYOffset;
        return Math.max(Math.floor(((screenHeight - itemHeight) / 2) + scrollOffset), 0);
    }, 
    
    /**
    ClearFieldDefaultText
    for use with form fields that have default text in them (for example, a search box 
    that says "Search" in it initially, but is cleared when you first focus it).  This 
    function is to be called when the field is focused by the user.
    elem: the form field DOM element
    text: the default text string used for the field
    color: (optional, defaults to #000000) the text color of user-entered text in the form field
    **/
    ClearFieldDefaultText: function(elem, text, color) {
        if (elem.value == text) {
            elem.value = "";
            elem.style.color = color ? color : "#000000";
        }
    }, 

    /**
    SetupFieldDefaultText
    for use with form fields that have default text in them (for example, a search box 
    that says "Search" in it initially, but is cleared when you first focus it).  This 
    function is to be called when the field is un-focused (blurred) by the user, and when 
    the form field is first displayed.  It enters the default text into the form field and 
    can change the field to use a different text color, for example a greyed-out color.
    elem: the form field DOM element
    text: the default text string used for the field
    color: (optional, defaults to #969696) the text color of the default text (often a lighter 
        greyed-out color) in the form field
    **/
    SetupFieldDefaultText: function(elem, text, color) {
        if (elem.value == text || elem.value == "") { 
            elem.value = text;
            elem.style.color = color ? color : "#969696";
        }
    }, 
    
    /**
    PreloadImages
    takes in an array of image URLs and replaces each array element with an 
    Image object sourced to the replaced URL, causing the image at that URL to 
    be loaded into memory and retained as long as the array is retained.  Useful 
    for loading a bunch of rollover or CSS-hidden images so that they will be 
    ready when they need to be displayed.
    imageUrls: an array of strings containing URLs to the images to pre-load.
    **/
    PreloadImages: function(imageUrls) { 
        for (var i=0; i < imageUrls.length; i++) { 
            var image = new Image();
            image.src = imageUrls[i];
            imageUrls[i] = image;
        }
    }
};

Pliner.Util.Forms = {
    /**
    CreateCookie
    creates a new cookie, or overwrites an existing one of the same name.
    name: the key to identify the cookie
    value: the text to be stored in the cookie
    days: (optional) the number of days the cookie should be valid for, if an expiration should be set
    **/
	CreateCookie: function(name, value, days) {
		var expires = "";
		if (days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			expires = "; expires="+date.toGMTString();
		}
		document.cookie = name+"="+value+expires+"; path=/";
	},

    /**
    ReadCookie
    reads the text stored an a cookie of the given name, if it exists in scope.
    nade: the key that identifies the cookie to be read
    returns: returns the text stored in the cookie, if it exits in scope, or an empty string if it does not
    **/
	ReadCookie: function(name) {
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return "";
	},

    /** 
    EraseCookie
    sets a cookie to expire immediately.
    name: the key that identifies the cookie to be expired
    **/
	EraseCookie: function(name) {
		CreateCookie(name,"",-1);
	},
	
	/**
	GetQueryString
	gets the key/value pairs passed to the webpage on the query string
	returns: an object where object[query-string-key] = query-string-value, 
	    allowing access to the query string elements by keys.
	**/
    GetQueryString: function() {
        var query = window.location.search.substring(1);
        var parms = query.split("&");
        var qs = new Object();
        
        for (var i=0; i < parms.length; i++) {
            var pos = parms[i].indexOf("=");
            if (pos > 0) {
                var key = parms[i].substring(0, pos);
                var val = parms[i].substring(pos + 1);
                qs[key] = val;
            }
        }
        
        return qs;
    }, 
    
    /**
    CallbackOnEnterPressed
    Runs a callback function when they key event passed to it indicates a 
    press of the enter key.  Used for allowing JavaScript-handled forms 
    to be activated by a press of the enter key inside a form field, just like 
    and HTML <form> would be submitted by the press of the enter key.  Use this 
    by placing it on the onkeypress event of the form fields inside the form:
    onkeypress="return CallbackOnEnterPressed(event, function() { alert("submitted"); });"
    Note that the event variable is implicitly generated by JavaScript when an 
    event occurs, so you just need to pass it on.
    e: the JavaScript event object created when the key event occurs.
    callbackFunction: the function to call when the enter key is pressed
    returns: returns true to allow a keystroke if it is not the enter key, and false 
        to cancel the keystroke if it is the enter key (so that the default action - 
        the form submitting and the webpage posting back - will be blocked and the 
        JavaScript action will happen instead).
    **/
    CallbackOnEnterPressed: function(e, callbackFunction) {
	    var keynum;
        if (!e) var e = window.event;
	    if(window.event) keynum = e.keyCode; // IE
	    else if(e.which) keynum = e.which;   // Netscape/Firefox/Opera

	    if (keynum == 13) {
		    callbackFunction();
		    return false;
	    }

	    return true;
    }, 
    
    /**
    StopBackspaceFromGoingBackAPage
    starts the page blocking unintended backspaces to stop people from accidentally going back. 
    If a user is typing in a text field, backspace still works, but anywhere else the keystroke 
    is canceled, preventing the user from hitting backspace thinking they were going to delete 
    text and instead going back a page.  This is useful on sites with many forms because users 
    avoid losing entered data by accidentally going back, and they avoid the "Webpage has Expired" 
    message whenever possible.
    based on: http://mspeight.blogspot.com/2007/05/how-to-disable-backspace-in-ie-and.html
    **/
    StopBackspaceFromGoingBackAPage: function() { 
        if (typeof(window.event) != "undefined") { // IE
            document.onkeydown = 
                function() { // IE
		            var t = event.srcElement.type;
		            var kc = event.keyCode;
		            return (kc != 8 || t == "text" || t == "password" || t == "textarea");
	            }
        }
        else {
            document.onkeypress = 
	            function(e) { // FireFox/Others
    		        var t = e.target.type;
		            var kc = e.keyCode;
                    return (kc != 8 || t == "text" || t == "password" || t == "textarea");
	            }
        }
    }
};

Pliner.Util.DataStructures = { 
	/**
	PriorityQueue
	constructor for a PriorityQueue data structure, which provides an 
	efficient means to maintain a set of items and retrieve the top item 
	in the sort order at any time
	comparerFunction: a pointer to a function that compares two of the objects of whatever type are 
        being stored in the queue, returing -1, 0, or 1 as an indicator of their sort order.  Items 
        that come first in the sort order go to the top of the queue
	**/
	PriorityQueue: function(comparerFunction) { 
		var private = {
			items: [], 
			
			/**
			GetLeftChild
			locates the position of the left child of an item in the array representation of the max-heap
			i: the item whose left child we want to get
			returns: the position in the array of the left child of element i in the array
			**/
			GetLeftChild: function(i) { 
				return (2 * i) + 1;
			}, 
			
			/**
			GetParent
			located the position of the parentof an item in the array representation of the max-heap
			i: the item whose parent we want to get
			returns: the position in the array of the parent of element i in the array
			**/
			GetParent: function(i) { 
				if (i <= 0) return -1;
				return Math.floor((i - 1) / 2);
			}, 
			
			/**
			GetLargestChild
			gets the position in the array of the largest of an element's child elements
			i; the item whose largest child we want to get
			returns: returns the position in the array of the largest child (according to the comparer function) of element i, 
			    either the left or the right child.  returns -1 if i has no children.  If i has only one child, that child is returned.
			**/
			GetLargestChild: function(i) { 
				var l = private.GetLeftChild(i);
				var r = l + 1;
				var largestChild = -1;
				
				if (l < public.count && private.items[l] != null) 
					largestChild = l;
				if (r < public.count && private.items[r] != null && (largestChild == -1 || public.comparer(private.items[l], private.items[r]) < 0))
					largestChild = r;
				
				return largestChild;
			}
		};
		
		var public = {
			/** the number of items in the queue **/
			count: 0, 
			/** the comparer function for whatever type of objects are stored in the queue **/
			comparer: comparerFunction, 
			
			/**
			Enqueue
			adds an object to the queue
			obj: the object to add to the queue
			**/
			Enqueue: function(obj) { 
				if (obj == null) return;
			
				// add the element to the end of the array
				var position = public.count;
				public.count++;
				private.items[position] = obj;
				
				// up-heap until the element is in the correct position
				while (position > 0 && public.comparer(private.items[position], private.items[private.GetParent(position)]) > 0) { 
					var parent = private.GetParent(position);
					var temp = private.items[position];
					private.items[position] = private.items[parent];
					private.items[parent] = temp;
					position = parent;
				}
			}, 
			
			/**
			Dequeue
			removes the maximum object (according to the comparer function) from the queue and returns it
			returns: the maximum object in the queue
			**/
			Dequeue: function() { 
				if (public.count <= 0) return null;
				
				// extract the maximum element
				var max = private.items[0];
				
				// put the last element in the heap into the root
				private.items[0] = private.items[public.count - 1];
				public.count--;
				
				// down-heap until the heap property is fixed
				var position = 0;
				var largestChild = private.GetLargestChild(position);
				
				while (largestChild > 0 && public.comparer(private.items[position], private.items[largestChild]) < 0) { 
					var temp = private.items[position];
					private.items[position] = private.items[largestChild];
					private.items[largestChild] = temp;
					position = largestChild;
					largestChild = private.GetLargestChild(position);
				}
				
				return max;
			}, 
			
			/**
			Peek
			allows the user to see the largest object (according to the comparer function) in the queue, but 
			does not remove it
			returns: the maximum object in the queue
			**/
			Peek: function() { 
				if (public.count <= 0) return null;
				return private.items[0];
			}, 
			
			/**
			Contains
			tells whether an object is in the queue
			obj: the object to find in the queue (be reference equality)
			returns: true if the object is in the queue, and false if it is not
			**/
			Contains: function(obj) { 
				for (var i=0; i < private.items.length; i++) 
					if (private.items[i] != null && private.items[i] == obj) 
						return true;		
				return false;
			}, 
			
			/**
			Clear
			clears the queue, removing all items
			**/
			Clear: function() { 
				private.items = [];
				public.count = 0;
			}
		};
		
		return public;
	}, 
	
	/**
	LinkedList
	constructor for the LinkedList object, which provides a linked list data structure 
	implementation
	**/
	LinkedList: function() { 
		var private = {
			head: null, 
		
			/**
			ListLink
			constructor for the ListLink object, which represents a link in the linked 
			list and the data contained in that link
			o: the object that is the data for this link
			n: a reference to the next link in the list, or null if there is no next link
			**/
			ListLink: function(o, n) { 
				var public = {
					obj: o, 
					next: n
				};
				
				return public;
			}
		};
		
		var public = { 
			/** the number of items in the linked list **/
			count: 0, 
			
			/**
			Add
			adds an object to the unordered linked list
			obj: the object to add to the list
			**/
			Add: function(obj) { public.Insert(obj, 0); }, 
			
			/**
			Insert
			inserts an object into the linked list at the given position.  Adds the item to the start of 
			the list if the index is less than 0, and to the end of the list if the index is off of the 
			end of the list
			obj: the object to add to the list
			index: the position to place the object in the list
			**/
			Insert: function(obj, index) { 
				var next = private.head;
				var prev = null;
				var pos = 0;
				
				for (; pos < public.count && pos < index; pos++) { 
					prev = next;
					next = prev.next;
				}
				
				var link = new private.ListLink(obj, null, null);
				link.next = next;
				if (prev != null) 
					prev.next = link;
				if (pos == 0)
					private.head = link;
				
				public.count++;
			}, 
			
			/**
			Remove
			removes the first reference-equal match to the given object from the list
			obj: the object to remove from the list.  Only the first instance is removed
			**/
			Remove: function(obj) { public.RemoveAt(public.IndexOf(obj)); }, 
			
			/**
			RemoveAt
			removes the item at the given index from the list.  If the index is not 
			in the list, no action is taken
			index: the index of the item to remove from the list
			**/
			RemoveAt: function(index) { 
				if (index >= public.count || index < 0) return;
				
				var prev = null;
				var curr = null;
				var next = private.head;
				
				for (var i=0; i < public.count && i <= index; i++) { 
					prev = curr;
					curr = next;
					next = curr.next;
				}
				
				if (prev != null)
					prev.next = next;
				if (index == 0) 
					private.head = next;
					
				public.count--;
			}, 
			
			/**
			Get
			gets the item at the given index from the list
			index: the index of the item to get from the list
			returns: returns the item at the given index, or null if the index is not in the list
			**/
			Get: function(index) { 
				if (index >= public.count || index < 0) return null;
				
				var curr = null;
				var next = private.head;
				
				for (var i=0; i < public.count && i <= index; i++) { 
					curr = next;
					next = curr.next;
				}
				
				return curr.obj;
			}, 
			
			/**
			Set
			sets the item at the given index in the list to the new value.  If the index is 
			not in the list, no action is taken
			obj: the new object to replace the current object at the given index in the list
			index: the index of the element to replace with the new object
			**/
			Set: function(obj, index) { 
				if (index >= public.count || index < 0) return;
				
				var curr = null;
				var next = private.head;
				
				for (var i=0; i < public.count && i <= index; i++) { 
					curr = next;
					next = curr.next;
				}
				
				curr.obj = obj;
			}, 
			
			/**
			Contains
			tells whether the given object is contained in the list (matched by 
			reference-equality)
			obj: the object to find in the list
			returns: true if the given object was found in the list, and false otherwise
			**/
			Contains: function(obj) { return public.IndexOf(obj) >= 0; }, 
			
			/**
			IndexOf
			gets the index of the first reference-equal match to the given object 
			in the list.
			obj: the object to find in the list
			returns: the index of the first occurance of the object in the list, or -1 if the object is not found
			**/
			IndexOf: function(obj) { 
				var curr = null;
				var next = private.head;
				
				for (var i=0; i < public.count; i++) { 
					curr = next;
					next = curr.next;
					
					if (curr.obj == obj) 
						return i;
				}
				
				return -1;
			}, 
			
			/**
			Clear
			clears the list, deleting all items
			**/
			Clear: function() { 
				private.head = null;
				public.count = 0;
			}, 
			
			/**
			ToArray
			copies the linked list to an array
			returns: the array that contains references to the same objects as those in the linked list
			**/
			ToArray: function() { 
				var array = [];
				var curr = null;
				var next = private.head;
				
				for (var i=0; i < public.count; i++) { 
					curr = next;
					array[i] = curr.obj;
					next = curr.next;
				}
			    
				return array;
			}
		};
		
		return public;
	}
}
