/*
 *  Standard JavaScript © 1996-2009, Horus Web Engineering Ltd
 *
 *  $Id: horus.js,v 1.156 2009-01-08 10:50:48 horus Exp $
 *
 *  licensed under the terms of the GNU Lesser General Public License:
 *    http://www.opensource.org/licenses/lgpl-license.php
 *
 */

if (!window.horus) window.horus=new Object;

// unreliable browser detects... don't use for anything critical
//
horus.opera=window.opera || navigator.userAgent.indexOf('Opera')>=0;

horus.khtml=
  !horus.opera &&
  (navigator.userAgent.indexOf('Safari')>=0 || navigator.userAgent.indexOf('KHTML')>=0);

horus.gecko=!horus.opera && !horus.khtml && navigator.userAgent.indexOf('Gecko')>=0;
horus.ie=!horus.opera && document.all;
horus.iefix=new Object;

if (horus.ie) {
  horus.iewin=navigator.userAgent.indexOf('Windows')>=0;
  horus.iemac=!horus.iewin;
  horus.ie=navigator.userAgent.match(/MSIE ([0-9]+\.[0-9]+)/);
  if (horus.ie) horus.ie=horus.ie[1];
  if (horus.ie) horus.ie=Number(horus.ie);
  horus.ieold=horus.iewin && horus.ie<7;
  horus.brokenDOM=horus.iewin && horus.ie<8;
} else
  horus.iewin=horus.iemac=horus.ieold=horus.brokenDOM=false;

horus._gocheck=false;	// kludgearound for IE/Win and Opera weirdness
horus._broken_scriptload=horus.khtml || horus.ieold;


horus.scriptLoad=
  function ( script ) {
    if (script.charAt(0)!='/') script='/common/js/'+script;
    if (horus.scriptLoad.loaded(script)) return;

    if (horus._broken_scriptload)
      document.writeln('<script type="text/javascript" src="'+script+'"></script>');
    else {
      if (!document.head) document.head=document.getElementsByTagName('head')[0];
      var s=document.createElement('script');
      s.type='text/javascript';
      s.src=script;
      document.head.appendChild(s);
    }
  };


horus.scriptLoad.loaded=
  function ( script ) {
    if (script.charAt(0)!='/') script='/common/js/'+script;
    if (horus.scriptLoad._loaded[script]) return true;
    horus.scriptLoad._loaded[script]=true;
    return false;
  };


horus.scriptLoad._loaded={};


horus.conditionalLoad=
  function ( condition, script ) {
    if (!condition) return;
    for (var i=1; i<arguments.length; i++) horus.scriptLoad(arguments[i]);
  };


// implement missing Array methods in Windows IE 5.0
//
horus.conditionalLoad(!Array.prototype.push, 'ie-win-5.0.js');

// fix (IE: totally, Safari: partially) broken String split method
//
horus.conditionalLoad('ab'.split(/a(.)/).length<3, 'string-split-fix.js');

// make splitting an empty string return an empty array, as per Perl
//
horus.conditionalLoad(''.split(',').length==1, 'string-split-empty.js');

if (horus.iewin) {
  // fix broken getElementById method
  //
  horus.conditionalLoad(horus.brokenDOM, 'ie-getelement-fix.js');

  // kludge in PNG alpha transparency for 5.5 <= IE/Win < 7
  //
  horus.conditionalLoad(horus.ie>=5.5 && horus.ie<7, 'pngfix.js');

  // min-width hack code for ie<7
  //
  horus.conditionalLoad(horus.ie<7, 'minwidth.js');

  // Chris Ridings' Google Autolink blocker
  //
  horus.scriptLoad('autoblink.js');
}

// form/page processing symbolic constants as per Perl and ColdFusion
//
horus.FORM_ENTRY    = 0;
horus.FORM_SUBMIT   = -1;
horus.FORM_REFRESH  = -2;
horus.FORM_RESET    = -3;
horus.FORM_NEW      = -4;
horus.FORM_ADD      = -5;
horus.FORM_DELETE   = -6;
horus.FORM_EDIT     = -7;
horus.FORM_NEXT     = -8;
horus.FORM_PREVIOUS = -9;
horus.FORM_SAVE     = -10;
horus.FORM_RESTORE  = -11;

horus.HTML_FOCUS    = 0x01;
horus.HTML_SCROLL   = 0x02;
horus.HTML_WAITBOX  = 0x04;
horus.HTML_NOBACK   = 0x08;
horus.HTML_VISIBLE  = 0x10;


horus.NBSP='\u00a0';
horus.NUL='\u2400';
horus.BR=[ 'br' ];


Function.prototype._apply=Function.prototype.apply;

Function.prototype.apply=
  function ( obj, argv ) {
    if (arguments.length==1 && horus.isArray(obj)) { argv=obj; obj=window }
    return this._apply(obj, argv);
  };


Number.prototype.ordinal=
  function () {
    var n=this;
    if (n<0) n=-n;
    n=n%100;
    if (n>3 && n<21) return 'th';
    n=n%10;
    if (n==1) return 'st';
    if (n==2) return 'nd';
    if (n==3) return 'rd';
    return 'th';
  };


String.prototype.possessive=
  function () {
    return this+(this.right(1)=='s' ? '\'' : '\'s');
  };


String.prototype.trim=
  function () {
    return this.replace(/[ \t\n\r]*$/, '').replace(/^[ \t\n\r]*/, '');
  };


String.prototype.contains=
  function ( pattern ) {
    return typeof pattern=='string' ? this.indexOf(pattern)>=0 : this.search(pattern)>=0;
  };


String.prototype.left=
  function ( count ) {
    return this.substring(0, count<0 ? this.length+count : count);
  };


String.prototype.right=
  function ( count ) {
    return this.substring(count<0 ? -count : this.length-count);
  };


Number.prototype.contains = function ( p ) { return String(this).contains(p) };
Number.prototype.left     = function ( c ) { return String(this).left(c) };
Number.prototype.right    = function ( c ) { return String(this).right(c) };


Number.prototype.toCurrency=
  function ( symbol ) {
    if (symbol==null) symbol='£';
    var display=this.toFixed(2);

    while (/[0-9]{4}[.,]/.test(display))
      display=display.replace(/([0-9])([0-9]{3})([.,])/, '$1,$2$3');

    return symbol+display;
  };


Number.prototype.toGrouped=
  function () {
    var display=this.toString();

    while (/[0-9]{4}(,|$)/.test(display))
      display=display.replace(/([0-9])([0-9]{3})(,|$)/, '$1,$2$3');

    return display;
  };


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


Array.prototype.find=
  function ( value, point ) {
    if (point==null) point=0;
    while (point<this.length && this[point]!=value) point++;
    return point<this.length ? point : null;
  };


Array.prototype.addList=
  function () {
    for (var i=0; i<arguments.length; i++) {
      var item=arguments[i];

      if (horus.isArray(item))
	while (item.length) this.push(item.shift());
      else
	this.push(item);

    }

    return this;
  };


Array.prototype.addString=
  function () {
    var prev=this.length && horus.isString(this[this.length-1]);

    for (var i=0; i<arguments.length; i++) {
      var obj=arguments[i];
      var next=horus.isString(obj);

      if (prev && next)
	this[this.length-1]+=obj;
      else
	this.push(obj);

      prev=next;
    }

    return this;
  };


Array.prototype.clean=
  function () {
    for (var i=this.length-1; i>=0; i--)
      if (this[i]=='' || this[i]==null)
	this.splice(i, 1);

    return this;
  };


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


horus.isString=
  function ( obj ) {
    return typeof obj=='string' || typeof obj=='object' && obj instanceof String;
  };


horus.isNumber=
  function ( obj ) {
    if (typeof obj=='number') return true;
    if (typeof obj=='object' && obj instanceof Number) return true;
    if (horus.isString(obj) && obj.test(/[+-]?\d+$/)) return true;
    return false;
  };


horus.isArray=
  function ( obj ) {
    if (obj==null || typeof obj!='object') return false;
    if (obj instanceof Array) return true;
    if (typeof obj.callee=='function' && typeof obj.length=='number') return true;
    return false;
  };


horus.isSimpleValue=
  function ( obj ) {
    if (/^(string|number|boolean)$/.test(typeof obj)) return true;
    if (!obj || typeof obj!='object') return false;
    return obj instanceof String || obj instanceof Number || obj instanceof Boolean;
  };


horus.isNode=
  function ( obj ) {
    if (!obj) return false;
    if (horus.brokenDOM) return typeof obj=='object' && obj.nodeType;
    return obj instanceof Node;
  };


horus.isElement=
  function ( obj ) {
    return horus.isNode(obj) && obj.nodeType==1
  };


horus.className=
  function ( object, className ) {
    var name=
      typeof object.className=='function' ? object.className() :
      object.className ? object.className : object.constructor.className;

    if (className==null) return name;
    if (typeof className!='string') className=horus.className(className);
    return name==className;
  };


horus.toString=
  function ( arg, quote, done ) {
    if (arg==null) return horus.NUL;
    if (quote && horus.isString(arg)) return '“'+arg+'”';
    if (horus.isSimpleValue(arg)) return arg;
    if (!done) done=[];
    var loop=done.find(arg);
    done.push(arg);

    if (horus.isArray(arg)) {
      if (loop) return '[ … ]';
      var text=[];
      for (var i=0; i<arg.length; i++) text.push(horus.toString(arg[i], true, done));
      return '[ '+text.join(', ')+' ]';
    }

    var tag='';

    if (horus.isElement(arg)) {
      if (arg.name) tag+=arg.name;
      if (arg.id) tag+='#'+arg.id;
      if (arg.className) tag+='.'+arg.className;
    } else
      tag=horus.className(arg);

    if (arg.toString!=Object.prototype.toString &&
	arg.toString!=horus.hash.prototype.toString)
      arg=arg.toString();
    else if (loop)
      arg='{ … }';
    else {
      var keys=horus.keys(arg);

      for (var k=0; k<keys.length; k++) {
	var key=keys[k];
	keys[k]=key+': '+horus.toString(arg[key], true, done);
      }

      arg='{ '+keys.join(', ')+' }';
    }

    if (tag!=null && tag!='') arg='«'+tag+'»'+arg;
    return arg;
  };


horus.alert=
  function () {
    var text=new Array;
    for (var i=0; i<arguments.length; i++) text.push(horus.toString(arguments[i]));
    alert(text.join(' '));
  };


horus.keys=
  function ( hash, all, sorter ) {
    var keys=new Array;

    for (var key in hash)
      if (all || hash.hasOwnProperty(key)) keys.push(key);

    return (arguments.length<3 ? keys.sort() : sorter ? keys.sort(sorter) : keys);
  };


horus.isEqual=
  function ( a, b ) {
    if (a==b) return true;
    if (a==null || b==null) return false;
    var aa=typeof a;
    if (aa!='object' || aa!=typeof b) return false;

    if (a instanceof Array) {
      if (!(b instanceof Array)) return false;
      if (a.length|=b.length) return false;

      for (var i=0; i<a.length; i++)
	if (!horus.isEqual(a[i], b[i])) return false;

    }

    if (!horus.isEqual(horus.keys(a), horus.keys(b))) return false;

    for (var k in a)
      if (!horus.isEqual(a[k], b[k])) return false;

    return true;
  };


horus.hash=function () { horus.hash.merge(this, arguments) };
horus.hash.className='horus.hash';


horus.hash.merge=
  function ( hash, argv, offset ) {
    if (offset==null) offset=0;

    for (var arg=0; arg<argv.length; arg++) {
      var from=argv[arg];

      if (from)
	if (horus.isString(from)) {
	  from=from.toLowerCase().split(/[, ]{1,}/);

	  for (var i=0; i<from.length; i++) {
	    var item=from[i];
	    var split=item.search(/=/);

	    if (split>=0) {
	      var val=item.substring(split+1);

	      if (val=='true' || val=='false')
		val=val=='true';
	      else if (val.match(/^[+-]?[0-9][0-9]*$/))
		val=Number(val);

	      hash[item.substring(0, split)]=val;
	    } else
	      if (item.substring(0, 2)=='no')
		hash[item.substring(2)]=false;
	      else
		hash[item]=true;

	  }
	} else
	  for (var key in from) hash[key]=from[key];

    }

    return hash;
  };


horus.hash.key   = function ( hash ) { for (var key in hash) return key; return null };
horus.hash.empty = function ( hash ) { return horus.hash.key(hash)==null };
horus.hash.get   = function ( hash, key, val ) { return key in hash ? hash.key : val };
horus.hash.add   = function ( hash ) { return horus.hash.merge(hash, arguments, 1) };
horus.hash.copy  = function () { return horus.hash.merge({}, arguments) };

horus.options    = function ( options, def ) { return new horus.hash(def, options) };


horus.hash.prototype.get=
  function ( key, val ) {
    return horus.hash.get(this, key, val);
  };


horus.hash.prototype.toString = function () { return horus.toString(this, true) };
horus.hash.prototype.add      = function () { return horus.hash.merge(this, arguments) };
horus.hash.prototype.key      = function () { return horus.hash.key(this) };
horus.hash.prototype.keys     = function () { return horus.keys(this) };
horus.hash.prototype.size     = function () { return this.keys().length };
horus.hash.prototype.isEmpty  = function () { return this.key()==null };
horus.hash.prototype.isEqual  = function ( hash ) { return horus.isEqual(this, hash) };


horus.callable=
  function ( method ) {
    return method && (typeof method=='function' || method instanceof Array);
  };


horus.call=
  function ( method ) {
    var argc=arguments.length;
    var object=window;
    var argv=new Array;
    if (argc>2) horus.call.addargs(argv, arguments[1], true);

    if (method instanceof Array) {
      var offset=0;
      if (typeof method[0]!='function') object=method[offset++];
      for (var ptr=offset+1; ptr<method.length; argv.push(method[ptr++]));
      method=method[offset];
      if (typeof method!='function') method=object[method];
    }

    if (typeof method!='function')
      throw 'horus.call called without a function or method parameter';

    if (argc>1) horus.call.addargs(argv, arguments[argc-1]);
    return method.apply(object, argv);
  };


horus.call.addargs=
  function ( argv, from, all ) {
    if (horus.isArray(from))
      for (var ptr=0; ptr<from.length; argv.push(from[ptr++]));
    else if (all || from!=null)
      argv.push(from);

  };


horus.eventListener=
  function ( element, event, action, wrap ) {
    if (typeof element=='string') element=document.getElementById(element);
    if (horus.gecko && event=='mousewheel') event='DOMMouseScroll';
    var reqaction=[ action ];

    if (wrap) {
      var a=action;

      if (arguments.length==4)
	action=function (e) { if (!e) e=window.event; return a(e, element) };
      else {
	var p=arguments[4];
	action=function (e) { if (!e) e=window.event; return a(e, element, p) };
      }

      reqaction.push(action);
    }

    if (!element._events) element._events={};
    if (!element._events[event]) element._events[event]=[];
    element._events[event].push(reqaction);

    if (window.addEventListener)
      element.addEventListener(event, action, false);
    else if (window.attachEvent)
      element.attachEvent('on'+event, action);
    else {
      var old=element['on'+event];

      element['on'+event]=
	!old ? action :
	function (e) {
          if (!e) e=window.event;
	  old(e, element);
	  return action(e, element);
        };

    }
  };


horus.removeListener=
  function ( element, event, action, wrap ) {
    if (!element._events) return;
    var events=element._events[event];
    if (!events) return;
    wrap=Boolean(wrap);

    for (var i=0; i<events.length; i++) {
      if (events[i][0]!=action) continue;
      if (wrap!=events[i].length>1) continue;
      if (wrap) action=events[i][1];

      if (window.removeEventListener)
	element.removeEventListener(event, action, false);
      else
	element.detachEvent('on'+event, action);

      events.splice(i, 1);
      return;
    }
  };


// mousewheel event data thanks to Adomas Paltanavičius, taken from
// http://adomas.org/javascript-mouse-wheel/, but with direction reversed (-ve is up)
//
horus.mousedelta=
  function ( event ) {
    var delta=0;
    if (!event) event=window.event;

    if (event.wheelDelta) { // IE/Opera
      delta=event.wheelDelta/120; 
      if (!horus.opera) delta=-delta;
    } else if (event.detail) // gecko
      delta=event.detail/3;

    if (event.preventDefault) event.preventDefault();
    event.returnValue=false;
    return delta;
  };


horus.winscroll=
  function ( width, height, scroll, xpos, ypos ) {
    var options=new Array;

    if (arguments.length>3 && arguments[3]) options.push(arguments[3]);
    if (width) options.push('width='+width);
    if (height) options.push('height='+height);
    if (xpos!=null) options.push((horus.ie ? 'left=' : 'screenX=')+xpos);
    if (ypos!=null) options.push((horus.ie ? 'top=' : 'screenY=')+ypos);

    if (scroll)
      if (scroll instanceof Array) {
	if (scroll[0]) options.push('scrollbars=yes');
	if (scroll[1]) options.push('resizable=yes');
      } else if (typeof scroll=='boolean' || scroll.contains(/^(yes|on|true)$/i)) {
	options.push('scrollbars=yes,resizable=yes');
      } else {
	options.push(scroll);
      }

    return options.join(',');
  };


horus.placewin=
  function ( name, source, xpos, ypos, width, height, scroll, rethandle ) {
    if (/^opener\./.test(name)) {
      name=name.replace(/^opener\./, '');

      if (opener && opener.horus)
	return opener.horus.placewin
	  (name, source, xpos, ypos, width, height, scroll, rethandle);
	
    }

    var wh=window.open(source, name, horus.winscroll(width, height, scroll, xpos, ypos));
    try { wh.focus() } catch ( err ) {};
    if (rethandle) return wh;
  };


horus.openwin=
  function ( name, source, width, height, scroll, rethandle ) {
    return horus.placewin(name, source, null, null, width, height, scroll, rethandle);
  };


horus.closewin=
  function ( dest ) {
    if (window.opener && window.opener!=window) {
      if (dest) window.opener.document.location.replace(dest);
      window.close();
    } else if (dest)
      window.document.location.replace(dest);

    return false;
  };


horus.repop=
  function ( name, opener, width, height, scroll ) {
    if (window.name==name) return;
    horus.openwin(name, window.location.pathname, width, height, scroll);
    window.location.replace(opener);
  };


horus.deframe=
  function ( replace ) {
    if (window.parent==window) return;

    if (replace)
      window.top.location.replace(window.location.href);
    else
      window.top.location.href=window.location.href;

  };


// Image rollover - normally triggered by onmouseover/onmouseout. The
// assumption is made that the image filename is <something>.<index>.<type>
// where <index> is an integer with zero as the base state.
//
// Parameters:
//   image - Image object, or image name/index within the document
//   over  - (optional) new image index, default 0
//
horus.mouseover=
  function ( image ) {
    var over=arguments.length>1 ? arguments[1] : 0;

    if (image instanceof Array)
      for (var iptr=0; iptr<image.length; iptr++) horus.mouseover(image[iptr], over);
    else {
      if (typeof image=='string') image=document.images[image];

      if (over=='*') {
	var old=Number(image.src.match(/\.(\d+)\.\w+$/)[1]);
	over=old%2 ? old-1 : old+1;
      }

      image.src=image.src.replace(/\.\d+(\.\w+)$/, '.'+over+'$1');
    }
  };


horus.rollover=function ( image ) { horus.mouseover(image, '*') };


// return window vertical scroll position
//
horus.scrollv=
  function () {
    if (document.documentElement && document.documentElement.scrollTop)
      return document.documentElement.scrollTop;

    if (document.body && document.body.scrollTop!=null)
      return document.body.scrollTop;

    return window.pageYOffset;
  };


// return window horizontal scroll position
//
horus.scrollh=
  function () {
    if (document.documentElement && document.documentElement.scrollLeft)
      return document.documentElement.scrollLeft;

    if (document.body && document.body.scrollLeft!=null)
      return document.body.scrollLeft;

    return window.pageXOffset;
  };


// return window size
//
horus.windowsize=
  function ( win ) {
    var size={};
    if (!win) win=window;

    if (win.innerHeight) {
      size.height=win.innerHeight;
      size.width=win.innerWidth;
    } else {
      win=win.document;

      if (win.documentElement && win.documentElement.clientHeight) {
	size.height=win.documentElement.clientHeight;
	size.width=win.documentElement.clientWidth;
      } else {
	size.height=win.body.clientHeight;
	size.width=win.body.clientWidth;
      }
    }

    return size;
  };


// return window size, and top, bottom, left and right coördinates
//
horus.windowpos=
  function () {
    var winsize=horus.windowsize();
    winsize.top=horus.scrollv();
    winsize.left=horus.scrollh();
    winsize.bottom=winsize.top+winsize.height;
    winsize.right=winsize.left+winsize.width;
    return winsize;
  };


horus.event=
  function ( event, root ) {
    if (event && typeof event.length=='number') event=event[0];

    if (event && event.rawevent) {
      if (event.root==root) {
	for (var k in event) this[k]=event[k];
	return this;
      }

      event=event.rawevent;
    } else if (!(event && event.type))
      event=window.event || event[0];

    this.rawevent=event;
    this.type=event.type;
    this.altKey=event.altKey;
    this.ctrlKey=event.ctrlKey;
    this.shiftKey=event.shiftKey;
    var pos=horus.windowpos();

    if (root) {
      this.root=root;
      root=horus.getposition(root);
      pos.top-=root.top;
      pos.left-=root.left;
    }

    this.y=pos.top+event.clientY;
    this.x=pos.left+event.clientX;

    if (window.event) {
      this.target=event.srcElement;
      this.keyCode=event.keyCode;

      if (event.srcElement && event.y!=event.clientY && event.x!=event.clientX &&
          (event.y<0 || event.y>=event.srcElement.offsetHeight ||
	   event.x<0 && event.x>=event.srcElement.offsetWidth)) {
	this.y+=10-event.y;
	this.x+=10-event.x;
      }
    } else {
      this.target=event.target;
      this.keyCode=event.which;
    }

    return this;
  };


horus.event.className='horus.event';


horus.event.prototype.control=function () { return this.target };


horus.event.prototype.character=
  function () {
    if (this._character==undefined) this._character=String.fromCharCode(this.keyCode);
    return this._character;
  };


horus.event.prototype.preventDefault=
  function () {
    if (window.event)
      this.rawevent.returnValue=false;
    else
      this.rawevent.preventDefault();

  };


horus.event.prototype.stopPropagation=
  function () {
    if (this.rawevent.stopPropagation)
      this.rawevent.stopPropagation();
    else
      this.rawevent.cancelBubble=true;

  };


horus.mouseinbox=
  function ( event, box ) {
    if (!box) return false;
    var event=horus.event(event, box);

    return event.y>=0 && event.y<box.offsetHeight &&
           event.x>=0 && event.x<box.offsetWidth;

  };


horus.toobject=
  function ( val ) {
    switch (typeof val) {

    case 'boolean':
      return new Boolean(val);

    case 'number':
      return new Number(val);

    case 'string':
      if (/^(true|false)$/.test(val)) return new Boolean(val=='true');
      if (/^[-+]?(\d*\.)?\d+$/.test(val)) return new Number(val);
      return new String(val);

    default:
      return val;

    }
  };


horus.toBoolean=
  function ( val ) {
    if (typeof val=='boolean') return val;
    if (typeof val=='number') return val!=0;
    if (!Boolean(val)) return false;
    if (val.trim().match(/^(0|f(alse)?|n(o)?|off)?$/i)) return false;
    return true;
  };


horus.todate=
  function ( val, def ) {
    if (val instanceof Date) return val;
    if (val==null || val=='') return def;

    var date=
      /^\{ts '(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)'}$/.exec(val) ||
      /^(\d+),(\d+),(\d+)(?:,(\d+),(\d+)(?:,(\d+)(?:,(\d+))?)?)?$/.exec(val);

    if (date) {
      for (var i=3; i<8; i++) if (!date[i]) date[i]=0;
      return new Date(date[1], date[2]-1, date[3], date[4], date[5], date[6], date[7]);
    }

    return new Date(val);
  };


horus.toform=
  function ( theForm ) {
    if (theForm==null)
      for (var i=0; i<document.forms.length; i++)
	if (theForm==null || document.forms[i].name!='statusboxform') {
	  theForm=document.forms[i];
	  if (!/(^statusboxform$|search)/.test(theForm.name)) break;
	}

    if (typeof theForm=='string' || typeof theForm=='number')
      theForm=document.forms[theForm];
    else if (theForm.form)
      theForm=theForm.form;

    return theForm;
  };


// nasty... have to do this because the argument "array" of a JavaScript
// function isn't a real Array, so we can't splice or shift it
//
horus.argv=
  function ( argv, odd ) {
    var argc=argv.length;
    var splice=odd==null || argc%2==(odd ? 0 : 1);
    var newargs=new Array(splice ? argc : argc+1);
    var outptr=0;
    if (!splice) newargs[outptr++]=null;
    for (inptr=0; inptr<argc; inptr++) newargs[outptr++]=argv[inptr];
    return newargs;
  };


horus.submitargv=
  function( argv ) {
    argv=horus.argv(argv, false);
    var theForm=argv[0];
    var validate=false;

    if (theForm!=null)
      if (theForm instanceof Array) {
	validate=theForm[1];
	theForm=theForm[0];
      } else if (typeof theForm=='function') {
	validate=theForm;
	theForm=null;
      }

    argv[0]=[ horus.toform(theForm), validate ];
    return argv;
  };


horus.formargv=
  function ( argv, odd ) {
    argv=horus.argv(argv, odd);
    argv[0]=horus.toform(argv[0]);
    return argv;
  };


horus.ismulti=
  function ( elem ) {
    if (elem instanceof Array) elem=elem[0];
    if (elem.tagName.toLowerCase()!='input') return false;
    return elem.type=='checkbox' || elem.type=='radio';
  };


horus.formfield=
  function () {
    var theForm, theField, multi;

    if (arguments[0] instanceof Array) {
      theForm=arguments[0][0];
      theField=arguments[0][1];
      multi=arguments[1];
    } else if (arguments.length>1) {
      theForm=arguments[0];
      theField=arguments[1];
      multi=arguments[2];
    } else
      theField=arguments[0];

    theForm=horus.toform(theForm);
    var isarray;

    if (typeof theField=='string' && theField.charAt(0)!='#') {
      var theName=theField;

      if (horus.brokenDOM) {
	// it beggars belief, really
	theField=[];
	isarray=true;

	for (var i=0; i<theForm.elements.length; i++)
	  if (theForm.elements[i].name==theName) theField.push(theForm.elements[i]);

	if (theField.length==0) theField=null;
      } else
	theField=theForm.elements[theName];

    } else if (typeof theField=='string')
      theField=document.getElementById(theField.substring(1));

    if (theField) {
      var theTag=theField.tagName || theField[0].tagName;

      if (theTag.toLowerCase()=='input') {
	if (multi==null) multi=horus.ismulti(theField);

	if (multi && !theField.length) {
	  theField=theForm.elements[theField.name];
	  if (!theField.length) theField=[ theField ];
	} else if (!multi && theField.length)
	  theField=theField[0];

      } else if (isarray)
	theField=theField[0];

    }

    return theField;
  };


horus.set=
  function ( theForm, argname, argvalue ) {
    var arg=horus.formfield(theForm, argname);
    if (arg) arg.value=argvalue;
  };


horus.toId=
  function ( node ) {
    if (typeof node=='object') {
      node=horus.getElement(node);
      if (node) node=node.id;
    }

    if (typeof node=='string') {
      node=node.replace(/^.*[^0-9]([0-9]*)$/, '$1');
      if (/^[0-9]+$/.test(node)) node=Number(node);
    }

    return node;
  };


horus.toNode=
  function ( prefix, id ) {
    return document.getElementById(prefix+horus.toId(id));
  };


horus.getElement=
  function ( item ) {
    if (!horus.isElement(item))
      if (typeof item=='string')
	item=document.getElementById(item);
      else if (item && typeof item.control=='function')
	item=item.control();

    return item;
  };


horus.visibility=
  function ( item, visibility ) {
    if (typeof visibility!='string' ||
	!/^(visible|hidden|collapse|inherit)$/.test(visibility))
      visibility=horus.toBoolean(visibility) ? 'visible' : 'hidden';

    if (item instanceof Array)
      for (var i=0; i<item.length; i++)
	horus.getElement(item[i]).style.visibility=visibility;

    else
      horus.getElement(item).style.visibility=visibility;

  };


// update visibility
//
// parameters:
//   1. id or list of ids to hide or show
//   2. optional id or list of ids to hide or restore
//   3. optional new state (default: state of first item in parameter 1 toggled)

horus.visible=
  function ( store ) {
    var restore=new Array;
    var newstate;
    var single;
    var imagelist=new Array;
    var argc=arguments.length-1;

    if (!(store instanceof Array)) store=[ store ];

    if (argc>0) {
      var lastarg=arguments[argc];
      var con=typeof lastarg;

      if (con=='boolean' || con=='number' || con=='string' && /^[01]?$/.test(lastarg)) {
	newstate=horus.toBoolean(lastarg) ? '' : 'none';
	argc--;
      } else if (con=='string' && lastarg.match(/^(none|inline|block|single)$/)) {
	if (lastarg=='single')
	  single=horus.getElement(store[0]);
	else
	  newstate=lastarg;

	argc--;
      }

      for (var ptr=argc; ptr>0; ptr--) {
	var arg=arguments[ptr];

	if (arg instanceof Array) {
	  restore=restore.concat(arg);
	} else if (typeof arg=='string') {
	  var img=document.images[arg];

	  if (img)
	    imagelist.push(img);
	  else
	    restore.push(arg);

	} else {
	  alert('horus.js: unrecognised parameter ('+arg+') passed to horus.visible');
	}
      }
    }

    if (newstate==null)
      newstate=horus.getElement(store[0]).style.display=='none' ? '' : 'none';

    var set=new Array;

    for (ptr=0; ptr<store.length; ptr++) {
      var element=horus.getElement(store[ptr]);
      if (!element) continue;

      horus.visible.savedstate[element.id]=
	element.style.display ? element.style.display : '';

      element.style.display=newstate;
      set[element.id]=true;
    }

    for (ptr=0; ptr<imagelist.length; ptr++) {
      var image=imagelist[ptr];
      var old=Number(image.src.match(/\.(\d+)\.\w+$/)[1]);
      over=newstate=='none' ? old|2 : old&~2;
      image.src=image.src.replace(/\.\d+(\.\w+)$/, '.'+over+'$1');
    }    

    for (ptr=0; ptr<restore.length; ptr++) {
      element=horus.getElement(restore[ptr]);
      if (!element) continue;

      if (newstate=='none')
	element.style.display='none';
      else if (horus.visible.savedstate[element.id])
	element.style.display=horus.visible.savedstate[element.id];

      set[element.id]=true;
    }

    if (single) {
      var theitems=document.getElementsByTagName(single.nodeName);
      var theclass=single.className;

      for (var itemptr=0; itemptr<theitems.length; itemptr++) {
	var thisitem=theitems[itemptr];

	if (thisitem.className==theclass && !set[thisitem.id])
	  thisitem.style.display='none';

      }
    }

    horus.matchheight();
  };

horus.visible.savedstate=new Array;


horus.putvisible=
  function () {
    var theForm=horus.formargv(arguments, false)[0];
    var visible=theForm._visible.value;
    if (visible.length==0) return;
    visible=visible.split(',');

    for (var ptr=0; ptr<visible.length; ptr++)
      horus.visible.savedstate[visible[ptr]]=true;

  };


horus.getvisible=
  function () {
    var visible=new Array;

    for (var ptr in horus.visible.savedstate)
      if (horus.visible.savedstate[ptr]) visible.push(ptr);

    return visible.join(',');
  };


horus._makelink=
  function ( argv ) {
    var url=argv[0];

    if (!/^(https?|ftp|mailto):/.test(url)) {
      var leadin=url.left(1);

      if (leadin=='?')
	url=document.location.pathname+url;
      else if (leadin!='/')
	url=document.location.pathname.replace(/\/[^\/]*$/, '/')+url;

      url=document.location.protocol+'//'+document.location.host+url;
    }

    var params=new Array;

    for (var ptr=1; ptr<argv.length; ptr++) {
      var arg=argv[ptr];

      if (arg!=null)
	if (typeof arg=='object')
	  for (tag in arg)
	    params.push(encodeURIComponent(tag)+'='+encodeURIComponent(arg[tag]));

	else {
	  var point=arg.indexOf('=');

	  if (point<0)
	    params.push(encodeURIComponent(arg));
	  else
	    params.push
	      (encodeURIComponent(arg.left(point))+'='+
	       encodeURIComponent(arg.right(-point-1)));

	}

    }

    if (params.length)
      url+=(url.indexOf('?')<0 ? '?' : '&')+params.join('&');

    return url;
  };


horus.makelink = function ( url ) { return horus._makelink(arguments) };
horus.linkto   = function ( url ) { document.location.href=horus._makelink(arguments) };
horus.noback   = function ()      { if (window.history) window.history.forward() };
horus.gocheck  = function ()      { if (horus._gocheck) horus._gocheck.submit() };


// generic form submit
//
horus.doit=
  function ( submitv, argv, noscroll ) {
    argv=horus.submitargv(argv);
    var parms=argv.shift();
    var theForm=parms[0];
    var validate=parms[1];
    var argc=argv.length;
    var scrollv=noscroll ? 'no' : 0;

    if (submitv==horus.FORM_RESET)
      theForm.reset();
    else if (theForm.onsubmit) {
      status=theForm.onsubmit();
      if (status==false) return false;
    }

    for (var i=0; i<argc; i+=2) {
      var argname=argv[i];
      var argvalue=argv[i+1];

      if (argname=='_scroll')
	scrollv=argvalue;
      else if (argname=='_submit')
	submitv=argvalue;
      else
	horus.set(theForm, argname, argvalue);

    }

    horus.set(theForm, '_visible', horus.getvisible());
    horus.set(theForm, '_scroll', scrollv=='no' ? 0 : horus.scrollv()+scrollv);
    horus.set(theForm, '_submit', submitv);

    if (validate) {
      var status=validate(theForm);

      if (typeof status=='string')
	if (status!='') {
	  alert(status);
	  status=false;
	} else
	  status=true;

      if (!status) return false;
    }

    if (!theForm.target || theForm.target=='_self')
      for (var ptr=0; ptr<theForm.length; ptr++) {
	var element=theForm.elements[ptr];
	if (element.type=='button' || element.type=='submit') element.disabled=true;
      }

    if (horus._waitbox && horus._popup_loaded) horus.waitbox(true);
    theForm.submit();
    if (horus.iewin || horus.opera) horus._gocheck=theForm; // IE/Win and Opera
  };


// form submit functions - the submit values are defined in Horus::HTML for Perl
// and cf_submit for ColdFusion
//
horus.reentry     = function () { return horus.doit(horus.FORM_ENTRY,    arguments) };
horus.go          = function () { return horus.doit(horus.FORM_SUBMIT,   arguments) };
horus.reload      = function () { return horus.doit(horus.FORM_REFRESH,  arguments) };
horus.nogo        = function () { return horus.doit(horus.FORM_RESET,    arguments) };
horus.do_new      = function () { return horus.doit(horus.FORM_NEW,      arguments) };
horus.do_add      = function () { return horus.doit(horus.FORM_ADD,      arguments) };
horus.do_delete   = function () { return horus.doit(horus.FORM_DELETE,   arguments) };
horus.do_edit     = function () { return horus.doit(horus.FORM_EDIT,     arguments) };
horus.do_next     = function () { return horus.doit(horus.FORM_NEXT,     arguments) };
horus.do_previous = function () { return horus.doit(horus.FORM_PREVIOUS, arguments) };
horus.do_save     = function () { return horus.doit(horus.FORM_SAVE,     arguments) };
horus.do_restore  = function () { return horus.doit(horus.FORM_RESTORE,  arguments) };


horus.scroller=
  function () {
    if (document.URL.match(/#/)) return;
    var theForm=horus.formargv(arguments, false)[0];
    if (theForm._scroll) window.scrollTo(0, parseInt(theForm._scroll.value));
  };


horus.focusbyid  = function ( id )           { horus.getElement(id).focus() };
horus.classbyid  = function ( id, newclass ) { horus.getElement(id).className=newclass };
horus.zindexbyid = function ( id, newz )     { horus.getElement(id).style.zIndex=newz };
horus.getbyid    = function ( id )           { return horus.getElement(id).innerHTML };
horus.setbyid    = function ( id, content )  { horus.getElement(id).innerHTML=content };
horus.addbyid    = function ( id, content )  { horus.getElement(id).innerHTML+=content };


horus.sync=
  function () {
    var argv=horus.formargv(arguments, false);
    horus.set(argv[0], argv[2], horus.formfield(argv).value);
  };


horus._wrap=
  function ( argv, action ) {
    argv=horus.formargv(argv, true);
    var theForm=argv[0];
    var theName=argv[1];
    if (typeof theName!='string') theName=theName.name;
    if (theForm._wrap==null) theForm._wrap={};
    if (theForm._wrap[theName]==null) theForm._wrap[theName]=0;

    if (action>0)
      theForm._wrap[theName]++;
    else if (action<0 && theForm._wrap[theName]>0)
      theForm._wrap[theName]--;

    return theForm._wrap[theName];
  };

horus.wrap    = function () { return horus._wrap(arguments, 1) };
horus.unwrap  = function () { return horus._wrap(arguments, -1) };
horus.wrapped = function () { return horus._wrap(arguments, 0) };


horus.setvar=
  function () {
    var argv=horus.formargv(arguments, false);
    var elem=horus.formfield(argv, false);
    var form=argv[0];
    var value=argv[2];

    if (elem.tagName.toLowerCase()=='select')
      horus.setselected(form, elem, value);
    else if (elem.type=='radio')
      horus.setradio(form, elem, value);
    else if (elem.type=='checkbox')
      horus.setchecked(form, elem, value);
    else
      elem.value=value;

  };


horus.getvar=
  function () {
    var argv=horus.formargv(arguments, true);
    var elem=horus.formfield(argv, false);
    if (!elem) return null;
    var form=argv[0];
    if (elem.tagName.toLowerCase()=='select') return horus.getselected(form, elem);
    if (elem.type=='radio') return horus.getradio(form, elem);
    if (elem.type=='checkbox') return horus.getchecked(form, elem);
    return horus.formfield(argv).value;
  };


horus.getvaropt=
  function () {
    var argv=horus.formargv(arguments, false);
    var elem=horus.formfield(argv, false);
    if (!elem) return null;
    var thevalue=elem.value;
    var options=horus.options(argv[2]);
    if (options.trim) thevalue=thevalue.trim();
    if (options.encode) thevalue=encodeURIComponent(thevalue);
    return thevalue;
  };


horus.getradio=
  function () {
    var argv=horus.formargv(arguments, true);
    var theRadio=horus.formfield(argv, true);
    if (!theRadio) return null;

    for (var i=0; i<theRadio.length; i++)
      if (theRadio[i].checked) return theRadio[i].value;

    return null;
  };


horus.setradio=
  function () {
    var argv=horus.formargv(arguments, false);
    var theRadio=horus.formfield(argv, true);
    var value=argv[2];

    if (value==null)
      for (var i=0; i<theRadio.length; i++)
	if (theRadio[i].checked) {
	  theRadio[i].checked=false;
	  return;
	}

    for (var i=0; i<theRadio.length; i++)
      if (theRadio[i].value==value) {
	theRadio[i].checked=true;
	return;
      }

  };


horus.getchecked=
  function () {
    var argv=horus.formargv(arguments, true);
    var theBoxes=horus.formfield(argv, true);
    if (!theBoxes) return null;
    var checked=new Array;

    for (var i=0; i<theBoxes.length; i++)
      if (theBoxes[i].checked) checked.push(theBoxes[i].value);

    return checked;
  };


horus.setchecked=
  function () {
    var argv=horus.formargv(arguments, false);
    var theBoxes=horus.formfield(argv, true);
    var value=argv[2];
    var i;

    if (value!=null)
      if (typeof value=='object') {
	if (value instanceof Array) {
	  var list=value;
	  value={};
	  for (i=0; i<list.length; value[list[i++]]=true);
	}

	for (i=0; i<theBoxes.length; i++)
	  theBoxes[i].checked=horus.hash.get(value, theBoxes[i].value);

      } else if (typeof value=='boolean') {
        for (i=0; i<theBoxes.length; i++) theBoxes[i].checked=value;
      } else {
	for (i=0; i<theBoxes.length; i++)
	  if (theBoxes[i].value==value) theBoxes[i].checked=!theBoxes[i].checked;

      }

  };


horus.clearchecked=
  function () {
    var argv=horus.formargv(arguments, true);
    var theBoxes=horus.formfield(argv, true);
    for (var i=0; i<theBoxes.length; i++) theBoxes[i].checked=false;
  };


horus.brokenselect=
  function ( theSelect ) {
    if (!horus.iewin) return 'value';
    var theOptions=theSelect.options;

    for (var i=0; i<theOptions.length; i++)
      if (theOptions[i].value!='') return 'value';

    return 'text';
  };


horus.getselected=
  function ( theForm, theSelect ) {
    var argv=horus.formargv(arguments, true);
    theSelect=horus.formfield(argv);
    var field=horus.brokenselect(theSelect);
    var theOptions=theSelect.options;

    if (theSelect.type=='select-one') {
      var selectedIndex=theSelect.selectedIndex;
      return selectedIndex>=0 ? theOptions[selectedIndex][field] : null;
    }

    var selected=new Array;

    for (var i=0; i<theOptions.length; i++)
      if (theOptions[i].selected) selected.push(theOptions[i][field]);

    return selected;
  };


horus._setselected=
  function ( argv ) {
    var theForm=argv[0];
    var theSelect=argv[1];
    if (horus.wrapped(theForm, theSelect)) return;
    var theSelect=horus.formfield(argv);
    var theValue=argv[2];
    var oldOption=theSelect.selectedIndex;
    var newOption=-1;

    if (theValue!=null) {
      var field=horus.brokenselect(theSelect);
      var theOptions=theSelect.options;

      for (var i=0; i<theOptions.length; i++) {
	if (theOptions[i][field]==theValue) {
	  newOption=i;
	  break;
	}
      }
    }

    if (newOption!=oldOption) {
      theSelect.selectedIndex=newOption;

      if (theSelect.onchange) {
	horus.wrap(theForm, theSelect);
	theSelect.onchange();
	horus.unwrap(theForm, theSelect);
      }
    }
  };


horus.setselected=
  function () {
    var argv=horus.formargv(arguments, false);
    for (var argp=2; argp<argv.length-1 && !argv[argp++]; ++argp);
    argv[2]=argv[argp];
    horus._setselected(argv);
  };


horus.setselectedif=
  function () {
    var argv=horus.formargv(arguments, true);

    if (argv[2]) {
      argv[2]=argv[3];
      horus._setselected(argv);
    }
  };


horus.selectall=
  function ( theForm, theSelect ) {
    var argv=horus.formargv(arguments, true);
    theSelect=horus.formfield(argv);
    var theOptions=theSelect.options;
    for (var i=0; i<theOptions.length; theOptions[i++].selected=true);
  };


horus.compare=
  function ( a, b ) {
    return a<b ? 1 : a>b ? -1 : 0;
  };


horus.comparenocase=
  function ( a, b ) {
    return horus.compare(a.toLowerCase(), b.toLowerCase());
  };


horus.seladd=
  function ( theForm, theSelect, theValue, options, sorter ) {
    var argv=horus.formargv(arguments, false);
    theSelect=horus.formfield(argv);
    theValue=argv[2];
    options=horus.options(argv[3]);
    sorter=argv[4];

    if (typeof theValue!='object') {
      var v=theValue;
      var split=v.search(/:/);
      theValue={};

      if (split>=0)
	theValue[v.substring(0, split)]=v.substring(split+1);
      else
	theValue[v]=v;

    }

    var theOptions=theSelect.options;
    var set=options.select || options.replace;

    if (options.clear)
      theOptions.length=0;
    else if (options.select && theSelect.type=='select-one' || options.replace)
      theSelect.selectedIndex=-1;

    var count=theOptions.length;

    if (typeof sorter!='function')
      sorter=sorter ? horus.compare : horus.comparenocase;

    if (theValue instanceof Array) {
      for (var i=0; i<theValue.length; i++) {
	var newopt=theValue[i];
	if (typeof newopt!='object') newopt=[ newopt, newopt ];
	horus.seladd.insert(theSelect, newopt, set, sorter);
      }
    } else if (theValue!=null)
      for (var tag in theValue)
	horus.seladd.insert(theSelect, [ theValue[tag], tag ], set, sorter);

    return theValue;
  };


horus.seladd.insert=
  function ( select, newopt, set, oldopt ) {
    select=horus.getElement(select);
    var options=select.options;
    var value=newopt.value;
    var text=newopt.text;

    if (newopt instanceof Array)
      newopt=new Option(newopt[0], newopt[1], false, false);

    if (typeof oldopt=='function') {
      var sorter=oldopt;
      oldopt=undefined;

      for (var ptr=0; ptr<options.length; ptr++) {
	var opt=options[ptr];

	if (sorter(text, opt.text, value, opt.value)>0) {
	  oldopt=horus.brokenDOM ? ptr : opt;
	  break;
	}
      }
    } else
      if (oldopt==null)
	oldopt=undefined;
      else if (typeof oldopt=='number' && !horus.brokenDOM)
	oldopt=options[oldopt];
      else if (horus.brokenDOM && typeof oldopt!='number') {
	for (ptr=0; ptr<options.length; ptr++)
	  if (options[ptr]==oldopt) {
	    oldopt=ptr;
	    break;
	  }

	if (typeof oldopt!='number') oldopt=undefined;
      }

    if (oldopt!=null || !horus.brokenDOM)
      select.add(newopt, oldopt);
    else
      newopt=options[options.length]=new Option(newopt.text, newopt.value, false, false);

    newopt.selected=set;
  };


horus.selshift=
  function ( theForm, source, dest, sorter ) {
    var argv=horus.formargv(arguments, true);
    theForm=argv[0];
    source=horus.formfield(theForm, argv[1]);
    if (source.selectedIndex<0) return;
    dest=horus.formfield(theForm, argv[2]);
    sorter=argv[3];
    var sourceopt=source.options;
    var sourceptr=sourceopt.length;
    var shifter=[];

    while (--sourceptr>=0) {
      var s=sourceopt[sourceptr];

      if (s.selected) {
	shifter.push(s);
	sourceopt[sourceptr]=null;
      }
    }

    return horus.seladd(theForm, dest, shifter, 'replace', sorter);
  };


horus.urlparam=
  function ( name, val ) {
    var re=new RegExp('[\?&]' + name + '=([^&]+)', 'i');
    var found=re.exec(window.top.location.search);

    if (!found) {
      re=new RegExp('.*\.cfm.*/' + name + '[.=/]([^?/]+)', 'i');
      found=re.exec(window.top.location.pathname);
    }

    if (found) val=found[1];
    return val;
  };


horus.testurl=
  function ( theForm, target, url ) {
    var argv=horus.formargv(arguments, false);
    theForm=argv[0];
    target=argv[1];
    url=argv[2];
    var protocol='';

    if (url instanceof Array) {
      protocol=horus.getselected(theForm, url[0]);
      url=url[1];
    }

    url=horus.getvar(theForm, url).trim();

    if (url=='')
      alert('your URL is blank');
    else {
      if (protocol=='' && url.charAt(0)!='/')
	protocol=url.match(/@/) ? 'mailto:' : 'http://';

      if (target instanceof Array)
	horus.openwin(target[0], protocol+url, target[1], target[2], target[3]);
      else
	horus.openwin(target, protocol+url);

    }
  };


horus.offsetParent=
  function ( obj ) {
    var parent=obj.offsetParent;

    if (horus.brokenDOM) {
      if (horus.ie>=7 && parent && parent.nodeName=='HTML') parent=document.body;

      for (var node=obj;
	   node!=parent && !/^t[dh]$/i.test(node.tagName);
	   node=node.parentNode);

      parent=node;
    }

    return parent;
  };


horus.positioned=
  function ( obj ) {
    if (obj._positioned==null)
      if (window.getComputedStyle)
	obj._positioned=getComputedStyle(obj, '').position!='static';
      else {
	var child=obj.firstChild;
	while (child && !horus.offsetParent(child)) child=child.nextSibling;
	obj._positioned=child && horus.offsetParent(child)==obj;
      }

    return obj._positioned;
  };


horus.getposition=
  function ( obj, root, debug ) {
    obj=horus.getElement(obj);
    var pos={ height: obj.offsetHeight, width: obj.offsetWidth };

    if (typeof root=='boolean')
      root=root ? null : horus.offsetParent(obj);
    else
      root=root ? horus.getElement(root) : null;

    if (debug)
      var status=[ obj.id+' - '+(root ? root.id : '*') ];

    var ptr=100;
    var lastref;
    pos.top=0;
    pos.left=0;

    while (obj && --ptr>0 && !horus.getposition.isroot(root, obj, lastref)) {
      var offsetParent=horus.offsetParent(obj);

      if (!(offsetParent && offsetParent==lastref)) {
	if (debug)
	  status.push
	    (obj.nodeName+'.'+obj.id+': '+obj.offsetTop+', '+obj.offsetLeft+' - '+
	     (offsetParent ? offsetParent.nodeName+'.'+offsetParent.id : horus.NUL));

	if (obj.offsetTop) pos.top+=obj.offsetTop;
	if (obj.offsetLeft) pos.left+=obj.offsetLeft;
	if (horus.brokenDOM && !offsetParent) break;
	lastref=offsetParent;
      }

      obj=obj.parentNode;
    }

    if (debug) {
      status.push('= '+pos.top+', '+pos.left);
      alert(status.join('\n'));
    }

    pos.bottom=pos.top+pos.height-1;
    pos.right=pos.left+pos.width-1;
    return pos;
  };


horus.getposition.isroot=
  function ( root ) {
    for (var i=1; i<arguments.length; i++) {
      var node=arguments[i];
      if (node && (node==root || node.nodeName=='BODY')) return true;
    }

    return false;
  };


horus.getheight=
  function () {
    var theheight=0;

    var items=arguments.length==1 && arguments[0] instanceof Array
      ? arguments[0] : arguments

    for (var i=0; i<items.length; i++) {
      var item=items[i];
      var add;
      var sub;
      var thisheight;

      if (item instanceof Array) {
	add=item[1];
	sub=item[2];
	item=item[0];
      }

      if (!item) continue;

      if (item=='*')
	thisheight=horus.windowsize().height;
      else {
	item=horus.getElement(item);
	thisheight=item.offsetHeight;
      }

      if (sub) {
	if (typeof sub!='number')
	  if (typeof add!='number')
	    sub=horus.getposition(add, sub).top;
	  else
	    sub=horus.getposition(sub, item).top;

	thisheight-=sub;
      }

      if (add) {
	if (typeof add!='number') add=horus.getposition(item, add).top;
	thisheight+=add;
      }

      if (thisheight>theheight) theheight=thisheight;
    }

    return theheight;
  };


horus.afterload=
  function () {
    if (!horus.loadaction) horus.loadaction=new Array;
    for (var i=0; i<arguments.length; i++) horus.loadaction.push(arguments[i]);
  };


horus.defer=
  function () {
    if (!horus.pendingaction) horus.pendingaction=new Array;
    for (var i=0; i<arguments.length; i++) horus.pendingaction.push(arguments[i]);
  };


horus.deferassign=
  function () {
    var argv=horus.formargv(arguments, false);
    if (!horus.pendingassign) horus.pendingassign=new Array;
    horus.pendingassign.push(argv);
  };


horus.resizeaction=
  function () {
    if (!horus.resizeactions) horus.resizeactions=new Array;

    if (arguments.length==1)
      horus.resizeactions.push(arguments[0]);
    else {
      var argv=new Array;
      for (var i=0; i<arguments.length; i++) argv.push(arguments[i]);
      horus.resizeactions.push(argv);
    }
  };


horus.execlist=
  function ( thelist ) {
    if (thelist)
      for (var i=0; i<thelist.length; i++) {
	var theaction=thelist[i];
	var thedelay=0;

	if (theaction instanceof Array && typeof theaction[0]=='number') {
	  thedelay=theaction[0];
	  theaction=theaction[1];
	  if (theaction instanceof Array) theaction=theaction[0][theaction[1]];
	  setTimeout(theaction, thedelay*100);
	} else if (typeof theaction=='function')
	  theaction();
	else if (theaction instanceof Array)
	  theaction[0][theaction[1]]();
	else
	  eval(theaction);

      }

  };


horus.reallydoresize=
  function () {
    horus.resizepending=null;

    // check if the size has *really* changed to catch spurious IE7 events
    var size=horus.windowsize();
    if (window._resizeHeight==size.height && window._resizeWidth==size.width) return;
    window._resizeHeight=size.height;
    window._resizeWidth=size.width;

    horus.execlist(horus.resizeactions);
  };


horus.doresize=
  function () {
    if (horus.resizeactions && !horus.resizepending)
      horus.resizepending=setTimeout(horus.reallydoresize, 500);

  };


horus.sizevalue=function ( v ) { return horus.isNumber(v) ? v+'px' : v };


horus.matchheight=
  function ( thewindow ) {
    if (arguments.length>1) {
      var thismatch=new Array(arguments.length);
      for (var i=0; i<arguments.length; i++) thismatch[i]=arguments[i];

      if (window.heightmatch)
	window.heightmatch.push(thismatch);
      else {
	window.heightmatch=[ thismatch ];
	horus.resizeaction(horus.matchheight.resized);
	// TODO: figure out why the second call is sometimes needed
	horus.defer(horus.matchheight, horus.matchheight);
      }

      return;
    }

    if (!thewindow || !thewindow.parent) thewindow=window;

    if (thewindow.heightmatch)
      for (var setindex=0; setindex<thewindow.heightmatch.length; setindex++) {
	var set=thewindow.heightmatch[setindex];
	var theheight=0;
	var maxheight=0;
	var argv=new Array;

	for (var i=0; i<set.length; i++) {
	  var item=set[i];
	  var thisheight;

	  if (item=='*' || item=='.') {
	    thisheight=horus.windowsize().height-1;
	    if (item=='.') maxheight=thisheight;
	  } else if (typeof item=='number') {
	    thisheight=item;
	  } else {
	    var adjust=0;
	    var skip=0;

	    if (item instanceof Array) {
	      if (item.length>1) adjust=item[horus.ieold && item.length>2 ? 2 : 1];
	      item=item[0];
	    }

	    if (typeof item=='string')
	      if (item=='*' || item=='.') {
		skip=2;
		thisheight=horus.windowsize().height+adjust-1;
		if (item=='.') maxheight=thisheight;
	      } else {
		if (item.charAt(0)=='-') {
		  skip=1;
		  item=item.substring(1);
		}

		item=thewindow.document.getElementById(item);
	      }

	    if (skip<2) {
	      if (!item) continue;
	      item.style.height=null;
	      var data=horus.getposition(item);
	      thisheight=data.top+data.height+adjust;

	      if (skip==0) {
		data.item=item;
		data.adjust=adjust;
		argv.push(data);
	      }
	    }
	  }

	  if (thisheight>theheight) theheight=thisheight;
	}

	if (maxheight>0) theheight=maxheight;

	for (var i=0; i<argv.length; i++) {
	  var data=argv[i];
	  data.item.style.height=(theheight-data.top-data.adjust)+'px';
	}
      }

    thewindow.heightpending=false;
  };


// indirect call because gecko passes a spurious extra parameter
horus.matchheight.call=function () { horus.matchheight() };


horus.matchheight.resized=
  function () {
    if (window.heightpending) return;
    window.heightpending=true;
    setTimeout(horus.matchheight.call, 10);
  };


horus.pending=
  function () {
    if (horus.pendingassign)
      for (var i=0; i<horus.pendingassign.length; i++) {
	var assign=horus.pendingassign[i];
	assign[0].elements[assign[1]].value=assign[2];
      }

    horus.execlist(horus.pendingaction);
  };


horus._optset=0;
horus._optreset=0;


horus.pageoptions=
  function ( opt ) {
    if (arguments.length==1 || arguments[1])
      horus._optset|=opt;
    else
      horus._optreset|=opt;

  };


horus.waittext=
  function ( thetext ) {
    window._waittext=thetext;
    horus.pageoptions(horus.HTML_WAITBOX, thetext!='');
  };


horus.pageloaded=
  function ( opt ) {
    if (!document.body) document.body=document.getElementsByTagName('body')[0];
    opt=((opt || 0)|horus._optset)&~horus._optreset;
    if (opt&horus.HTML_VISIBLE) horus.putvisible();
    if (opt&horus.HTML_NOBACK) horus.noback();
    if (opt&horus.HTML_WAITBOX) window._waitbox=true;
    if (opt&horus.HTML_SCROLL) horus.scroller();
    if (opt&horus.HTML_FOCUS) try { window.focus() } catch( err ) {};
    horus._pageloaded=true;
    horus.execlist(horus.loadaction);
    if (horus.pendingaction || horus.pendingassign) setTimeout(horus.pending, 10);
    horus._popflag=true;
  };


horus.eventListener(window, 'load', horus.pageloaded);
horus.eventListener(window, 'resize', horus.doresize);
