/**
 * Shorthand for document.getElementById($id)
 */
function $($id) {
  return (isString($id)) ? document.getElementById($id) : $id;
}

/**
 * Shorthand for document.getElementsByTagName($tag)
 */
function $tag($tag) {
  return document.getElementsByTagName($tag);
}

/** console functions */
function clog(message) { if (window.console && console.log) console.log(message); else if (window.opera) opera.postError(message); }
function cwarn(message) { if (window.console && console.warn) console.warn(message); else if (window.opera) opera.postError(message); }
function cerror(message) { if (window.console && console.error) console.error(message); else if (window.opera) opera.postError(message); }
function cinfo(message) { if (window.console && console.info) console.info(message); else if (window.opera) opera.postError(message); }
function cdebug(message) { if (window.console && console.debug) console.debug(message); else if (window.opera) opera.postError(message); }
function ctrace() { if (window.console && console.trace) console.trace(); else if (window.opera) opera.postError('Trace not supported'); }

/**
 * Shorthand for document.getElementById($id).style
 * Return null if style not found
 */
function styleOf($elem) {
  var $e = $($elem);
  if ($e && $e.style) return $e.style;
  else return null;
}

//function is_array(param) { return typeof(param)=="object" && param.length }
function isString(param) { return typeof(param)=="string" }
// Fast string trimming.  Should I cache the regx?  http://blog.stevenlevithan.com/archives/faster-trim-javascript
function trim(str) { return str ? str.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : str; }
// Check if a strings ends with another
function endsWith($str, $end) { return $str.slice(-$end.length) == $end;  }
// Check if a strings starts with another
function startsWith($str, $start) { return $str.slice(0, $start.length) == $start;  }

// Get an optional array value without warnings
function aget(ary, index, default_value) { if (typeof(ary) == 'object' || ary instanceof Array) return ary[index] || default_value; else return ary; }
// Find item in array.  Will try indexOf first.
function inArray(item, ary) { if (ary && ary.indexOf) return ary.indexOf(item) >= 0; else if (ary && ary.length > 0) for (var i = 0; i < ary.length; i++) if (item == ary[i]) return true; return false; }
// Return all intersecting values in both array
function intersectArray(ary1, ary2) {
  var result = [];
  for (var i = 0; i < ary1.length; i++)
    if (inArray(ary1[i], ary2)) result.push(ary1[i]);
  return result;
}
// Returns all keys of an array/object
function keys(obj) { var result = []; for (var i in obj) result.push(i); return result; }
// Returns all values of an array/object
function values(obj) { var result = []; for (var i in obj) result.push(obj[i]); return result; }
// Return true if there is any duplicate values un the array.  Does not work with objects
function hasDuplicate(ary) {
  var appeared = [];
  var current = null;
  for (var i in ary) {
    current = ary[i];
    if (inArray(current, appeared)) return true
    else appeared.push(current);
  }
  return false;
}
// If it is known to be a simple primitive array, just use array.concat() to clone, not this.
function cloneArray(from) {
  if (!from) return from;
  var result=[];
  for (var i = 0; i < from.length; i++) {
    var value = from[i];
    result[i] = (typeof value == 'object') ? cloneObject(value) : value;
  }
  return result;
}
// Also copy getter and setter, if the property is enumerable.
function cloneObject(from) {
  if (!from) return from;
  // Detect special clones
  if (from instanceof Array)
    return cloneArray(from);
  else if (from instanceof RegExp)
    return new RegExp(from.source, ''+(from.global?'g':'')+(from.ignoreCase?'i':'')+(from.multiline?'m':''));
  else if (typeof from != 'object')
    return from;
  // Normal clones
  var result={};
  for (var i in from) {
    if (from.__lookupGetter__) {
      var getset = false;
      if (from.__lookupGetter__(i)) {
        result.__defineGetter__(i, from.__lookupGetter__(i));
        getset = true;
      }
      if (from.__lookupSetter__(i)) {
        result.__defineSetter__(i, from.__lookupSetter__(i));
        getset = true;
      }
      if (getset) continue;
    }
    var value = from[i];
    result[i] = (typeof value == 'object') ? cloneObject(value) : value;
  }
  return result;
}



/**
 * Generate get parameter from form of given id.
 * @param form string id of form
 */
function formToParam(id) {
  var list = document.getElementById(id).elements;
  var result = '';
  for (var i=0;i<list.length;i++) {
    var input=list[i];
    if (!input.name) continue;
    switch(input.type) {
      case 'text':
      case 'hidden':
      case 'password': result += escape(input.name)+'='+escape(input.value)+'&'; break;
      case 'checkbox':
      case 'radio': if (input.checked) result += escape(input.name)+'='+escape(input.value)+'&'; break;
      case 'select-one': result += escape(input.name)+'='+escape(input.options[input.selectedIndex].value)+'&'; break;
      case 'select-multiple': {
        var options = input.options;
        for (var j=0;j<options.length;j++) {
          var opt = options[j];
          if (!opt.selected) continue;
          result += escape(input.name)+'='+escape(opt.value)+'&';
        }
      } break;
      default:; // Button, image and reset ignored
    }
  }
  return result.substr(0, result.length-1);
}

/**
 * Toggle the display style of given element between 'none' and default
 * $elem can be an Element, element id, or comma separated element id
 * Return nothing.
 */
function toggleHidden($elem) {
  if (!$elem) return false;
  // Find element if given id
  if (isString($elem)) {
    if ($elem.indexOf(',')>=0) {
      var $list = $elem.split(',');
      for (var $i=0;$i<$list.length;$i++)
        toggleHidden($list[$i]);
      return false;
    } else {
      $elem = document.getElementById($elem);
      if (!$elem) return false;
    }
  }
  if (!$elem.style) return false;
  var $style = $elem.style;
  $style.display = ($style.display) ? '' : 'none';
  return true;
}

/**
 * Hide given element.
 * Parameter can be an element, element id, or comma separated element id.
 * Return nothing.
 */
function hide($elem) {
  if (isString($elem))
    if ($elem.indexOf(',')>=0) {
      var $list = $elem.split(',');
      for (var $i=0;$i<$list.length;$i++)
        hide($list[$i]);
    } else {
      var $style = styleOf(trim($elem));
      if ($style) $style.display='none';
    }
  else if ($elem.length) {
    for (var $i = 0; $i < $elem.length; $i++)
      if ($elem[$i]) hide($elem[$i]);
  } else
    $elem.style.display='none';
  return false;
}

/**
 * Show given element.
 * Parameter can be an element, element id, or comma separated element id.
 * Return nothing.
 */
function show($elem) {
  if (isString($elem))
    if ($elem.indexOf(',')>=0) {
      var $list = $elem.split(',');
      for (var $i=0;$i<$list.length;$i++)
        show($list[$i]);
    } else {
      var $style = styleOf(trim($elem));
      if ($style) $style.display='';
    }
  else if ($elem.length) {
    for (var $i = 0; $i < $elem.length; $i++)
      if ($elem[$i]) show($elem[$i]);
  } else
    $elem.style.display='';
  return false;
}

/**
 * Setup javascript css class, initialise hsrc image rollover.
 */
function addiah_init() {
  window.onload = null;
  var sl = document.styleSheets.length;
  var css_count = 0;
  // Set dynamic style
  for (var i=0; i < sl; i++) {
    var style = document.styleSheets[i];
    // Get stylesheet
    if (style.cssRules) style = style.cssRules;
    else if (style.rules) style = style.rules;
    else continue;
    // Find rule
    var rl = style.length;
    for (j=0; j < rl; j++) {
      var rule = style[j];
      if (rule.selectorText == '.js_show') {
        // Reveal .js_show
        rule.style.display = 'none';
        rule.style.display = '';
        if (++css_count >= 2) { i = sl; break; } // Continue if found all rules
      } else if (rule.selectorText == '.js_hide') {
        // Hide .js_hide
        rule.style.display = '';
        rule.style.display = 'none';
        if (++css_count >= 2) { i = sl; break; } // Continue if found all rules
      }
    }
  }
  // Set hsrc hover from www.youngpup.net
  var img,sh,sn,sd;
  var il = document.images.length;
  for(var i=0; i < il; i++) {
    var img = document.images[i];
    sh = img.getAttribute("hsrc");
    if (!img.n && img.src && sh){
      if (img.getAttribute("realSrc")) sn = img.getAttribute("realSrc"); // IE 6 png hover fix
      else sn = img.getAttribute("src");
      img.n = new Image();
      img.h = new Image();
      img.n.src = sn;
      img.h.src = sh;
      img.onmouseover = hsrcHoverOn;
      img.onmouseout = hsrcHoverOff;
    }
  }
}
function hsrcHoverOn() { this.src = this.h.src; }
function hsrcHoverOff() { this.src = this.n.src; }

/**
 * Fetch a url and invoke function on success or failure
 *
 * @param string url URL to request
 * @param function success Function to invoke on success, with request object as parameter
 * @param function success Function to invoke on failure, with request object as parameter
 */
function ajax_load(url, success, fail) {
  if (window.XMLHttpRequest)
    var req = new XMLHttpRequest();
  else if (window.ActiveXObject)
    var req = new ActiveXObject("Msxml2.XMLHTTP");
  var time = 0;
  req.open('GET',url,true);
  req.onreadystatechange = function(){
    if (req.readyState == 4) {
      // Fx throw exception on user abort, however we can't distinguish it from script error
      if (req.status == 200) {
//        clog('AJAX loaded in '+(new Date().valueOf()-time)+'ms - '+url);
        success(req);
      } else if (fail) fail(req);
      else { cerror('AJAX request failed'); cerror(req); }
//      else window.defaultStatus = 'AJAX request failed - '+url;
      success = fail = time = null;
      req.onreadystatechange = null; // Have to delay until we finish important things due to IE 6 bug
      req = null;
    }
  }
//  clog('AJAX load             - '+url);
  time = new Date().valueOf();
  req.send(null);
  return req;
}

/**
 * An event handler that cancles the action.  Always return false.
 * Example: return cancelAction(e)
 */
function cancelAction(e) {
  if (e && e.preventDefault) return e.preventDefault();
  else return false;
}

/** Disable submit button of a form, preventing it from submitting */
function disableForm(id) {
  var form = document.getElementById(id);
  if (!form) { cerror('Form '+id+' not found, cannot disable'); return; }
  form.onsubmit = cancelAction;
  var list = form.elements;
  for (var i=0;i<list.length;i++) {
    var input=list[i];
    if (input.type == 'submit' || input.type=='image') input.disabled=true;
  }
}

/** Enable submit button of a form, optionally assigning onsubmit handler */
function enableForm(id, onsubmit) {
  var form = document.getElementById(id);
  if (!form) { cerror('Form '+id+' not found, cannot enable'); return; }
  if (onsubmit) form.onsubmit = onsubmit;
  var list = form.elements;
  for (var i=0;i<list.length;i++) {
    var input=list[i];
    if ((input.type == 'submit' || input.type=='image') && input.disabled) input.disabled=null;
  }
}


/** Disable all controls of a form */
function disableControls(id) {
  var form = document.getElementById(id);
  if (!form) { cerror('Form '+id+' not found, cannot disable'); return; }
  form.onsubmit = cancelAction;
  var list = form.elements;
  for (var i=0;i<list.length;i++) {
    var input=list[i];
    if (input) input.disabled=true;
  }
}

/** Enable all controls */
function enableControls(id, onsubmit) {
  var form = document.getElementById(id);
  if (!form) { cerror('Form '+id+' not found, cannot enable'); return; }
  if (onsubmit) form.onsubmit = onsubmit;
  var list = form.elements;
  for (var i=0;i<list.length;i++) {
    var input=list[i];
    if (input && input.disabled) input.disabled=null;
  }
}

/** Set height of iframe to equals to its content.  Width is not affected. */
function iframeFit(id) {
  if (!frames[id]) { cerror('Iframe '+id+' not found, cannot fit'); return; }
  var body = frames[id].document.body;
  if (body.scrollHeight)
    document.getElementById(id).style.height = (body.scrollHeight)+'px';
  else if (body.offsetHeight)
    document.getElementById(id).style.height = (body.offsetHeight)+'px';
}