function VarUtils(){
}

/**
 * Returns true if a value is not undefined or null.
 */
VarUtils.prototype.isDef=function(value){
    return (typeof(value) != "undefined") && value!=null;
}

/**
 * Returns true if value is has a value other than 'null' or the empty string.
 */
VarUtils.prototype.hasValue=function(value){
    return this.isDef(value) && !(""===(value.replace(/^\s*/,'').replace(/\s*$/,'')));
}

/**
 * Returns true if (string paramter) value is a number otherwise false.
 */
VarUtils.prototype.isNumber=function(value){
    return (typeof(value) == "string") && !value.replace(/\$,\./,value).match(/[^0-9]+/);
}

/**
 * Returns -1 if a &lt; b, 0 if a=b, 1 if a &gt; b.
 */
VarUtils.prototype.compare=function(a,b){
    var results=0;
    if (this.isNumber(a) && this.isNumber(b)){
        results=a-b;
    } else {
        results+=(a<b?-1:(a>b?1:0));
    }

    return results;
}

/**
 * Returns true if <code>value</code> is between <code>low</code> and <code>high</code>.
 * The default operation is inclusive, set <code>exclusive</code> to <code>true</code> if
 * you want value to match either <code>low</code> or <code>high</code>.
 */
VarUtils.prototype.isBetween=function(value, low, high, exclusive){
    var results=false;
    if (varUtils.isDef(exclusive) && exclusive){
        results=(value>low && value<high);
    } else {
        results=(value>=low && value<=high);
    }
    return results;
}


VarUtils.prototype.formatCurrency=function(value){
    var formatted=value;
    if (varUtils.isDef(value) && !isNaN(value)){
        var dollarsAndCents=value.split(/\./);
        var dollar=dollarsAndCents[0];
        formatted=dollar;
        if (dollar.length>3){
            var splitPoint=dollar.length-3;
            var lastThreeDigits=dollar.slice(splitPoint);
            formatted=this.formatCurrency(dollar.slice(0,splitPoint))+","+lastThreeDigits
        }
        formatted+=(dollarsAndCents.length>1?"."+dollarsAndCents[1]:"");
    }

    return formatted;
}

/**
 * Performs a binary search over <code>sortedArray</code> and returns either the index
 * at which the element (primatives only) appears or null.  
 */
VarUtils.prototype.search=function(sortedArray, element, lower, upper){
	var index=null;
	lower=this.isDef(lower)?lower:0;
	upper=this.isDef(upper)?upper:sortedArray.length;
	var midpoint=Math.floor((upper-lower)/2);
	
	if (upper>lower){
		if((upper-lower)==1){
			index=sortedArray[midpoint]===element?0:null;
		} else {
			var pivot=sortedArray[midpoint];
			if (element===pivot){
				index=midpoint;
			} else if (element<pivot) {
				index=this.search(sortedArray, element, 0, midpoint);
			} else {
				index=this.search(sortedArray, element,  midpoint, upper);
			}
		}
	}
	
	return index;
}

var varUtils=new VarUtils();


function URLUtils(){
}

/**
 * Returns the path portion of the url, everything after the host:port
 * up to the query string including any fragments (the bit between '#' and '?').
 */
URLUtils.prototype.getPath=function(){
    var path=null;
    if (varUtils.isDef(window.location.pathname)){
        path=window.location.pathname;
    } else {
        var location=window.location.split(/\/(?!\/)/);
        if (location.length>1){
            path=location[1];
            var qsDelim=path.indexOf('?');
            if (qsDelim>=0){
                path=path.substring(qsDelim+1);
            }
        }
    }

    return path;
}

/**
 * Returns everything after the question mark.
 */
URLUtils.prototype.getQueryString=function(){
    var queryString=null;
    if (varUtils.isDef(window.location.search)){
        queryString=window.location.search;
        queryString=queryString.substring(queryString.indexOf("?")+1);
    } else if (window.location.indexOf("?")>=0){
        queryString=window.location.substring(window.location.indexOf("?")+1);
    }

    return queryString;
}

/**
 * Returns the entire set of query string parameters as an associative array.
 */
URLUtils.prototype.getQueryStringParams=function(){
    var params=new Object();
    var queryString=this.getQueryString();
    var paramValues=queryString.split(/\&/);
    for (var i=0;i<paramValues.length;i++){
        var paramValue=paramValues[i].split(/=/);
        params[paramValue[0]]=paramValue[1];
    }

    return params;
}

/**
 * Returns the value (only) of a single query string parameter.
 */
URLUtils.prototype.getQueryStringParam=function(param){
    var paramValue=null;
    var qs=window.location.search;
    if (varUtils.isDef(qs)){
        var qsStart=qs.indexOf("?");
        var params=this.getQueryStringParams();
        if (varUtils.hasValue(params[param])){
            paramValue=params[param];
        }
        /*
        if (qsStart>=0){
            var queryString=qs.substring(qsStart+1);
            if (varUtils.isDef(queryString) && queryString.indexOf(param)>=0){
                var matches=new RegExp(param+"=([^&]+)").exec(queryString);
                paramValue=matches[1];
            }
        }
        */
    }

    return paramValue;
}

URLUtils.prototype.getReferrer=function(){
	return document.referrer;
}

var urlUtils=new URLUtils();

function CookieUtils(){
}


/**
 * Creates a cookie, name, with value, value. Cookie will expire
 * in days, um, days from the moment it is set. Leave days blank or negative
 * if the cookie should expire at the end of the session or 0 if it
 * should be deleted.
 */
CookieUtils.prototype.createCookie=function (name,value,days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
}

/**
 * returns the cookie with name, name.
 */
CookieUtils.prototype.readCookie=function (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 c.substring(nameEQ.length,c.length);
    }
    return null;
}

/**
 * Deletes the cookie with name 'name'.
 */
CookieUtils.prototype.eraseCookie=function (name) {
    createCookie(name,"",0);
}

/**
 * Routines to help deal with referrals.
 * @return
 */
function ReferrerUtils(){
    this.cookieUtils=new CookieUtils();
}

ReferrerUtils.prototype.setWeddingChannelCookie=function(){
    var cookieName="referrer-mcf";
    var params=urlUtils.getQueryStringParams();
    var sessionId=params.wc_sid;
    var wcRef=params.wcref;
    var registryId=(urlUtils.getPath().indexOf("registry")>=0 && varUtils.isDef(params.id)) ? params.id : null;

    if (varUtils.isDef(sessionId) || varUtils.isDef(wcRef)){
        this.cookieUtils.createCookie(cookieName,"weddingchannel|"
                +registryId+"|"
                +(varUtils.isDef(sessionId) ? sessionId:"null")
                +"|{referrer:'weddingchannel',referrerData:{sessionId:'"
                +(varUtils.isDef(sessionId) ? sessionId:"null")
                +"',registryId:"+registryId
                +",utm_source:"+(varUtils.isDef(params.utm_source) ? "'"+params.utm_source+"'":null)
                +",utm_medium:"+(varUtils.isDef(params.utm_medium) ? "'"+params.utm_medium+"'":null)
                +",utm_campaign:"+(varUtils.isDef(params.utm_campaign) ? "'"+params.utm_campaign+"'":null)
                +"}}");
    }

}

ReferrerUtils.prototype.setNextJumpCookie=function(){
    var cookieName="referrer-nxj";
    var params=urlUtils.getQueryStringParams();

    var u1=params.u1;

    if (varUtils.isDef(u1) /* && urlUtils.getReferrer()=='www.nextjump.com'*/){
        this.cookieUtils.createCookie(cookieName,"nextjump|"
                +u1
                +"|{referrer:'nextjump',referrerData:{u1:'"
                +(varUtils.isDef(u1) ? u1:"null")
                +"',id:"+(varUtils.isDef(params.ID) ? "'"+params.ID+"'":null)
                +",categoryId:"+(varUtils.isDef(params.CategoryID) ? "'"+params.CategoryID+"'":null)
                +"}}");
    }
}

function MenuUtils(){
    this.visibleMenu=null;
    this.menuSource=null;
    this.timer=null;
    this.visibleDuration=(3*1000);
    this.context=null;
}

MenuUtils.prototype.showMenu=function(parent, menu){
    this.visibleMenu=menu.cloneNode(true);

    this.visibleMenu.setStyle({position: "absolute"});
    this.visibleMenu.setStyle({left: (parent.offsetLeft+"px")});
    this.visibleMenu.setStyle({visibility: "visible"});

    menu.parentNode.appendChild(this.visibleMenu);
    this.menuSource=menu;
}

MenuUtils.prototype.hideMenu=function(menu){
    if (varUtils.isDef(this.timer)){
        clearTimeout(this.timer);
    }

    menu.setStyle({visibility: "hidden"});
    menu.setStyle({position: "static"});
    menu.parentNode.removeChild(menu);
    this.visibleMenu=null;
    this.menuSource=null;
}

MenuUtils.prototype.toggleVisibility=function (parent, menu){
    if (varUtils.isDef(menu)){
        if (varUtils.isDef(this.visibleMenu)){
            this.hideMenu(this.visibleMenu);
        }

        this.showMenu(parent,menu);
    }
}

MenuUtils.prototype.startHideTimer=function (menu){
    if (varUtils.isDef(this.menuSource) && this.menuSource===menu){
        //this.timer=setTimeout("menutils.hideMenu(menutils.visibleMenu)",this.visibleDuration);
    }
}

MenuUtils.prototype.getUrl=function (url){
    return this.context+url;
}

function ImageUtils(appContext, staticImgRoot, productImgRoot){
    this.appContext=appContext;
    this.staticImgRoot=staticImgRoot;
    this.fullPath=productImgRoot;
    this.detailPath="SKU/SKU_detail/main_variation_default_view_1_WIDTHxHEIGHT.jpg";
    this.otherPath="SKU/generated/SKU_default_1_WIDTHxHEIGHT.jpg";
    	

    this.productSizeMapping={
            detail:{
            placeholder:staticImgRoot+"/placeholder100x100.jpg",
            file:{
                width:450,
                height:500
            },
            web:{
                width:450,
                height:500
            }
        },
        large:{
            placeholder:staticImgRoot+"/placeholder100x100.jpg",
            file:{
                width:250,
                height:250
            },
            web:{
                width:250,
                height:250
            }
        },
        medium:{
            placeholder:staticImgRoot+"/placeholder100x100.jpg",
            file:{
                width:130,
                height:130
            },
            web:{
                width:120,
                height:120
            }
        },
        smallmedium:{
            placeholder:staticImgRoot+"/placeholder100x100.jpg",
            file:{
                width:100,
                height:100
            },
            web:{
                width:88,
                height:88
            }
        },
        small:{
            placeholder:staticImgRoot+"/placeholder100x100.jpg",
            file:{
                width:50,
                height:50
            },
            web:{
                width:48,
                height:48
            }
        }
    }
}

/**
 * Returns the path to the image for a given sku. This is based on the set
 * of images for the fluid displays and there is no check to ensure that the
 * returned path actually points to something. If there is no image the path will
 * ultimately return a 404
 *
 * sku - Product sku
 * sizeKey - One of: small, smallmedium, medium, large. Sizes return images:
 * 48x48, 88x88, 120x120, and 250x250 respectively
 *
 * isDetail - set to true if you want the larger size image that is normally displayed
 * when viewing a single product.
 *
 */
ImageUtils.prototype.getProductImgPath=function(sku,sizeKey,isDetail){
    var path=null;
    if(!varUtils.isDef(this.productSizeMapping[sizeKey])){
        sizeKey="medium";
    }

    if (varUtils.isDef(isDetail) && isDetail){
        path=this.fullPath+this.detailPath;
    } else {
        path=this.fullPath+this.otherPath;
    }

    path=path.replace(/SKU/g,sku);

    var size=this.productSizeMapping[sizeKey];
    path=path.replace(/HEIGHT/,size.file.height);
    path=path.replace(/WIDTH/,size.file.width);

    return path;
}
/**
 * Returns a fully formatted image tag for a given product (sku).
 *
 * sku - Product sku
 * sizeKey - One of: small, smallmedium, medium, large. Sizes return images:
 * 48x48, 88x88, 120x120, and 250x250 respectively
 *
 * alt - value for the alt attribute.
 *
 * isDetail - set to true if you want the larger size image that is normally displayed
 * when viewing a single product.
 *
 * forcePlaceholder - The current method of rendering an image tag does not confirm whether or not
 * an image is available for the product unless test is true. If you have an alternate means of
 * testing for the existence of an image and need to use the place holder set this to true. The correct
 * sized place holder will be returned.
 *
 * test - will check to see if the file exists; if not the returned tag references
 * the appropriate place holder.
 */
ImageUtils.prototype.getProductImgTag=function(sku,size,alt,isDetail,forcePlaceholder,test){
    var tag="<img ID HEIGHT WIDTH SRC ALT/>";
    var size=this.productSizeMapping[size];
    var imgPath=forcePlaceholder?size.placeholder:this.getProductImgPath(sku,size,isDetail);
    var idPrefix="pimg-";

    if (varUtils.isDef(test) && test){
        var callback=function(data,textStatus){
            if(!data.exists || textStatus!='success'){
                jQuery("#"+idPrefix+data.sku).attr("src",size.placeholder);
            }
        }
        jQuery.get(this.appContext+"/util/file_test.jsp",{file:imgPath,psku:sku},callback,"json");
    }

    tag=tag.replace(/ID/,"id='"+idPrefix+sku+"'")
        .replace(/HEIGHT/,"height='"+size.web.height+"'")
        .replace(/WIDTH/,"width='"+size.web.width+"'")
        .replace(/SRC/,"src='"+imgPath+"'")
        .replace(/ALT/,"ALT='"+alt+"'");

    return tag;
}

ImageUtils.prototype.setDetailPath=function(path){
	this.detailPath=path;
}

ImageUtils.prototype.setOtherPath=function(path){
	this.otherPath=path;
}

ImageUtils.prototype.setPlacerholderPath=function(size,path,webWidth, webHeight){
	this.productSizeMapping[size].placeholder=path;
	this.productSizeMapping[size].web.width=varUtils.isDef(webWidth)?webWidth:this.productSizeMapping[size].web.width;
	this.productSizeMapping[size].web.height=varUtils.isDef(webHeight)?webHeight:this.productSizeMapping[size].web.height;
}

/**
 * Maps values returned from the solr calls to human readable values.
 *
 * Solr response conversions (see db: product_spec_option, option_paradigm, option_paradigm_value)
 */
function SolrFilterUtils(translationMap){
    this.translationMap=translationMap;
    this.renderHandlers={
            renderByValue:function(renderCallback, dropDown){
            var option="";
            var values=dropDown.data.values;
            var increment=dropDown.data.increment;
            var selectedValue=decodeURIComponent(dropDown.selectedValue);
            for(var i = 0; i < values.length; i+=increment){
                if (values[i+1]>0){
                    var value=values[i].replace(/\"/,"");
                    var label=varUtils.isDef(dropDown.translator)?dropDown.translator(value,dropDown):value;
                    if (varUtils.hasValue(label)){
                        option+=renderCallback.renderDropDownOption(value,label,value==selectedValue);
                    }
                }
            }

            return option;
        },
        renderByKey:function(renderCallback, dropDown){
            var option="";
            var values=dropDown.data.values;
            var selectedValue=decodeURIComponent(dropDown.selectedValue);
            for(var i in values){
                var label=values[i];
                option+=renderCallback.renderDropDownOption(i,label,i==selectedValue);
            }

            return option;
        },
        renderPrice:function(renderCallback, dropDown){
            var option="";
            var values=dropDown.data.values;
            var selectedValue=decodeURIComponent(dropDown.selectedValue);
            for(var i in values){
                    if (values[i]>0){
                    var value=i.replace(/\"/,"");
                    var highLow=value.replace(/price:\[(\d+(\.\d+)?) TO ((\d+(\.\d+)?)|(\*))\]/g,"$1,$3").split(/,/);
                    var label=(highLow[1]=="*"?"":"$")+varUtils.formatCurrency(highLow[0])+" TO $"+varUtils.formatCurrency(highLow[1]);
                    if ((highLow[0]+0)==0){
                        label="Under $"+varUtils.formatCurrency(highLow[1]);
                    }
                    if (highLow[1]=="*"){
                    	label="$"+varUtils.formatCurrency(highLow[0])+" and above";
                    }
                    if (varUtils.hasValue(label)){
                        option+=renderCallback.renderDropDownOption(value,label,value==selectedValue);
                    }
                }
            }

            return option;
        }
    }
}



/**
 * Factory functions for dropdowns
 * A drop down set is a json array with a bunch of drop downs in it, i.e.:
 * dropdownset=[drop down 1, drop down 2, drop down n].
 *
 * Each drop down is a json object with the following properties:
 * name - The control (a select box) will be rendered with this name
 * label - If present, a <label> element with this text will be rendered (before the control in the doc tree)
 * unselectedText - The value that appears in the drop down when nothing has been selected (alternate label)
 * unselectedValue - The value that goes with the text, normally the empty string
 * selectedValue - Use to set the selected option of the control (from an earlier selection)
 * data - Data object that populates the drop down
 * transator - Function that converts the data to human readable text (for example if the data is an array of category ids you can translate them into names). The translate function is here on the drop down instead of the data so that the same dataset can be used for multiple drop downs
 * xlateData - Used by the translator to convert data value to text (currently for the label portion of the drop down option only)
 * onchangeHandler - Function to handle change events, the default is to resubmit the search.
 *
 * Data objects have the following attributes:
 * increment - Solr returns a count for each facet item. In the facet field array the count is always the n+1 element so you want to skip over it.
 * accessor - Function to retrieve data from the solr data set. Currently there are three canned accessors: byValue, byKey (for facet queries), byPrice (basically byKey but with special handling for currency)
 * values - The solr data.
 */
SolrFilterUtils.prototype.makeDropDown=function(label, header, name, increment, accessor, values, selectedValue){
    var dropdown=new Object();
    dropdown.label=label;
    dropdown.unselectedText=header;
    dropdown.name=name;
    dropdown.selectedValue=selectedValue;

    var data=new Object();
    data.increment=increment;
    data.accessor=accessor;
    data.values=values;

    dropdown.data=data;

    return dropdown;
}

SolrFilterUtils.prototype.renderDropDownOption=function(value,label,isSelected){
    return '<option value="'+encodeURIComponent(value)+'"'+(isSelected?' selected ':'')+'>'+label+'</option>';
}

SolrFilterUtils.prototype.renderDropDown=function(dropDown, labelIsVisible){
    var onChangeHandler=varUtils.isDef(dropDown.onChangeHandler)?dropDown.onChangeHandler:"filterSearchResults(this,0);";
    var label="<label class='"+(labelIsVisible?"label":"hide")+"' for='NAME'>TEXT</label>";
    var select="<select name='NAME' onchange='"+onChangeHandler+"' class='select'>";

    var control=label.replace(/NAME/,dropDown.name).replace(/TEXT/,dropDown.label);
    control+=select.replace(/NAME/,dropDown.name);
    if(varUtils.isDef(dropDown.unselectedText)){
        control+="<option value='VALUE'>LABEL</option>".replace(/VALUE/,varUtils.isDef(dropDown.unselectedValue)?dropDown.unselectedValue:"").replace(/LABEL/,dropDown.unselectedText);
    }

    var options="";
    if (varUtils.isDef(dropDown.data.values)){
        options=dropDown.data.accessor(this,dropDown);
    }

    return control+options+"</select>";
}

/**
 * Renders each drop down in <code>dropDowns</code> as a select box, appended to <code>parentElement's</code>
 * children list.
 *
 * @param dropDowns - list of drop downs
 * @param parentElement - DOM element the select boxes should be appended to. Each drop down will be appended
 * to <code>parentElement</code> in the order it is received.
 * @param update - If update is true <code>parentElement's</code> children will be removed from the document
 * before the drop downs are appended.
 * @param location - allows for location specific handling, on of <ul><li>content-top</li><li>footer</li></ul>. At the
 * moment this is just hard coded so that if location=footer a visible label is append just before the select box is created.
 */
SolrFilterUtils.prototype.renderDropDowns=function(dropDowns,parentElement,update,location){
    if (dropDowns){
        if (update){
            jQuery("#"+parentElement).empty();
        }
        for(var i = 0; i < dropDowns.length; i++){
            /*
            if (!varUtils.isDef(dropDowns[i].onChangeHandler)){
                if (location=="footer"){
                    dropDowns[i]['onChangeHandler']="solrFilterUtils.updateFilters(this.form)";
                } else {
                    dropDowns[i]['onChangeHandler']="solrFilterUtils.filterSearchResults(this.form,0)";
                }
            }
            */
            var control=
                    (location=="footer"?"<li>":"")
                    +this.renderDropDown(dropDowns[i],location=="footer")
                    +(location=="footer"?"</li>":"");
            jQuery(control).appendTo("#"+parentElement);
        }
    }
}

SolrFilterUtils.prototype.filterSearchResults=function(form,page){
    var currentPageInputField=jQuery("#current_page_id");
    if (varUtils.isDef(currentPageInputField)){
        currentPageInputField.val(page);
    }
    form.submit();
}

SolrFilterUtils.prototype.updateFilters=function(form){
    var searchAction=contextPath+"/sitesearch/search_do";
    var params=new Object();
    params["search_type"]="filter_refresh";
    params["primary_search_type"]=form.search_type.value;
    var selectBoxes=jQuery("select[name^='filter_by']");
    for (var i=0;i<selectBoxes.length;++i){
        var value=selectBoxes[i].value;
        if (!varUtils.hasValue(value)){
            value="";
        }
        params[selectBoxes[i].name]=value;
    }
    jQuery.getJSON(searchAction,params,
        function(responseData){
            data=responseData;
            initializeData();
            for (var i in params){
                if (i.match(/^filter_by/)){
                    var j=0;
                    var temp=params[i];
                    for (;dropDownSets[dropDownSet][j].name!=i;++j){}
                        if (dropDownSets[dropDownSet][j].name==i){
                            dropDownSets[dropDownSet][j].selectedValue=params[i].replace(/\%5C/,"'");
                        }
                }
            }
            solrFilterUtils.renderDropDowns(dropDownSets[dropDownSet],form.id+"Controls",true,"footer");
        });
}

var solrFilterUtils=new SolrFilterUtils();


/**
 * Paginates the json result set from a solr filter call. This class expects a certain object structure so if you supply
 * it anything other than the default json rendering from solr you'll need to override the existing methods.
 *
 * @param searchType - The search type, i.e. cat_product_search.
 * @param itemsPerPage - number of result documents to be displayed on any page. <code>totalItems</code> divided by this value plus the modulus
 * gives the total number of pages. Setting this value to negative indicates that all results should be displayed - i.e. 'view all'; the direct page links will
 * not be available.
 * @param maxItemsPerPage - Maximum number of items that can appear on a page before the pagination is forced. <code>maxItemsPerPage</code> works in tandem
 * with itemsPerPage in that even if the user want's to view all (sets itemsPerPage<=0) a hard upper limit of <code>maxItemsPerPage</code> will be set. Set this
 * to null or undefined if you want true view all.
 * @param numPageLinks - Number of page links to display. The links are windowed so that the current page is more or less centered in the listing.
 * @param pageLinkTarget - The action that each pagination link should point to
 * @param currentPage - The current page.
 *
 */
function SolrResultPaginator(searchType, itemsPerPage, maxItemsPerPage, numPageLinks, pageLinkTarget, currentPage){
    this.currentPage = currentPage;
    this.maxItemsPerPage=maxItemsPerPage;
    this.itemsPerPage=itemsPerPage;
    this.numberOfDirectLinks=numPageLinks;
    this.searchType=searchType;

    this.upperPaginationContainer="<p class='paging' id='upperPaginationContainer'></p>";
    this.searchAction=contextPath+"/sitesearch/search_do";
    this.nextIcon=staticImgPath +"/next_page_arrow.gif";
    this.prevIcon=staticImgPath +"/prev_page_arrow.gif";
    this.spacer=staticImgPath +"/placeHolder_large.gif";

}

SolrResultPaginator.prototype.setCurrentPage = function(currentPage){this.currentPage=currentPage;}
SolrResultPaginator.prototype.setUpperPaginationContainer=function(container){this.upperPaginationContainer=container;}
SolrResultPaginator.prototype.setMaxItemsPerPage=function(maxItemsPerPage){this.maxItemsPerPage=maxItemsPerPage;}
SolrResultPaginator.prototype.setItemsPerPage=function(itemsPerPage){this.itemsPerPage=itemsPerPage;}
SolrResultPaginator.prototype.setNumDirectLinks=function(numPageLinks){this.numberOfDirectLinks=numPageLinks;}
SolrResultPaginator.prototype.setSearchAction=function(action){this.searchAction=action;}
SolrResultPaginator.prototype.setSearchType=function(searchType){this.searchType=searchType;}
/**
 * @param nextIcon - url to an image to represent the 'forward' control
 */
SolrResultPaginator.prototype.setNextIcon=function(path){this.nextIcon=path;}
/**
 * @param prevIcon - url to an image to represent the 'back' control
 */
SolrResultPaginator.prototype.setPrevIcon=function(path){this.prevIcon=path;}

SolrResultPaginator.prototype.renderResultSetSizeSelector=function(itemsPerPage){
    var itemsPerPageSelect="<select name='resultSetSizeSelector' id='resultSetSizeSelector' onchange='jQuery("+'"#resultSetSize"'+").val(this.value>0?this.value:12);filterSearchResults(0,0);'><!--<option value='0'>paginate</option>--><option value='12'"+(this.itemsPerPage==12?" selected":"")+">12 items/page</option><option value='24'"+(this.itemsPerPage==24?" selected":"")+">24 items/page</option><option value='36'"+(this.itemsPerPage==36?" selected":"")+">36 items/page</option><option value='48'"+(this.itemsPerPage==48?" selected":"")+">48 items/page</option></select>&nbsp;";
    return itemsPerPageSelect;
}
/**
 * Renders the pagination controls; each control delegates to the 'filterSearchResults' function.
 * parentElement - The dom element the controls will be appended to
 * totalItems - Total number of results documents to be displayed
 *
 */
SolrResultPaginator.prototype.renderPagination=function(parentElement,totalItems){
    var template=" Page START of: END";
    var directLink="<a href='#' onclick='SEARCHACTION; return false;'>PAGE</a>&nbsp;";
    var spacer="<img alt='spacer' src='"+this.spacer+"' style='height:4px; width:4px'/>";
    var previous="<a href='#' onclick='filterSearchResults(null,"+(this.currentPage-1)+");'><img alt='prev page' src='"+this.prevIcon+"'/></a>";
    var next="<a href='#' onclick='filterSearchResults(null,"+(this.currentPage+1)+");'><img alt='next page' src='"+this.nextIcon+"'/></a>";
    var viewAll="<a href='#' onclick='jQuery("+'"#resultSetSize"'+").val(-1);filterSearchResults(null,0);'>view all</a>";
    var currentPage="<span style='font-weight:bold; font-size:110%'>PAGE</span>&nbsp;";

    if (varUtils.isDef(this.maxItemsPerPage)){
        if (this.itemsPerPage<=0 && totalItems>this.maxItemsPerPage){
            this.itemsPerPage=this.maxItemsPerPage;
        }
    } else if (this.itemsPerPage<=0){
        this.itemsPerPage=this.totalItems;
    }

    var totalPages=(this.itemsPerPage<=0 ? 1 : (Math.floor(totalItems/this.itemsPerPage)+(totalItems%this.itemsPerPage>0?1:0)));
    if (totalPages<=this.numberOfDirectLinks){
        this.numberOfDirectLinks=totalPages;
    }

    parentElement.text(template.replace(/START/,(this.currentPage+1)).replace(/END/,totalPages));
    if(totalPages>1){
        var startOffset=this.currentPage-Math.floor((this.numberOfDirectLinks-1)/2);
        if ((startOffset+this.numberOfDirectLinks)>totalPages){
            startOffset=totalPages-this.numberOfDirectLinks;
        }
        if (startOffset<0){
            startOffset=0;
        }

        var links="";
        for(var i=0; i<this.numberOfDirectLinks;++i){
            var page=(startOffset+i);
            if (page!=this.currentPage){
                links+=directLink.replace(/PAGE/,page+1).replace(/SEARCHACTION/,"filterSearchResults(null,"+(page)+")");
            } else {
                links+=currentPage.replace(/PAGE/,page+1);
            }
        }

        var prev=spacer;
        if (this.currentPage>0){
        	prev="&nbsp;"+previous;
        }
        links=prev+links;

        if (this.currentPage<(totalPages-1)){
            links+=next;
        }
        jQuery(links).prependTo(parentElement);

        if (varUtils.isDef(this.maxItemsPerPage) && totalItems<=this.maxItemsPerPage){
            jQuery(viewAll).prependTo(parentElement);
        } else {
            jQuery(this.renderResultSetSizeSelector(this.itemsPerPage)).prependTo(parentElement);
        }
    } else if (this.itemsPerPage<=0){
        jQuery(this.renderResultSetSizeSelector(this.itemsPerPage)).prependTo(parentElement);
    }
}

/**
 * The Solr spellcheck handler will return a list of potential corrections for each field
 * that a term is queried against... and not collate them. This means that if you query against
 * 5 fields you'll get five corrections all of which might be the same. This routine
 * attempts to collate the suggestions. 
 * 
 * The returned value is a map of the original terms and an array of their corrections, i.e.
 * orginal term='selt' returned map would be {selt:[salt,...]}
 * 
 * @param suggestions - The suggestion portion of the Solr query results. Naturally this assumes
 * that your solr write type is json; you'll have to write your own parser for XML.
 */
SolrResultPaginator.prototype.collateSpellingSuggestions=function(suggestions){
	var collated=new Object();
	for (var i=0;i<suggestions.length;i+=2){
		var term=suggestions[i];
		var corrections=suggestions[i+1].suggestion;
		if (!varUtils.isDef(collated[term])){
			collated[term]=corrections.sort();
		} else {
			for (j=0; j<corrections.length;++j){
				if(varUtils.search(collated[term],corrections[j])==null){
					collated[term].push(corrections[j]);
					collated[term]=collated[term].sort();
				}
			}
			//collated[term]=collated[term].concat(corrections).sort();
		}
	}
	return collated;
}

/**
 * If you've got the spell check handler setup on your Solr instance you can use this routine to render the results. Writer type
 * must be json.
 * 
 * @param container - The html <span style="font-weight:bold">element</span> where the results should be inserted.
 * @param doc - Entire solr response doc (as JSON).
 * @param preRenderCallback - Function that will be called before rendering begins (use this to set up an containing elements for example).
 * <p>The call back should take the following params: <ul><li>HTML element (container)</li><li>Suggestions (map of original terms to suggestions/corrections)</li>
 * <li>callbackParams</li></ul>
 * @param postRenderCallback - Function that will be after before rendering completes.
 * @param renderCallback - Function that will handle actual rendering.
 * @param callbackParams - This will be passed to your call back functions as is (use for passing along environment information for example).
 */
SolrResultPaginator.prototype.renderSpellingSuggestions=function(container,doc,preRenderCallback,postRenderCallback,renderCallback,callbackParams){
	if(varUtils.isDef(doc.spellcheck) && varUtils.isDef(doc.spellcheck.suggestions) && doc.spellcheck.suggestions.length>0){
		var suggestions=this.collateSpellingSuggestions(doc.spellcheck.suggestions);
		if (varUtils.isDef(preRenderCallback)){
			preRenderCallback(container,suggestions,callbackParams);
		} else {
			jQuery("<p>Did you mean:</p><ul id='misspellingCont'><ul>").appendTo(container);
			container=jQuery("#misspellingCont");
		}
		for (var i in suggestions){
			if (varUtils.isDef(renderCallback)){
				renderCallback(container,i,suggestions[i],callbackParams);
			} else {
				var item="<li>SUGGESTION</li>";
				var terms="";
				for (var j=0;j<suggestions[i].length;++j){
					terms+=suggestions[i][j];
					if(j<suggestions[i].length){
						terms+=",";
					}
				}
				jQuery(item.replace(/SUGGESTION/,terms)).appendTo(container);
			}
		}
		/*
		for (var i=0;i<doc.spellcheck.suggestions.length;i+=2){
			var currSearchTerm=doc.spellcheck.suggestions[i];
			var suggestion=doc.spellcheck.suggestions[i+1];
			if (!varUtils.isDef(prevSearchTerm) || prevSearchTerm!=currSearchTerm){
				if (varUtils.isDef(renderCallback)){
					renderCallback(container,currSearchTerm,suggestion,callbackParams);
				} else {
					var item="<li>SUGGESTION</li>";
					var terms="";
					for (var j=0;j<suggestion.suggestion.length;++j){
						terms+=suggestion.suggestion[j];
						if(j<suggestion.suggestion.length){
							terms+=",";
						}
					}
					jQuery(item.replace(/SUGGESTION/,terms)).appendTo(container);
				}
			}
			var prevSearchTerm=currSearchTerm;
	    }
	    */
		if (varUtils.isDef(postRenderCallback)){
			postRenderCallback(container,suggestions,callbackParams);
		}
	}
}

/**
 * Function to handle querying the Solr server via AJAX. Use this object in conjunction with the filter utils
 * for a complete JavaScript Solr solution.
 * @param url - From the scheme through the path, i.e. http://your.solr.server/your/solr/search-handler
 * @param writerType - One of XML,JSON
 * @param fieldList - Field list, '*'=all fields
 * @param exclusions - a JSON object of field/value combinations that should not be included in the search results.
 * This value should alread be coded Lucene/Solr format, i.e.:{price:"[0 TO 0]"}. Separate values with a comma if you
 * would like to exclude multiple values per field.
 * @return
 */
function SolrUtils(url, writerType, fieldList, exclusions){
    this.fieldList=varUtils.hasValue(fieldList)?fieldList:"*";
    this.setWriterType(writerType);
    this.url=url;
    this.init();
}

/**
 * Sets the query generator back to a know state.
 */
SolrUtils.prototype.init=function(){
    this.sort={asc:new Object(),desc:new Object()};
    this.facetQueries=new Array();
    this.facetFields=new Array();
    this.queryParams=new Object();
    this.filter=new Object();
    /*
    this.query=null;
    this.filter=null;
    this.facetFields=null;
    */
}
/**
 * Parameter factory function - creates a basic parameter from which all other parameter types inherit.
 * @param inclusive - true to allow documents with this value to appear in the results, false to prohibit
 * @param field - field name
 * @param value - The value, strings that include spaces will be wrapped with double quotes indicated that solar
 * should treat them as phrases.
 * @boost boost - Boost value, documents with a higher boost are given greater weight in the search results.
 */
SolrUtils.prototype.makeParameter=function(required,inclusive,field,value,boost){
    function Parameter(required,inclusive,field,value,boost){
    	this.required=(required===true);
        this.inclusive=(inclusive===true);
        this.field=field;
        this.value=value;
        this.boost=boost;
        this.type="parameter";
    }

    /**
     * Returns true if <code>value</value> starts and ends with
     * something other than whitespace but has whitespace somewhere
     * in the middle.
     */
    Parameter.prototype.isPhrase=function(value){
        return /^(\w+\s+)+\w+$/.test(value);
    }

    Parameter.prototype.compare=function(a,b){
        return varUtils.compare(a,b);
    }

    Parameter.prototype.renderValue=function(value,boost){
        var template="VALUEBOOST";
        var results="";
        var isPhrase=false;
        if (varUtils.hasValue(value)){
            value=value.trim();
            value=(this.isPhrase(value)?'\"'+value+'\"':value);
            results=template.replace(/VALUE/,value)
                .replace(/BOOST/,(varUtils.isDef(boost) && boost>1)?("^"+boost):"");
        }
        return results;
    }
    /**
     * Returns this parameter type.
     */
    Parameter.prototype.getType=function(){
        return this.type;
    }

    /**
     * Attempts to merge two parameters into one. This does nothing but raise an exception if you
     * attempt to merge the values of two parameters from different fields - that's a no-no.
     */
    Parameter.prototype.merge=function(param){
        if (param.field!=this.field){
            throw "You are attempting to merge two parameters for two different fields types: "+this.field+" and "+param.field;
        }
    }

    /**
     * Renders the parameter for the query string. Override this method if you need something beyond the default rendering
     * scheme.
     */
    Parameter.prototype.render=function(){
        alert("Render not implemented for this parameter type!");
    }

    return new Parameter(required,inclusive,field,value,boost);
}
/**
 * Single value parameter factory method.
 * @param inclusive - true to allow documents with this value to appear in the results, false to prohibit
 * @param field - field name
 * @param value - The value, strings that include spaces will be wrapped with double quotes indicated that solar
 * should treat them as phrases.
 * @boost boost - Boost value, documents with a higher boost are given greater weight in the search results.
 */
SolrUtils.prototype.makeSingleValueParameter=function(required,inclusive,field,value,boost){
    function SingleValueParameter(){
        this.type="single";
    };
    SingleValueParameter.prototype=this.makeParameter(required,inclusive,field,value,boost);
    SingleValueParameter.prototype.mergeParameter=SingleValueParameter.prototype.merge;
    SingleValueParameter.prototype.merge=function(param){
        var compatibleTypes=["single","multi"];
        var type=param.getType();
        var merged=null;

        this.mergeParameter.call(this,param);
        if (!compatibleTypes.some(function(element, index, array){return element==type;})){
            throw "You are attempting to merge two incompatible types: "+type+" and "+this.getType();
        } else {
            var values=this.value.concat(param.value);
            merged=SolrUtils.prototype.makeMultiValueParameter(this.required,this.inclusive,this.field,values,this.boost);
        }

        return merged;
    }

    SingleValueParameter.prototype.render=function(){
        var template="INCLUSIVEFIELD:VALUE";
        return template.replace(/INCLUSIVE/,(this.reqired?"+":(this.inclusive?"":"-")))
            .replace(/FIELD/,this.field)
            .replace(/VALUE/,this.renderValue(this.value,this.boost));
    }

    return new SingleValueParameter();
}
/**
 * Multi value parameter factory method - applies grouping when rendered.
 * @param inclusive - true to allow documents with this value to appear in the results, false to prohibit
 * @param field - field name
 * @param values - Array or comma delimited string of values
 * @boost boost - Boost value, documents with a higher boost are given greater weight in the search results.
 */
SolrUtils.prototype.makeMultiValueParameter=function(required,inclusive,field,values,boost){
    if (typeof(values)=='string'){
        values=values.split(",");
    }

    function MultiValueParameter(){
        this.type="multi";
    };
    MultiValueParameter.prototype=this.makeSingleValueParameter(required,inclusive,field,values,boost);
    MultiValueParameter.prototype.render=function(){
        var template="INCLUSIVEFIELD:(VALUES)";
        var values="";
        for (var i=0; i<this.value.length;++i){
            values+=(this.renderValue(this.value[i])+(i<this.value.length-1?" OR ":""));
        }

        return template.replace(/INCLUSIVE/,(this.reqired?"+":(this.inclusive?"":"-"))).replace(/FIELD/,this.field).replace(/VALUES/,values);
    }

    return new MultiValueParameter();
}
/**
 * Range parameter factory method.
 * @param inclusive - true to allow documents with this range to appear in the results, false to prohibit
 * @param rangeInclusive - true of the solr should include values at the extreme ends of the range.
 * @param field - field name
 * @param values - Array of values. The array should contain an even number of values with the odd elements as the
 * low end of the range and the even elements the high end.
 * @boost boost - Boost value, documents with a higher boost are given greater weight in the search results.
 */
SolrUtils.prototype.makeRangeParameter=function(required,inclusive,rangeRequired,rangeInclusive,field,values,boost){
    function RangeParameter(rangeRequired,rangeInclusive,rangeBoost,values){
		this.rangeRequired=varUtils.isDef(rangeRequired)?rangeRequired:false;
		this.rangeInclusive=varUtils.isDef(rangeInclusive)?rangeInclusive:true;
		this.rangeBoost=rangeBoost;
		this.type="range";

        if (values.length%2!=0){
            throw "The values array for a range parameter must have an even number of values";
        }
    }

    RangeParameter.prototype=this.makeParameter(required,inclusive,field,values);
    RangeParameter.prototype.mergeParameter=RangeParameter.prototype.merge;

    RangeParameter.prototype.isOverlap=function(range){
        return (varUtils.isBetween(this.values[0],range.values[0],range.values[1])
                || varUtils.isBetween(this.values[1],range.values[0],range.values[1]));
    }

    RangeParameter.prototype.merge=function(param){
        this.mergeParameter.call(this,param);
        var merged=null;
        if (!param.getType()==this.getType()){
            throw "You are attempting to merge two incompatible types: "+param.getType()+" and "+this.getType();
        } else {
            merged=SolrUtils.prototype.makeRangeParameter(
            		    this.required,
                        this.inclusive,
                        this.rangeRequired,
                        this.rangeInclusive,
                        this.field,
                        this.value.concat(param.value).sort(this.compare),
                        this.boost);
        }

        return merged;
    }
    RangeParameter.prototype.render=function(){
        var template="INCLUSIVEFIELD:GROUPSRANGEGROUPE";
        var range="OPENLOW TO HIGHCLOSEBOOST";

        var ranges="";
        for (var i=0;i<this.value.length;i+=2){
            ranges+=range.replace(/OPEN/,(this.rangeInclusive?"[":"{"))
                .replace(/LOW/,this.value[i]==-Infinity?"*":this.value[i])
                .replace(/HIGH/,this.value[i+1]==Infinity?"*":this.value[i+1])
                .replace(/CLOSE/,(this.rangeInclusive?"]":"}"))
                .replace(/BOOST/,(varUtils.isDef(this.rangeBoost) && this.rangeBoost>1)?("^"+this.rangeBoost):"");
            if (i+2<this.value.length){
                ranges+=" OR ";
            }
        }

        var groupStart="";
        var groupEnd="";
        if ((this.value.length/2)>1){
            groupStart="(";
            groupEnd=")";
        }


        return template.replace(/INCLUSIVE/,(this.reqired?"+":(this.inclusive?"":"-")))
            .replace(/FIELD/,this.field)
            .replace(/GROUPS/,groupStart)
            .replace(/RANGE/,ranges)
            .replace(/GROUPE/,groupEnd);
    }

    return new RangeParameter(rangeRequired,rangeInclusive,boost,values);
}

/**
 * Attempts to merge two parameters together. Single value+multi or single+single value returns a multi value,
 * range+range will either return a array containing the two range parameters or include new ranges
 * where there is overlap. Range parameters cannot be combined with any other parameter type.
 *
 * The assumption here is that the parameters are for the same field - an exception is thrown if parameters
 * for two different fields are merged or if two incompatible fields are merged.
 */

SolrUtils.prototype.setWriterType=function(writerType){
    this.writerType=(varUtils.isDef(writerType) && varUtils.hasValue({xml:"xml",json:"json"}[writerType]))?writerType:"xml";
}

/**
 * Adds a field sort to the query results
 */
SolrUtils.prototype.addSort=function(field, direction){
    direction=(varUtils.isDef(direction) && varUtils.hasValue({asc:"asc",desc:"desc"}[direction]))?direction:"asc";
}

/**
 * Adds a parameter to the search query.
 */
SolrUtils.prototype.addQueryParameter=function(parameter){
    if (varUtils.isDef(this.queryParams[parameter.field])){
        this.queryParams[parameter.field]=this.queryParams[parameter.field].merge(parameter);
    } else {
        this.queryParams[parameter.field]=parameter;
    }
}

/**
 * Adds a parameter to the filter query.
 */
SolrUtils.prototype.addFilterParameter=function(parameter){
    if (varUtils.isDef(this.filter[parameter.field])){
        this.filter[parameter.field]=this.filter[parameter.field].merge(parameter);
    } else {
        this.filter[parameter.field]=parameter;
    }
}

/**
 * Exclusions are a way of globally excluding documents with the stated values from the results. They
 * will be included in every query.
 */
SolrUtils.prototype.addExclusion=function(field, value){

}

/**
 * Adds a facet query - Values can be a single value, an array, multiple values separated by commas (in which
 * case it will be converted to an array), or an array of JSON objects containing ranges, i.e. [{low:<value>, high:<value>},...]
 */
SolrUtils.prototype.addFacetQuery=function(facetQuery){
    this.facetQueries.push(facetQuery);
}

/**
 * Adds fields for faceting. Faceting provides a count for each document in the result count
 * set that contains that term in it's index. Facets work best on fields that don't have an
 * extra processing on them such as stemming.
 */
SolrUtils.prototype.addFacetField=function(field){

    if (!this.facetFields.some(function(element, index, array){return element==field;})){
        this.facetFields.push(field);
    }
}

/**
 * Helper method to add a field to the query string (correctly delimited,etc.).
 * This won't be used if you use the <code>executeQuery</code> method.
 */
SolrUtils.prototype.addFieldToQuery=function(query, field, delimiter){
    var param=varUtils.isDef(field.render)?field.render():field;
    return varUtils.hasValue(query)?(query+delimiter+param):param;
}

/**
 * Returns the generated query string. This is mostly for debugging purposes as you can paste this
 * into a browser address bar and check your results. That said is should work perfectly as the
 * target of an AJAX call or whatever.
 */
SolrUtils.prototype.getQueryString=function(startingDoc,resultSetSize,facet){
    var query="";
    for (param in this.queryParams){
        query=this.addFieldToQuery(query,this.queryParams[param]," ");
    }

    var filter="";
    for (param in this.filter){
        filter=this.addFieldToQuery(filter,this.filter[param]," ");
    }

    var facet="";
    if (this.facetFields.length>0 || this.facetQueries.length>0){
        facet=this.addFieldToQuery(facet,"facet=true","&");
        facet=this.addFieldToQuery(facet,"facet.method=enum","&");
        facet=this.addFieldToQuery(facet,"facet.mincount=1","&");
        facet=this.addFieldToQuery(facet,"facet.sort=false","&");

        for (var i=0;i<this.facetFields.length;++i){
            facet=this.addFieldToQuery(facet,"facet.field="+this.facetFields[i],"&");
        }
        for (var i=0;i<this.facetQueries.length;++i){
            facet=this.addFieldToQuery(facet,"facet.query="+encodeURIComponent(this.facetQueries[i].render()),"&");
        }
    }

    var queryString="";
    queryString=this.addFieldToQuery(queryString,"wt="+this.writerType,"&");
    queryString=this.addFieldToQuery(queryString,"fl="+this.fieldList,"&");
    queryString=this.addFieldToQuery(queryString,"rows="+resultSetSize,"&");
    queryString=this.addFieldToQuery(queryString,"start="+startingDoc,"&");
    queryString=this.addFieldToQuery(queryString,"fq="+encodeURIComponent(filter),"&");
    queryString=this.addFieldToQuery(queryString,facet,"&");
    queryString=this.addFieldToQuery(queryString,"q="+encodeURIComponent(query),"&");

    return queryString;
}

/**
 * Executes the query by sending a AJAX call to the server. This method will use the AJAX
 * functions of jQuery to put together the qs parameters so it won't exactly match what you'll
 * get back from getQueryString but functionally they'll be identical.
 *
 * If the writer type (result format type) is xml any xslt passed in will be applied otherwise it will
 * be ignored.
 */
SolrUtils.prototype.executeQuery=function(startingDoc,resultSetSize,mlt,callback){
    var params=new Object();
    params["wt"]=this.writerType;
    params["fl"]=this.fieldList;
    params["rows"]=resultSetSize;
    params["start"]=startingDoc;

    if (this.facetFields.length>0 || this.facetQueries.length>0){
        params["facet"]=true;
        params["facet.method"]="enum";
        params["facet.mincount"]="1";
        params["facet.sort"]=false;
        params["facet.field"]=this.facetFields;
        for (var i=0;i<this.facetQueries.length;++i){
            params["facet.query"]=this.facetQueries[i].render();
        }
    }

    var query=null;
    for (param in this.queryParams){
        query=this.addFieldToQuery(query,this.queryParams[param]," ");
    }
    params["q"]=query;

    var filter=null;
    for (param in this.filter){
        filter=this.addFieldToQuery(filter,this.filter[param]," ");
    }
    params["fq"]=filter;

    var results=null;
    try {
        jQuery.get(this.url,params,callback,this.writerType);
    } catch (e){
        alert(e);
    }
}

/**
 * WindowUtils wraps the ui functions in jQuery isolating the client from any underlying implemention details. In practice
 * this is probably just unnecessary an confusing and the client should just directly interact with their library of choice
 * (Google, YUI, Prototype? You decide!).
 * @return
 */
function WindowUtils(){
    var dialogs=null;
}

WindowUtils.prototype.getMaxZIndex=function(){
    var zElements=jQuery("*[style*='z-index']");
    var maxIndex=0;

    for (var i=0;i<zElements.length;++i){
        var element=jQuery(zElements[i]);
        var zIndex=(element.css("z-index")*1);
        if (zIndex>maxIndex){
            maxIndex=zIndex;
        }
    }

    return maxIndex;
}

WindowUtils.prototype.createDialog=function(containingElement,id,title,contents){
    //jQuery("<div id='"+id+"' style='visibility:hidden'></div>").appendTo(containingElement);
    jQuery("<div id='"+id+"'></div>").appendTo(containingElement);
    var dialog=jQuery("#"+id);
    dialog.html(contents);
    dialog.show();

    var config=new Object();
    config['title']=title;
    config['stack']=true;
    config['draggable']=true;
    config['zIndex']=this.getMaxZIndex()+2000;
    jQuery(dialog).dialog(config);

    return jQuery(dialog).dialog(config);
}

WindowUtils.prototype.destroyDialog=function(dialog){
}

WindowUtils.prototype.createOverlay=function(color){
}

WindowUtils.prototype.destroyOverlay=function(overlay){
}

WindowUtils.prototype.createLightbox=function(height,width,position){
}

WindowUtils.prototype.destroyLightbox=function(lightbox){
}

/**
 * Email Utility functions.
 */
function EmailUtils(parentElementId,formElementId,counterElementId,varName){
    this.windowUtils=new WindowUtils();
    this.additionFriendDialog=null;
    this.parentElementId=parentElementId;
    this.formElementId=formElementId;
    this.counterElementId=counterElementId;
    this.varName="emailUtils";
}


EmailUtils.prototype.showFriendDialog=function(){
    this.additionFriendDialog=this.windowUtils.createDialog(jQuery("#"+this.parentElementId),"friendDialog","Message will be sent to the following:",this.getTipText());
}

EmailUtils.prototype.validateParams=function(name, email){
    var isValid=varUtils.isDef(name)
        && varUtils.hasValue(name.val())
        && varUtils.isDef(email)
        && varUtils.hasValue(email.val());
    return isValid;
}

EmailUtils.prototype.getTipText=function(){
    var additionalFriends=jQuery("input[additionalFriend='true']");
    var template="<li id='ID'><button onclick='"+this.varName+".removeFriend(jQuery(this).parent())'>x</button>NAME</li>";
    var tipText="";
    if (additionalFriends.length>0){
        tipText="<ul>";//"<p>Your message will be sent to the following friends:</p><ul>";
        for (var i=0;i<additionalFriends.length;++i){
            var nameEmail=additionalFriends[i].value.split(/;/);
            tipText+=template.replace(/ID/,(nameEmail[0]+";"+nameEmail[1])).replace(/NAME/,nameEmail[0]);
        }
        /*
        tipText+=additionalFriends.reduce(function(previousValue, currentValue, index, array){
            var friend=previousValue;
            return friend+"<li>"+currentValue.val().split(/;/)[0]+"</li>";
        },"");
        */
        tipText+="</ul>";
    }

    return tipText;
}

EmailUtils.prototype.removeFriend=function(friend){
    jQuery("input[value='"+friend.attr("id")+"']").remove();
    jQuery(friend).remove();
    this.updateCounter();
}

EmailUtils.prototype.updateCounter=function(){
    if (varUtils.isDef(this.counterElementId)){
        var additionalFriends=jQuery("input[additionalFriend='true']");
        var additionalFriendCount=0;
        if (varUtils.isDef(additionalFriends)){
            additionalFriendCount=additionalFriends.length;
            if (additionalFriendCount>0){
                var name=null;
                if (additionalFriendCount>1){
                    name=additionalFriends[additionalFriendCount-1].value.split(/;/)[0]
                } else {
                    name=additionalFriends.val().split(/;/)[0]
                }
                var counter="<span id='additionFriendsMessage'>Message will be sent to NAMEADDITIONAL.</span>";
                counter=counter.replace(/NAME/,name).replace(/ADDITIONAL/,(additionalFriendCount>1?(" plus "+(additionalFriendCount-1)+" other <a onclick='"+this.varName+".showFriendDialog()' href='javascript:void(0);'>friend(s)</a>"):""));
                jQuery("#"+this.counterElementId).html(counter);
            } else {
                jQuery("#"+this.counterElementId).empty();
                if (varUtils.isDef(this.additionFriendDialog)){
                    this.additionFriendDialog.dialog("destroy");
                }
            }
        }
    }
}

EmailUtils.prototype.addFriend=function(name, email){
    if (this.validateParams(name,email)){
        var template="<input type='hidden' name='NAME' value='VALUE' additionalFriend='true'>";
        template=template.replace(/NAME/,"friend").replace(/VALUE/,name.val()+";"+email.val());
        jQuery(template).appendTo("#"+this.formElementId);
        this.updateCounter();
        //name.val("");
        //email.val("");

    }
}

EmailUtils.prototype.getFriendValue=function(name, email){
    return name.val()+";"+email.val();
}
