
function getCaptcha(){
    ajax = new AjaxRequest('POST', '/login.php/captcha', true, 3000);
    ajax.setCallback( saEmailCallback );
    ajax.setParameter('type', 'captcha');
    ajax.setParameter('rand', '652891');
    ajax.send();
}

function saSendEmail(page_title){
    is_modal = true;
    var username = util.readCookie('bake_username');
    ajax = new AjaxRequest('POST', '/login.php/check', true, 3000);
    ajax.setCallback( saEmailCallback );
    ajax.setParameter('type', 'email_login');
    if(username){
       var challenge = calcMD5(username) + '0759bc79b1b0de541a216cafa4fa1d8e46';
       ajax.setParameter('challenge', challenge);
    }
    ajax.send();
}

function saEmailCallback(XmlResp){
	if(!XmlResp) {
		return false;
	}
	else {
        var msgnb = XmlResp.getElementsByTagName("nb")[0].firstChild.nodeValue;
		var msgdesc = XmlResp.getElementsByTagName("desc")[0].firstChild.nodeValue;
		var act = XmlResp.getElementsByTagName("action")[0].firstChild.nodeValue;
        if( act == 'email_login' ) {
            showSAOverlayBubble(1, 'email');
            var loggedin = XmlResp.getElementsByTagName("logged-in")[0].firstChild.nodeValue;
            if(loggedin > 0) {
                var username = XmlResp.getElementsByTagName("username")[0].firstChild.nodeValue;
                var email = XmlResp.getElementsByTagName("email")[0].firstChild.nodeValue;
                if( username && email ) {
                    var input_elem = document.getElementById("sender_name");
                    if(input_elem) {
                        input_elem.value = username + " <"+email+">";
                    }
                }
                else {
                    var input_elem = document.getElementById("sender_name");
                    if(input_elem) {
                    }
                }
            }
            else {
                var input_elem = document.getElementById("sender_name");
                if(input_elem) {
                }
            }
        }
        else if( act == 'sa_email' ) {
            var msg_sent = XmlResp.getElementsByTagName("msg_sent");
            if( msg_sent[0].firstChild && msg_sent[0].firstChild.nodeValue > 0 ) {
                ShowSAMessage('',msgdesc,'#666');
                setTimeout('saBubbleClose();', 1200);
            }
            else {
                var email_error = document.getElementById("sabubble_email_error");
                if(email_error) {
                    email_error.innerHTML = msgdesc;
                    email_error.style.display = '';
                }
                showSAOverlayBubble(1, 'email');
            }
        }
        else if( act == 'captcha'){
                var captcha_encrypt = XmlResp.getElementsByTagName("encrypt")[0].firstChild.nodeValue;
                var captcha_iv = XmlResp.getElementsByTagName("iv")[0].firstChild.nodeValue;
                var captcha_raw_encrypt = XmlResp.getElementsByTagName("raw_encrypt")[0].firstChild.nodeValue;
                var captcha_raw_iv = XmlResp.getElementsByTagName("raw_iv")[0].firstChild.nodeValue;
                if(document.getElementById('captcha_image')){
                   document.getElementById('captcha_image').src = 'http://www.pricegrabber.com/cptch_img.php?s='+captcha_raw_encrypt+'&iv='+captcha_raw_iv;
                }
                if(document.getElementById('captcha_value')){
                   document.getElementById('captcha_value').value = captcha_encrypt;
                }
                if(document.getElementById('captcha_iv')){
                   document.getElementById('captcha_iv').value = captcha_iv;
                }
        }
    }
}


function showSAOverlayBubble(show,type) {
	showSAOverlay(show);

	if(!show && !saBubbleIsClosed()) {
		document.getElementById('saoverlaybubble').style.left = '-1000px';
                if(document.getElementById('captcha_image')){ document.getElementById('captcha_image').src = 'http://i.pgcdn.com/images/spacer.gif'; }
	}
	else if(show){
                      getCaptcha();
           
           if(document.getElementById('sabubblemsg')) {
	      document.getElementById('sabubblemsg').style.display = (type=='msg' ? '' : 'none'); 
           }
           if(document.getElementById('sabubbleemail')) {
              document.getElementById('sabubbleemail').style.display = (type=='email' ? '' : 'none'); 
           }
		var width = DomUtils.getWindowWidth();
		var height = DomUtils.getWindowHeight();
		var scrollX = DomUtils.getWindowScrollX();
		var scrollY = DomUtils.getWindowScrollY();
		if(width==0 || height==0 || typeof window.opera!="undefined") {
			if(typeof window.opera=="undefined" && document.documentElement.clientWidth) {
				width = document.documentElement.clientWidth;
				height = document.documentElement.clientHeight;
			}
			else {
				width = document.body.clientWidth;
				height = document.body.clientHeight;
			}
		}
         
		if(scrollX==0 || scrollY==0) {
			scrollX = document.documentElement.scrollLeft;
			scrollY = document.documentElement.scrollTop;
		}
		var bwidth = DomUtils.getElementWidth(document.getElementById('saoverlaybubble'));
		var bheight = DomUtils.getElementHeight(document.getElementById('saoverlaybubble'));
        
		document.getElementById('saoverlaybubble').style.top = (scrollY + (height/2) - (bheight/2)) + 'px';
		document.getElementById('saoverlaybubble').style.left = (scrollX + (width/2) - (bwidth/2)) + 'px';
        
	}
}


function saBubbleClose() {
    is_modal = false;
    document.getElementById("sabubble_email_error").style.display = "none";
    document.getElementById('sabubblemsgimage').style.display = 'none';
	showSAOverlayBubble(0,'');
}

function saBubbleIsClosed() {
	return parseInt(document.getElementById('saoverlaybubble').style.left)>0 ? false : true;
}


function showSAOverlay(show) {
    var isIE6 = DomUtils.browser.isIE6();
	if(!show) {
		document.getElementById('login_overlay').style.display = 'none';
        if(isIE6){ 
            document.getElementById('shim').style.display = 'none';
        }
	}
	else {
        var scrollX = DomUtils.getWindowScrollX();
        var scrollY = DomUtils.getWindowScrollY();
        var height = DomUtils.getElementHeight(document.body);
        var width = DomUtils.getElementWidth(document.body);
        if(height < screen.height) {
            height = (screen.height * .8);
        }

        //THIS IS FOR OPERA ONLY 
        //opacity doesn't exist, so we just don't show the overlay
        if (typeof window.opera == "undefined") {
            document.getElementById('login_overlay').style.width = width + 'px';
            document.getElementById('login_overlay').style.height = height + 'px';
            document.getElementById('login_overlay').style.display = 'block';
        }  

        //ADD IFRAME SHIM - for IE6 so select box doesn't show through the popup
        //instead of adding the code for the shim to each page, and potentially missing one, create it manually and append to the body
        if(isIE6){ 
            if(document.getElementById('shim') == null){				
                var shim = document.createElement('iframe');
                shim.setAttribute('src', 'javascript:false;');
                shim.setAttribute('scrolling', 'no');
                shim.setAttribute('frameborder', '0');
                shim.setAttribute('id', 'shim');

                //set needed styles for the shim
                shim.style.zIndex = 99;
                shim.style.display = "none";
                shim.style.position = "absolute";
                shim.style.backgroundColor = "transparent";
                shim.style.top = "0px";
                shim.style.left = "0px";
                document.body.appendChild(shim);
            }  
            document.getElementById('shim').style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'; 
            document.getElementById('shim').style.width = width + 'px';
            document.getElementById('shim').style.height = height + 'px';
            document.getElementById('shim').style.display = 'block';
        }
    }
}


function submitSAOverlay(id_type, p_id, url) {
    var errors = 0;
    var error_msg = '';
    if( document.sa.sender_name.value=='' ) {
        errors = 1;
        error_msg = 'Please supply a name.';
    }
    else if( !isValidEmail(document.sa.friend_e.value) ) {
        errors = 1;
        error_msg = 'Invalid email address.';
    }
    if( errors == 1 ) {
        document.getElementById("sabubble_email_error").innerHTML = error_msg;
    }
    else if( errors == 0 ) {
        saBubbleClose();
        ShowSAMessage('<img src="/images/small_wait.gif" width=16 height=16>','Please hold while we process your request ...','#666666');
        ajax = new AjaxRequest('GET', '/get_prod_email.php', true, 3000);
        ajax.setParameter('sender_name', document.sa.sender_name.value);
        ajax.setParameter('friend_email', document.sa.friend_e.value);
        ajax.setParameter('captcha_value', document.sa.captcha_value.value);
        ajax.setParameter('captcha_response', document.sa.captcha_response.value);
        ajax.setParameter('captcha_iv', document.sa.captcha_iv.value);
        ajax.setParameter('email_msg', document.sa.email_msg.value);
        ajax.setParameter('url', url);
        ajax.setParameter('type', 'sa_email');
        ajax.setParameter('id_type',id_type);
        ajax.setParameter('p_id',p_id);
       ajax.setCallback( saEmailCallback );
        ajax.send();
    }
}


function ShowSAMessage(img,txt,color) {
	document.getElementById('sabubblemsgbody').style.color = color;
	document.getElementById('sabubblemsgbody').innerHTML = txt;
	if(img == '') {
		document.getElementById('sabubblemsgimage').style.display = 'none';
	}
	else {
		document.getElementById('sabubblemsgimage').innerHTML = img;
		document.getElementById('sabubblemsgimage').style.display = '';
	}
	showSAOverlayBubble(1,'msg');
}

function isValidEmail(emailStr) {
    var emailPat=/^(.+)@(.+)$/;
    var specialChars="\\(\\)<>@,;:\\\\\\\"\\.\\[\\]";
    var validChars="\[^\\s" + specialChars + "\]";
    var quotedUser="(\"[^\"]*\")";
    var ipDomainPat=/^\[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\]$/;
    var atom=validChars + '+';
    var word="(" + atom + "|" + quotedUser + ")";
    var userPat=new RegExp("^" + word + "(\\." + word + ")*$");
    var domainPat=new RegExp("^" + atom + "(\\." + atom +")*$");
    var matchArray=emailStr.match(emailPat);
    if (matchArray==null) {
        return false;
    }
    var user=matchArray[1];
    var domain=matchArray[2];
    if (user.match(userPat)==null) {
        return false;
    }
    var IPArray=domain.match(ipDomainPat);
    if (IPArray!=null) {
        for (var i=1;i<=4;i++) {
            if (IPArray[i]>255) {
                return false;
            }
        }
        return true;
    }
    var domainArray=domain.match(domainPat);
    if (domainArray==null) {
        return false;
    }
    var atomPat=new RegExp(atom,"g");
    var domArr=domain.match(atomPat);
    var len=domArr.length;
    if (domArr[domArr.length-1].length<2 || domArr[domArr.length-1].length>4) {
        return false;
    }
    if (len<2) {
        return false;
    }
    return true;
}

// set this global var if on a secure page for ajax calls
var PG_use_https;
/**
 *  Add user favorite
 *   ajax call to add a user favorite (merchant or brand)
 */ 
function addUserFavorite(user_id, preference_id_type, preference_id, element_id, element_class, element_content, callback)
{

	// if no user id is given, then load the login overlay bubble
	if (!user_id) {
		user_id = util.readCookie('bake_userid');

		// this hack is for IE6 only.... we saved the userid in document.login cuz it cant read cookies well
		if (!user_id && document.login && document.login.loginSuccessUserid) {
			user_id = document.login.loginSuccessUserid;
		}
		// end IE6 hack
		
		if (!user_id) {
			if (document.login) {
				// set login success callback
				// (try adding to favorites again, once login is successful)
				document.login.loginSuccessCallback = function () { addUserFavorite('', preference_id_type, preference_id, element_id, element_class, element_content, callback); };
			}

                        callbackfuncafterlogin = "addUserFavorite('"+(user_id?user_id:"")+"','"+preference_id_type+"','"+preference_id+"','"+element_id+"','"+element_class+"','"+(element_content?element_content:"")+"','"+(callback?callback:"")+"');";
                        //Tracking from where we require user login/registration
                        if (preference_id_type == 'vendor') refer_page = 'Add Brand to Favs';
                        if (preference_id_type == 'merchant') refer_page = 'Add Merchant to Favs';
                        
                        iframe_login('login_registration', callbackfuncafterlogin, refer_page);
			return;
		}
	}

	if (!user_id) {
		return false;
	}
	
//	console.log('addUserFavorite ' + user_id + ' - ' + preference_id_type + ' - ' + preference_id);
	var ajax = new AjaxRequest('GET', '/ajax.php', true, 3000);
	ajax.setParameter('action', 'addUserFavorite');
	ajax.setParameter('user_id', user_id);
	ajax.setParameter('preference_id_type', preference_id_type);
	ajax.setParameter('preference_id', preference_id);
	ajax.setParameter('element_id', element_id);
	ajax.setParameter('element_class', element_class);
	if (element_content) {
		ajax.setParameter('element_content', element_content);
	}
	
	// lame secure hack
	if (PG_use_https) {
		ajax.setParameter('secure', 1);
	}

	if (callback) {
		ajax.setCallback(callback);
	}
	else {
	    ajax.setCallback(addUserFavoriteResponse);
	}
	ajax.send();
}

/**
 * default ajax response after adding user favorite
 */ 
function addUserFavoriteResponse(xml)
{
	// get json
    var data = eval('(' + xml.getElementsByTagName('json')[0].firstChild.nodeValue + ')');

	if (!data['success']) {
		return false;
	}    
	
    if (data['element_id']) {
	    em = document.getElementById(data['element_id']);
	    if (data['element_class']) {
		    em.className = data['element_class'];
		}
	    em.innerHTML = data['message'];
	}

	// update browse favorite stores/brands link if its on the page
	// todo: this should be moved elsewhere
	var store_link = document.getElementById('browse_favorite_stores_link');
	if (store_link && data['preference_id_type'] == "merchant") {
		// update count
		stores_link_count_em = document.getElementById('favorite_stores_count');
		var count = parseInt(stores_link_count_em.innerHTML);
		count++;
		stores_link_count_em.innerHTML = count;
		// update href link
		store_link.href += '/retid[]=' + data['preference_id'];
	}
	var brands_link = document.getElementById('browse_favorite_brands_link');
	if (brands_link && data['preference_id_type'] == "vendor") {
		// update count
		brands_link_count_em = document.getElementById('favorite_brands_count');
		var count = parseInt(brands_link_count_em.innerHTML);
		count++;
		brands_link_count_em.innerHTML = count;
		// update href link
		brands_link.href += '/vendorIds[]=' + data['preference_id'];
	}
        
}

/**
 *	delete a user preference/favorite
 */ 
function deleteUserPreference(user_preference_id, preference_id_type, element_id, callback)
{
	user_id = util.readCookie('bake_userid');

	if (!user_id) {
		LoginOverlay_login();
		return;
	}

	var ajax = new AjaxRequest('GET', '/ajax.php', true, 3000);
	ajax.setParameter('action', 'deleteUserFavorite');
	ajax.setParameter('user_id', user_id);
	ajax.setParameter('secure', 1);
	ajax.setParameter('user_preference_id', user_preference_id);
	ajax.setParameter('preference_id_type', preference_id_type);
	ajax.setParameter('element_id', element_id);
    ajax.setCallback(callback);
	ajax.send();
}

// functions related to login popup to load the external JS files required to handle it
function LoginOverlay_login() {
        if(!loginLoaded) {
                var s2 = document.createElement('script'); s2.src = login_pop_script;
                document.body.appendChild(s2);
        }
        if(!ajaxLoaded) {
                var s1 = document.createElement('script'); s1.src = login_pop_ajax;
                document.body.appendChild(s1);
        }
        LoginOverlay_waitForLoad();
}
function LoginOverlay_waitForLoad() {
        if(!loginLoaded) {
                if (window.login_js) {
                        loginLoaded = 1;
                        ShowLoginMessage('<img src="'+login_pop_wait+'" width=16 height=16>',login_pop_msg,'#666666');}
                else {
                        setTimeout('LoginOverlay_waitForLoad("");',200);
                        return 0;
                }
        }
        if(!ajaxLoaded) {
                if(window.ajax) {
                        ajaxLoaded = 1;
                }
                else {
                        setTimeout('LoginOverlay_waitForLoad("");',200);
                        return 0;
                }
        }
        if (ajaxLoaded && loginLoaded) {
                showLoginBubble();
        }
}
var ajaxLoaded = 0;
var ulistsLoaded = 0;

function addProdToList_callback(HtmlResp) {
   var my_overlay = document.createElement('div');
   my_overlay.setAttribute('id', 'overlay');
   var my_overlaybubble = document.createElement('div');
   my_overlaybubble.setAttribute('id', 'overlaybubble');
   my_overlaybubble.innerHTML = HtmlResp;
   document.body.appendChild(my_overlay);
   document.body.appendChild(my_overlaybubble);
   addProdToListPost();

}

function addProdToList() {
  if(!document.getElementById('overlaybubble')){
     ajax = new AjaxRequest('POST', '/ulists.php/bubbleContent', true, 3000);
     if(ulist_masterid>0){
        ajax.setParameter('masterid', ulist_masterid);
     }
     if(ulist_isbn>0){
        ajax.setParameter('isbn', ulist_isbn);
     }
     ajax.setResponseType('html');
     ajax.setCallback( addProdToList_callback );
     ajax.send();
   }else{
      addProdToListPost();
   }
}

function addProdToListPost() {
  is_modal = true;
  if(!ulistsLoaded) {
	var s2 = document.createElement('script'); s2.src = 'http://www.pricegrabber.com/js/ulists_from_prodpage.js.php?prodpage_lists_only=1';
	document.body.appendChild(s2);
  }
  if(!ajaxLoaded) {
	var s1 = document.createElement('script'); s1.src = 'http://ah.pricegrabber.com/js/classes/ajaxrequest.js';
	document.body.appendChild(s1);
  }
  waitForLoad();
}
function waitForLoad() {
	if(!ulistsLoaded) {
		if(window.ulists_js) { ulistsLoaded = 1; ShowListMessage('<img src="http://i.pgcdn.com/images/small_wait.gif" width=16 height=16>','Please hold while we process your request ...','#666666');}
		else { setTimeout('waitForLoad("");',200); return 0; }
	}
	if(!ajaxLoaded) {
		if(window.ajax) { ajaxLoaded = 1; }
		else { setTimeout('waitForLoad("");',200); return 0; }
	}
	if(ajaxLoaded && ulistsLoaded) { addToList(); }
}
function hideQuality(quality) {
	var folder_icon = document.getElementById('folder_icon_' + quality);
	if( folder_icon ){
		folder_icon.src = 'http://i.pgcdn.com/images/getprod2/folder_close_ico.gif';
	}

    for( var i=0; i<=offer_dom_ids[quality].length; i++) {
        YAHOO.util.Dom.setStyle(offer_dom_ids[quality][i], 'display', 'none'); 
    }
    var q_tog_elem = document.getElementById('quality_toggle_'+quality);

    if( q_tog_elem) {
        q_tog_elem.setAttribute('onClick', 'function() { showQuality("'+quality+'"); }');
        q_tog_elem.onclick = function() { showQuality(quality); };
    }
    omnitureQualityToggle(quality,"close");
}

function showQuality(quality) {
	
	var folder_icon = document.getElementById('folder_icon_' + quality);
	if( folder_icon ){
		folder_icon.src = 'http://i.pgcdn.com/images/getprod2/folder_open_ico.gif';
	}

    for( var i=0; i<=offer_dom_ids[quality].length; i++) {
        YAHOO.util.Dom.setStyle(offer_dom_ids[quality][i], 'display', '');
    }
    var q_tog_elem = document.getElementById('quality_toggle_'+quality);
    if( q_tog_elem) {
        q_tog_elem.setAttribute('onClick', 'function() { hideQuality("'+quality+'"); }');
        q_tog_elem.onclick = function() { hideQuality(quality); };
    }
    omnitureQualityToggle(quality,"open");
}

var cleared = 0;
function clearZipEntry(zip_input) {
    if( cleared == 0 ) {
    document.forms.zipEntryForm.form_zip_code.value = '';
    cleared = 1;
    }
}

function toggleZipEntryForm() {
        var zip_entry_display = YAHOO.util.Dom.getStyle('nozipContent', 'display');
        if( !zip_entry_display ) {
        zip_entry_display = 'none';
    }
        if( zip_entry_display == 'none' ) {
        no_zip_style = '';
    }
        else {
        no_zip_style = 'none';
    }
        YAHOO.util.Dom.setStyle('nozipContent', 'display', no_zip_style);
}

/**
 *
 * $Id: util.js 63843 2009-02-09 23:15:05Z kchiu $
 * $Author: kchiu $
 * $Revision: 63843 $
 * $Name$
 * $Date: 2009-02-09 15:15:05 -0800 (Mon, 09 Feb 2009) $
 *
 * @package     PriceGrabber
 * @category    JavaScript
 *
 * @author      Philip Snyder <philip@pricegrabber.com>
 * @copyright   Copyright &copy; 2006 2007, Philip Snyder, PriceGrabber.com
 * @version     $Revision: 63843 $
 *
 * @todo        Finish documentation.
 */

/**
 * Checks the object to see if it implements a certain function.
 *
 * @access public
 * @since  v1.1
 * @param  string   funcName   Name of function to check for
 * @return boolean
 */



/**
 * Generates an xml string representation of the object.
 *
 * @access public
 * @since  v1.1
 * @param  string   tagname   Tag name to use for the object
 * @return string
 */
/*
Object.prototype.simpleXmlify = function(tagname) {
    var xml = "<"+tagname;
    for (var prop in this) {
        if (!(this[prop] instanceof Function)) {
            xml += " "+prop+"=\""+this[prop]+"\"";
        }
    }
    xml += "/>";
    return xml;
}
*/


/**
 * Finds the index of a value in the array or returns false if not found.
 *
 * @acccess public
 * @since   v1.1
 * @param   mixed val     Value to search array for
 * @return  int | false
 */
Array.prototype.inArray = function(val) {
    for (var i=0; i<this.length; i++) {
        if (this[i] == val) return i;
    }
    return false;
}
// Remove object helper methods from array
Array.prototype.simpleXmlify   = null;
// Remove object helper methods from Error
Error.prototype.simpleXmlify   = null;






/**
 * Generalize namespace for utility functions
 *
 * @access public
 * @since  v1.1
 */
var util = new Object;






/**
 * Manages image cacheing
 *
 * @todo   Confirm this image cacheing scheme works in IE & FF via fiddler
 *
 * @access public
 * @since  v1.1
 */
util.resourceManager = {

    cache: new Array,

    index: new Array,

    /**
     * Handles cacheing and returns the image src of a url.
     * 
     * @access public
     * @since  v1.1
     * @param  string   url
     * @return Image.src
     */ 
    get: function(url) {
        var ptr = util.resourceManager.index.inArray(url);
        if (ptr === false) {
            ptr                                 = util.resourceManager.index.length;
            util.resourceManager.index[ptr]     = url;
            util.resourceManager.cache[ptr]     = new Image;
            util.resourceManager.cache[ptr].src = url;
        }
        return util.resourceManager.cache[ptr].src;
    }
    
};



/**
 * This function returns the name of a given function.
 *
 * It does this by converting the function to a string,
 * then using a regular expression to extract the
 * function name from the resulting code.
 *
 * @access public
 * @since  v1.1
 * @param  Function  f
 * @return string
 */
function funcname(f) {
    var matches = f.toString().match(/function (\w*)/);
    if ((matches == null) || (matches.length == 0)) return "anonymous";
    if (matches.length == 2) return matches[1];
    else return matches.join(',');
}

/**
 * This function returns a string that contains a "stack trace".
 *
 * @access public
 * @since  v1.1
 * @return string
 */
function stacktrace() {
    var s = "";  // This is the string we'll return.
    // Loop through the stack of functions, using the caller property of
    // one arguments object to refer to the next arguments object on the
    // stack.
    for (var a=arguments.caller; a!=null; a=a.caller) {
        // Add the name of the current function to the return value.
        s += funcname(a.callee) + "\n";
        // Because of a bug in Navigator 4.0, we need this line to break.
        // a.caller will equal a rather than null when we reach the end 
        // of the stack. The following line works around this.
        if (a.caller == a) break;
    }
    return s;
}















/***** BEGIN MESSAGE QUEUE *******/




util.MessageQueue = function(type) {
    this.queued = new Array;
    this.containerId = 'message';
    this.type = type || util.MessageQueue.types.ALL;
    this.queueId = util.MessageQueue.queues.length;
    this.waitingForResponse = false;
    util.MessageQueue.queues[this.queueId] = this;
    return this;
};

util.MessageQueue.settings = {
    timer:     null,
    interval:  5, // in seconds
    immediate: true
};

util.MessageQueue.types  = { NONE:0, ERROR:1, DEBUG:2, USER:4, ALL:7 };
util.MessageQueue.queues = [];


util.MessageQueue.attach = function(queue) {
    if (!util.MessageQueue.queues.inArray(queue)) {
        util.MessageQueue.queues.push(queue);
    }
    return true;
}

util.MessageQueue.detach = function(queue) {
    var retain = [];
    var found  = false;
    while (util.MessageQueue.queues.length) {
        var queuePop = util.MessageQueue.queues.pop();
        if (queue !== queuePop) retain.push(queuePop);
        else                    found = queuePop;
    }
    util.MessageQueue.queues = retain;
    return found;
}


util.MessageQueue.prototype.add = function(obj, test) {
    //alert('util.MessageQueue.add called: '+obj.title+' -> '+obj.content);
    test = test === false ? false : true;
    test = true;
    if ((test && this.isMessage(obj)) || !test) {
        //alert(obj);
        this.queued.push(obj);
        //alert(this.queued.toString());
    }
    if (util.MessageQueue.settings.immediate) this.renderNext();
}

util.MessageQueue.prototype.isMessage = function(obj) {
    return obj.implementsProp('title') && obj.implementsProp('content') && obj.implementsProp('type');
} 

util.MessageQueue.prototype.render = function(type) {
    if (this.queued.length > 0) {
        var body = document.getElementsByTagName('body')[0];
        if (body) {
            var types = util.MessageQueue.types;
            type = type <= types.ALL ? type : types.ALL;
            var messages = new Array;
            if (type == types.ALL) {
                messages = this.queued;
                this.queued = new Array;
            } else {
                var retain = new Array;
                for (var i=0; i<this.queued.length; i++) {
                    var msg = this.queued[i];
                    if (msg.type == type) {
                        messages.push(msg);
                    } else {
                        retain.push(msg);
                    }
                }
                this.queued = retain;
            }
            if (!this.container) {
                this.container = document.createElement('div');
                this.container.className = 'messageContainer';
                body.appendChild(this.container);
            }
            for (var i=0; i<messages.length; i++) {
                var mesg = messages[i];
                mesg.displayHandler();
            }
        }
    }
}









util.MessageQueue.prototype.renderNext = function(force) {
    force = force || false;
    if (force) {
        this.waitingForResponse = false;
        clearInterval(this.renderNextTimeout);
        this.renderNextTimeout = null;
    }
    if (this.queued.length > 0) {
        if (!this.waitingForResponse) {
            //Assert.warn('util.MessageQueue.renderNext:displaying from queue '+this.queueId+', queue length = '+this.queued.length);
            var body = document.getElementsByTagName('body')[0];
            if (!body) throw new Error('Unable to find body element');
            var message = null;
            var queueHead = new Array;
            do {
                message = this.queued.shift();
                //Assert.warn('util.MessageQueue.renderNext:shift message type = '+message.type);
                if (message.type != this.type && this.type != util.MessageQueue.types.ALL) {
                    //Assert.warn('util.MessageQueue.renderNext:not all queue, not our message type');
                    queueHead.push(message);
                    message = null;
                }
            } while (!message && this.queued.length > 0);
            while (queueHead.length > 0) {
                this.queued.unshift(queueHead.shift());
            }
            if (message) {
                this.waitingForResponse = true;
                message.displayHandler();
            }
        } else if (!this.renderNextTimer) {
            //Assert.warn('util.MessageQueue.renderNext:setting renderNext timer -- '+this.queued.length+' messages remain');
            this.renderNextTimer = setInterval('util.MessageQueue.queues['+this.queueId+'].renderNext()', 1000);
        }
    }
}




window.messageQueue = new util.MessageQueue(util.MessageQueue.types.ALL);


/***** END MESSAGE QUEUE *******/

















/***** COOKIE MANAGEMENT *****/

/**
 * Creates a cookie via javascript
 *
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @param  string    value
 * @param  int       ttl      Time to live, in seconds - defaults to session [optional]
 * @param  string    path     [optional]
 * @param  string    domain   [optional]
 * @param  boolean   secure   [optional]
 * @return void
 */
util.createCookie = function createCookie(name,value) {
    // Support optional arguments
    var argv    = arguments;
    var argc    = arguments.length;
    var ttl     = (argc > 2) ? argv[2]*1000 : null;
    var path    = (argc > 3) ? argv[3]      : null;
    var domain  = (argc > 4) ? argv[4]      : null;
    var secure  = (argc > 5) ? argv[5]      : false;
    // If we have a ttl to work with, calculate the expires
    if (ttl) {
        var expires = new Date();
        expires.setTime(expires.getTime()+ttl);
    }
    // Create the actual cookie
    document.cookie = name + "=" + escape (value) + 
                      ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + 
                      ((path    == null) ? "" : ("; path=" + path)) + 
                      ((domain  == null) ? "" : ("; domain=" + domain)) + 
                      ((secure  == true) ? "; secure" : "");
}

/**
 * Returns the value of a cookie
 * 
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @return string
 */
util.readCookie = function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return unescape(c.substring(nameEQ.length,c.length));
    }
    return null;
}

/**
 * Removes a cookie
 *
 * @since  1.1.2.8
 * @access public
 * @param  string    name
 * @return void
 */
util.eraseCookie = function eraseCookie(name) {
    util.createCookie(name,"",-1);
}


/**
 * js/classes/browser.js
 *
 *
 *
 * @package     PriceGrabber
 * @subpackage  Utils
 * @category    JavaScript
 *
 * @author      Philip Snyder <philip@pricegrabber.com>
 * @copyright   Copyright &copy; 2005, Philip Snyder, PriceGrabber.com
 * @version     v0.0.1a
 *
 */







function Browser() { }

Browser.prototype             = Object;
Browser.prototype.constructor = Browser;
Browser.superclass            = Object.constructor;


Browser.regexes = {
    overflowX: new Array(/Firefox\/1\.0/),
    overflowY: new Array(/Firefox\/1\.0/)
};

Browser.supportsOverflowX = function() {
    for (var i=0; i<Browser.regexes.overflowX.length; i++) {
        var regex = Browser.regexes.overflowX[i];
        if (navigator.userAgent.match(regex)) {
            return false;
        }
    }
    return true;
}

Browser.supportsOverflowY = function() {
    for (var i=0; i<Browser.regexes.overflowY.length; i++) {
        var regex = Browser.regexes.overflowY[i];
        if (navigator.userAgent.match(regex)) {
            return false;
        }
    }
    return true;
}

Browser.supportsDom = function() {
    return (document.getElementById) ? true : false;
}

Browser.isIE = function() {
    return (document.all && navigator.appName.indexOf('Microsoft Internet Explorer') > -1) ? true : false;
}

Browser.isSafari = function() {
    return (navigator.userAgent.toLowerCase().indexOf('safari') > -1) ? true : false;
}
/**
 * $Id: baseobject.js 64367 2009-02-20 01:56:36Z kchiu $
 * $Author: kchiu $
 * $Revision: 64367 $
 * $Name$
 * $Date: 2009-02-19 17:56:36 -0800 (Thu, 19 Feb 2009) $
 *
 * @jsRequire interfaces.Interface
 *
 * BaseObject is a starter object for any JavaScript object
 * created. Once an object adds the BaseObject to its prototype
 * chain, it gains the functionality of all methods and variables
 * defined in this object, making BaseObject the perfect place
 * to incorporate other JavaScript libraries such as the Interface
 * library.
 *
 * @version    $Revision: 64367 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 */



/**
 * BaseObject Constructor / Definition
 *
 * @access public
 * @since  v1.1
 * @param  string      id
 * @return BaseObject
 */
function BaseObject(id) {
    // Method definitions
    this.getProperty = BaseObject_GetProperty;
    this.setProperty = BaseObject_SetProperty;
    this.getId       = BaseObject_GetId;
    this.setId       = BaseObject_SetId;
    // Member definitions
    this.properties = [];
    // Initialization
    this.setId(id);
    return this;
}

// Setup BaseObject prototype chain
BaseObject.prototype             = new Object;
BaseObject.prototype.constructor = BaseObject;
BaseObject.superclass            = Object.prototype;



/**
 * Returns the value of a property.
 *
 * @access public
 * @since  v1.1
 * @return mixed
 */
function BaseObject_GetProperty(name) {
    if (this.properties[name]) return this.properties[name];
    else                       return null;
}

/**
 * Sets the value of a property.
 *
 * @access public
 * @since  v1.1
 * @param  string  name
 * @param  mixed   value
 * return  boolean
 */
function BaseObject_SetProperty(name, value) {
    this.properties[name] = value;
    return true;
}

/**
 * Returns the id of the object.
 *
 * @access public
 * @since  v1.1
 * @return string
 */
function BaseObject_GetId() {
    return this.getProperty('id');
}

/**
 * Sets the id of the object.
 *
 * @access public
 * @since  v1.1
 * @param  string   id
 * @return boolean
 */
function BaseObject_SetId(id) {
    return this.setProperty('id', id);
}


/**
 * Extend the BaseObject class with any known included
 * functionality (for example Interface.implement)
 *
 * The reverse of this test can be found in interfaces.Interface
 * so that either file can be included first and the
 * functionality is still in place.
 */
if (typeof(Interface_Implement) == 'function') BaseObject.prototype.implement = Interface_Implement;
/**
 * $Id: interface.js 64367 2009-02-20 01:56:36Z kchiu $
 * $Author: kchiu $
 * $Revision: 64367 $
 * $Name$
 * $Date: 2009-02-19 17:56:36 -0800 (Thu, 19 Feb 2009) $
 *
 *
 *
 * This Interface JavaScript library serves to implement
 * a sort-of hacked interface extension to the standard
 * JavaScript language.
 *
 * The Interface function is never intended to be
 * invoked directly but rather in a prototype chain.
 *
 * Documentation will be available at:
 *
 * http://wiki.pricegrabber.com/moin.cgi/PhilipSnyder
 *
 *
 *
 * @version    $Revision: 64367 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 */

/**
 * Interface Constructor / Definition
 *
 * This is NOT intended to be instantiated directly. See
 * documentation for a complete explanation.
 *
 * @access public
 * @since  v1.1
 * @return Interface
 */
function Interface() {
    this.implement = Interface_Implement;
    return this;
}

/**
 * Confirms complete implementation of passed in Interface function reference.
 *
 * This method checks the object in question and validates that it has
 * either implemented or borrowed every method & variable defined in
 * the interfaces it claims to implement.
 *
 * @access public
 * @since  v1.1
 * @return void
 */
function Interface_Implement(ifaceRef) {
    var tmpObj  = new ifaceRef();
    var objName = (this.constructor+'').substr(('function ').length, (this.constructor+'').indexOf('(') - 'function '.length);
    for (var prop in tmpObj) {
        var typeCheck = false;
        var isFunc    = false;
        eval('isFunc    = (typeof(tmpObj.'+prop+') == "function");');
        eval('typeCheck = (typeof(this.'+prop+')   == typeof(tmpObj.'+prop+'));');
        if (prop != 'implement' && !typeCheck) {
            if (isFunc) throw new Error(objName+'.'+prop+"() not implemented!");
            else        throw new Error(objName+'.'+prop+" is either a wrong type or missing.");
        }
    }
}

/**
 * Attach the implement() method to our BaseObject as
 * well, and all classes extending BaseObjects will have
 * it available to them (and all at the cost of 1
 * function loaded into memory).
 */
if (typeof(BaseObject) == 'function') BaseObject.prototype.implement = Interface_Implement;
/**
 * $Id: anchoredinterface.js 64367 2009-02-20 01:56:36Z kchiu $
 * $Author: kchiu $
 * $Revision: 64367 $
 * $Name$
 * $Date: 2009-02-19 17:56:36 -0800 (Thu, 19 Feb 2009) $
 *
 * @jsRequire DomUtils
 * @jsRequire interfaces.Interface
 *
 *
 * @version    $Revision: 64367 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 */

/**
 * AnchoredInterface Constructor / Definition
 *
 * This interface is built on top of the Interface object
 * and is NOT intended to be instantiated directly. See
 * documentation on interfaces.Interface for a complete
 * explanation.
 *
 * @access public
 * @since  v1.1
 * @return AnchoredInterface
 */
function AnchoredInterface() {
    this.elemId            = null;
    this.anchorId          = null;
    this.disableScroll     = false;
    this.getAnchorX        = AnchoredInterface_GetAnchorX;
    this.getAnchorY        = AnchoredInterface_GetAnchorY;
    this.getAnchorZ        = AnchoredInterface_GetAnchorZ;
    this.getAnchorWidth    = AnchoredInterface_GetAnchorWidth;
    this.getAnchorHeight   = AnchoredInterface_GetAnchorHeight;
    this.getElemWidth      = AnchoredInterface_GetElemWidth;
    this.getElemHeight     = AnchoredInterface_GetElemHeight;
    this.getAnchorPosition = AnchoredInterface_GetAnchorPosition;
    this.setAnchor         = AnchoredInterface_SetAnchor;
    this.alignElement      = AnchoredInterface_AlignElement;
}

// Setup AnchoredInterface prototype chain
AnchoredInterface.prototype             = new Interface;
AnchoredInterface.prototype.constructor = AnchoredInterface;
AnchoredInterface.superclass            = Interface.prototype;

/**
 * Constants defining anchor points around the element.
 *
 * !DO NOT MODIFY THIS!
 *
 * @access public
 * @since  v1.1
 * @var    AnchoredInterface.ALIGN   struct
 */
AnchoredInterface.ALIGN = { RIGHT_TOP: 1, RIGHT_BOTTOM: 2, LEFT_BOTTOM: 3, LEFT_TOP: 4 };

/**
 * Returns the anchor element's X coordinate.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorX() {
    return DomUtils.getElementLeft(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's Y coordinate.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorY() {
    return DomUtils.getElementTop(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's Z index.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorZ() {
    return DomUtils.getZIndex(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's width.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorWidth() {
    //window.messageQueue.add( new Message(funcname(this), 'getting anchor width') );
    return DomUtils.getElementWidth(document.getElementById(this.anchorId));
}

/**
 * Returns the anchor element's height.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorHeight() {
    return DomUtils.getElementHeight(document.getElementById(this.anchorId));
}

/**
 * Returns the element's width.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetElemWidth() {
    //window.messageQueue.add( new Message(funcname(this), 'getting element width') );
    return DomUtils.getElementWidth(document.getElementById(this.elemId));
}

/**
 * Returns the element's height.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetElemHeight() {
    return DomUtils.getElementHeight(document.getElementById(this.elemId));
}

/**
 * Calculates the best alignment position for the element based on window size and position of the anchor element.
 *
 * Returns a value from the AnchoredInterface.ALIGN struct.
 *
 * @access public
 * @since  v1.1
 * @return integer
 */
function AnchoredInterface_GetAnchorPosition() {
    //window.messageQueue.add( new Message('AnchoredInterface_GetAnchorPosition', 'called') );
    var pos    = false;
    var elem   = document.getElementById(this.elemId);
    if (!elem)   throw new Error('Unable to find element: '+this.elemId);
    var anchor = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);

    //window.messageQueue.add( new Message(funcname(this), 'starting getElementWidth') );
    var wWidth  = parseInt(DomUtils.getWindowWidth(window));
    var wHeight = parseInt(DomUtils.getWindowHeight(window));
    var scrollX = parseInt(DomUtils.getWindowScrollX(window));
    var scrollY = parseInt(DomUtils.getWindowScrollY(window));
    var aWidth  = parseInt(DomUtils.getElementWidth(anchor));
    var aHeight = parseInt(DomUtils.getElementHeight(anchor));
    var aX      = parseInt(DomUtils.getElementLeft(anchor));
    var aY      = parseInt(DomUtils.getElementTop(anchor));
    var aWidth  = parseInt(DomUtils.getElementWidth(anchor));
    var aHeight = parseInt(DomUtils.getElementHeight(anchor));
    var eWidth  = parseInt(DomUtils.getElementWidth(elem));
    var eHeight = parseInt(DomUtils.getElementHeight(elem));
    //window.messageQueue.add( new Message(funcname(this), 'passed getElementWidth') );


    if (this.disableScroll) {
    
        /**
         * DO NOT TOUCH. I'M SERIOUS. This is difficult enough to figure out the first time. ;)
         */
        var enoughRoomOnTop    = (aY - scrollY > eHeight ? true : false);
        var enoughRoomOnBottom = (wHeight + scrollY - aHeight - aY > eHeight ? true : false);
        var enoughRoomOnLeft   = (aX - scrollX + aWidth > eWidth ? true : false);
        var enoughRoomOnRight  = (wWidth - aX + scrollX > eWidth ? true : false);
    
        /**
         * Preferred position order:
         *
         *   left top
         *   left bottom
         *   right bottom
         *   right top
         *
         * Please note that this is the position of the anchor in
         * relation to the main element, NOT the other way around
         */
//alert('enoughRoomOnTop= '+enoughRoomOnTop+'\nenoughRoomOnBottom= '+enoughRoomOnBottom+'\nenoughRoomOnLeft= '+enoughRoomOnLeft+'\nenoughRoomOnRight= '+enoughRoomOnRight);
        // Left Top
        if (enoughRoomOnBottom && enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        // Left Bottom
        } else if (enoughRoomOnTop && enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_BOTTOM;
        // Right Bottom
        } else if (enoughRoomOnTop && enoughRoomOnLeft) {
            pos = AnchoredInterface.ALIGN.RIGHT_BOTTOM;
        // Right Top
        } else if (enoughRoomOnBottom && enoughRoomOnLeft) {
            pos = AnchoredInterface.ALIGN.RIGHT_TOP;
        } else if (!enoughRoomOnRight) {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        } else {
            pos = AnchoredInterface.ALIGN.LEFT_TOP;
        }
        return pos;
    
    } else {
    
        //how much is missing to display on the right (<=0 means enough to display)
        var not_enough_right = ((aX-scrollX) +  eWidth) - wWidth;
        //how much is missing to display on the left (<=0 means enough to display)
        var not_enough_left =  eWidth - (aX-scrollX);
        //how much is missing to display on the left if not scrolled (<=0 means enough to display)
        var not_enough_absolute_left = eWidth - aX;
    
        //how much is missing to display on the bottom (<=0 means enough to display)
        var not_enough_bottom = ((aY-scrollY) +  eHeight) - wHeight;
        //how much is missing to display on the top (<=0 means enough to display)
        var not_enough_top =  eHeight - (aY-scrollY);
        //how much is missing to display on the top if not scrolled (<=0 means enough to display)
        var not_enough_absolute_top = eHeight - aY;
    
        //alert("window is "+wWidth+" by "+wHeight+" and scrolled to "+scrollX+" by "+scrollY+"\nthumb is "+aWidth+" by "+aHeight+" and at "+aX+" by "+aY+"\npop is "+eWidth+" by "+eHeight+"\n\nif there is enough room, value <=0:\n\nright: "+not_enough_right+"\nleft: "+not_enough_left+"\nabsleft: "+not_enough_absolute_left+"\nbottom: "+not_enough_bottom+"\ntop: "+not_enough_top+"\nabstop: "+not_enough_absolute_top);
    
        var horiz = '';
        var vertic = '';
        var ss = 10;
    
        // #1 pref position: top right
        // #2 pref position: bottom right
        // #3 pref position: top left
        // #4 pref position: bottom left
        // if we fit or are closer from fitting to the right than to the left or there is not enough room on the left
        if (not_enough_right <= 0 || not_enough_right < not_enough_left || not_enough_absolute_left > 0) {
            horiz = 'right';
            // scroll if necessary
            if (not_enough_right > 0) {
                for (var i=0; i<=(not_enough_right/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window))+'+ss+',parseInt(DomUtils.getWindowScrollY(window)))',100);
                }
            }
        // else if we are closer from the left and there is room if we scroll
        } else {
            horiz = 'left';
            // scroll if necessary
            if (not_enough_left > 0 && !this.disableScroll) {
                for (var i=0; i<=(not_enough_left/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window))-'+ss+',parseInt(DomUtils.getWindowScrollY(window)))',100);
                }
            }
        }
    
        // if we are closer from fitting to the top than to the bottom and there is enough room at the top
        if (not_enough_top <= 0 || (not_enough_top < not_enough_bottom && not_enough_absolute_top <= 0)) {
            vertic = 'top';
            // scroll if necessary
            if (not_enough_top > 0 && !this.disableScroll) {
                for (var i=0;i<=(not_enough_top/(ss*2))+1;i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window)),parseInt(DomUtils.getWindowScrollY(window))-'+ss+')',100);
                }
            }
        // else if we are closer from the top and there is room if we scroll
        } else {
            vertic = 'bottom';
            // scroll if necessary
            if (not_enough_bottom > 0 && !this.disableScroll) {
                for (var i=0; i<=(not_enough_bottom/(ss*2))+1; i++) {
                    setTimeout('window.scroll(parseInt(DomUtils.getWindowScrollX(window)),parseInt(DomUtils.getWindowScrollY(window))+'+ss+')',100);
                }
            }
        }
    
        if      (horiz == 'right' && vertic == 'top'   ) pos = AnchoredInterface.ALIGN.RIGHT_TOP;
        else if (horiz == 'right' && vertic == 'bottom') pos = AnchoredInterface.ALIGN.RIGHT_BOTTOM;
        else if (horiz == 'left'  && vertic == 'top'   ) pos = AnchoredInterface.ALIGN.LEFT_TOP;
        else if (horiz == 'left'  && vertic == 'bottom') pos = AnchoredInterface.ALIGN.LEFT_BOTTOM;
    
        return pos;
    }
}

/**
 * Sets the anchor element.
 *
 * @access public
 * @since  v1.1
 * @param  DOMElement  anchor
 * @return void
 */
function AnchoredInterface_SetAnchor(anchor) {
    //alert('anchor id: '+anchor.id);
    this.anchorId = anchor.id;
}

/**
 * Aligns the element based on the best placement determined by AnchoredInterface_GetAnchorPosition.
 *
 * @access public
 * @since  v1.1
 * @param  integer   pos   A value from the AnchoredInterface.ALIGN struct
 * @return void
 */
function AnchoredInterface_AlignElement(pos) {
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'called') );
    var elem   = document.getElementById(this.elemId);
    if (!elem)   throw new Error('Unable to find element: '+this.elemId);

    var anchor = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);

    pos = pos || this.getAnchorPosition();

    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'starting getElementWidth') );
    var aLeft   = DomUtils.getElementLeft(anchor);
    var aTop    = DomUtils.getElementTop(anchor);
    var aHeight = DomUtils.getElementHeight(anchor);
    var aWidth  = DomUtils.getElementWidth(anchor);
    var eHeight = DomUtils.getElementHeight(elem);
    var eWidth  = DomUtils.getElementWidth(elem);
    /*alert('aLeft: '+aLeft+"\n"+
          'aTop: '+aTop+"\n"+
          'aHeight: '+aHeight+"\n"+
          'aWidth: '+aWidth+"\n"+
          'eHeight: '+eHeight+"\n"+
          'eWidth: '+eWidth+"\n");*/
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'passed getElementWidth') );
    switch (pos) {
        /**
         * Right Top alignment means:
         *
         *             +--------+
         *             | anchor |
         *             +--------+
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.RIGHT_TOP:
            elem.style.left = parseInt( aLeft - eWidth  + aWidth  )+'px';
            elem.style.top  = parseInt( aTop  + aHeight )+'px';
            break;
        /**
         * Right Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         *             +--------+
         *             | anchor |
         *             +--------+
         */
         case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
            elem.style.left = parseInt( aLeft - eWidth + aWidth )+'px';
            elem.style.top  = parseInt( aTop  + aHeight )+'px';
            break;
        /**
         * Left Top alignment means:
         *
         *    +--------+
         *    | anchor |
         *    +--------+
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         */
         case AnchoredInterface.ALIGN.LEFT_TOP:
            elem.style.left = parseInt( aLeft )+'px';
            elem.style.top  = parseInt( aTop + aHeight)+'px';
            break;
        /**
         * Left Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------------+
         *    +--------+
         *    | anchor |
         *    +--------+
         */
         case AnchoredInterface.ALIGN.LEFT_BOTTOM:
        default:
            elem.style.left = parseInt( aLeft )+'px';
            elem.style.top  = parseInt( aTop - eHeight )+'px';
            break;
    }
    // fanatic memory cleanup
    elem   = null;
    anchor = null;
    //window.messageQueue.add( new Message('AnchoredInterface_AlignElement', 'finished') );
}

/**
 * $Id: calloutinterface.js 76074 2009-11-19 20:53:10Z kchiu $
 * $Author: kchiu $
 * $Revision: 76074 $
 * $Name$
 * $Date: 2009-11-19 12:53:10 -0800 (Thu, 19 Nov 2009) $
 *
 * @jsRequire DomUtils
 * @jsRequire interfaces.Interface
 * @jsRequire interfaces.AnchoredInterface
 *
 *
 * @version    $Revision: 76074 $
 * @author     Philip Snyder <philip@pricegrabber.com>
 * @copyright  Copyright &copy; 2006, Philip Snyder, PriceGrabber.com
 * @see        interfaces.Interface
 * @see        interfaces.AnchoredInterface
 */

/**
 * CalloutInterface Constructor / Definition
 *
 * This interface is built on top of the Interface object
 * and is NOT intended to be instantiated directly. See
 * documentation on interfaces.Interface for a complete
 * explanation.
 *
 * @access public
 * @since  v1.1
 * @return CalloutInterface
 */
function CalloutInterface() {
    this.calloutId      = null;
    this.elemId         = null;
    this.anchorId       = null;
    this.calloutPadding = 0;
    // Callout interface implementation
    this.erase             = CalloutInterface_Erase;
    this.draw              = CalloutInterface_Draw;
    this.alignElement      = CalloutInterface_AlignElement;
    // Anchored interface implementation
    this.disableScroll     = false;
    this.getAnchorX        = AnchoredInterface_GetAnchorX;
    this.getAnchorY        = AnchoredInterface_GetAnchorY;
    this.getAnchorZ        = AnchoredInterface_GetAnchorZ;
    this.getAnchorWidth    = AnchoredInterface_GetAnchorWidth;
    this.getAnchorHeight   = AnchoredInterface_GetAnchorHeight;
    this.getElemWidth      = AnchoredInterface_GetElemWidth;
    this.getElemHeight     = AnchoredInterface_GetElemHeight;
    this.getAnchorPosition = AnchoredInterface_GetAnchorPosition;
    this.setAnchor         = AnchoredInterface_SetAnchor;
    this.implement(AnchoredInterface);
    return this;
} // End CalloutInterface

// Setup CalloutInterface prototype chain
CalloutInterface.prototype             = new Interface;
CalloutInterface.prototype.constructor = CalloutInterface;
CalloutInterface.superclass            = Interface.prototype;






/****** BEGIN EDIT SECTION ******/

/**
 * General Settings
 *
 * Defines general settings for the callout interface. These can
 * be overridden by any script after inclusion of this file. 
 *
 * Example:
 *
 *    <script language="JavaScript">
 *    CalloutInterface.settings.images.left.src = 'http://i.pgcdn.com/images/callout/left_arrow.gif';
 *    // Or even...
 *    CalloutInterface.settings.images.left = { src: 'http://i.pgcdn.com/images/callout/left_arrow.gif', height: 20, width: 10 };
 *    </script>
 *
 * Note that 'http://i.pgcdn.com' is included... if you
 * don't include the full path, the url will be relative to your
 * web server -- which in general is wrong except for during
 * development.
 *
 * @access public
 * @since  v1.1
 * @var    CalloutInterface.settings   struct
 */
CalloutInterface.settings = {
    images: {
        left:   { src: util.resourceManager.get('http://i.pgcdn.com/images/balloon/left_arrow.gif'),   height: 23, width: 11 },
        top:    { src: util.resourceManager.get('http://i.pgcdn.com/images/balloon/top_arrow.gif'),    height: 11, width: 23 },
        right:  { src: util.resourceManager.get('http://i.pgcdn.com/images/balloon/right_arrow.gif'),  height: 21, width: 11 },
        bottom: { src: util.resourceManager.get('http://i.pgcdn.com/images/balloon/bottom_arrow.gif'), height: 11, width: 23 }
    }
};

/****** END EDIT SECTION ******/







/**
 * CalloutInterface.erase()
 *
 * "Erases" the callout by removing the dom elements created with CalloutInterface.draw().
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function CalloutInterface_Erase() {
    //window.messageQueue.add( new Message('CalloutInterface_Erase', 'called') );
    var callout = document.getElementById(this.calloutId);
    if (callout) {
        //callout.parentNode.removeChild(callout);
        DomUtils.removeElement(callout);
    }
    callout = null;
    //window.messageQueue.add( new Message('CalloutInterface_Erase', 'finished') );
} // End CalloutInterface_Erase


/**
 * CalloutInterface.draw()
 *
 * "Draws" the actual callout by creating the necessary dom elements and applying them to
 * the object's element.
 *
 * @access public
 * @since  v0.0.1a
 * @return void
 */
function CalloutInterface_Draw() {
    //window.messageQueue.add( new Message('CalloutInterface_Draw', 'called') );
    if (!this.calloutId) this.calloutId = this.id+'_Callout';
    var elem    = document.getElementById(this.elemId);
    if (!elem) throw new Error('Unable to find element: '+this.elemId);
    var anchor  = document.getElementById(this.anchorId);
    if (!anchor) throw new Error('Unable to find anchor element: '+this.anchorId);
    if (elem && anchor) {
        elem.style.padding = '1px';
        elem.style.border  = '1px solid #dadada';
        //elem.style.backgroundColor = '#ffffff';
        var callout = document.getElementById(this.calloutId);
        if (callout && callout.parentNode) DomUtils.removeElement(callout);
        var pos                  = this.getAnchorPosition();
        callout                  = document.createElement('img');
        callout.id               = this.calloutId;
        callout.style.position   = 'absolute';
        callout.style.visibility = 'hidden';
        var body = document.getElementsByTagName('body')[0];
        body.appendChild(callout);
        var imgDetails;
        //alert('Callout interface: '+pos);

        switch (pos) {
            case AnchoredInterface.ALIGN.RIGHT_TOP:
                //if (typeof(writeDebug) == 'function') writeDebug('right_top');
                imgDetails        = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'right_top: '+imgDetails.src) );
                callout.src       = util.resourceManager.get(imgDetails.src);
                callout.height    = imgDetails.height;
                callout.width     = imgDetails.width;
                //callout.style.top = (DomUtils.getElementHeight(elem)+2)+'px';
                //if (DomUtils.browser.isIE() && document.compatMode == 'BackCompat') {
                //    eStyle  = DomUtils.getCurrentStyle(elem);
                //    height  = parseInt(DomUtils.getElementHeight(elem));
                //    callout.style.top = parseInt(height+1)+'px';
                //}
                callout.style.left = this.calloutPadding+'px';
                break;
            case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
                //if (typeof(writeDebug) == 'function') writeDebug('right_bottom');
                imgDetails         = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'right_bottom: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = '-'+(callout.height+(DomUtils.browser.isIE() ? -1 : -1))+'px';
                //callout.style.left = this.calloutPadding+'px';
                break;
            case AnchoredInterface.ALIGN.LEFT_TOP:
                imgDetails         = CalloutInterface.settings.images.top;
                //window.messageQueue.add( new Message('callout img', 'left_top: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = (DomUtils.getElementHeight(elem)+2)+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'getting elem width for callout') );
                //callout.style.left = (DomUtils.getElementWidth(elem)-1-callout.width-this.calloutPadding)+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'finished elem width for callout') );
                break;
            case AnchoredInterface.ALIGN.LEFT_BOTTOM:
            default:
                //if (typeof(writeDebug) == 'function') writeDebug('left_bottom');
                imgDetails         = CalloutInterface.settings.images.bottom;
                //window.messageQueue.add( new Message('callout img', 'left_bottom: '+imgDetails.src) );
                callout.src        = util.resourceManager.get(imgDetails.src);
                callout.height     = imgDetails.height;
                callout.width      = imgDetails.width;
                //callout.style.top  = '-'+(callout.height+(DomUtils.browser.isIE() ? -1 : -1))+'px';
                //window.messageQueue.add( new Message('CalloutInterface_Draw', 'getting elem width for callout') );
                //callout.style.left = (DomUtils.getElementWidth(elem)-1-callout.width-this.calloutPadding)+'px';
                //callout.style.left = (DomUtils.getElementLeft(elem)-1-callout.width-this.calloutPadding)+'px';
                //window.messageQueue.add( new Message(funcname(this), 'finished elem width for callout') );
                break;
        }

        //elem.appendChild(callout);
        //callout.style.zIndex = parseInt(DomUtils.getZIndex(elem))+1;
        //this.alignElement(pos);
        //CalloutInterface_AlignElement.call(this, pos);
    }
    //window.messageQueue.add( new Message('CalloutInterface_Draw', 'finished') );
} // End CalloutInterface_Draw


/**
 * CalloutInterface.alignElement()
 *
 * Aligns the object's element by adjusting its left & top by the proper calculations (which are done
 * in this function) on the height & width & style of the callout.
 *
 * @access public
 * @since  v0.0.1a
 * @see    AnchoredInterface
 * @param  string    pos      One of the constants defined in AnchoredInterface.ALIGN
 * @return void
 */
function CalloutInterface_AlignElement(pos) {
    //window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'called') );
    pos = pos || this.getAnchorPosition();
    var elem    = document.getElementById(this.elemId);
    if (!elem)    throw new Error('Unable to find element: '+this.elemId);
    var callout = document.getElementById(this.calloutId);
    if (!callout) throw new Error('Unable to find callout element: '+this.calloutId);
    var anchor  = document.getElementById(this.anchorId);
    if (!anchor)  throw new Error('Unable to find anchor element: '+this.anchorId);

    var aTop    = parseInt( DomUtils.getElementTop(anchor) );
    var aLeft   = parseInt( DomUtils.getElementLeft(anchor) );
    var aHeight = parseInt( DomUtils.getElementHeight(anchor) );
    var aWidth  = parseInt( DomUtils.getElementWidth(anchor) );

    var eTop    = parseInt( DomUtils.getElementTop(elem) );
    var eLeft   = parseInt( DomUtils.getElementLeft(elem) );
    var eHeight = parseInt( DomUtils.getElementHeight(elem) );
    var eWidth  = parseInt( DomUtils.getElementWidth(elem) );

    var cHeight = parseInt( DomUtils.getElementHeight(callout) );
    var cWidth  = parseInt( DomUtils.getElementWidth(callout) );
    switch (pos) {
        /**
         * Right Top alignment means:
         *
         *             +--------+
         *             | anchor |
         *             +--------+
         *                 /\
         *    +-----------'  '--+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.RIGHT_TOP:
            // Adjust main element's position to accomodate callout element
            elem.style.top       = parseInt( eTop  + cHeight )+'px';
            elem.style.left      = parseInt( eLeft + this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt( aWidth / 2 ) - parseInt(cWidth / 2) )+'px';
            callout.style.top    = parseInt( aTop  + aHeight + 1)+'px';
            callout.style.zIndex = parseInt(DomUtils.getZIndex(elem))+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: right_top') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Right Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +-----------,  ,--+
         *                 \/
         *             +--------+
         *             | anchor |
         *             +--------+
         */
        case AnchoredInterface.ALIGN.RIGHT_BOTTOM:
            // Adjust main element's position to accomodate callout element
            elem.style.top       = parseInt( eTop  + cHeight - 1 )+'px';
            elem.style.left      = parseInt( eLeft + this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt(aWidth/2) - parseInt( cWidth / 2) )+'px';
            callout.style.top    = parseInt( aTop + aHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: right_bottom') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Left Top alignment means:
         *
         *    +--------+
         *    | anchor |
         *    +--------+
         *        /\
         *    +--'  '-----------+
         *    | elem            |
         *    +-----------------+
         */
        case AnchoredInterface.ALIGN.LEFT_TOP:
            // Adjust main element's position to account for the callout
            elem.style.top       = parseInt( eTop + cHeight - 1 )+'px';
            elem.style.left      = parseInt( eLeft - this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft + parseInt(aWidth / 2) - parseInt(cWidth / 2) - 1 )+'px';
            callout.style.top    = parseInt( aTop + aHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: left_top') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
        /**
         * Left Bottom alignment means:
         *
         *    +-----------------+
         *    | elem            |
         *    +--,  ,-----------+
         *        \/
         *    +--------+
         *    | anchor |
         *    +--------+
         */
        case AnchoredInterface.ALIGN.LEFT_BOTTOM:
        default:
            // Adjust main element's position to account for the callout
            elem.style.top       = parseInt( eTop - cHeight - 2)+'px';
            elem.style.left      = parseInt( eLeft - this.calloutPadding )+'px';
            // Position the callout element
            callout.style.left   = parseInt( aLeft - parseInt( cWidth / 2 ) + parseInt( aWidth / 2 ) - 1 )+'px';
            callout.style.top    = parseInt( aTop - cHeight )+'px';
            callout.style.zIndex = parseInt( DomUtils.getZIndex(elem) )+1;
/*
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'alignment position: left_bottom') );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.left   = '+DomUtils.getElementLeft(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.top    = '+DomUtils.getElementTop(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.height = '+DomUtils.getElementHeight(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'anchor.style.width  = '+DomUtils.getElementWidth(anchor)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.left     = '+DomUtils.getElementLeft(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.top      = '+DomUtils.getElementTop(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.height   = '+DomUtils.getElementHeight(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'elem.style.width    = '+DomUtils.getElementWidth(elem)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.left  = '+DomUtils.getElementLeft(callout)) );
            window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'callout.style.top   = '+DomUtils.getElementTop(callout)) );
*/
            break;
    }
    //window.messageQueue.add( new Message('CalloutInterface_AlignElement', 'finished') );
} // End CalloutInterface_AlignElement

