function $$CN(element, name) {
  return Element.getElementsByClassName(element, name);  
}




// minimal resource queue
Effect.SlowQueue = Class.create();
Object.extend(Object.extend(Effect.SlowQueue.prototype, Effect.ScopedQueue.prototype), {
  initialize: function() {    
    Effect.ScopedQueue.prototype.initialize.call(this);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();
    
    var position = (typeof effect.options.queue == 'string') ? 
      effect.options.queue : effect.options.queue.position;
    
    switch(position) {
      case 'front':
        // move unstarted effects after this effect  
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        // start effect after last queued effect has finished
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }
    
    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);
    
    if(!this.interval) 
      this.interval = setInterval(this.loop.bind(this), 150);
  }
});


Effect.FrameAnimator = Class.create();
Object.extend(Object.extend(Effect.FrameAnimator.prototype, Effect.Base.prototype), {
  initialize: function(callback) {
    this.callback = callback;

    var options = Object.extend({
      transition: Effect.Transitions.linear,
      duration:   600.0,   // seconds
      fps:        6.25,      
      from:       0.0,
      to:         1.0,
      sync:       true
    }, arguments[1] || {});
    this.start(options);
    
    this.queue = new Effect.SlowQueue();
    this.queue.add(this);    
  },
  update: function(position) {
    this.callback();
  }
});

/**
 * Joao Prado Maia's work from Scriptaculous Treasure Chest
 *
 * http://wiki.script.aculo.us/scriptaculous/show/EffectsTreasureChest
 */

Effect.SlideRightIntoView = function(element) {
  $(element).style.width = '0px';
  $(element).style.overflow = 'hidden';
  $(element).firstChild.style.position = 'relative';
  Element.show(element);
  new Effect.Scale(element, 100,
    Object.extend(arguments[1] || {}, {
      duration:   0.2,
      scaleContent: false,
      scaleY: false,
      scaleMode: 'contents',
      scaleFrom: 0,
      afterUpdate: function(effect){}
    })
  );
}

Effect.SlideRightOutOfView = function(element) {
  $(element).style.overflow = 'hidden';
  $(element).firstChild.style.position = 'relative';
  Element.show(element);
  new Effect.Scale(element, 0,
    Object.extend(arguments[1] || {}, {
      transition: Effect.Transitions.linear,
      duration:   0.2,
      scaleContent: false,
      scaleY: false,
      afterUpdate: function(effect){},
      afterFinish: function(effect)
        { Element.hide(effect.element); }
    })
  );
}

Effect.SlideLeftAndRight = function(element) {
  element = $(element);
  if(Element.visible(element)) new Effect.SlideRightOutOfView(element);
  else new Effect.SlideRightIntoView(element);
}

Effect.BlindRight = function(element) {
  element = $(element);
    var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({ 
    scaleContent: false, 
    scaleY: false,
    scaleFrom: 0,
    transition: Effect.Transitions.linear,
    duration: 0.2,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({width: '0px'}).show(); 
    },  
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || {}));
}

Effect.BlindLeft = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false, 
      scaleY: false,                     
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      } 
    }, arguments[1] || {})
  );
}

Position.setOffsets = function(element, left, top) {  
  // set offsets
  element.style.left  = left + "px";
  element.style.top   = top + "px";
}


Position.cloned = function(source, target) {
  var options = Object.extend({
    setLeft:    true,
    setTop:     true,
    setWidth:   true,
    setHeight:  true,
    offsetTop:  0,
    offsetLeft: 0,
    scrollViewportY: false,
    scrollViewportX: false,
    adjustPositionToViewportY: false,
    adjustPositionToViewportX: false,
    altSourceY: null,
    verticalScrollBarWidth: 20,
    viewportPaddingX: 5,
    viewportPaddingY: 5
  }, arguments[2] || {})

  // find page position of source
  source = $(source);
  var p = Position.page(source);

  // If altTargetY, recalculate p's Y value
  var altSourceY = null;
  var altP = null;
  if(options.altSourceY) {
    altSourceY = $(options.altSourceY);
    altP = Position.page(altSourceY);
    p[1] = altP[1];
  }

  // find coordinate system to use
  target = $(target);
  var delta = [0, 0];
  var parent = null;
  // delta [0,0] will do fine with position: fixed elements,
  // position:absolute needs offsetParent deltas
  if (Element.getStyle(target,'position') == 'absolute') {
    parent = Position.offsetParent(target);
    delta = Position.page(parent);
  }

  // correct by body offsets (fixes Safari)
  if (parent==document.body) {
    delta[0] -= document.body.offsetLeft;
    delta[1] -= document.body.offsetTop;
  }

  // Cloned vals (which may or may not be used & adjusted, based on options)
  var clonedPos = { targetTop: p[1] - delta[1] + options.offsetTop,
                    targetLeft: p[0] - delta[0] + options.offsetLeft,
                    targetWidth: source.offsetWidth,
                    targetHeight: source.offsetHeight};

  // If specified, adjust the position of target to fit in viewport
  if (options.adjustPositionToViewportX || options.adjustPositionToViewportY) {
    clonedPos = Position.adjustPositionToViewport(target, Object.extend(options, clonedPos));
  }

  // set position
  if(options.setLeft)   target.style.left  = clonedPos.targetLeft + "px";
  if(options.setTop)    target.style.top   = clonedPos.targetTop + "px";
  if(options.setWidth)  target.style.width = clonedPos.targetWidth + "px";
  if(options.setHeight) target.style.height = clonedPos.targetHeight + "px";

  // Scroll viewport if specified, unless the pos of the target itself has been adjusted
  if ((options.scrollViewportX && !options.adjustPositionToViewportX)||
      (options.scrollViewportY && !options.adjustPositionToViewportY)) {
    Position.scrollIntoView(target, Object.extend(options, clonedPos));
  }
}

Position.adjustPositionToViewport = function(target, options) {
    target = $(target);

    var viewportLeft  =  window.pageXOffset
                      || document.documentElement.scrollLeft
                      || document.body.scrollLeft
                      || 0;
    var viewportTop   =  window.pageYOffset
                      || document.documentElement.scrollTop
                      || document.body.scrollTop
                      || 0;

    var shiftX = 0;
    var shiftY = 0;

    // adjust Y pos, if specified and necessary
    var targetWidth = 0;
    if (options.adjustPositionToViewportX) {
      var viewportWidth = Math.max(0, self.innerWidth - options.verticalScrollBarWidth) // all except Explorer
                        || document.documentElement.clientWidth // strict Explorer
                        || document.body.clientWidth // other Explorers
                        || 0;
      //viewportWidth = viewportWidth - options.verticalScrollBarWidth; //pad for scrollbars

      if (options.setWidth) {
        targetWidth = options.targetWidth;
      } else {
        targetWidth = Element.getDimensions(target).width;
      }
      var targetLeft = options.targetLeft;

      shiftX = Math.min(0, (targetLeft + options.viewportPaddingX) - viewportLeft) // target lies left of viewport
            || Math.max(0, (targetLeft + targetWidth + options.viewportPaddingX)
                            - (viewportLeft + viewportWidth)) // target lies right of viewport
            || 0;
    }

    // adjust Y pos, if specified and necessary
    var targetHeight = 0;
    if (options.adjustPositionToViewportY) {
      var viewportHeight = self.innerHeight // all except Explorer
                        || document.documentElement.clientHeight // Explorer 6 Strict Mode
                        || document.body.clientHeight // other Explorers
                        || 0;

      if (options.setHeight) {
        targetHeight = options.targetHeight;
      } else {
        targetHeight = Element.getDimensions(target).height;
      }
      var targetTop = options.targetTop;

      shiftY = Math.min(0, (targetTop + options.viewportPaddingY) - viewportTop) // target lies above viewport
            || Math.max(0, (targetTop + targetHeight + options.viewportPaddingY)
                           - (viewportTop + viewportHeight)) // target lies below viewport
            || 0;
    }

    return {  targetTop: options.targetTop - shiftY,
              targetLeft: options.targetLeft - shiftX,
              targetWidth: options.targetWidth,
              targetHeight: options.targetHeight};
}

// scroll viewport, ensuring target is fully visible
// x-browser knowledge from http://www.quirksmode.org/viewport/compatibility.html
Position.scrollIntoView = function(target, options) {
    target = $(target);

  // We need to enable the element in case its visibility increases
  // the size of the full page.
  //if (Element.getStyle(target,'display') == "none"){
  //  var els = target.style;
  //  var originalVisibility = els.visibility;
  //  var originalPosition = els.position;
  //  els.visibility = "hidden";
  //  els.position = "absolute";
  //  els.display = "";
  //}

    if (!options) {
      var options = {scrollViewportY: true, setTop: true, viewportPaddingX: 5};
    }

    var viewportLeft  =  window.pageXOffset
                      || document.documentElement.scrollLeft
                      || document.body.scrollLeft
                      || 0;
    var viewportTop   =  window.pageYOffset
                      || document.documentElement.scrollTop
                      || document.body.scrollTop
                      || 0;
    var tPos = Position.cumulativeOffset(target);

    var shiftX = 0;
    var shiftY = 0;
    // scroll X, if specified and necessary
    if ((options.setLeft || options.setWidth) && options.scrollViewportX) {
      var viewportWidth = self.innerWidth // all except Explorer
                        || document.documentElement.clientWidth // Explorer 6 Strict Mode
                        || document.body.clientWidth // other Explorers
                        || 0;
      var targetWidth = Element.getDimensions(target).width;
      var targetLeft = (options.targetLeft) ? options.targetLeft : tPos[0];

      shiftX = Math.min(0, (targetLeft + options.viewportPaddingX) - viewportLeft) // target lies left of viewport
            || Math.max(0, (targetLeft + targetWidth + options.viewportPaddingX)
                            - (viewportLeft + viewportWidth)) // target lies right of viewport
            || 0;
    }

    // scroll Y, if specified and necessary
    if ((options.setTop || options.setHeight) && options.scrollViewportY) {
      var viewportHeight = self.innerHeight // all except Explorer
                        || document.documentElement.clientHeight // Explorer 6 Strict Mode
                        || document.body.clientHeight // other Explorers
                        || 0;
      var targetHeight = Element.getDimensions(target).height;
      var targetTop = (options.targetTop) ? options.targetTop : tPos[1];

      //alert(tPos[1]);
      //alert(targetTop);
      //alert(targetHeight);
      //alert(viewportTop);
      //alert(viewportHeight);

      shiftY = Math.min(0, (targetTop + options.viewportPaddingY) - viewportTop) // target lies above viewport
            || Math.max(0, (targetTop + targetHeight + options.viewportPaddingY)
                           - (viewportTop + viewportHeight)) // target lies below viewport
            || 0;
    }

    if (shiftX || shiftY) {
      window.scrollTo(viewportLeft + shiftX, viewportTop + shiftY);
    }
}

// Override scriptaculous to prevent interference with png fix
Element.setOpacity = function(element, value){
  element= $(element);
  var els = element.style;
  if (value == 1){
    els.opacity = '0.999999';
    if(/MSIE/.test(navigator.userAgent))
      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
  } else {
    if(value < 0.00001) value = 0;
    els.opacity = value;
    if(/MSIE/.test(navigator.userAgent))
      els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
        " alpha(opacity="+value*100+")";
  }
}


Ajax.PutDeleteExtensions = {
  request: function(url) {
    
    
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post', 'put', 'delete'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Hash.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      if (this.options.onCreate) this.options.onCreate(this.transport);  
      Ajax.Responders.dispatch('onCreate', this, this.transport);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous)
        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = ( this.method == 'post' ||
                    this.method == 'put'  ||
                    this.method == 'delete' ) ? (this.options.postBody || params) : null;

      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },
  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.options.method == 'post' ||
        this.options.method == 'put'  ||
        this.options.method == 'delete')
    {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (typeof extras.push == 'function')
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  }
};

Object.extend(Ajax.Request.prototype, Ajax.PutDeleteExtensions);
Object.extend(Ajax.Updater.prototype, Ajax.PutDeleteExtensions);


// Returns an array with all elems === to elem removed
Array.remove = function(array, elem) {
  var result = [];
  var len = array.length;
  for (var i = 0; i < len; i++) {
    var el = array[i];
    if (el !== elem) {
      result.push(el);
    }
  }
  return result;
};

if (!Array.prototype.remove) {
  Array.prototype.remove = function(elem) {
    return Array.remove(this, elem);
  }
}

// Computes the difference of arrays
Array.diff = function(array1, array2, comp) {
  var result = [];
  array1.sort((comp)? comp : function(a,b){return a-b;});
  array2.sort((comp)? comp : function(a,b){return a-b;});
  var i = 0;
  var j = 0;
  while (i < array1.length) { //i:a1 ptr, j:a2 ptr
    if (array2[j] < array1[i] && j < array2.length) {j++; continue;}
    if (i > 0 && array1[i-1] === array1[i]) {i++; continue;}
    if (array1[i] != undefined && array1[i] !== array2[j]) {
      result.push(array1[i]);
      i++;
    } else {
      i++; j++;
    }
  }
  return result;
};

if (!Array.prototype.diff) {
  Array.prototype.diff = function(array2) {
    return Array.diff(this, array2);
  }
}


String.explode = function(item, delimiter) {
  tempArray=new Array(1);
  var Count=0;
  var tempString=new String(item);

  if (typeof delimiter == "string") {
    while (tempString.indexOf(delimiter)>0) {
      tempArray[Count]=tempString.substr(0,tempString.indexOf(delimiter));
      tempString=tempString.substr(tempString.indexOf(delimiter)+1,tempString.length-tempString.indexOf(delimiter)+1);
      Count=Count+1
    }
    tempArray[Count]=tempString;
  } else if (delimiter instanceof Array) {
    var nextDelimiterIdx = -1;
    for(var i = 0; i < delimiter.length ; i++) {
      nextDelimiterIdx = Math.max(nextDelimiterIdx, tempString.indexOf(delimiter[i]));
    }
    while (nextDelimiterIdx > 0 ) {
      tempArray[Count]=tempString.substr(0,nextDelimiterIdx);
      tempString=tempString.substr(nextDelimiterIdx+1, tempString.length-nextDelimiterIdx+1);
      Count=Count+1;
      nextDelimiterIdx = -1
      for(var i = 0; i < delimiter.length ; i++) {
        nextDelimiterIdx = Math.max(nextDelimiterIdx, tempString.indexOf(delimiter[i]));
      }
    }
    tempArray[Count]=tempString;
  } else {
    tempArray[0] = item;
  }
  return tempArray;
};

if (!String.prototype.explode) {
  String.prototype.explode = function(delimiter) {
    return String.explode(this, delimiter);
  }
}

document.validateId = function(id) {
  var elemId = id;
  // there's already an elem with that name, don't specify an id
  if ($(elemId)) {
    elemId = null;
  }
  return elemId;
}

Element.forceHandCursor = function(element) {
  element = $(element);
  if( navigator.appVersion.indexOf('MSIE') > 0 ) {
    element.setStyle($H({cursor: "hand"}));
  } else {
    element.setStyle($H({cursor: "pointer"}));
  }
}

Element.removeChildren = function(element) {
  element = $(element);
  while ( element.hasChildNodes() ) element.removeChild(element.firstChild);
}

Element.moveChildren = function(fromElem, toElem) {
  fromElem = $(fromElem);
  toElem = $(toElem);
  while ( fromElem.hasChildNodes() ) toElem.appendChild(fromElem.removeChild(fromElem.firstChild));
}

Element.setInnerText = function(element, text) {
  element = $(element);
  Element.removeChildren(element);
  if (text) element.appendChild(document.createTextNode(text));
}

Element.getVisibleBackgroundColor = function(element, allowTransparent) {
    element = $(element);
    while ( element.parentNode && (!element.style || Element.getStyle(element, 'background-color') == 'transparent')) {
      element = element.parentNode;
    }
    if (allowTransparent) {
      return (element.style) ? Element.getStyle(element, 'background-color').parseColor('transparent') : 'transparent';
    } else {
      return (element.style) ? Element.getStyle(element, 'background-color').parseColor('#ffffff') : '#ffffff';
    }
}

Element.prependChild = function(element, newChild){
  element = $(element);
  newChild = $(newChild);
  
  if (element.hasChildNodes()) {
    element.insertBefore(newChild, element.firstChild);
  } else {
    element.appendChild(newChild);
  }
  
  return newChild;
}

Element.insertAfter = function(newElement, element){
  element = $(element);
  newElement = $(newElement);

  if(!element.parentNode) return false;
   
  if (element.nextSibling) {
    element.parentNode.insertBefore(newElement, element.nextSibling);
  } else {
    element.parentNode.appendChild(newElement);
  }
  
  return newElement;
}

Element.firstWithClassName = function(element, className) {
  var elements = Element.getElementsByClassName(element, className);
  return (elements && elements.size > 0) ? elements.first() : null;
}

// Determines if the element is in the dom.
Element.inDOM = function(element) {
  if(element == document.body) return element;

  while (element = element.parentNode)
    if (element == document.body) return true;

  return false;
}

Element.isElement = function(object) {
  if (!object || !((typeof object == 'string') || (typeof object == 'object'))) return false;
  object = $(object);
  return (object.nodeType) ? true : false;
}

Class.isClass = function(object) {
  return (object && (typeof object == 'function') && object.prototype.initialize) ? true : false;
}

Field.selectOption = function(field, value) {
  element = $(field);
  var values, length = element.length;
  if (!length || element.tagName.toLowerCase() != 'select') return null;

  for (var i = 0; i < length; i++) {
    var opt = element.options[i];
    if (opt.value == value) {
      opt.selected = true;
    } else if (opt.selected) {
      opt.selected = false;
    }
  }  
}

// Override prototype to allow serializing disabled elems.
Form.serializeElements = function(elements, getHash) {
  var data = elements.inject({}, function(result, element) {
    if (element.name) {
      var key = element.name, value = $(element).getValue();
      if (value != undefined) {
        if (result[key]) {
          if (result[key].constructor != Array) result[key] = [result[key]];
          result[key].push(value);
        }
        else result[key] = value;
      }
    }
    return result;
  });

  return getHash ? data : Hash.toQueryString(data);
}


var Dimension = new Object();
Object.extend(Dimension, {
  getRemoteImageDims: function(url) {
    // preload image
    //var preloaded = new Array();
    //preloaded[0] = new Image();
    //preloaded[0].src = url;    
    
    // create an img elem, an inject into DOM
    var testImg = document.createElement("img");
    testImg.src = url;
    testImg.alt = "";
    testImg.style.position = 'absolute';
    testImg.style.zIndex = -20;
    document.body.appendChild(testImg);    
    
    return Element.getDimensions(testImg);
  },
  getSquareInscribedDims: function(origW, origH, squareDim, enlarge) {
    var ret = {};
    var w = squareDim;
    var h = squareDim;
    ret.origWidth = origW;
    ret.origHeight = origH;
    
    var wRatio = ret.origWidth / w;
    var hRatio = ret.origHeight / h;

    if ( !enlarge && (wRatio <= 1.0 && hRatio <= 1.0) ) {
      // Orig is smaller, so do not enlarge it
      setDim = null;
    }
    else {
      // Must set the dimension that has the greater ratio first
      setDim = (wRatio > hRatio) ? 'w' : 'h';
    }

    switch (setDim) {
      case 'w':
        // Set width to target size, then scale height
        ret.scaledWidth = w;
        ret.scaledHeight = Math.round(ret.scaledWidth * (ret.origHeight / ret.origWidth));
        break;
      case 'h':
        // Set height to target size, then scale width
        ret.scaledHeight = h;
        ret.scaledWidth = Math.round(ret.scaledHeight * (ret.origWidth / ret.origHeight));
        break;
      default:
        // Maintain original dimensions
        ret.scaledWidth = ret.origWidth;
        ret.scaledHeight = ret.origHeight;
        break;
    }
    
    return ret;
  }
});

// API objects
var Mblst = {}
var Fb = {}

Mblst.calcHighlightColor = function(color, defaultColor) {
  color = color.parseColor('#ffffff');
  if (color == '#ffffff') {
    return (defaultColor || "#ffff99");
  } else {
    var r, g, b;
    r = ((r = parseInt(color.slice(1,3), 16) + 32) < 0xff) ? r : 0xff;
    g = ((g = parseInt(color.slice(3,5), 16) + 32) < 0xff) ? g : 0xff;
    b = ((b = parseInt(color.slice(5,7), 16) + 32) < 0xff) ? b : 0xff;
    return "#"+r.toColorPart()+g.toColorPart()+b.toColorPart();
  }
}

// REST request builders
var YahooService = {}
YahooService.appid = 'moblastic';
YahooService.mapsUrl = function() {
  var resource = '/yahooservice/MapsService/V1/geocode';
  return resource;
}

YahooService.mapsSerialize = function(form) {
  var qs = 'appid='+encodeURIComponent(YahooService.appid);
  return qs+'&'+Form.serialize(form);
}

var YahooLocalService = {}
YahooLocalService.appid = 'moblastic';
YahooLocalService.mapsUrl = function() {
  var resource = '/yahooservice/LocalSearchService/V3/localSearch';
  return resource;
}

YahooLocalService.mapsSerialize = function(form) {
  var qs = 'appid='+encodeURIComponent(YahooLocalService.appid);
  return qs+'&'+Form.serialize(form);
}

// Partner auth constants
var partner_param_string  = "";
var partner_user = "";

Mblst.currBaseUrl = null;

Mblst.Url = {};
// BLAST RSRC
Mblst.Url.blast = function(uid, resid) { return '/rest/'+uid+'/blasts/'+resid; }
Mblst.Url.blastC = function(uid) { return '/rest/'+uid+'/blasts'; }
Mblst.Url.blastSentC = function(uid) { return '/rest/'+uid+'/blasts/sent'; }
// CONVERSATION RSRC
Mblst.Url.conversation = function(uid, resid) { return '/rest/'+uid+'/conversations/'+resid; }
Mblst.Url.conversationByMessage = function(uid, msgid) { return '/rest/'+uid+'/conversations/message/'+msgid; }
Mblst.Url.conversationC = function(uid) { return '/rest/'+uid+'/conversations'; }
// EVENT RSRC
Mblst.Url.event = function(uid, eventtype, resid) { return '/rest/'+uid+'/events/'+eventtype+'/'+resid; }
Mblst.Url.eventC = function(uid) { return '/s3/events/'+uid; }
// FAV PHOTO RSRC
Mblst.Url.favPhoto = function(uid, resid) { return '/rest/'+uid+'/favorites/photos/'+resid; }
Mblst.Url.favPhotoC = function(uid) { return '/rest/'+uid+'/favorites/photos'; }
// FAV STAMP RSRC
Mblst.Url.favStamp = function(uid, resid) { return '/rest/'+uid+'/favorites/stamps/'+resid; }
Mblst.Url.favStampC = function(uid) { return '/rest/'+uid+'/favorites/stamps'; }
// FRIEND RSRC
Mblst.Url.friend = function(uid, resid) { return '/rest/'+uid+'/friends/'+resid; }
Mblst.Url.friendC = function(uid) { return '/rest/'+uid+'/friends'; }
// FRIEND REQ RSRC
Mblst.Url.friendRequest = function(uid, resid) { return '/rest/'+uid+'/friends/'+resid+'/request'; }
// MEDIA RSRC
Mblst.Url.publicmediaC = function(uid) { return '/rest/public/'+uid+'/media'; }
Mblst.Url.publicmediausernameC = function(uid) { return '/rest/public/'+uid+'/media/username'; }
// PHOTO RSRC
Mblst.Url.photo = function(uid, resid) { return '/rest/'+uid+'/photos/'+resid; }
Mblst.Url.anonPhoto = function(resid) { return '/rest/photos/'+resid; }
Mblst.Url.publicphoto = function(uid, resid) { return '/rest/public/'+uid+'/photos/'+resid; }
Mblst.Url.publicphotouser = function(uid, resid) { return '/rest/public/photos/'+resid+'/users/'+uid; }
Mblst.Url.photoC = function(uid) { return '/rest/'+uid+'/photos'; }
Mblst.Url.publicphotoC = function(uid) { return '/rest/public/'+uid+'/photos'; }
Mblst.Url.mblstphotoC = function() { return '/rest/photos'; }
// PHOTO COMMENT RSRC
Mblst.Url.photoComment = function(uid, photoid, resid) { return '/rest/'+uid+'/photos/'+photoid+'/comments/'+resid; }
Mblst.Url.photoCommentC = function(uid, photoid) { return '/rest/'+uid+'/photos/'+photoid+'/comments'; }
// PROFILE RSRC
Mblst.Url.profile = function(uid) { return '/rest/'+uid+'/profile'; }
Mblst.Url.anonProfile = function(resid) { return '/rest/profile/'+resid; }
// SERVICES RSRC
Mblst.Url.yahooContactsService = function() { return '/rest/services/yahoocontacts'; }
Mblst.Url.gmailContactsService = function() { return '/rest/services/gmailcontacts'; }
Mblst.Url.myspaceProfileService = function(uid) { return '/rest/public/'+uid+'/services/myspaceprofile'; }
Mblst.Url.myspaceProfileImportService = function(uid) { return '/rest/public/'+uid+'/services/myspaceprofileimport'; }
Mblst.Url.myspaceSSPostService = function(uid) { return '/rest/public/'+uid+'/services/myspacesspost'; }
// STAMP RSRC
Mblst.Url.stampC = function(categories, type) { if (!type) type=''; return '/rest/stamps/categories:'+categories+'/type:'+type; }
Mblst.Url.publicstampC = function(categories, type) { if (!type) type=''; return '/rest/public/stamps/categories:'+categories+'/type:'+type; }
// SUBSCRIPTION RSRC
Mblst.Url.subscription = function(uid, subscribedUid) { return '/rest/'+uid+'/subscriptions/'+subscribedUid; }
// TEMP PHOTO RSRC
Mblst.Url.tempPhotoC = function() { return '/rest/temp/photos'; }
// USER RSRC
Mblst.Url.userByPhNumber = function(phNumber) { return '/rest/user/phnumber/'+phNumber; }
Mblst.Url.userByMblEmail = function(mblEmail) { return '/rest/user/mblemail/'+mblEmail; }
Mblst.Url.userByDisplayName = function(displayName) { return '/rest/user/displayname/'+displayName; }
// USER STAMPED PHOTO RSRC
Mblst.Url.photostampeduser = function(uid, photoid) { return '/rest/photos/'+photoid+'/stampeduser/'+uid; }
// VIDEO RSRC
Mblst.Url.video = function(uid, resid) { return '/rest/'+uid+'/videos/'+resid; }
Mblst.Url.anonVideo = function(resid) { return '/rest/videos/'+resid; }
// VIDEO COMMENT RSRC
Mblst.Url.videoComment = function(uid, videoid, resid) { return '/rest/'+uid+'/videos/'+videoid+'/comments/'+resid; }
Mblst.Url.videoCommentC = function(uid, videoid) { return '/rest/'+uid+'/videos/'+videoid+'/comments'; }

// Composite Resource components
Mblst.Url.curruserComp = function(uid) { return '/rest/comp/'+uid+'/curruser'; }

Mblst.Url.tickets = function(succeeded, failed, pending) {
  var qs = 'succeeded='+encodeURIComponent(succeeded);
  qs+= '&failed='+encodeURIComponent(failed);
  qs+= '&pending='+encodeURIComponent(pending);
  qs+= '&cb='+(new Date().getTime());
  return '/rest/tickets/?'+qs;
}

Mblst.Url.rsrcC = function(baseurlGen, uid, rsrcids) {
  var baseurl = baseurlGen(uid);
  return baseurl+'/'+rsrcids.join(',');
}

Mblst.Url.pageBase = function(baseurl, uid, search, sort, filter, ordinality) {
  var searchSeg = '';
  sort = (sort) ? ('/sort:'+sort) : '';
  filter = (filter) ? ('/filter:'+filter) : '';
  ordinality = (ordinality) ? ('/ordinality:'+ordinality) : '';
  if (search)
    searchSeg = (search.match(/^[a-zA-Z]+$/)) ? '/tags/'+search : '/tags:'+search.replace(/\s+/gi, '+');
  return baseurl+searchSeg+sort+ordinality+filter;
}

Mblst.Url.addParam = function(url, name, value) {
  if (!url.match(/\?/)) {
    url += "?";  
  } else if (url.match(/\?.*[^?]$/)){
    url += "&";
  }
  return url+name+"="+encodeURIComponent(value);
}

Mblst.UrlBuilder = {};
Mblst.UrlBuilder.relativePage = function(baseurlfunc, sort, filter, ordinality) {    
  var builder = function (uid, resid, relation) {
    var baseurl = baseurlfunc(uid, resid);
    var bSort = (sort) ? ('/sort:'+sort) : '';
    var bFilter = (filter) ? ('/filter:'+filter) : '';
    var bOrdinality = (ordinality) ? ('/ordinality:'+ordinality) : '';
    var order_relation = (relation) ? ('/relation:'+relation) : '';
    return baseurl+bSort+bOrdinality+bFilter+order_relation;
  }.bind(this);
  
  return builder;
}
Mblst.UrlBuilder.stampFilterPage = function(baseurlfunc, basecategories, type) {    
  var builder = function (tag) {
    var bCats = (tag) ? basecategories+'+'+tag : basecategories;
    var baseurl = baseurlfunc(bCats, type);
    return baseurl;
  }.bind(this);
  
  return builder;
}

// Moblastic specific resources
Mblst.Url.flirter = function(uid) { return '/rest/'+uid+'/flirter'; }
Mblst.Url.txtPopup = function(uid) { return '/rest/'+uid+'/txtpopup'; }

// Txtmessager specific resources
Mblst.Url.slideshow = function(uid) { return '/rest/'+uid+'/slideshow'; }
Mblst.Url.messageC = function(toUserId) { return '/rest/'+toUserId+'/messages'; }
Mblst.Url.contactmessageC = function(uid, toContactId) { return '/rest/'+uid+'/contacts/'+toContactId+'/messages'; }
Mblst.Url.msgthread = function(uid, resid) { return '/rest/'+uid+'/msgthreads/'+resid; }
Mblst.Url.grouptxtC = function(toListId) { return '/rest/'+toListId+'/grouptxts'; }
Mblst.Url.myspaceTxtmsgrPostService = function(uid) { return '/rest/public/'+uid+'/services/myspacetxtmsgrpost'; }
Mblst.Url.myspaceTxtflirterPostService = function(uid) { return '/rest/public/'+uid+'/services/myspacetxtflirterpost'; }

// Partner specific resources
Mblst.Url.partnerphotoC = function(uid,aid,subj_id) {
  if (uid && aid)  
    return '/rest/partner/fb/'+uid+'/photos/album/'+aid;
  else if (subj_id)
    return '/rest/partner/fb/photos/users/'+subj_id;
  else
    return '/rest/partner/fb/'+uid+'/photos';
}
Mblst.Url.partnerphotoalbumC = function(uid) { return '/rest/partner/fb/'+uid+'/photoalbums'; }
Mblst.Url.partnerfriendphotoalbumC = function(uid) { return '/rest/partner/fb/'+uid+'/friends/photoalbums'; }
Mblst.Url.partnerfriendC = function(uid) { return '/rest/partner/fb/'+uid+'/friends'; }


// Functions to access non-REST urls
Mblst.WWW = {}
Mblst.WWW.userLoc = function(uid, res) { return '/loc_dd.php?user_id='+uid+'&loc='+res; }
Mblst.WWW.userLocEvent = function(ulocid, eventcdate) { return '/loc_dd.php?user_loc_id='+ulocid+'&event_cdate='+eventcdate; }

Mblst.loadSerialize = function(value, options) {
  var result = [];
  if (this.options.mblstLoadOptions.outputType)
    result.push('mimeType='+encodeURIComponent(this.options.mblstLoadOptions.outputType));
  if (this.options.mblstLoadOptions.dataSet)
    result.push('dataSet='+encodeURIComponent(this.options.mblstLoadOptions.dataSet));
  if (this.options.mblstLoadOptions.dataRep)
    result.push('dataRep='+encodeURIComponent(this.options.mblstLoadOptions.dataRep));
  if (this.options.mblstLoadOptions.view)
    result.push('view='+encodeURIComponent(this.options.mblstLoadOptions.view));
  if (this.options.mblstLoadOptions.transport)
    result.push('transport='+encodeURIComponent(this.options.mblstLoadOptions.transport));
  if (this.options.mblstLoadOptions.dataSetRsrc)
    result.push('dataSetRsrc='+encodeURIComponent(this.options.mblstLoadOptions.dataSetRsrc));
  if (this.options.mblstLoadOptions.auxJSON)
    result.push('auxJSON='+encodeURIComponent(this.options.mblstLoadOptions.auxJSON));
  if (value && this.options.mblstLoadOptions.valueName) {
    result.push(encodeURIComponent(this.options.mblstLoadOptions.valueName)+'='+encodeURIComponent(value));
  } else if (value){
    result.push('value='+encodeURIComponent(value));
  }
  
  if (partner_param_string) {
    result.push(partner_param_string);
  }
  
  return result.join("&");
}

Mblst.requestSerialize = function(form, value, options) {
  var result = [];

  var reqOpts = Object.extend(this.options && this.options.mblstReqOptions || {}, options || {});

  if (reqOpts.retries)
    result.push('retries='+encodeURIComponent(reqOpts.retries));
  if (reqOpts.retryDelay)
    result.push('retryDelay='+encodeURIComponent(reqOpts.retryDelay));
  if (reqOpts.retryDecay)
    result.push('retryDecay='+encodeURIComponent(reqOpts.retryDecay));
  if (reqOpts.page)
    result.push('page='+encodeURIComponent(reqOpts.page));
  if (reqOpts.count)
    result.push('count='+encodeURIComponent(reqOpts.count));
  if (reqOpts.outputType)
    result.push('mimeType='+encodeURIComponent(reqOpts.outputType));
  if (reqOpts.dataSet)
    result.push('dataSet='+encodeURIComponent(reqOpts.dataSet));
  if (reqOpts.dataRep)
    result.push('dataRep='+encodeURIComponent(reqOpts.dataRep));
  if (reqOpts.view)
    result.push('view='+encodeURIComponent(reqOpts.view));
  if (reqOpts.transport)
    result.push('transport='+encodeURIComponent(reqOpts.transport));
  if (reqOpts.dataSetRsrc)
    result.push('dataSetRsrc='+encodeURIComponent(reqOpts.dataSetRsrc));
  if (reqOpts.auxJSON)
    result.push('auxJSON='+encodeURIComponent(reqOpts.auxJSON));
  if (reqOpts.valueName) {
    result.push(encodeURIComponent(reqOpts.valueName)+'='+encodeURIComponent(value));
  } else if (value){
    result.push('value='+encodeURIComponent(value));
  }
  if(form) result = result.concat(Form.serialize(form));

  if (partner_param_string) {
    result.push(partner_param_string);
  }
  
  return result.join("&");
}

Mblst.partnerAuthSerialize = function() {
  var params = [];
  
  if (partner_session_key)
    params.push(partner_auth_prefix+"_session_key="+encodeURIComponent(partner_session_key));
  if (partner_user)
    params.push(partner_auth_prefix+"_user="+encodeURIComponent(partner_user));
  if (partner_api_key)
    params.push(partner_auth_prefix+"_api_key="+encodeURIComponent(partner_api_key));
  if (partner_in_iframe)
    params.push(partner_auth_prefix+"_in_iframe="+encodeURIComponent(partner_in_iframe));
  if (partner_time)
    params.push(partner_auth_prefix+"_time="+encodeURIComponent(partner_time));
  if (partner_sig)
    params.push(partner_auth_prefix+"="+encodeURIComponent(partner_sig));
  if (partner_auth_token)
    params.push("auth_token="+encodeURIComponent(partner_auth_token));    

  return params;
}

Mblst.paginateRequestSerialize = function(page, count, options, auxQueryParams) {
  options = Object.extend(options || {}, {
    page: page,
    count: count
  });
  var auxparams = (auxQueryParams) ? '&'+auxQueryParams : '';
  return Mblst.requestSerialize.bind(this)(null, null, options)+auxparams;
}

Mblst.responseIsSuccess = function(transport) {
    return transport.status == undefined
        || transport.status == 0
        || (transport.status >= 200 && transport.status < 300);
}

// Positioning functions
Mblst.Position = {}
Mblst.Position.topCenterOnTopCenter = function (referenceElem, containerElem) {
  var refElemOffsetLeft = ((Element.getDimensions(referenceElem).width/2.0) - (Element.getDimensions(containerElem).width/2)) + this.options.refOffsetLeft ;
  return {offsetLeft: refElemOffsetLeft, offsetTop: this.options.refOffsetTop};
}
Mblst.Position.topCenterOnBottomCenter = function (referenceElem, containerElem) {
  var refElemOffsetLeft = ((Element.getDimensions(referenceElem).width/2.0) - (Element.getDimensions(containerElem).width/2)) + this.options.refOffsetLeft ;
  return {offsetLeft: refElemOffsetLeft, offsetTop: referenceElem.offsetHeight + this.options.refOffsetTop};
}

Mblst.Position.topRightOnBottomRight = function (referenceElem, containerElem) {
return {offsetLeft: this.options.refOffsetLeft - Element.getDimensions(containerElem).width + Element.getDimensions(referenceElem).width, offsetTop: referenceElem.offsetHeight};
}

Mblst.Position.topRightOnTopLeft = function (referenceElem, containerElem) {
return {offsetLeft: this.options.refOffsetLeft - Element.getDimensions(containerElem).width, offsetTop: this.options.refOffsetTop};
}

Mblst.Position.topLeftOnTopLeft = function(referenceElem, containerElem) {
  return {offsetLeft: this.options.refOffsetLeft, offsetTop: this.options.refOffsetTop};
}

Mblst.Position.topLeftOnBottomLeft = function(referenceElem, containerElem) {
  return {offsetLeft: this.options.refOffsetLeft, offsetTop: referenceElem.offsetHeight};
}

Mblst.Position.topLeftOnTopRight = function(referenceElem, containerElem){
  return {offsetLeft: Element.getDimensions(referenceElem).width + this.options.refOffsetLeft, offsetTop: this.options.refOffsetTop};
}

Mblst.Position.bottomRightOnBottomRight = function (referenceElem, containerElem) {
return {offsetLeft: this.options.refOffsetLeft - Element.getDimensions(containerElem).width + Element.getDimensions(referenceElem).width, offsetTop: Element.getDimensions(referenceElem).height - Element.getDimensions(containerElem).height + referenceElem.offsetHeight};
}

Mblst.Position.bottomLeftOnBottomLeft = function (referenceElem, containerElem) {
return {offsetLeft: this.options.refOffsetLeft, offsetTop: Element.getDimensions(referenceElem).height - Element.getDimensions(containerElem).height};
}

function StringBuffer() {
  this.__strings__ = new Array;
}

StringBuffer.prototype.append = function (str) {
  this.__strings__.push(str);
};

StringBuffer.prototype.toString = function () {
  return this.__strings__.join("");
};

function redirect( newUrlStr ) {
  location.href = newUrlStr;
  return true;
}

function capitalizeWords( str ) {
  return str.toLowerCase().replace(/\b[a-z]/g, function(c){return c.toUpperCase();});
}

function hashify(stringArray, matches) {
  var lookupTable = new Array( );
  for (var i = 0; i < stringArray.length; i++) {
      lookupTable["a"] = matches("a");
      lookupTable["b"] = matches("b");
      lookupTable["c"] = matches("c");
      lookupTable["d"] = matches("d");
      lookupTable["e"] = matches("e");
      lookupTable["f"] = matches("f");
      lookupTable["g"] = matches("g");
      lookupTable["h"] = matches("h");
      lookupTable["i"] = matches("i");
      lookupTable["j"] = matches("j");
      lookupTable["k"] = matches("k");
      lookupTable["l"] = matches("l");
      lookupTable["m"] = matches("m");
      lookupTable["n"] = matches("n");
      lookupTable["o"] = matches("o");
      lookupTable["p"] = matches("p");
      lookupTable["q"] = matches("q");
      lookupTable["r"] = matches("r");
      lookupTable["s"] = matches("s");
      lookupTable["t"] = matches("t");
      lookupTable["u"] = matches("u");
      lookupTable["v"] = matches("v");
      lookupTable["w"] = matches("w");
      lookupTable["x"] = matches("x");
      lookupTable["y"] = matches("y");
      lookupTable["z"] = matches("z");
  }
  return lookupTable;
}

// Validators //
function valHexColor( hexColor ) {
    if ( (hexColor.length != 6) ||
         (!hexColor.match(/^[0-9A-Fa-f]+$/)) ) {
        alert("All colors must be 6 characters, consisting of the numbers 0-9 and/or the letters A-F.\nExample: 0099C9.");
        return false;
    }
    return true;
}

// Nice mouseover logic
// http://www.quirksmode.org/js/mouseov.html
var W3CDOM = (document.createElement && document.getElementsByTagName);

var mouseOvers = new Array();
var mouseOuts = new Array();

// Add: Event.observe(window, 'load', initMos);
// to pages where we want auto init of mouseovers
function initMos()
{
	if (!W3CDOM) return;
	var nav = document.getElementById('mouseovers');
	var imgs = nav.getElementsByTagName('img');
    manualInitMos(imgs);
}

function manualInitMos( imgs ) {
	for (var i=0;i<imgs.length;i++)
	{
		imgs[i].onmouseover = mouseGoesOver;
		imgs[i].onmouseout = mouseGoesOut;
		var suffix = imgs[i].src.substring(imgs[i].src.lastIndexOf('.'));
		mouseOuts[i] = new Image();
		mouseOuts[i].src = imgs[i].src;
		mouseOvers[i] = new Image();
		mouseOvers[i].src = imgs[i].src.replace(/_off(\.[^.]+)$/, '_on$1');
		imgs[i].number = i;
	}
}

function mouseGoesOver()
{
	this.src = mouseOvers[this.number].src;
}

function mouseGoesOut()
{
	this.src = mouseOuts[this.number].src;
}

var preloadedImgs = new Array();
function preloadImages (img_filenames) {
  for (var i=0;i<img_filenames.length;i++)
  {
    preloadedImgs[i] = new Image();
    preloadedImgs[i].src = img_filenames[i];
  }
}
function preloadImage(img_filename) {
  preloadedImgs[0] = new Image();
  preloadedImgs[0].src = img_filename;
}
function hcf(text1,text2){
  var gcd=1;
  if (text1>text2) {text1=text1+text2; text2=text1-text2; text1=text1-text2;}
  if ((text2==(Math.round(text2/text1))*text1)) {gcd=text1}else {
   for (var i = Math.round(text1/2) ; i > 1; i=i-1) {
    if ((text1==(Math.round(text1/i))*i))
     if ((text2==(Math.round(text2/i))*i)) {gcd=i; i=-1;}
   }
  }
  return gcd;
}

function lcm(t1,t2){
  var cm=1;
  var f=hcf(t1,t2);
  cm=t1*t2/f;
  return cm;
}

/*
 * (c)2006 Dean Edwards/Matthias Miller/John Resig
 * Special thanks to Dan Webb's domready.js Prototype extension
 * and Simon Willison's addLoadEvent
 *
 * For more info, see:
 * http://dean.edwards.name/weblog/2006/06/again/
 * http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
 * http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 * 
 * Thrown together by Jesse Skinner (http://www.thefutureoftheweb.com/)
 *
 *
 * To use: call addDOMLoadEvent one or more times with functions, ie:
 *
 *    function something() {
 *       // do something
 *    }
 *    addDOMLoadEvent(something);
 *
 *    addDOMLoadEvent(function() {
 *        // do other stuff
 *    });
 *
 */
 
function addDOMLoadEvent(func) {
   if (!window.__load_events) {
      var init = function () {
          // quit if this function has already been called
          if (arguments.callee.done) return;
      
          // flag this function so we don't do the same thing twice
          arguments.callee.done = true;
      
          // kill the timer
          if (window.__load_timer) {
              clearInterval(window.__load_timer);
              window.__load_timer = null;
          }
          
          // execute each function in the stack in the order they were added
          for (var i=0;i < window.__load_events.length;i++) {
              window.__load_events[i]();
              window.__load_events[i][0] = null;
          }
          window.__load_events = null;
      };
   
      // for Mozilla/Opera9
      if (document.addEventListener) {
          document.addEventListener("DOMContentLoaded", init, false);
      }
      
      // for Internet Explorer
      /*@cc_on @*/
      /*@if (@_win32)
          document.write("<scr"+"ipt id=__ie_onload defer src=//0><\/scr"+"ipt>");
          var script = document.getElementById("__ie_onload");
          script.onreadystatechange = function() {
              if (this.readyState == "complete") {
                  init(); // call the onload handler
              }
          };
      /*@end @*/
      
      // for Safari
      if (/WebKit/i.test(navigator.userAgent)) { // sniff
          window.__load_timer = setInterval(function() {
              if (/loaded|complete/.test(document.readyState)) {
                  init(); // call the onload handler
              }
          }, 10);
      }
      
      // for other browsers
      window.onload = init;
      
      // create event function stack
      window.__load_events = [];
   }
   
   // add function to event stack
   window.__load_events.push(func);
}

/**
 * We still have a problem with google ads delaying page load.
 * Trying less elegant approach of calling a stack of functions
 * before the closing body tag.
 */
var __tail_events = [];
function addMarkupTailEvent(func) {
   // add function to event stack
  __tail_events.push(func);
}
function markupTail() {
  // execute each function in the stack in the order they were added
  for (var i=0;i < __tail_events.length;i++) {
    __tail_events[i]();
    __tail_events[i][0] = null;
  }
  __tail_events = false;
}


// Similar to $, but also try to extract element from object args.
function $E(arg) {
  if (typeof arg == 'string') {
    return $(arg);
  }
  else if(typeof arg == 'object' && !Element.isElement(arg)) {
    // arg is not a string or DOM element,
    // try to extract element from object
    return $(arg.containerElem || arg.element);
  }
  else if(Element.isElement(arg)) {
    return arg;
  }
  
  return null;
}

var ControlEvent = new Object();
ControlEvent.Event = Class.create();
ControlEvent.Event.prototype = {
  initialize: function(name, source) {
    this.source = source;  // object initiating the event
    this.name = name;
  }  
}

Object.extend(ControlEvent, {
   
  source: function(event) {
    return event.source;
  },
  
  element: function(event) {
    return $E(event.source);
  },

  // build a suitable ControlEvent target from arg
  target: function(arg) {
    if(Class.isClass(arg)) {
      // arg is Class, suitable  
      return arg;
    } else {
      // DOM element will be returned if arg is suitable
      return $E(arg); 
    }
  },

  observers: false,

  notify: function(name, source) {
    if (!ControlEvent.observers) return;
    var event = new ControlEvent.Event(name, source);

    ControlEvent.observers.each( function(o) {
      if(name == o[1]) {
        if(Class.isClass(o[0]) && (source instanceof o[0])) {
          // target is a matching class
          o[2](event);
        }
        else if(ControlEvent.element(event) == o[0]) {
          // target is a matching element
          o[2](event);          
        }
      }
    });
  },

  observe: function(target, name, observer) {
    target = ControlEvent.target(target); //will be Element object or Class   
    if (target) ControlEvent._observeAndCache(target, name, observer);
  },

  stopObserving: function(target, name, observer) {
    target = ControlEvent.target(target); //will be Element object or Class
    var cachedObserver = ControlEvent.observers.find( function(o) {
      return (o[0]==target && o[1]==name && o[2]==observer) ? true : false;
    });
    if (!cachedObserver) return;
    
    // remove reference to target
    cachedObserver[0] = null;
    
    // remove reference to closure
    cachedObserver[2] = Prototype.emptyFunction;
    
    this.observers = this.observers.reject( function(o) { return o==cachedObserver });
  },
  
  unloadCache: function() {
    if (!ControlEvent.observers) return;
    for (var i = 0, length = ControlEvent.observers.length; i < length; i++) {
      ControlEvent.observers[i][0] = null;
      ControlEvent.observers[i][2] = Prototype.emptyFunction;
    }
    ControlEvent.observers = false;
  },
  
  _observeAndCache: function(target, name, observer) {
    if (!this.observers) this.observers = [];
    this.observers.push([target, name, observer]);
  }  
});

/* prevent memory leaks in IE */
if (Prototype.Browser.IE)
  Event.observe(window, 'unload', ControlEvent.unloadCache);
