/*
 * Adams & Remers
 *
 * Script: AdamsRemersUtils.js
 * Utility functions
 *
 * By Andy Smith, Preview Graphics
 * www.preview.co.uk
 *
 * Includes code by Douglas Crockford and Scott Andrew (see comments)
 */
 

/*
 * Functional programming utilities
 * --------------------------------------------------------------------------
 */
 
function filter(pred, list)
{
  var result = [];
  var rescount = 0;
  for (var i = 0; i < list.length; i++) {
      if ((typeof list[i] != 'undefined') && pred(list[i])) {
          result[rescount] = list[i];  // push not supported in IE5
	  rescount++;
      }
  }
  return result;
}
 
function map(fn, list)
{
  var result = [];
  for (var i = 0; i < list.length; i++) {
      if (typeof list[i] != 'undefined') {
          result[i] = fn(list[i]);
      }
  }
  return result;
}


/*
 * Object-orientated programming utilities
 * --------------------------------------------------------------------------
 */

/* 
 * Utility functions by Douglas Crockford - from:
 * http://www.crockford.com/javascript/inheritance.html
 * http://www.crockford.com/javascript/remedial.html
 */

/* Type testing functions */

function isAlien(a) {
   return isObject(a) && typeof a.constructor != 'function';
}

function isArray(a) {
    return isObject(a) && a.constructor == Array;
}

function isBoolean(a) {
    return typeof a == 'boolean';
}

function isEmpty(o) {
    var i, v;
    if (isObject(o)) {
        for (i in o) {
            v = o[i];
            if (isUndefined(v) && isFunction(v)) {
                return false;
            }
        }
    }
    return true;
}

function isFunction(a) {
    return typeof a == 'function';
}

function isNull(a) {
    return typeof a == 'object' && !a;
}

function isNumber(a) {
    return typeof a == 'number' && isFinite(a);
}

function isObject(a) {
    return (a && typeof a == 'object') || isFunction(a);
}

function isString(a) {
    return typeof a == 'string';
}

function isUndefined(a) {
    return typeof a == 'undefined';
}
   
/*
 * Add a method to a 'class'. Use like:
 *    Class.method('name', func)
 * where Class is the class constructor function.
 */

Function.prototype.method = function (name, func) {
  this.prototype[name] = func;
  return this;
};

/*
 * Make a class inherit from another class. Use like:
 *     Subclass.inherits(Superclass)
 * The subclass will have a method 'uber' which is used to access overridden
 * methods. For instance:
 *     subclassInstance.uber('method')
 * returns the superclass's definition of 'method' operating on the subclass
 * instance.
 */

Function.method('inherits', function (parent) {
  var d = 0, p = (this.prototype = new parent());
  this.method('uber', function uber(name) {
    var f, r, t = d, v = parent.prototype;
    if (t) {
        while (t) {
            v = v.constructor.prototype;
            t -= 1;
        }
        f = v[name];
    } else {
        f = p[name];
        if (f == this[name]) {
            f = v[name];
        }
    }
    d += 1;
    r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
    d -= 1;
    return r;
  });
  return this;
});

/*
 * 'Swiss' multiple inheritance. Allows a class to inherit only a set
 * of named methods from another class. For instance:
 *     Subclass.swiss(OtherSuperclass, ['foo', 'bar'])
 * means Subclass will have the 'foo' and 'bar' methods from OtherSuperclass
 * but does not affect any other methods or an existing superclass.
 */

Function.method('swiss', function (parent) {
  for (var i = 1; i < arguments.length; i += 1) {
      var name = arguments[i];
      this.prototype[name] = parent.prototype[name];
  }
  return this;
});

/*
 * Apply a function as if it were a method of an object. Use like:
 *     func.apply(obj, 1, 2, 3)
 * will call func(1,2,3) with 'this' bound to obj. This is built in to most
 * modern browsers.
 */

if (typeof Function.apply == 'function') {
    Function.method('apply', function (o, a) {
        var r, x = '____apply';
        if (!isObject(o)) {
            o = {};
        }
        o[x] = this;
        switch ((a && a.length) || 0) {
        case 0:
            r = o[x]();
            break;
        case 1:
            r = o[x](a[0]);
            break;
        case 2:
            r = o[x](a[0], a[1]);
            break;
        case 3:
            r = o[x](a[0], a[1], a[2]);
            break;
        case 4:
            r = o[x](a[0], a[1], a[2], a[3]);
            break;
        case 5:
            r = o[x](a[0], a[1], a[2], a[3], a[4]);
            break;
        case 6:
            r = o[x](a[0], a[1], a[2], a[3], a[4], a[5]);
            break;
        default:
            alert('Too many arguments to apply.');
        }
        delete o[x];
        return r;
    });
} 


/*
 * String processing utilities
 * --------------------------------------------------------------------------
 */

/*
 * Determine whether an item is present in a space-separated list (such
 * as an HTML class attribute).
 */
 
function getSpaceListItem(list, item)
{
  var itemre = new RegExp('\\b' + item + '\\b');
  return itemre.test(list);
}

/*
 * Add or remove an item from a space-separated list (such as an HTML
 * class attribute). If present is true, the returned string will
 * include the item; if it is false, the returned string will not
 * include it.
 */
 
function setSpaceListItem(list, item, present)
{
  var itemre = new RegExp('\\b' + item + '\\b');
  if (present) {
      // See if the item is already in the list, and if not add it.
      if (itemre.test(list)) {
          return list;
      } else {
          return list + ' ' + item;
      }
  } else {
      // Remove the item from the list if it exists.
      return list.replace(itemre, '');
  }
}


/*
 * DOM node utilities
 * --------------------------------------------------------------------------
 */

/*
 * Get a list of all the child elements of a node - like node.children[]
 * but that doesn't work in Mozilla.
 */

function childElements(node)
{
  return filter(function (nd) { return (nd.nodeType == 1); },
                node.childNodes);
}


/*
 * DOM event utilities
 * --------------------------------------------------------------------------
 */

/*
 * Cross-browser event handler registration
 * By Scott Andrew - http://www.scottandrew.com/weblog/articles/cbs-events
 */

function addEvent(obj, evType, fn)
{
  if (obj.addEventListener) {
      obj.addEventListener(evType, fn, true);
      return true;
  } else if (obj.attachEvent) {
      var r = obj.attachEvent("on"+evType, fn);
      return r;
  } else {
      return false;
  }
}

/*
 * Cross-browser event target
 */
 
function getEventTarget(ev)
{
  var target;
  if (ev.target) {                   /* W3C/Mozilla */
      target = ev.target;
  } else if (ev.srcElement) {        /* Internet Explorer */
      target = ev.srcElement;
  } else {                           /* No support */
      target = null;
  }
  if (target && target.nodeType && (target.nodeType == 3)) {
      target = target.parentNode;    /* Safari bug fix */
  }
  return target;
}

/*
 * Get the link containing an event target. When a link is clicked, the
 * event may be fired on a child element of the link rather than the
 * link itself. To use event handlers that need link properties, we need
 * to find the link. If the event target is a link, return it; otherwise
 * check its parent node, and its parent and so on, until a link is found,
 * or return null if the event target is not inside a link.
 */

function getEventLinkTarget(ev)
{
  var el = getEventTarget(ev);
  while (el) {
      if (el.nodeName == 'A') return el;
      el = el.parentNode;
  }
  return null;
}

/*
 * Cross-browser method to stop an event's default action. Designed to
 * be called from an event handler like this:
 *
 *    return stopEvent(e);
 *
 * In browsers that support the preventDefault method, this is used;
 * Mozilla carries out the default method without this, even if the
 * event handler returns false, if the handler was added with
 * addEventListener(). (I'm not sure if this is the correct behaviour).
 * Other browsers use the return value from the event handler.
 */
 
function stopEventDefault(ev)
{
  if (ev.preventDefault) ev.preventDefault();
  return false;
}


/*
 * HTML utilities
 * --------------------------------------------------------------------------
 */

/*
 * Check whether an element has a particular class in its list of classes.
 */
 
function checkClass(el, cls)
{
  return getSpaceListItem(el.className, cls);
}

/*
 * Add or remove a class from an element's list of classes. If present
 * is true, the class list will include the specified class; if false,
 * the class list will not include it.
 */
 
function setClass(el, cls, present)
{
  el.className = setSpaceListItem(el.className, cls, present);
}
