/*
  Makes '$()' shorthand for 'document.getElementById()'. If you pass it an
  it'll return an array of elements.
*/
function $() {
  var elements = [];
  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string') {
      elements.push(document.getElementById(element));
    }
  }
  return (elements.length == 1) ? elements[0] : elements;
}

/*
  ------------------------------------------------------------------------------
  Lots of handy little functions that get used over and over.
  ------------------------------------------------------------------------------
*/
var util = {};

util.padLeft = function(string, padChar, length) {
  string = string + '';
  while (string.length < length) {
    string = padChar + string;
  }
  return string;
};

util.padRight = function(string, padChar, length) {
  while (string.length < length) {
    string = string = padChar;
  }
  return string;
};

util.addEvent = function(element, event, func) {
  if (element.addEventListener) {
    element.addEventListener(event, func, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + event, func);
  }
};

util.removeEvent = function(element, event, func) {
  if (element.removeEventListener) {
    element.removeEventListener(event, func, false);
  } else if (element.detachEvent) {
    element.detachEvent('on' + event, func);
  }
};

util.findPos = function(obj) {
  var curleft = curtop = 0;
  if (obj.offsetParent) {
    do {
      curleft += obj.offsetLeft;
      curtop += obj.offsetTop;
    } while (obj = obj.offsetParent);
  }
  return [curleft, curtop];
};

util.getEventTarget = function(e) {
  var target;
  if (e.target) target = e.target;
  else if (e.srcElement) target = e.srcElement;
  /* Defeat Safari bug */
  if (target.nodeType == 3) {
    target = target.parentNode;
  }
  return target;
};

util.getMousePosition = function(e) {
  var posx = 0;
  var posy = 0;
  if (!e) var e = window.event;
  if (e.pageX || e.pageY) 	{
    posx = e.pageX;
    posy = e.pageY;
  } else if (e.clientX || e.clientY) {
    posx = e.clientX + document.body.scrollLeft
      + document.documentElement.scrollLeft;
    posy = e.clientY + document.body.scrollTop
      + document.documentElement.scrollTop;
  }
  return [posx, posy];
};

/*
  Perform an asynchronous request, then call the appropriate handler based on
  success or failure of the request.
*/
util.httpRequest = function(url, success, failure, opt_postData) {
  var xmlHttp = util.getHTTPObject();
  var method = (opt_postData) ? 'POST' : 'GET';
  xmlHttp.open(method, url, true);
  if (opt_postData) {
    xmlHttp.setRequestHeader('Content-Type',
        'application/x-www-form-urlencoded');
  }
  xmlHttp.onreadystatechange = function () {
    util.httpRequestCallback(xmlHttp, success, failure);
  };
  xmlHttp.send(opt_postData);
};

/*
  Analyze the result of an asynchronous request and execute the appropriate
  handler.
*/
util.httpRequestCallback = function(xmlHttp, success, failure) {
  if (xmlHttp.readyState != 4) return;
  if (xmlHttp.status != 200 && xmlHttp.status != 304) {
    failure(xmlHttp);
  } else {
    success(xmlHttp);
  }
};

util.getHTTPObject = function() {
  var xmlhttp;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (E) {
      xmlhttp = false;
      }
    }
  @else
    xmlhttp = false;
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp = false;
    }
  }
  return xmlhttp;
};

/*
  Cross-browser method for setting element opacity.
*/
util.setOpacity = function (element, value) {
  if (element.filters) {
    /* For IE */
    try {
      element.style.filter = 'alpha(opacity=' + value + ')';
    } catch (e) { }
  } else {
    /* For pretty much everyone else */
    element.style.opacity = '' + (value / 100);
  }
};

/*
  Cross-browser method for getting element opacity.
*/
util.getOpacity = function (element) {
  if (element.style.opacity) {
    var opacity = parseFloat(element.style.opacity) * 100;
  }
  if (element.filters) {
    try {
      var opacity = element.filters['alpha'].opacity;
    } catch (ex) { }
  }
  return (typeof(opacity) == 'undefined') ? 0 : opacity;
};

/*
  Determine the size of the page.
  (credit quirksmode.org)
*/
util.getPageSize = function() {
  var xScroll, yScroll;
  if (window.innerHeight && window.scrollMaxY) {
    xScroll = window.innerWidth + window.scrollMaxX;
    yScroll = window.innerHeight + window.scrollMaxY;
  } else if (document.body.scrollHeight > document.body.offsetHeight) {
    // all but Explorer Mac
    xScroll = document.body.scrollWidth;
    yScroll = document.body.scrollHeight;
  } else {
    // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
    xScroll = document.body.offsetWidth;
    yScroll = document.body.offsetHeight;
  }
  var windowWidth, windowHeight;
  if (self.innerHeight) { // all except Explorer
    if (document.documentElement.clientWidth) {
      windowWidth = document.documentElement.clientWidth;
    } else {
      windowWidth = self.innerWidth;
    }
    windowHeight = self.innerHeight;
  } else if (document.documentElement &&
      document.documentElement.clientHeight) {
    // Explorer 6 Strict Mode
    windowWidth = document.documentElement.clientWidth;
    windowHeight = document.documentElement.clientHeight;
  } else if (document.body) {
    // other Explorers
    windowWidth = document.body.clientWidth;
    windowHeight = document.body.clientHeight;
  }
  // for small pages with total height less then height of the viewport
  if (yScroll < windowHeight) {
    pageHeight = windowHeight;
  } else {
    pageHeight = yScroll;
  }
  // for small pages with total width less then width of the viewport
  if (xScroll < windowWidth) {
    pageWidth = xScroll;
  } else {
    pageWidth = windowWidth;
  }
  arrayPageSize = new Array(pageWidth, pageHeight, windowWidth, windowHeight)
  return arrayPageSize;
};

util.formatDate = function(format, date) {
  var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
      'August', 'September', 'October', 'November', 'December'];
  var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
      'Saturday'];
  var ordinal = ['st', 'nd', 'rd', 'th'];
  var daysInAMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  var leapYear = (date.getYear() % 4 == 0);
  var leapMonth = (leapYear && date.getMonth() == 1);
  var formatter = {
    'd' : util.padLeft(date.getDate(), '0', 2),
    'D' : days[date.getDay()].substring(0, 3),
    'j' : date.getDate() + 1,
    'l' : days[date.getDay()],
    'N' : date.getDay() + 1,
    'S' : ordinal[Math.min(date.getDay(), 3)],
    'w' : date.getDay(),
    'z' : Math.ceil((date - (new Date(date.getFullYear(),0,1))) / 86400000),
    'W' : Math.ceil((date - (new Date(date.getFullYear(),0,1))) / 86400000 / 7),
    'F' : months[date.getMonth()],
    'm' : util.padLeft(date.getMonth() + 1, '0', 2),
    'M' : months[date.getMonth()].substring(0, 3),
    'n' : date.getMonth() + 1,
    't' : daysInAMonth[date.getMonth()] + (leapMonth) ? 1 : 0,
    'L' : (leapYear) ? 1 : 0,
    'o' : '', // No idea what's going on with this one
    'Y' : date.getFullYear(),
    'y' : (date.getFullYear() + '').substring(2, 2),
    'a' : (date.getHours() < 11) ? 'am' : 'pm',
    'A' : (date.getHours() < 11) ? 'AM' : 'PM',
    'B' : '', // No idea what's going on with this one
    'g' : (date.getHours() % 12) + 1,
    'G' : date.getHours() + 1,
    'h' : util.padLeft((date.getHours() % 12), '0', 2),
    'H' : util.padLeft(date.getHours(), '0', 2),
    'i' : util.padLeft(date.getMinutes(), '0', 2),
    's' : util.padLeft(date.getSeconds(), '0', 2),
    'u' : date.getMilliseconds() + date.getSeconds() * 1000
    // That should about do it, the rest isn't very useful
  };

  var format = format.split('');
  var result = [];
  for (var i = 0; i < format.length; i++) {
    if (formatter[format[i]]) {
      result.push(formatter[format[i]]);
    } else {
      result.push(format[i]);
    }
  }
  return result.join('');
};

util.buildUrl = function(url, parameters) {
  var parsedUrl = {};
  var pieces = (url + '').split('?');
  parsedUrl.page = pieces[0];
  parsedUrl.parameters = {};
  var query = pieces[1].split('&');
  for (var i = 0; i < query.length; i++) {
    var temp = query[i].split('=');
    parsedUrl.parameters[temp[0]] = temp[1];
  }
  for (var i in parameters) {
    parsedUrl.parameters[i] = parameters[i];
  }
  var newParameters = [];
  for (var i in parsedUrl.parameters) {
    newParameters.push(i + '=' + parsedUrl.parameters[i]);
  }
  return parsedUrl.page + '?' + newParameters.join('&');
};

/*
  Determine how far the page has been scrolled in the x and y directions.
  (credit quirksmode.org)
*/
util.getPageScroll = function() {
  var xScroll, yScroll;
  if (self.pageYOffset) {
    yScroll = self.pageYOffset;
    xScroll = self.pageXOffset;
  } else if (document.documentElement && document.documentElement.scrollTop) {
    // Explorer 6 Strict
    yScroll = document.documentElement.scrollTop;
    xScroll = document.documentElement.scrollLeft;
  } else if (document.body) {
    // all other Explorers
    yScroll = document.body.scrollTop;
    xScroll = document.body.scrollLeft;
  }
  arrayPageScroll = new Array(xScroll,yScroll)
  return arrayPageScroll;
};

/*
  When you absolutely positively must escape every single character in the
  string. (unlike the native 'escape()' function)
*/
util.escapeText = function(text) {
  var escapedText = '';
  for (var i = 0; i < text.length; i++) {
    var char = text.charCodeAt(i);
    escapedText += '%' + char.toString(16);
  }
  return escapedText;
};

/*
  ------------------------------------------------------------------------------
  Observer Design Pattern
  ------------------------------------------------------------------------------
*/
function Observer() { }

Observer.prototype.update = function() {
  throw "Sub-class failed to implement 'update' method";
};

function Subject() {
  this.observers = [];
}

Subject.prototype.notify = function() {
  for (var i = 0; i < this.observers.length; i++) {
    this.observers[i].update();
  }
};

Subject.prototype.addObserver = function(observer) {
  this.observers.push(observer);
};

Subject.prototype.removeObserver = function(observer) {
  this.observers.remove(observer);
};

/*
  ------------------------------------------------------------------------------
  Extensions to built-in functions/objects.
  ------------------------------------------------------------------------------
*/

/*
  The most straight-forward type of inheritance you can get in JavaScript
  without being forced to invoke the constructor of the super class. (e.g.
  foo.prototype = new bar())
  This gives you easy multiple inheritance, the only issue is that calling
  methods of parent classes needs to be hardcoded since each class doesn't know
  who it is inheriting from.
*/
Function.prototype.inherits = function(baseClass) {
  for (var i in baseClass.prototype) {
    this.prototype[i] = baseClass.prototype[i];
  }
};

Function.prototype.bind = function() {
  var f = this;
  var curried = [];
  for (var i = 0; i < arguments.length; i++) {
    curried.push(arguments[i]);
  }
  var object = curried.shift();
  return function() {
    var args = [];
    for (var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    return f.apply(object, curried.concat(args));
  }
};

Function.prototype.bindAsEventListener = function() {
  var f = this;
  var args = [];
  for (var i = 0; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  var object = args.shift();
  return function(event) {
    return f.apply(object, [event || window.event].concat(args));
  }
};

Array.prototype.contains = function(value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      return true;
    }
  }
  return false;
};

Array.prototype.clone = function() {
  var clone = [];
  for (var i = 0; i < this.length; i++) {
    clone.push(this[i]);
  }
  return clone;
};

/*
  Removes the specified value from the array. Returns the element if found,
  otherwise returns false.
*/
Array.prototype.remove = function(value) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == value) {
      this.splice(i, 1);
      return
    }
  }
  return false;
};

String.prototype.startsWith = function(string) {
   return (this.substring(0, string.length) == string);
};

String.prototype.contains = function(string, caseInsensitive) {
  if (caseInsensitive) {
    return (this.toLowerCase().indexOf(string.toLowerCase()) != -1);
  } else {
    return (this.indexOf(string) != -1);
  }
};
