/**
 * uCode / Javascript
 * rev. 3787 (2007-09-07)
 *
 * Copyright (c) 2005-2007 Pause Productions (http://www.pause.ca/)
 * This code may not be distributed or reproduced without a license.
 *
 * Packaged on 2007-09-14
 * 
 * DO NOT EDIT THIS FILE! If you find bugs in uCode, or wish to add 
 * functionality, edit the originals and repackage them.
 * 
 * The person who customized this uCode package selected:
 * - uColumns.class.js rev. 3787 (dated 2007-09-07)
 * - uCommon.js rev. 3726 (dated 2007-08-29)
 * - uShow.class.js rev. 3758 (dated 2007-09-05)
 * - uValidate.js rev. 3726 (dated 2007-08-29)
 *
 * Use this quick select string when re-packaging this file:
 * js.col|js.com|js.shw|js.val
 *
 * The following were also included due to requirements:
 * - uCommon.php rev. 3726 (dated 2007-08-29)
 * - uSanitize.php rev. 2850 (dated 2007-05-04)
 * - uValidate.php rev. 3726 (dated 2007-08-29)
 *
 * One or more of the modules included have supporting CSS files. To 
 * make sure your layout is correct, you should have uCode.css included
 * on your resulting HTML file. If you do not have uCode.css, you can
 * generate one from the package server by selecting the same options as
 * are listed above.
 *
 * One or more of the included modules have supporting Javascript files.
 * To make sure these modules function properly please make sure uCode.js
 * is included in your resulting HTML. If you do not have uCode.js you can
 * generate one from the package server using the same options above.
 */
function decimals(value,decimals) {
  if (value=='') return '';
  value = value * 1;
  if (value==0) return '';
  if (value==NaN) return '';

  // round to that many places
  var power = Math.pow(10,decimals);
  value = Math.round(value * power) / power;
  
  // add 0's if necessary
  value = value.toString().split('.');
  if (value.length==1) value[1] = '';
  
  for (var t=value[1].length; t<decimals; t++) value[1] += '0';
  
  value = value[0]+'.'+value[1];
  
  return value;

}

function isNumeric(value) {
	if (isNaN(parseFloat(value))) return false;
	return true;
}

function isArray(value) {
  if (!isObject(value) || value.constructor.toString().indexOf("Array") == -1) return false;
  return true;
}

function isInt(value) {
	if (typeof value=='number' && Math.floor(value)==value) return true;
	return false;
}

function isString(value) {
  return typeof value == 'string';
}

function isFunction(value) {
  return typeof value == 'function';
}

function isObject(value) {
  return (typeof value == 'object' && !!value) || isFunction(value);
}

function isBool(value) {
  return typeof value == 'boolean';
}

function indexOf(obj,key) {
	for (i in obj) {
		if (obj[i]==key) return i;
	}
	return -1;
}

function hasClass(obj,className) {
	var classes = obj.className.split(' ');
	if (indexOf(classes,className)==-1) return(false);
	return(true);
}

function addClass(obj,className) {
	if (hasClass(obj,className)) return;
	var classes = obj.className.split(' ');
	classes.push(className);
	obj.className = classes.join(' ');
}

function removeClass(obj,className) {
	if (!hasClass(obj,className)) return;
	var classes = obj.className.split(' ');
	var newClasses = [];
	for (var t=0; t<classes.length; t++) {
		if (classes[t]==className) continue;
		newClasses.push(classes[t]);
	}
	obj.className = newClasses.join(' ');
}


/**
 * Used from the Prototype Libaray (with minor modifications)
 */
Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
}

function ObserveEvent(element, name, observer) {
  element = $(element);
  if (element.addEventListener) {
    element.addEventListener(name, observer, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + name, observer);
  }
}

function StopObservingEvent(element,name,observer) {
  if (element.removeEventListener) {
    element.removeEventListener(name, observer, false);
  } else if (element.detachEvent) {
    element.detachEvent('on' + name, observer);
  }
}

var _CACHEDIDELEMENTS = {};
function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string') {
    	if (!_CACHEDIDELEMENTS[element]) _CACHEDIDELEMENTS[element] = document.getElementById(element);
    	element = _CACHEDIDELEMENTS[element];
    }

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
};

function getPosition(element)
{
    return [DL_GetElementLeft(element),DL_GetElementTop(element)];
}

function getStyle(element, style) {
    element = $(element);
    var value = element.style[camelize(style)];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[camelize(style)];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  }

  
  function positionedOffset(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        p = getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  }
  
  function camelize(value) {
    var oStringList = value.split('-');
    if (oStringList.length == 1) return oStringList[0];

    var camelizedString = value.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  };
  
  /*
  
function getXY(el){ // this initially used Position.cumulativeOffset but it is not accurate enough
        var p, pe, b, scroll, bd = document.body;
        el = $(el);

        if(el.getBoundingClientRect){ // IE
            b = el.getBoundingClientRect();
            scroll = fly(document).getScroll();
            return [b.left + scroll.left, b.top + scroll.top];
        } else{
            var x = el.offsetLeft, y = el.offsetTop;
            p = el.offsetParent;

            // ** flag if a parent is positioned for Safari
            var hasAbsolute = false;

            if(p != el){
                while(p){
                    x += p.offsetLeft;
                    y += p.offsetTop;

                    // ** flag Safari abs position bug - only check if needed
                    if(Ext.isSafari && !hasAbsolute && fly(p).getStyle("position") == "absolute"){
                        hasAbsolute = true;
                    }

                    // ** Fix gecko borders measurements
                    // Credit jQuery dimensions plugin for the workaround
                    if(Ext.isGecko){
                        pe = fly(p);
                        var bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
                        var bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;

                        // add borders to offset
                        x += bl;
                        y += bt;

                        // Mozilla removes the border if the parent has overflow property other than visible
                        if(p != el && pe.getStyle('overflow') != 'visible'){
                            x += bl;
                            y += bt;
                        }
                    }
                    p = p.offsetParent;
                }
            }
            // ** safari doubles in some cases, use flag from offsetParent's as well
            if(Ext.isSafari && (hasAbsolute || fly(el).getStyle("position") == "absolute")){
                x -= bd.offsetLeft;
                y -= bd.offsetTop;
            }
        }

        p = el.parentNode;

        while(p && p != bd){
            // ** opera TR has bad scroll values, so filter them jvs
            if(!Ext.isOpera || (Ext.isOpera && p.tagName != 'TR' && fly(p).getStyle("display") != "inline")){
                x -= p.scrollLeft;
                y -= p.scrollTop;
            }
            p = p.parentNode;
        }
        return [x, y];
    },

    setXY : function(el, xy){ // this initially used Position.cumulativeOffset but it is not accurate enough
        el = Ext.fly(el, '_setXY');
        el.position();
        var pts = el.translatePoints(xy);
        if(xy[0] !== false){
            el.dom.style.left = pts.left + "px";
        }
        if(xy[1] !== false){
            el.dom.style.top = pts.top + "px";
        }
    },

    setX : function(el, x){
        this.setXY(el, [x, false]);
    },

    setY : function(el, y){
        this.setXY(el, [false, y]);
    }
};

*/


/**
 * Used from http://www.webreference.com/dhtml/diner/realpos4/9.html
 */
function DL_GetElementLeft(eElement)
{
   if (!eElement && this)                    // if argument is invalid
   {                                         // (not specified, is null or is 0)
      eElement = this;                       // and function is a method
   }                                         // identify the element as the method owner

   var DL_bIE = document.all ? true : false; // initialize var to identify IE

   var nLeftPos = eElement.offsetLeft;       // initialize var to store calculations
   var eParElement = eElement.offsetParent;  // identify first offset parent element

   while (eParElement != null)
   {                                         // move up through element hierarchy

      if(DL_bIE)                             // if browser is IE, then...
      {
         if( (eParElement.tagName != "TABLE") && (eParElement.tagName != "BODY") )
         {                                   // if parent is not a table or the body, then...
            nLeftPos += eParElement.clientLeft; // append cell border width to calcs
         }
      }
      else                                   // if browser is Gecko, then...
      {
         if(eParElement.tagName == "TABLE")  // if parent is a table, then...
         {                                   // get its border as a number
            var nParBorder = parseInt(eParElement.border);
            if(isNaN(nParBorder))            // if no valid border attribute, then...
            {                                // check the table's frame attribute
               var nParFrame = eParElement.getAttribute('frame');
               if(nParFrame != null)         // if frame has ANY value, then...
               {
                  nLeftPos += 1;             // append one pixel to counter
               }
            }
            else if(nParBorder > 0)          // if a border width is specified, then...
            {
               nLeftPos += nParBorder;       // append the border width to counter
            }
         }
      }
      nLeftPos += eParElement.offsetLeft;    // append left offset of parent
      eParElement = eParElement.offsetParent; // and move up the element hierarchy
   }                                         // until no more offset parents exist
   return nLeftPos;                          // return the number calculated
}

function DL_GetElementTop(eElement)
{
   if (!eElement && this)                    // if argument is invalid
   {                                         // (not specified, is null or is 0)
      eElement = this;                       // and function is a method
   }                                         // identify the element as the method owner

   var DL_bIE = document.all ? true : false; // initialize var to identify IE

   var nTopPos = eElement.offsetTop;         // initialize var to store calculations
   var eParElement = eElement.offsetParent;  // identify first offset parent element

   while (eParElement != null)
   {                                         // move up through element hierarchy
      if(DL_bIE)                             // if browser is IE, then...
      {
         if( (eParElement.tagName != "TABLE") && (eParElement.tagName != "BODY") )
         {                                   // if parent a table cell, then...
            nTopPos += eParElement.clientTop; // append cell border width to calcs
         }
      }
      else                                   // if browser is Gecko, then...
      {
         if(eParElement.tagName == "TABLE")  // if parent is a table, then...
         {                                   // get its border as a number
            var nParBorder = parseInt(eParElement.border);
            if(isNaN(nParBorder))            // if no valid border attribute, then...
            {                                // check the table's frame attribute
               var nParFrame = eParElement.getAttribute('frame');
               if(nParFrame != null)         // if frame has ANY value, then...
               {
                  nTopPos += 1;              // append one pixel to counter
               }
            }
            else if(nParBorder > 0)          // if a border width is specified, then...
            {
               nTopPos += nParBorder;        // append the border width to counter
            }
         }
      }

      nTopPos += eParElement.offsetTop;      // append top offset of parent
      eParElement = eParElement.offsetParent; // and move up the element hierarchy
   }                                         // until no more offset parents exist
   return nTopPos;                           // return the number calculated
}


function toggle(id) {
	id = $(id);
	if (id.style.display=='none') show(id);
	else hide(id);
}

function show(id) {
	id = $(id);
	if (id) id.style.display = '';
}

function hide(id) {
	id = $(id);
	if (id) id.style.display = 'none';
}




/**
 * Used from http://jquery.com/ (jquery dimensions plugin)
 * with heavy modifications to make it (mostly) standalone
 */
/*
	var b = navigator.userAgent.toLowerCase();

	// Figure out what browser is being used
	var _browser = {};
	_browser.safari = b.match(/webkit/);
	_browser.opera = b.match(/opera/);
	_browser.msie = b.match(/msie/);
	_browser.mozilla = (b.match(/mozilla/) && !b.match(/compatible|webkit/));
	_browser.boxModel = !_browser.msie || document.compatMode == "CSS1Compat";


	function offset(elem) {
		var x = 0, y = 0, sl = 0, st = 0,
		    parent = elem, op, parPos, elemPos = getStyle(elem, 'position'),
		    mo = _browser.mozilla, ie = _browser.msie, sf = _browser.safari, oa = _browser.opera,
		    absparent = false, relparent = false, 
		    options = { margin: true, border: true, padding: false, scroll: true, lite: false };
		
		if (elem.tagName.toLowerCase() == 'body') {
			// Safari is the only one to get offsetLeft and offsetTop properties of the body "correct"
			// Except they all mess up when the body is positioned absolute or relative
			x = elem.offsetLeft;
			y = elem.offsetTop;
			// Mozilla ignores margin and subtracts border from body element
			if (mo) {
				x += parseInt(getStyle(elem, 'marginLeft')) + (parseInt(getStyle(elem, 'borderLeftWidth'))*2);
				y += parseInt(getStyle(elem, 'marginTop'))  + (parseInt(getStyle(elem, 'borderTopWidth')) *2);
			} else
			// Opera ignores margin
			if (oa) {
				x += parseInt(getStyle(elem, 'marginLeft'));
				y += parseInt(getStyle(elem, 'marginTop'));
			} else
			// IE does not add the border in Standards Mode
			if (ie && jQuery.boxModel) {
				x += parseInt(getStyle(elem, 'borderLeftWidth'));
				y += parseInt(getStyle(elem, 'borderTopWidth'));
			}
		} else {
			do {
				parPos = getStyle(parent, 'position');
			
				x += parent.offsetLeft;
				y += parent.offsetTop;

				// Mozilla and IE do not add the border
				if (mo || ie) {
					// add borders to offset
					x += parseInt(getStyle(parent, 'borderLeftWidth'));
					y += parseInt(getStyle(parent, 'borderTopWidth'));

					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					if (mo && parPos == 'absolute') absparent = true;
					// IE does not include the border on the body if an element is position static and without an absolute or relative parent
					if (ie && parPos == 'relative') relparent = true;
				}

				op = parent.offsetParent;
				do {
					if (options.scroll) {
						sl += parent.scrollLeft;
						st += parent.scrollTop;
					}
				
					if (mo && parent != elem && getStyle(parent, 'overflow') != 'visible') {
						x += parseInt(getStyle(parent, 'borderLeftWidth'));
						y += parseInt(getStyle(parent, 'borderTopWidth'));
					}
				
					parent = parent.parentNode;
				} while (parent != op);
				parent = op;

				if (parent.tagName.toLowerCase() == 'body' || parent.tagName.toLowerCase() == 'html') {
					// Safari and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
					if ((sf || (ie && _browser.boxModel)) && elemPos != 'absolute' && elemPos != 'fixed') {
						x += parseInt(getStyle(parent, 'marginLeft'));
						y += parseInt(getStyle(parent, 'marginTop'));
					}
					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					// IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
					if ( (mo && !absparent && elemPos != 'fixed') || 
					     (ie && elemPos == 'static' && !relparent) ) {
						x += parseInt(getStyle(parent, 'borderLeftWidth'));
						y += parseInt(getStyle(parent, 'borderTopWidth'));
					}
					break; // Exit the loop
				}
			} while (parent);
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

		return returnValue;
	}
	
function handleOffsetReturn(elem, options, x, y, sl, st) {
	if ( !options.margin ) {
		x -= parseInt(getStyle(elem, 'marginLeft'));
		y -= parseInt(getStyle(elem, 'marginTop'));
	}

	// Safari and Opera do not add the border for the element
	if ( options.border && (_browser.safari || _browser.opera) ) {
		x += parseInt(getStyle(elem, 'borderLeftWidth'));
		y += parseInt(getStyle(elem, 'borderTopWidth'));
	} else if ( !options.border && !(_browser.safari || _browser.opera) ) {
		x -= parseInt(getStyle(elem, 'borderLeftWidth'));
		y -= parseInt(getStyle(elem, 'borderTopWidth'));
	}

	if ( options.padding ) {
		x += parseInt(getStyle(elem, 'paddingLeft'));
		y += parseInt(getStyle(elem, 'paddingTop'));
	}
	
	// do not include scroll offset on the element
	if ( options.scroll ) {
		sl -= elem.scrollLeft;
		st -= elem.scrollTop;
	}

	return options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
	                      : { top: y, left: x };
};
	*/
	


var uColumns = function(element,columnCount,options) {
  this.initialize(element,columnCount,options);
}
uColumns.prototype = {
	
	initialize: function(elementName,columnCount,options) {
		this.elementName = elementName;
		this.element = $(elementName);
    this.columnCount = columnCount;
    if (!options) options = {};
    this.gutterWidth = parseInt(options.gutterWidth || 10);
    this.columnHeight = parseFloat(options.columnHeight) || null;
    this.columnWidth = parseFloat(options.columnWidth) || null;
    this.lineHeight = parseFloat(options.lineHeight) || parseFloat(getStyle(this.element,'line-height')) || null;
    if (options.columnDivs) {
    	this.columnDivs = [];
    	for (var t=0; t<options.columnDivs.length; t++) {
    		this.columnDivs[t] = $(options.columnDivs[t]);
    		if (!this.columnDivs[t]) {
    			alert("Unable to find element named "+options.columnDivs[t]);
    			return false;
    		}
    	}
    }
    this.page = parseInt(options.page || 0);
    this.prevDiv = $(options.prevDiv) || null;
    this.nextDiv = $(options.nextDiv) || null;
    if (this.columnDivs || this.columnHeight) this.autoHeight = false;
    else this.autoHeight = true;
    // if page is specified in the url, use that
    
    this.columns = [];
    this.pageCount = 1;
    
		// create this.columnCount copies of the 
		this.html = this.element.innerHTML;
		this.element.innerHTML = '';

    if (!this.columnDivs) {
	    if (!this.columnWidth) this.calculateWidths();
    	this.createColumnDivs();
    }
    this.createColumns();
    this.alignColumns();
	},
	
	parseMetric: function(number) {
    if (!number) return null;
	  if (number.match(/em$/i)) return 'em';
	  if (number.match(/px$/i)) return 'px';
	  if (number.match(/pt$/i)) return 'pt';
	  if (number.match(/in$/i)) return 'in';
	  if (number.match(/cm$/i)) return 'cm';
	  if (number.match(/mm$/i)) return 'mm';
	  if (number.match(/pc$/i)) return 'pc';
	  return 'px';
	},
	
	calculateWidths: function() {
		var elementWidth = this.element.clientWidth;
		var gutterWidth = this.gutterWidth * (this.columnCount-1);
		this.columnWidth = Math.floor((elementWidth - gutterWidth) / this.columnCount);
	},
	
	createColumnDivs: function() {
		this.element.style.position = 'relative';

		this.columnDivs = [];
		var left = 0;
		for (var t=0; t<this.columnCount; t++) {
			this.columnDivs[t] = document.createElement('div');
			this.columnDivs[t].id = this.elementName+'_column'+t;
			this.columnDivs[t].style.width = this.columnWidth+'px';
			this.columnDivs[t].style.position = 'absolute';
			this.columnDivs[t].style.margin = '0px';
			this.columnDivs[t].style.padding = '0px';
			this.columnDivs[t].style.borderWidth = '0px';
			this.columnDivs[t].style.top = '0px';
			this.columnDivs[t].style.left = left+'px';
			this.element.appendChild(this.columnDivs[t]);

			// if column height is automatic, get it
			if (t==0 && this.autoHeight) {
				this.columnHeight = Math.ceil(this.columns[0].clientHeight / this.columnCount);
				if (this.lineHeight) {
					this.columnHeight = Math.ceil(this.columnHeight / this.lineHeight);
					this.columnHeight = this.columnHeight * this.lineHeight;
				}
			}
			this.columnDivs[t].style.height = this.columnHeight+'px';

			// increment
			left += this.columnWidth + this.gutterWidth;
		}
	},
	
	createColumns: function() {
		for (var t=0; t<this.columnCount; t++) {
			var container = this.columnDivs[t];
   		container.style.display = '';
   		container.style.overflow = 'hidden';
			this.columns[t] = document.createElement('div');
			this.columns[t].id = this.elementName+'_column'+t+'_inner';
			this.columns[t].style.width = container.clientWidth+'px';
			this.columns[t].style.position = 'absolute';
			this.columns[t].innerHTML = this.html;
			this.columns[t].style.margin = '0px';
			this.columns[t].style.padding = '0px';
			this.columns[t].style.borderWidth = '0px';
			this.columns[t].style.left = '0px';
			if (this.lineHeight) this.columns[t].style.lineHeight = this.lineHeight+'px';
			container.appendChild(this.columns[t]);
		}
		
		// do we need to make the first column clickable?
		// if autoheight is set then we're displaying everything at once, no need to click
		if (!this.autoHeight) {
			var prevClick = this.prevPage.bindAsEventListener(this);
			if (!this.prevDiv) this.prevDiv = this.columns[0];
			ObserveEvent(this.prevDiv,'click',prevClick);

			var nextClick = this.nextPage.bindAsEventListener(this);
			if (!this.nextDiv) this.nextDiv = this.columns[this.columns.length-1];
			ObserveEvent(this.nextDiv,'click',nextClick);
		}
		
		// calculate total page height
		this.pageHeight = 0;
		for (var t=0; t<this.columnCount; t++) {
			this.pageHeight += this.columnDivs[t].clientHeight;
		}

		// calculate number of pages
		this.pageCount = Math.ceil(this.columns[0].clientHeight / this.pageHeight);
	},
	
	// align the tops of columns based on what page we're viewing...
	alignColumns: function() {
    if (this.page<0) this.page = 0;
    if (this.page>this.pageCount-1) this.page = this.pageCount-1;
		var left = 0;
		// top is page * number of columns * column height
		var top = this.page * this.pageHeight * -1;
		for (var t=0; t<this.columnCount; t++) {
			this.columns[t].style.top = top+'px';
			// increment
			top -= this.columnDivs[t].clientHeight;
		}
		
		this.managePageLinks();
	},
	
	managePageLinks: function() {
		// if our prev / next links are our columns, don't do nothing
		if (this.prevDiv == this.columns[0]) return true;
		
    // if height is automatic, hide prev and next page links (if they exist)
    if (this.autoHeight) {
    	if (this.prevDiv) this.prevDiv.style.display = 'none';
    	if (this.nextDiv) this.nextDiv.style.display = 'none';
    	return true;
    }

    // else show them as needed
    if (this.prevDiv) {
    	if (this.page>0) this.prevDiv.style.display = '';
    	else this.prevDiv.style.display = 'none';
    }

    if (this.nextDiv) {
    	if (this.page<this.pageCount-1) this.nextDiv.style.display = '';
    	else this.nextDiv.style.display = 'none';
    }
	},
	
	nextPage: function() {
		if (this.page==this.pageCount-1) return false;
		this.page++;
		this.alignColumns();
	},
	
	prevPage: function() {
		if (this.page==0) return false;
		this.page--;
		this.alignColumns();
	},
	
	showPage: function(page) {
		this.page = parseInt(page);
		this.alignColumns();
	}
	
}

/**
 * uShow provides easy to implement DHTML slideshows containing any type 
 * or amount of HTML content. Simply create a new uShow object, create
 * as many slides as you want (assigning an html element to that slide)
 * add the slide to the slideshow, and you're done. You can specify 
 * if the slideshow auto-plays, if controls are shown (or you can build
 * your own controls and call the appropriate methods on click), specify
 * transitions (currently only "fade" is supported), specify per show 
 * and/or frame delay times, etc.
 */

/**
 * Create a uShow simply by calling new uShow(). eg:
 *   var slideshow = new uShow();
 * Nothing else is required. However, you can specify the following
 * options if you like:
 *   uShow.repeat [Boolean] True if you want the show to loop (default: True)
 *   uShow.delay [Int] The default slide delay in seconds (default: 5)
 *   uShow.transition [String] The default transition type, eg: 'fade' (default: 'fade')
 *   uShow.transitionTime [Float] The default transition time, in seconds (default: 0.5)
 *   uShow.controls [Boolean] Whether to automatically draw Prev/Next/Play/Stop controls or not on page load (default: true)
 *   uShow.autoplay [Boolean] Whether or not to automatically "play" the show on page load (default: true)
 *   uShow.controlsElement [HTMLElement] If specified, controls are drawn in this element, else in the main element
 * Once you've setup your slideshow you'll need to add some slides to it. See the 
 * documentation for uSlide for specific details, then pass your new slide to 
 * uShow. eg:
 *   uShow.addSlide(slide);
 * Do this as many times as you have slides.
 * If you want to create your own controls instead of using the build in ones,
 * set uShow.controls to false, then create elements that call on of the following
 * methods, depending on what you want it to do:
 *   uShow.clickPlay();
 *   uShow.clickStop();
 *   uShow.clickPrev();
 *   uShow.clickNext();
 */
var uShow = function() {
  this.initialize();
}
uShow.prototype = {
	
	uid: 0,
	
	initialize: function() {
		this.uid = uShow.prototype.uid++;
		/* 
		 * if autoplay is on, set the slideshow to run once the page is loaded
		 */
		this.initListen = this.init.bindAsEventListener(this);
		ObserveEvent(window,'load',this.initListen);
		
		// editable vars
		this.repeat = true;
		this.delay = 5;
		
		/**
		 * Possible values (at the moment) are 'fade' and 'none'. You can add your own
		 * transition effects by creating uShow* classes which follow the same format
		 * as the ones included (uShowFade and uShowNone)
		 */
		this.transition = 'fade';
		this.transitionTime = 0.5;
		this.controls = true;
		this.autoplay = true;
		this.controlsElement = null;
		
		/**
		 * Init events with empty data
		 */
		this.onTransitionStart = null;
		this.onTransitionComplete = null;
		this.onTransitionCancel = null;
		
		// internal vars
		this.slides = [];
		this.slidePos = 0;

		/* 
		 * Set event observers for later use
		 */
		this.nextListen = this.next.bindAsEventListener(this);
	
		this.clickNextListen = this.clickNext.bindAsEventListener(this);
		this.clickPrevListen = this.clickPrev.bindAsEventListener(this);
		this.clickPlayListen = this.clickPlay.bindAsEventListener(this);
		this.clickStopListen = this.clickStop.bindAsEventListener(this);
	},
	
	/**
	 * Adds a slide to the slideshow.
	 * @param uSlide slide The slide to add
	 * @return Boolean True on success, false on failure
	 */
	addSlide: function(slide) {
		// add a slide to the slides array
		this.slides.push(slide);
		// if it's the first slide added, set our parent element
		if (this.slides.length==1) {
			this.element = slide.element.parentNode;
			// if the parent isn't relatively positioned, warn the user
			if (getStyle(this.element,'position')!='absolute') this.element.style.position = 'relative';
		}
		// init the element
		slide.prepare();
		// and hide it if it's not the first one
		if (this.slides.length>1) slide.hide();
		// success
		return(true);
	},
	
	/**
	 * Internal function called automatically when the page is completed loading.
	 * Checks the slideshow's 'controls' and 'autoplay' variables and calls the
	 * appropriate functions if they're set to true.
	 * @private
	 */
	init: function() {
		// if autoplay is set, start playing
		if (this.autoplay) this.play();
		if (this.controls) this.drawControls();
	},
	
	/**
	 * Internal function used to set a timer to advance to the next slide. If you
	 * want to cause the slideshow to advance using your own button, you probably
	 * want to call uShow.clickPlay() instead.
	 * @private
	 */
	play: function() {
		this.autoplay = true;
		// get the slide we're on
		var slide = this.slides[this.slidePos];
		// set the initial delay
		var delay = this.getDelay(slide);
		// if delay is 0, stop
		if (delay==0) {
			this.stop();
		}
		else {
			this.timeout = setTimeout(this.nextListen,delay*1000);
		}
	},
	
	/**
	 * Public function to be called when the user clicks the slideshow's "play" button.
	 */
	clickPlay: function() {
		this.autoplay = true;
		this.next();
	},
	
	/**
	 * Public function to be called when the user clicks the slideshow's "next" button.
	 */
	clickNext: function() {
		this.next();
	},
	
	/**
	 * Public function to be called when the user clicks the slideshow's "previous" button.
	 */
	clickPrev: function() {
		this.previous();
	},
	
	/**
	 * Public function to be called when the user clicks the slideshow's "stop" button.
	 */
	clickStop: function() {
		this.stop();
	},
	
	/**
	 * Private function that gets the delay for a particular slide. If the slide has
	 * it's own delay set, that is returned, else the default slideshow delay is
	 * returned. This 'delay' is the time to show it on the screen before beginning
	 * the transition to the next slide.
	 * @private
	 * @param uSlide slide The slide to get the delay time for.
	 * @return integer The delay time to use (in seconds)
	 */
	getDelay: function(slide) {
		// if the slide has a delay set, use that
		if (slide.delay!=undefined) return(slide.delay);
		// else use the default
		return(this.delay);
	},
	
	/**
	 * Private function that gets the transition effect for a particular slide. If 
	 * the slide has it's own transition set, that is returned, else the default 
	 * slideshow effect is returned. 
	 * @private
	 * @param uSlide slide The slide to get the effect time for.
	 * @return string The effect to be shown
	 */
	getTransition: function(slide) {
		// if the slide has a delay set, use that
		if (slide.transition!=undefined) return(slide.transition);
		// else use the default
		return(this.transition);
	},
	
	/**
	 * Private function to stop the slideshow when it's playing. If you want to stop
	 * the show using your own busson, you should call uShow.clickStop() intead.
	 * @private
	 */
	stop: function() {
		// if there's another slide queued up for display, cancel it
		if (this.timeout) clearTimeout(this.timeout);
		this.timeout = null;
		// turn off play
		this.autoplay = false;
	},
	
	/**
	 * Private function which advances the slideshow by one and calls the required
	 * transition effect via uShow.transitStart
	 * @private
	 */
	next: function() {
		if (this.timeout) clearTimeout(this.timeout);
		// get the current slide and the next one
		var current = this.slides[this.slidePos];
		this.slidePos++;
		if (this.slidePos>=this.slides.length) {
			// if we don't allow repeat, don't allow next
			if (!this.repeat) return(false);
			// else loop
			this.slidePos=0;
		}
		var next = this.slides[this.slidePos];
		this.transitStart(current,next);
	},
	
	/**
	 * Private function which decrements the slideshow by one and calls the required
	 * transition effect via uShow.transitStart
	 * @private
	 */
	previous: function() {
		if (this.timeout) clearTimeout(this.timeout);
		// get the current slide and the prev one
		var current = this.slides[this.slidePos];
		this.slidePos--;
		if (this.slidePos<0) {
			// if we don't allow repeat, don't allow previous
			if (!this.repeat) return(false);
			// else loop
			this.slidePos=this.slides.length-1;
		}
		var prev = this.slides[this.slidePos];
		this.transitStart(current,prev);
	},
	
	/**
	 * Private function which transitions to the specified slide id 
	 * @private
	 */
	show: function(slideId) {
		// if the current is the specified, just return
		if (slideId==this.slidePos) return true;

		if (this.timeout) clearTimeout(this.timeout);
		// get the current slide and the prev one
		var current = this.slides[this.slidePos];
		if (!this.slides[slideId]) return false;
		this.slidePos = slideId;
		var next = this.slides[this.slidePos];
		this.transitStart(current,next);
	},
	
	/**
	 * Internal function that starts a transition from one slide to another.
	 * @private
	 * @param uSlide fromSlide The outgoing slide
	 * @param uSlide toSlide The incoming slide
	 */
	transitStart: function(fromSlide,toSlide) {
		// if there's a transition still in progress, force it's completion before starting a new one
		this.transitCancel();
		if (this.onTransitionStart) this.onTransitionStart();
		// fading is the only transition we support at the moment
		var transition = this.getTransition(toSlide);
		switch (transition) {
			case 'fade':
			  this.transit = new uShowFade(this);
			  break;
			case 'none':
			default:
			  this.transit = new uShowNone(this);
			  break;
		}
		this.transit.start(fromSlide,toSlide);
	},
	
	/**
	 * Internal function called by the transition in progress when the 
	 * effect is complete.
	 * @private
	 */
	transitComplete: function() {
		this.transit = undefined;
		if (this.onTransitionComplete) this.onTransitionComplete();
		if (this.autoplay) this.play();
	},
	
	/**
	 * Internal function called when a transition is in progress and another
	 * one needs to be started (cancel the old one to make way for the new)
	 * @private
	 */
	transitCancel: function() {
		if (this.transit) this.transit.forceComplete();
		this.transit = undefined;
		if (this.onTransitionCancel) this.onTransitionCancel();
	},
	
	/**
	 * Internal function used to draw the default controls to manage the slideshow
	 * (eg: next, previous, start, stop). These can be styled using css, or you
	 * can cancel the drawing of these alltogether by specifying 
	 * uShow.controls = false.
	 * @private
	 */
	drawControls: function() {
		// where do we attach these to?
		var element = $(this.controlsElement);
		if (!element) element = this.element;
		if (!element) return(false);

		this.buttonPrev = document.createElement('div');
		this.buttonPrev.className = 'uShow_prev uShow_button';
		this.buttonPrev.onclick = this.clickPrevListen;
		this.buttonPrev.innerHTML = 'Previous';
		element.appendChild(this.buttonPrev);

		this.buttonPlay = document.createElement('div');
		this.buttonPlay.className = 'uShow_play uShow_button';
		this.buttonPlay.onclick = this.clickPlayListen;
		this.buttonPlay.innerHTML = 'Play';
		element.appendChild(this.buttonPlay);

		this.buttonStop = document.createElement('div');
		this.buttonStop.className = 'uShow_stop uShow_button';
		this.buttonStop.onclick = this.clickStopListen;
		this.buttonStop.innerHTML = 'Stop';
		element.appendChild(this.buttonStop);

		this.buttonNext = document.createElement('div');
		this.buttonNext.className = 'uShow_next uShow_button';
		this.buttonNext.onclick = this.clickNextListen;
		this.buttonNext.innerHTML = 'Next';
		element.appendChild(this.buttonNext);
	}
	
};



/**
 * Very simple transition effect. Every transition needs to accept the
 * parent uShow as a constructor parameter, and contain the methods 'start'
 * and 'forceComplete'. When the effect is complete, they MUST call the
 * parent slideshow's transitComplete() function so the slideshow can
 * continue. Anything else is up to the transition effect.
 */
var uShowNone = function(parentUShow) {
	this.initialize(parentUShow);
}
uShowNone.prototype = {	
	initialize: function(parentUShow) {
		/**
		 * Keeps track of the parent slideshow object so we can call transitComplete()
		 * later when the effect is done.
		 */
		this.parentUShow = parentUShow;
	},
	
	/**
	 * Called by the slideshow object to start the transition.
	 * @param uSlide fromSlide The outgoing slide
	 * @param uSlide toSlide The new to be displayed slide
	 */
	start: function(fromSlide,toSlide) {
		toSlide.show();
		fromSlide.hide();
		this.parentUShow.transitComplete();
	},
	
	/**
	 * This method must exist because the parent slideshow may be forced to
	 * call it if the transition needs to be sped up (eg: the user clicked
	 * the next button 3 times in a row)
	 */
	forceComplete: function() {
	}
	
};




/**
 * A slightly more complex transition effect. It places the incoming slide
 * behind the outgoing slide and then fades the outgoing slide out, removing
 * it at the end.
 */
var uShowFade = function(parentUShow) {
	this.initialize(parentUShow);
}
uShowFade.prototype = {
	initialize: function(parentUShow) {
		/**
		 * Keeps track of the parent slideshow object so we can call transitComplete()
		 * later when the effect is done.
		 */
		this.parentUShow = parentUShow;
	},
	
	/**
	 * Called by the slideshow object to start the transition.
	 * @param uSlide fromSlide The outgoing slide
	 * @param uSlide toSlide The new to be displayed slide
	 */
	start: function(fromSlide,toSlide) {
		/**
		 * A listener used by the animation.
		 */
		this.fadeListen = this.fadeStep.bindAsEventListener(this);

		// The number of steps in the transition. More steps is smoother but
		// requires more CPU cycles.
		this.fadeSteps = 20;
	
	  // the amount of time taken to do the fade is retrived from the
	  // uShow object that created this transition
		this.fadeAmount = 100 / this.fadeSteps;
		this.fadeTime = this.parentUShow.transitionTime * 1000 / this.fadeSteps;

		this.fadeFrom = fromSlide;
		this.fadeTo = toSlide;
		
		// make sure the new slide is under the old one
		this.fadeFrom.onTop();
		this.fadeTo.onBottom();
		// show the new slide (but it should be hidden underneith the old one)
		this.fadeTo.show();
		// now transition the two, measure the time it took to do one
		this.fadeOpacity = 100;
	
		this.fadeStep();
	},
	
	/**
	 * This method must exist because the parent slideshow may be forced to
	 * call it if the transition needs to be sped up (eg: the user clicked
	 * the next button 3 times in a row)
	 */
	forceComplete: function() {
		// stop the event
		this.fadeTo.setOpacity(100);
		this.fadeFrom.hide();
		this.fadeTo.show();
		this.fadeStop();
	},
	
	/**
	 * Performs one frame of the fade animation. Decrements the opacity, applys
	 * it, and sets a timeout for the next frame.
	 */
	fadeStep: function() {
		if (!this.fadeFrom || !this.fadeTo) return(false);
		// set transparency
		this.fadeOpacity -= this.fadeAmount;
		this.fadeOpacity = Math.round(this.fadeOpacity);
		// if we're below 0, set it to 0 and stop
		if (this.fadeOpacity<0) this.fadeOpacity = 0;
		this.fadeFrom.setOpacity(this.fadeOpacity);
		// if we're at 0, stop
		if (this.fadeOpacity==0) this.fadeStop();
		if (this.fadeTimeout) clearTimeout(this.fadeTimeout);
		this.fadeTimeout = setTimeout(this.fadeListen,this.fadeTime);
	},
	
	/**
	 * Called when the fade is complete. Hides the outgoing slide completely and
	 * resets the vars.
	 */
	fadeStop: function() {
		this.fadeFrom.hide();
		this.fadeFrom = undefined;
		this.fadeTo = undefined;
		this.fadeOpacity = undefined;
		if (this.fadeTimeout) clearTimeout(this.fadeTimeout);
		this.fadeTimeout = undefined;
		this.parentUShow.transitComplete();
	}
	
};




/**
 * uSlide is the storage object for each slide. To create a new slide call
 * new uSlide(elementId), after which you can specify variables specific to
 * that slide. Make sure you add this new slide to the slideshow after you're
 * done configuring it!
 * eg:
 *   var slideshow = new uShow();
 *   var slide = new uSlide('mySlide1');
 *   slide.delay = 20;
 *   slide.transition = 'none';
 *   slideshow.addSlide(slide);
 */
var uSlide = function(elementId) {
  this.initialize(elementId);
}
uSlide.prototype = {
	
	initialize: function(elementId) {
		this.elementId = elementId;
		this.element = $(elementId);
		if (!this.element) return(false);
		
		this.delay = undefined;
		this.transition = undefined;
		this.transitionTime = undefined;
	},
	
	prepare: function() {
		if (!this.element) return(false);
		// make it absolutely positioned
		this.element.style.position = 'absolute';
		this.element.style.top = '0px';
		this.element.style.left = '0px';
	},
	
	hide: function() {
		if (!this.element) return(false);
		this.element.style.display = 'none';
	},
	
	show: function() {
		if (!this.element) return(false);
		this.element.style.display = '';
		// reset
		this.setOpacity(100);
	},
	
	onTop: function() {
		this.element.style.zIndex = '1000';
	},
	
	onBottom: function() {
		this.element.style.zIndex = '999';
	},
	
	setOpacity: function(opacity) {
		// ie
		if (document.all) {
			this.element.style.filter = "alpha(style=0,opacity:" + opacity + ")"; // IE
		}
		// everything else
		else {
		  this.element.style.KHTMLOpacity = opacity/100;
		  this.element.style.opacity = opacity/100;
		  this.element.style.MozOpacity = opacity/100;
		}
	}
	
};





/**
 * Used from the Prototype Libaray (with minor modifications)
 */
Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
}

function ObserveEvent(element, name, observer) {
  if (element.addEventListener) {
    element.addEventListener(name, observer, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + name, observer);
  }
}

function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
};


/**
 * Pause Productions Validation Functions
 * @author Travis Richardson <travis@pause.ca>
 * @package Pause-uCodeJS
 * @subpackage uTools
 */



/**
 * Provides simple variable validation and type casting
 *
 * @param mixed $value The variable you want to test / cast. 
 * 
 * If validating a boolean type, '1',1,'true','t','y','yes','on' are considered true, and '0',0,'false','f','n','no','off' are considered false. Everything else is "unmatched" and will return null. 
 * 
 * Note that for email validation, only ascii characters are accepted. IDN (non-ascii) domains and email addresses will be returned NULL (as in, invalid). This shouldn't be a big deal, but is worth noting here.
 * 
 * @param string $type One of 'int'/'integer', 'str'/'string', 'dec'/'float', 'bool'/'boolean', or 'email'/'e-mail'. 
 * 
 * uValidate can also deal with arrays by appending "array:" to the beginning of the type. Eg, to validate an array of integers between 1 and 10 call uValidate($someVar,'array:int',1,10); 
 * 
 * @param mixed $test1 The first validation test
 * 
 * If $type is an int or float, then $test1 is the minimum possible value
 * 
 * If $type is a str then $test1 can either be an array (accepted values), a string (regular expression test), or an integer (minimum length). 
 * 
 * $test1 is not used for boolean tests.
 * 
 * $test1 is not used for email tests.
 * 
 * @param mixed $test2 The second validation test
 * 
 * If $type is an int or float, then $test2 is the maximum possible value
 * 
 * If $type is a string and $test2 is an integer then $test2 is the maximum length
 * 
 * $test2 is not used for boolean tests
 * 
 * $test2 is not used for email tests
 * 
 * @return mixed Returns null if the value failed the test(s), or a type cast value if it succeeded.
 * 
 * If your $type didn't match any known type, the value is returned unchanged.
 * 
 * Note that when asking for a boolean it's easy to confuse a return value of false with a failed value of null. Use === to make sure.
 */
function uValidate(value,type,test1,test2) {

  if (type.indexOf('array:')==0) {
  	type = type.substr(6);
  	if (!isArray(value)) value = [value];
  	for (i in value) {
  		value[i] = uValidate(value[i],type,test1,test2);
  	}
  	return value;
  }

	switch(type) {
		case 'int':
		case 'integer':
		  // not numeric
		  if (!isNumeric(value)) return undefined;
		  // decimal
		  if (parseInt(value)!=value) return undefined;
		  // check for appropriate values
		  if (isArray(test1) && test1.indexOf(value)==-1) return undefined;
		  // check for minimum
		  if (!isArray(test1) && test1!=undefined && value<test1) return undefined;
		  // check for maximum
		  if (test2!=undefined && value>test2) return undefined;
		  // make it an int (typecast it)
		  value = parseInt(value);
		  return value;
		
		case 'dec':
		case 'decimal':
		case 'float':
		  // not numeric
		  if (!isNumeric(value)) return undefined;
		  // decimal
		  if (parseFloat(value)!=value) return undefined;
		  // check for appropriate values
		  if (isArray(test1) && test1.indexOf(value)==-1) return undefined;
		  // check for minimum
		  if (!isArray(test1) && test1!=undefined && value<test1) return undefined;
		  // check for maximum
		  if (test2!=undefined && value>test2) return undefined;
		  // make it an int (typecast it)
		  value = parseFloat(value);
		  return value;
		
		case 'str':
		case 'string':
		  // we refuse to process objects or arrays this way
		  if (isObject(value) || isArray(value)) return undefined;
		  // return TRUE and FALSE for boolean types
		  if (isBool(value)) {
		  	if (value==true) return 'TRUE';
		  	return 'FALSE';
		  }
		  // typecast it to be a string
		  value = value.toString();
		  // if typecasting failed, set to a blank string
		  if (!value) value = '';
		  // if the first test is an array of values, test for a match
		  if (isArray(test1) && test1.indexOf(value)==-1) return undefined;
		  // if test1 is a string, it's a regex
		  if (isString(test1)) {
		  	var regex = new RegExp(test1);
		  	if (!value.match(regex)) return undefined;
		  }
		  // if test1 is an int, make sure string is at least this long
		  if (isInt(test1) && value.length<test1) return undefined; 
		  // if test2 is an int, make sure string is at most this long
		  if (isInt(test2) && value.length>test2) return undefined; 
		  // success
		  return value;
    
    case 'bool':
    case 'boolean':
      value = uValidate(value,'str');
      value = value.toUpperCase();
      if (value=='1' || value==1 || value=='ON' || value=='TRUE' || value=='T' || value=='YES' || value=='Y') return true;
      if (value=='0' || value==0 || value=='OFF' || value=='FALSE' || value=='F' || value=='NO' || value=='N') return false;
      return undefined;
      
    case 'email':
    case 'e-mail':
      value = uValidate(value,'str',test1,test2);
      if (isString(value) && value=='') return value;
      value = uValidate(value,'str','/^[a-z0-9\_\-\.]+\@[a-z0-9\-\.]+\.[a-z]{2,}$/i');
      return value;
      
    default:
      return undefined;

	}
}
  



