var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

var quantityMessage = null;
var scOrderLoaded = false;
var scSummaryLoaded = false;
var MSG_INVALID_PACKAGE_QUANTITY = 'Please check a products quantity';
var Qs_Array = {
    get: function (data, field)
    {
        if (typeof field != 'undefined') {
            field = field.replace(/\]/g, '');
            var parts = field.split(/\[/);
            while ((name = array_shift(parts))) {
                if (!array_key_exists(name, data)) {
                    return null;
                }
                data = data[name];
            }
        }
        return data;
    },

    set: function (data, field, value)
    {
        if (typeof field == 'undefined') {
            return false;
        }
        field = field.replace(/\]/g, '');
        var parts = field.split(/\[/);
        var _data = data;
        while ((name = array_shift(parts))) {
            if (parts.length == 0) {
                _data[name] = value;
                break;
            }
            if (!array_key_exists(name, _data)) {
                _data[name] = {};
            }
            _data = _data[name];
        }
        return true;
    }
}

var App_ShoppingCart = {
    options: {},

    setOptions: function (options)
    {
        if (typeof options == 'undefined') {
            return false;
        }
        App_ShoppingCart.options = options;
    },

    setOption: function (name, value)
    {
        return Qs_Array.set(App_ShoppingCart.options, name, value)
    },

    addOptions: function (options)
    {
        $.extend(true, App_ShoppingCart.options, options);
    },

    getOption: function (name)
    {
        return Qs_Array.get(App_ShoppingCart.options, name);
    }
};

function lookupOnSubmit()
{
    showProgress();
    setTimeout('loadProduct();', 1000);
}

function renderPartNote(product)
{
    var html = '';
    var purch_note = $('purch_note', product).text();
    if (!purch_note.length) {
        return html;
    }
    var title = $('suffix', product).text() + ' - ';
    var short_num = $('short_num', product).text();
    var long_num = $('long_num', product).text();
    if (long_num.length) {
        title += long_num;
    } else {
        title += short_num;
    }
    html += '<a class="part_note_link" href = "#" onclick = "return false" title="hideselects=[off] cssbody=[hintbody] '
         + 'cssheader=[hinthdr] header=[' + htmlspecialchars(title) + '] body=['
         + nl2br(htmlspecialchars(purch_note)) + ']">'
         + '<img style="vertical-align:middle" src = "img/note.png" alt = "" class="part_note_icon" />'
         + '</a>';
    return html;
}

function showMessageCatalogAccessDeny()
{
    alert('You do not currently have cataloging privileges.\nPlease contact your salesperson for further details!');
}

function shoppingCartShowMessage() 
{
    if (typeof quantityMessage == 'string' && quantityMessage.length && scSummaryLoaded && scOrderLoaded) {
        alert(quantityMessage);
        quantityMessage = null;
    }
}

var newAddedProducts = new Array(12);
var SCPQMTimers = {};

function scPakageQuantityMessageHide(id_product) 
{
    $('#msg_pkg_qty_' + id_product).remove();
    if (SCPQMTimers[id_product]) {
        SCPQMTimers[id_product] = null;
    }
}

function scQuantityBtnOnFocus(quantityElement)
{
    scIsValidPakageQuantity(quantityElement);
}

function scQuantityBtnOnBlur(quantityElement)
{
    var id_product = scGetProductIdByQuantityElement(quantityElement);
    scPakageQuantityMessageHide(id_product);
}

function scPQMessageClearTimer(id_product)
{
    if (SCPQMTimers[id_product]) {
        clearTimeout(SCPQMTimers[id_product]);
    }
}

function scPQMessageStartTimer(id_product)
{
    scPQMessageClearTimer(id_product);
    SCPQMTimers[id_product] = setTimeout('scPakageQuantityMessageHide(' + id_product + ')', 1000);
}

function scGetProductIdByQuantityElement(quantityElement)
{
    var parts = quantityElement.id.match(/(\d+)/g);
    var id_product = 0;
    if (parts.length) {
        id_product = parts[parts.length - 1];
    }
    return id_product;
}

function scIsValidPakageQuantity(quantityElement)
{
    var id_product = scGetProductIdByQuantityElement(quantityElement);
    var quantity = parseInt(quantityElement.value, 10);
    if (isNaN(quantity)) {
        //return true;
    }
    var inputPakageQuantity = getPreviousTag(quantityElement, 'INPUT');
    var td = getParentTag(quantityElement, 'TD');
    if (!inputPakageQuantity) {
        return true;
    }
    var pkg_qty = parseInt(inputPakageQuantity.value, 10);
    if (pkg_qty == 0 || pkg_qty == 1) {
        return true;
    }
    if (quantity % pkg_qty == 0) {
        scPakageQuantityMessageHide(id_product);
        return true;
    }
    //$(quantityElement).unbind('blur');
    //$(quantityElement).blur();
    //$(quantityElement).blur(function onblur(event) {scIsValidPakageQuantity(this);});
    //$(quantityElement).unbind('focus', scQuantityBtnOnFocus);
    //$(quantityElement).focus(scQuantityBtnOnFocus);
    var timerEvents = 'onmouseout = "scPQMessageStartTimer(' + id_product + ')" onmouseover = "scPQMessageClearTimer(' + id_product + ')" ';
    var left = -210 + Math.floor(td.clientWidth / 2);
    var tipMessage = 'This item must be ordered in multiples of <span class="msg_pkg_qty_value">' + pkg_qty + '</span>';
    if (isNaN(quantity)) {
        //tipMessage = 'Quantity must be numeric';
    }
    var html = '<div id = "msg_pkg_qty_' + id_product + '" '
             + 'onclick = "$(this).remove()" style="position:relative;">'
             + '<div ' + timerEvents + 'style = "cursor:pointer; width:230px; position:absolute; text-align:left; top:-30px; left:' + left + 'px;">'
             + '<div ' + timerEvents + 'style = "background-color:#FAFB89; padding:5px; border:solid red 1px; border-bottom:none;">'
             + tipMessage
             + '</div>'
             + '<div ' + timerEvents + 'style = "background-image:url(\'img/pkg_qty_tip_bottom.png\'); background-repeat:no-repeat; height:5px;"></div>'
             + '</div>'
             + '</div>';
    var container = getParentTag(inputPakageQuantity, 'TD');
    // if (inputPakageQuantity.parentNode && inputPakageQuantity.parentNode.tagName == 'SPAN') {
        // container = inputPakageQuantity.parentNode;
    // } else {
        // container = inputPakageQuantity;
    // }
    $('#msg_pkg_qty_' + id_product).remove();
    $(container).prepend(html);
    return false;
    /*
    var productName = $('.product_name_' + id_product + ':first').text();
    var message = 'The quantity you entered for part ' + productName + ' is not a multiple \n' 
                + 'of ' + pkg_qty + '. This part has not been added to your cart. Please enter\n'
                + 'a quantity that is a multple of ' + pkg_qty + ' to meet our packaging \n'
                + 'requirements';
    return message;
    alert(message);
    return false;
    */
}

function scShowPQAlert()
{
    var message = 'This item must be ordered in specific multiples. \n'
                + 'See the yellow pop-up next to the "Order Qty" field.';
    alert(message);
}


function scQuantityOnBlur()
{
    scIsValidPakageQuantity.call(this);
}

function addProduct(id)
{
    //alert('addProduct');
    showProgress();
    var t = $.makeArray($('.buy_qut'));
    var x = new Object();
    pattern = /^\d+$/
    var flag = false;
    var is_valid_pkg_qty = true;
    
    $('div.showcnt>form>span.showcnt>input.buy_qut').each( function () {
        if (this.value.match(pattern)) {
            if (!scIsValidPakageQuantity(this)) {
                is_valid_pkg_qty = false;
                return true;
            }
            x[this.id] = this.value;
            if (this.id) {
                str = this.id;
                newAddedProducts.push(str.substring(10));
            }
            flag = true;
        }
    });
    if (!is_valid_pkg_qty) {
        hideProgress();
        scShowPQAlert();
        return false;
    }
    if(flag){
        $.post(
             "cart?action=additem&redir=shopping/wizard", 
            x,
            addProductSuccess
            );
    } else {
        alert("You should fill in at least one 'Quantity' field. Please use numbers.");
		$('#loadprogress').hide();
    }
    return false;
}

function proceedToCheckoutOnSubmit()
{
    var err = [];
    var validQuantity = true;
    $('#orderForm input.quantity').each(function(){
        if (!scIsValidPakageQuantity(this)) {
            validQuantity  = false;
        }
    });
    if (!validQuantity) {
        err.push(MSG_INVALID_PACKAGE_QUANTITY);
    }
    if (err.length) {
        var msgError = '';
		for (var i = 0; i < err.length; i++) {
			msgError += err[i] + "\n";
		}
		alert(msgError);
		return false;
	}
    return confirmExit();
}

function confirmExit()
{
    var cnf_msg = "You changed quantities for parts on this page, but you have not added them to your cart.\nThese items will be added to your order in the quantities you specified if you click OK.";

    if (isQuantityChanged) {
        if (confirm(cnf_msg)) {
            if (!validQuantityValues($('#orderForm').formToArray())) {
                return false;
            }
            var options = {
                dataType: 'xml',
                success: function(responseXML, status, form) {
                    var data = $('list', responseXML).text();
                    $('a#lastAddedProduct').remove();
                    alert(data);
                },
                beforeSubmit: validQuantityValues
            };
            $('#orderForm').ajaxSubmit(options);
        }
    }
    return true;
}

function addProductSuccess()
{
	loadOrder();
	loadSummary();
}

function shoppingCartRenderNotFoundNotice(isSearchResults)
{
    var search_notice_box = '<div style="padding: 5px 5px 10px 10px;">';
    if (isSearchResults) {
        search_notice_box += '<strong>If the results above are still not what you are looking for, please use one of the options below:</strong> '
    }
    search_notice_box += '<div><img src="img/li.png" align="top">&nbsp;&nbsp; Please start a new search below or <a href="info/contact">contact us</a> for more information.</div>'
        + '<div><img src="img/li.png" align="top">&nbsp;&nbsp; <a href="service/request_a_product?part_number=' + num_desc + '">Click here</a> '
        + 'if you would like this part to be researched by Total Auto.</div>'
        + '</div>';
    $('#search_notice_box').html(search_notice_box);
}

function shoppingCartRenderClossReference(products, num_desc)
{
    var search_message_box = '<img align="middle" src="img/important.png"><span style="color:#FE0000;"><strong>' + num_desc + ' is not one of our current part numbers, listed below are any possible cross-references for ' + num_desc + '</strong></span>';
    $('#search_message_box').html(search_message_box);
    shoppingCartRenderNotFoundNotice((($("product", products).size() + $("cross_reference", products).size()) > 0));
    
    $('#cross_reference tr:gt(0)').remove();
 
     //amount of table columns
     var table_cols = $('#searched tr:eq(0) td').size();
         
     $("cross_reference", products).each(function(n){
        
        var s_num = $("cross_reference>long_num:eq("+n+")", products).text() ? $("cross_reference>long_num:eq("+n+")", products).text() : $("cross_reference>short_num:eq("+n+")", products).text;
        var style = "";
        var quant_msg = '';
        
        $('<tr id="tr_' + $("cross_reference>id:eq("+n+")", products).text()+'" class="light_yellow_row"><td>'
                            + '<a target="_blank" href="' + $("cross_reference>pop_image:eq("+n+")", products).text()
                            + '" onclick="return pop_up(this);" onmouseover="return pop_up(this);" onmouseout="return close_pop_up(this);"><img src="' + $("cross_reference>image:eq("+n+")", products).text() + '"></a>'
                + '</td><td>' + $("cross_reference>mfg:eq("+n+")", products).text()
                + '</td><td>' + $("cross_reference>maker:eq("+n+")", products).text()
                + '</td><td>' + $("cross_reference>description:eq("+n+")", products).text()
                + '</td><td><a title="click to use" href="nonmenu/prctsearchres?part=number&num_desc=' + s_num + '&subm=Search" '
				+ 'onclick="customerLog(\'CLICKED_CROSS-REFERENCE_LINK\', [\'' 
				+ (s_num.replace(/'"/, "\'")) + "', '" + (num_desc.replace(/'"/, "\'"))
				+ '\']); loadProduct(\''+s_num+'\', \'number\', 1); return false;">' + s_num + "</a>"
                + '</td>' +
          '</tr>')
        .appendTo('#cross_reference');
    });
}

function shoppingCartShowCrossReference()
{
    $('#cross_reference_available_msg').remove();
    $('#cross_reference').removeClass().addClass('showcnt');
    $('#cross_reference_header').before('<tr id="title_search_results_cross_reference" class="yellow_box"><td colspan="5"><div style="padding:5px; color:red; text-align:left;"><strong>Search results from the cross reference database:</strong></div></td></tr>');
    $('#cross_reference_header').addClass('yellow_row');
    document.location.hash = 'crossReferenceAnchor';
}

Array.prototype.inArray = function ( search_phrase )
{
  for( var i = 0; i < this.length; i++ )
  {
    if( search_phrase == this[i] )
    {
      return i;
    }
  }
  return false;
}

function getElementPos(obj){
	var l = 0;
	var t = 0;
	var w = obj.offsetWidth;
	var h = obj.offsetHeight;
	while (obj) {
		l += obj.offsetLeft;
		t += obj.offsetTop;
		if ((obj.tagName != "TABLE") && (obj.tagName != "BODY")) {
			l += (obj.clientLeft)?obj.clientLeft:0;
			t += (obj.clientTop)?obj.clientTop:0;
		}
		obj = obj.offsetParent;
	}
	var res = new Object();
	res.x = l;
	res.y = t;
	res.left = l;
	res.top = t;
	res.w = w;
	res.h = h;
	res.width = w;
	res.height = h;
	return res;
}

function getPreviousTag(obj, tag)
{
	var tmp = obj;
	while (tmp = tmp.previousSibling) {
		if (tmp.nodeName == tag) {
			return tmp;
		}
	}
	return null;
}

function getNextTag(obj, tag)
{
	var tmp = obj;
	while (tmp = tmp.nextSibling) {
		if (tmp.nodeName == tag) {
			return tmp;
		}
	}
	return null;
}

function getParentTag(obj, tag)
{
	var tmp = obj;
	while (tmp = tmp.parentNode) {
		if (tmp.nodeName == tag) {
			return tmp;
		}
	}
	return null;
}

	function getScrollY() 
	{
		scrollY = 0;    
		if (typeof window.pageYOffset == "number") {
			scrollY = window.pageYOffset;
		} else if (document.documentElement && document.documentElement.scrollTop) {
			scrollY = document.documentElement.scrollTop;
		}  else if (document.body && document.body.scrollTop) {
			scrollY = document.body.scrollTop; 
		} else if (window.scrollY) {
			scrollY = window.scrollY;
		}
		return scrollY;
	}
  
	function getInnerHeight() 
	{
	    height = 0;
	    if (window.innerHeight) {
			height = window.innerHeight - 18;
	  	} else if (document.documentElement && document.documentElement.clientHeight) {
	  		height = document.documentElement.clientHeight;
	  	} else if (document.body && document.body.clientHeight) {
	  		height = document.body.clientHeight;
		}
		return height;
	}

	function popUpDivImage_correctPosition(img, y)
	{
		var scrollY = getScrollY();
		var innerHeight = getInnerHeight();
		var imgW = img.width;
		var imgH = img.height;
		
		var max_y = scrollY + innerHeight - imgH;
		var msg = 'ScrollY:'+getScrollY()+"\n"+
			'y='+y+"\n"+
			'MAX Y:'+max_y+"\n"+
			imgW + 'x' +imgH+"\n"+
			'window.innerHeight = '+innerHeight;
		//alert(msg);
		//document.title = scrollY + ' ' + innerHeight;
		if (y > max_y) {
			y = max_y;
			y-=40;
		}
		img.parentNode.style.top = y+'px';
		img.parentNode.style.zIndex = 199;
		return;
	}
	
	function initLinkShowImg()
	{
		$('a.linkShowImg').each(function (n) {
			var current = this;
			this.onclick = showPopupImage;
			this.onmouseover = showPopupImage;
			this.onmouseout = function(event){
				$('div.popUpDivImage').remove();
				return false;
			}
		});
	}
		
	function showPopupImage(event)
	{
		$('div.popUpDivImage').remove();
		var pos = getElementPos(this.parentNode);
//        alert(pos.x + ' ' + this.parentNode);
		$('td.tdShowImg').removeClass('tdShowImg');
		$('<div class="popUpDivImage" style="top:-800px; left:' + (pos.x + pos.w) + 'px;"><img onload = "popUpDivImage_correctPosition(this, '+pos.y+')" src="'+this.href+'"></div>').prependTo(document.body);
		//$('<div class="popUpDivImage" style="left:'+window.event.offsetX+'px;"><img onload = "popUpDivImage_correctPosition(this, '+pos.y+')" src="'+this.href+'"></div>').prependTo(document.body);
		$(this.parentNode).addClass('tdShowImg');
		return false;
	}

	function addCommas(nStr)
	{
	    nStr += '';
	    x = nStr.split('.');
	    x1 = x[0];
	    x2 = x.length > 1 ? '.' + x[1] : '';
	    var rgx = /(\d+)(\d{3})/;
	    while (rgx.test(x1)) {
	        x1 = x1.replace(rgx, '$1' + ',' + '$2');
	    }
	    return x1 + x2;
	}
	
	function outMoney(amount)
	{
		var val = 0;
		if (typeof amount == 'string') {
			val = parseFloat(amount);
		} else if (typeof amount == 'number') { 
			val = amount;
		} else {
			return '';
		}
		return addCommas(val.toFixed(2));
	}

	function validQuantityValues(data, set, options) 
	{
        //alert('validQuantityValues');
        inputNamePattern = /^quantity\[\d+\]$/
        pattern = /^\d+$/
        var is_valid_pkg_qty = true;
        var is_valid_qty = true;
        for (i = 0; i < data.length - 1; i++) {
            var validName = inputNamePattern.test(data[i]['name']);
            var validValue = pattern.test(data[i]['value']);
            if (validName && !validValue) {
                alert('All fields of quantity must be numeric');
                return false;
            }
        }
        for (i = 0; i < data.length - 1; i++) {
            var validName = inputNamePattern.test(data[i]['name']);
            if (validName) {
                var element = $('input[name="' + data[i]['name'] + '"]').get(0);
                if (element) {
                    if (!scIsValidPakageQuantity(element)) {
                        is_valid_pkg_qty = false;
                    };
                }
            }
        }
        if (!is_valid_pkg_qty) {
            scShowPQAlert();
            return false;
        }
        return true;
	}
    
    function scAfterQuantityUpdate(elements) 
    {
        inputNamePattern = /^quantity\[\d+\]$/
		pattern = /^\d+$/
		for (var i in elements) {
			var validName = inputNamePattern.test(elements[i].name);
			if (validName) {
                var value = parseInt(elements[i].value, 10);
                if (value == 0) {
                    var parts = $('input[name="' + elements[i].name + '"]').attr('id').split(/[\]\[]/, 2);
                    var id_product = parts[1];
                    $('#squantity_' + id_product).val('');
                }
            }
        }
    }
	
	function confirmBuy()
	{
	    var requestData = new Object();
	    pattern = /^\d+$/
	    var isEmptyRequest = true;
        var is_valid_pkg_qty = true;
        
	    $('form.formBuyProd div.showcnt input.buy_qut').each( function () {
			if (this.value.match(pattern)) {
				requestData[this.id] = this.value;
				isEmptyRequest = false;
                if (!scIsValidPakageQuantity(this)) {
                    is_valid_pkg_qty = false;
                }
			}
		});
		$('div.showcnt>form>span.showcnt>input.buy_qut').each( function () {
			if (this.value.match(pattern)) {
				requestData[this.id] = this.value;
				isEmptyRequest = false;
                if (!scIsValidPakageQuantity(this)) {
                    is_valid_pkg_qty = false;
                }
			}
		});
		
	    if(!isEmptyRequest) {
			var cnf_msg = "You entered quantities for parts on this page, but you have not added them to your cart.\nThese items will be added to your order in the quantities you specified if you click OK.";
			if (confirm(cnf_msg)) {
                if (!is_valid_pkg_qty) {
                    hideProgress();
                    scShowPQAlert();
                    return false;
                }
				$.ajaxSetup({async: false});
				//$.post('cart?action=additem&redir=shopping/wizard', requestData, addProductSuccess);
				$.post('cart?action=additem&redir=shopping/wizard', requestData);
			}
	    }
		return true;
	}
	
	// this array alwo exists in Lib/smarty.func.php
	var RED_SUFFIXES = ['GMX', 'CHX', 'FDX', 'HAX', 'NSX', 'TAX'];
	function is_red_suffix(_suffix)
	{
		var ret = RED_SUFFIXES.inArray(_suffix);
		if (typeof ret == 'boolean' && ret == false) {
			return false;
		}
		return true;
	}
	
function openPopupByLocation(location, target, pW, pH)
{
	if (typeof pW != 'number') {
		pW = screen.width/2;
	}
	if (typeof pH != 'number') {
		pH = screen.height - screen.height/3;
	}
	var top = screen.height/2-pH/2;
	var left = screen.width/2-pW/2; 
	var params = 'toolbar=0,location=0,menubar=0,resizable=1,status=0,scrollbars=yes,screenX='
		+left+',screenY='+top+',top='+top+',left='+left
		+',width='+pW+',height='+pH;
	var wnd = window.open(location, target, params);
		wnd.opener = self;
		wnd.focus();
}

function number_format( number, decimals, dec_point, thousands_sep ) 
{
    var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
    var d = dec_point == undefined ? "." : dec_point;
    var t = thousands_sep == undefined ? "," : thousands_sep, s = n < 0 ? "-" : "";
    var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
    return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
}

function rewardPoints(points, qty)
{
    var points = number_format(parseFloat(points), 2, '.', ',');
    var qty = parseFloat(qty);
    if (qty) {
        points *= qty;
    }
    var result = number_format(points, 2, '.', ',');
    if (result == '0.00') {
        return '0';
    }
    return result;
}

function formErrorOnClick(elementName)
{
    document.location.hash = 'anchor_' + elementName;
    // var anchor = $('a[name="anchor_' + elementName + '"]').get(0);
    // if (anchor) {
        // var td = getParentTag(anchor, 'TD');
        // if (td) {
            // $('[name]:eq(1)', td).focus();
        // }
    // }
    return false;
}

function array_shift(array)
{
    if (array.length > 0) {
        return array.shift();
    }
    return null;
}

function array_key_exists(key, search)
{
    if( !search || (search.constructor !== Array && search.constructor !== Object) ){
        return false;
    }
    return key in search;
}

function customerLog(id_event, params, skip)
{
    
    if (typeof params == 'string') {
        params = [params];
    } else if (typeof params == 'undefined') {
        params = [];
    }
    
    if (typeof skip == 'undefined') {
        skip = 0;
    }
    var requestData = {id_event: id_event, skip: skip};
    for (var i = 0; i < params.length; i++) {
        requestData['params[' + i + ']'] = params[i];
    }
    $.ajax({
        url: '__customer_log',
        type: 'POST',
        async: false,
        data: requestData
    });
    return true;
}

function trim (str, charlist) 
{
    var whitespace, l = 0, i = 0;
    str += '';
	if (!charlist) {
        // default list
        whitespace = " \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000";
    } else {
        // preg_quote custom list
        charlist += '';
        whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '\$1');
    }
    l = str.length;
    for (i = 0; i < l; i++) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(i);
            break;
        }
    }
    
    l = str.length;
    for (i = l - 1; i >= 0; i--) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(0, i + 1);
            break;
        }
    }
    return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}

function sprintf( ) 
{
    var regex = /%%|%(\d+\$)?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
    var a = arguments, i = 0, format = a[i++];

    // pad()
    var pad = function(str, len, chr, leftJustify) {
        var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
        return leftJustify ? str + padding : padding + str;
    };

    // justify()
    var justify = function(value, prefix, leftJustify, minWidth, zeroPad) {
        var diff = minWidth - value.length;
        if (diff > 0) {
            if (leftJustify || !zeroPad) {
                value = pad(value, minWidth, ' ', leftJustify);
            } else {
                value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
            }
        }
        return value;
    };

    // formatBaseX()
    var formatBaseX = function(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
        // Note: casts negative numbers to positive ones
        var number = value >>> 0;
        prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
        value = prefix + pad(number.toString(base), precision || 0, '0', false);
        return justify(value, prefix, leftJustify, minWidth, zeroPad);
    };

    // formatString()
    var formatString = function(value, leftJustify, minWidth, precision, zeroPad) {
        if (precision != null) {
            value = value.slice(0, precision);
        }
        return justify(value, '', leftJustify, minWidth, zeroPad);
    };

    // finalFormat()
    var doFormat = function(substring, valueIndex, flags, minWidth, _, precision, type) {
        if (substring == '%%') return '%';

        // parse flags
        var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false;
        var flagsl = flags.length;
        for (var j = 0; flags && j < flagsl; j++) switch (flags.charAt(j)) {
            case ' ': positivePrefix = ' '; break;
            case '+': positivePrefix = '+'; break;
            case '-': leftJustify = true; break;
            case '0': zeroPad = true; break;
            case '#': prefixBaseX = true; break;
        }

        // parameters may be null, undefined, empty-string or real valued
        // we want to ignore null, undefined and empty-string values
        if (!minWidth) {
            minWidth = 0;
        } else if (minWidth == '*') {
            minWidth = +a[i++];
        } else if (minWidth.charAt(0) == '*') {
            minWidth = +a[minWidth.slice(1, -1)];
        } else {
            minWidth = +minWidth;
        }

        // Note: undocumented perl feature:
        if (minWidth < 0) {
            minWidth = -minWidth;
            leftJustify = true;
        }

        if (!isFinite(minWidth)) {
            throw new Error('sprintf: (minimum-)width must be finite');
        }

        if (!precision) {
            precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
        } else if (precision == '*') {
            precision = +a[i++];
        } else if (precision.charAt(0) == '*') {
            precision = +a[precision.slice(1, -1)];
        } else {
            precision = +precision;
        }

        // grab value using valueIndex if required?
        var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

        switch (type) {
            case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad);
            case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
            case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
            case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
            case 'i':
            case 'd': {
                        var number = parseInt(+value);
                        var prefix = number < 0 ? '-' : positivePrefix;
                        value = prefix + pad(String(Math.abs(number)), precision, '0', false);
                        return justify(value, prefix, leftJustify, minWidth, zeroPad);
                    }
            case 'e':
            case 'E':
            case 'f':
            case 'F':
            case 'g':
            case 'G':
                        {
                        var number = +value;
                        var prefix = number < 0 ? '-' : positivePrefix;
                        var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
                        var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
                        value = prefix + Math.abs(number)[method](precision);
                        return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
                    }
            default: return substring;
        }
    };

    return format.replace(regex, doFormat);
}


var Qs_Message = Class.create();

Qs_Message.prototype = {
    
    initialize: function (messages)
    {
        this.messages = messages;
    },
    
    get: function (name, language)
    {
        if (typeof language == 'undefined') {
            language = CURR_LANG;
        }
        if (typeof this.messages[language] == 'undefined') {
            return '';
        }
        if (typeof this.messages[language][name] == 'string') {
            return this.messages[language][name];
        }
        if (typeof this.messages[DEFAULT_LANGUAGE][name] == 'string') {
            return this.messages[DEFAULT_LANGUAGE][name];
        }
        return '';
    }
}

function is_string (/*anything*/ it)
{
    return !!arguments.length && it != null && (typeof it == "string" || it instanceof String); // Boolean
}

function is_array (/*anything*/ it)
{
    return it && (it instanceof Array || typeof it == "array"); // Boolean
}

function is_object (mixed_var)
{
    if(mixed_var instanceof Array) {
        return false;
    } else {
        return (mixed_var !== null) && (typeof( mixed_var ) == 'object');
    }
}

function intval (mixed_var, base) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: stensi
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   input by: Matteo
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // *     example 1: intval('Kevin van Zonneveld');
    // *     returns 1: 0
    // *     example 2: intval(4.2);
    // *     returns 2: 4
    // *     example 3: intval(42, 8);
    // *     returns 3: 42
    // *     example 4: intval('09');
    // *     returns 4: 9
    // *     example 5: intval('1e', 16);
    // *     returns 5: 30

    var tmp;

    var type = typeof( mixed_var );

    if (type === 'boolean') {
        return (mixed_var) ? 1 : 0;
    } else if (type === 'string') {
        tmp = parseInt(mixed_var, base || 10);
        return (isNaN(tmp) || !isFinite(tmp)) ? 0 : tmp;
    } else if (type === 'number' && isFinite(mixed_var) ) {
        return Math.floor(mixed_var);
    } else {
        return 0;
    }
}

var Qs_Form = {

    buttonOnClick: function (e)
    {
        var form = this.form;
        form.clk = this;
        if (this.type == 'image') {
            if (e.offsetX != undefined) {
                form.clk_x = e.offsetX;
                form.clk_y = e.offsetY;
            } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
                var offset = $(this).offset();
                form.clk_x = e.pageX - offset.left;
                form.clk_y = e.pageY - offset.top;
            } else {
                form.clk_x = e.pageX - this.offsetLeft;
                form.clk_y = e.pageY - this.offsetTop;
            }
        }
        setTimeout(function() {
            form.clk = form.clk_x = form.clk_y = null;
        }, 10);
    },

    setValue: function(/*Object*/obj, /*String*/name, /*String*/value)
    {
        var val = obj[name];
        if(is_string(val)) {
            obj[name] = [val, value];
        } else if (is_array(val)) {
            val.push(value);
        } else {
            obj[name] = value;
        }
    },

    toObject: function (/*DOMNode||String*/ formNode)
    {
        var ret = {};
        var exclude = 'file|submit|image|reset|button|';
        if (is_string(formNode)) {
            formNode = document.getElementById(formNode);
        }
        for (var i = 0; i<formNode.elements.length; i++) {
            var item = formNode.elements[i];
            if (!item) {
                continue;
            }
            var _in = item.name;
            var type = (item.type||"").toLowerCase();
            if(_in && type && exclude.indexOf(type) == -1 && !item.disabled){
                if (type == "radio" || type == 'checkbox') {
                    if (item.checked) {
                        Qs_Form.setValue(ret, _in, item.value);
                    }
                } else if (item.multiple) {
                    ret[_in] = [];
                    for (var j in item.options) {
                        if (
                            item.options[j] != null
                            && typeof item.options[j].tagName == 'string'
                            && item.options[j].tagName == 'OPTION'
                            && item.options[j].selected
                        ) {
                            Qs_Form.setValue(ret, _in, item.options[j].value);
                        }
                    }
                } else {
                    Qs_Form.setValue(ret, _in, item.value);
                    if(type == 'image'){
                        ret[_in+".x"] = ret[_in+".y"] = ret[_in].x = ret[_in].y = 0;
                    }
                }
            }
        }
        if (formNode.clk && !formNode.clk.disabled && formNode.clk.name) {
            if (formNode.clk.type == 'image') {
                ret[formNode.clk.name + '.x'] = formNode.clk_x;
                ret[formNode.clk.name + '.y'] = formNode.clk_y;
            } else {
                ret[formNode.clk.name] = formNode.clk.value;
            }
        }
        return ret; // Object
    }
}

function get_html_translation_table(table, quote_style) 
{
    var entities = {}, histogram = {}, decimal = 0, symbol = '';
    var constMappingTable = {}, constMappingQuoteStyle = {};
    var useTable = {}, useQuoteStyle = {};
    
    useTable      = (table ? table.toUpperCase() : 'HTML_SPECIALCHARS');
    useQuoteStyle = (quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT');
    
    // Translate arguments
    constMappingTable[0]      = 'HTML_SPECIALCHARS';
    constMappingTable[1]      = 'HTML_ENTITIES';
    constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
    constMappingQuoteStyle[2] = 'ENT_COMPAT';
    constMappingQuoteStyle[3] = 'ENT_QUOTES';
    
    // Map numbers to strings for compatibilty with PHP constants
    if (!isNaN(useTable)) {
        useTable = constMappingTable[useTable];
    }
    if (!isNaN(useQuoteStyle)) {
        useQuoteStyle = constMappingQuoteStyle[useQuoteStyle];
    }
    if (useTable == 'HTML_SPECIALCHARS') {
        // ascii decimals for better compatibility
        entities['38'] = '&amp;';
        entities['60'] = '&lt;';
        entities['62'] = '&gt;';
    } else if (useTable == 'HTML_ENTITIES') {
        // ascii decimals for better compatibility
	    entities['38'] = '&amp;';
	    entities['60'] = '&lt;';
	    entities['62'] = '&gt;';
	    entities['160'] = '&nbsp;';
	    entities['161'] = '&iexcl;';
	    entities['162'] = '&cent;';
	    entities['163'] = '&pound;';
	    entities['164'] = '&curren;';
	    entities['165'] = '&yen;';
	    entities['166'] = '&brvbar;';
	    entities['167'] = '&sect;';
	    entities['168'] = '&uml;';
	    entities['169'] = '&copy;';
	    entities['170'] = '&ordf;';
	    entities['171'] = '&laquo;';
	    entities['172'] = '&not;';
	    entities['173'] = '&shy;';
	    entities['174'] = '&reg;';
	    entities['175'] = '&macr;';
	    entities['176'] = '&deg;';
	    entities['177'] = '&plusmn;';
	    entities['178'] = '&sup2;';
	    entities['179'] = '&sup3;';
	    entities['180'] = '&acute;';
	    entities['181'] = '&micro;';
	    entities['182'] = '&para;';
	    entities['183'] = '&middot;';
	    entities['184'] = '&cedil;';
	    entities['185'] = '&sup1;';
	    entities['186'] = '&ordm;';
	    entities['187'] = '&raquo;';
	    entities['188'] = '&frac14;';
	    entities['189'] = '&frac12;';
	    entities['190'] = '&frac34;';
	    entities['191'] = '&iquest;';
	    entities['192'] = '&Agrave;';
	    entities['193'] = '&Aacute;';
	    entities['194'] = '&Acirc;';
	    entities['195'] = '&Atilde;';
	    entities['196'] = '&Auml;';
	    entities['197'] = '&Aring;';
	    entities['198'] = '&AElig;';
	    entities['199'] = '&Ccedil;';
	    entities['200'] = '&Egrave;';
	    entities['201'] = '&Eacute;';
	    entities['202'] = '&Ecirc;';
	    entities['203'] = '&Euml;';
	    entities['204'] = '&Igrave;';
	    entities['205'] = '&Iacute;';
	    entities['206'] = '&Icirc;';
	    entities['207'] = '&Iuml;';
	    entities['208'] = '&ETH;';
	    entities['209'] = '&Ntilde;';
	    entities['210'] = '&Ograve;';
	    entities['211'] = '&Oacute;';
	    entities['212'] = '&Ocirc;';
	    entities['213'] = '&Otilde;';
	    entities['214'] = '&Ouml;';
	    entities['215'] = '&times;';
	    entities['216'] = '&Oslash;';
	    entities['217'] = '&Ugrave;';
	    entities['218'] = '&Uacute;';
	    entities['219'] = '&Ucirc;';
	    entities['220'] = '&Uuml;';
	    entities['221'] = '&Yacute;';
	    entities['222'] = '&THORN;';
	    entities['223'] = '&szlig;';
	    entities['224'] = '&agrave;';
	    entities['225'] = '&aacute;';
	    entities['226'] = '&acirc;';
	    entities['227'] = '&atilde;';
	    entities['228'] = '&auml;';
	    entities['229'] = '&aring;';
	    entities['230'] = '&aelig;';
	    entities['231'] = '&ccedil;';
	    entities['232'] = '&egrave;';
	    entities['233'] = '&eacute;';
	    entities['234'] = '&ecirc;';
	    entities['235'] = '&euml;';
	    entities['236'] = '&igrave;';
	    entities['237'] = '&iacute;';
	    entities['238'] = '&icirc;';
	    entities['239'] = '&iuml;';
	    entities['240'] = '&eth;';
	    entities['241'] = '&ntilde;';
	    entities['242'] = '&ograve;';
	    entities['243'] = '&oacute;';
	    entities['244'] = '&ocirc;';
	    entities['245'] = '&otilde;';
	    entities['246'] = '&ouml;';
	    entities['247'] = '&divide;';
	    entities['248'] = '&oslash;';
	    entities['249'] = '&ugrave;';
	    entities['250'] = '&uacute;';
	    entities['251'] = '&ucirc;';
	    entities['252'] = '&uuml;';
	    entities['253'] = '&yacute;';
	    entities['254'] = '&thorn;';
	    entities['255'] = '&yuml;';
    } else {
        throw Error("Table: "+useTable+' not supported');
        return false;
    }
    if (useQuoteStyle != 'ENT_NOQUOTES') {
        entities['34'] = '&quot;';
    }
    if (useQuoteStyle == 'ENT_QUOTES') {
        entities['39'] = '&#039;';
    }
    // ascii decimals to real symbols
    for (decimal in entities) {
        symbol = String.fromCharCode(decimal)
        histogram[symbol] = entities[decimal];
    }
    return histogram;
}

function htmlspecialchars (string, quote_style) 
{
    var hash_map = {}, symbol = '', tmp_str = '', entity = '';
    tmp_str = string.toString();
    if (false === (hash_map = this.get_html_translation_table('HTML_SPECIALCHARS', quote_style))) {
        return false;
    }
    hash_map["'"] = '&#039;';
    for (symbol in hash_map) {
        entity = hash_map[symbol];
        tmp_str = tmp_str.split(symbol).join(entity);
    }
    return tmp_str;
}

function nl2br (str, is_xhtml) 
{
    var breakTag = '';
    breakTag = '<br />';
    if (typeof is_xhtml != 'undefined' && !is_xhtml) {
        breakTag = '<br>';
    }
    return (str + '').replace(/([^>]?)\n/g, '$1'+ breakTag +'\n');
}
function echo () {
    // http://kevin.vanzonneveld.net
    // +   original by: Philip Peterson
    // +   improved by: echo is bad
    // +   improved by: Nate
    // +    revised by: Der Simon (http://innerdom.sourceforge.net/)
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Eugene Bulkin (http://doubleaw.com/)
    // +   input by: JB
    // +   improved by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
    // %        note 1: If browsers start to support DOM Level 3 Load and Save (parsing/serializing),
    // %        note 1: we wouldn't need any such long code (even most of the code below). See
    // %        note 1: link below for a cross-browser implementation in JavaScript. HTML5 might
    // %        note 1: possibly support DOMParser, but that is not presently a standard.
    // %        note 2: Although innerHTML is widely used and may become standard as of HTML5, it is also not ideal for
    // %        note 2: use with a temporary holder before appending to the DOM (as is our last resort below),
    // %        note 2: since it may not work in an XML context
    // %        note 3: Using innerHTML to directly add to the BODY is very dangerous because it will
    // %        note 3: break all pre-existing references to HTMLElements.
    // *     example 1: echo('<div><p>abc</p><p>abc</p></div>');
    // *     returns 1: undefined

    var arg = '', argc = arguments.length, argv = arguments, i = 0;
    var win = this.window;
    var d = win.document;
    var ns_xhtml = 'http://www.w3.org/1999/xhtml';
    var ns_xul = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; // If we're in a XUL context

    var holder;

    var stringToDOM = function (str, parent, ns, container) {
        var extraNSs = '';
        if (ns === ns_xul) {
            extraNSs = ' xmlns:html="'+ns_xhtml+'"';
        }
        var stringContainer = '<'+container+' xmlns="'+ns+'"'+extraNSs+'>'+str+'</'+container+'>';
        if (win.DOMImplementationLS &&
            win.DOMImplementationLS.createLSInput &&
            win.DOMImplementationLS.createLSParser) { // Follows the DOM 3 Load and Save standard, but not
            // implemented in browsers at present; HTML5 is to standardize on innerHTML, but not for XML (though
            // possibly will also standardize with DOMParser); in the meantime, to ensure fullest browser support, could
            // attach http://svn2.assembla.com/svn/brettz9/DOMToString/DOM3.js (see http://svn2.assembla.com/svn/brettz9/DOMToString/DOM3.xhtml for a simple test file)
            var lsInput = DOMImplementationLS.createLSInput();
            // If we're in XHTML, we'll try to allow the XHTML namespace to be available by default
            lsInput.stringData = stringContainer;
            var lsParser = DOMImplementationLS.createLSParser(1, null); // synchronous, no schema type
            return lsParser.parse(lsInput).firstChild;
        }
        else if (win.DOMParser) {
            // If we're in XHTML, we'll try to allow the XHTML namespace to be available by default
            try {
                var fc = new DOMParser().parseFromString(stringContainer, 'text/xml');
                if (!fc || !fc.documentElement ||
                        fc.documentElement.localName !== 'parsererror' ||
                        fc.documentElement.namespaceURI !== 'http://www.mozilla.org/newlayout/xml/parsererror.xml') {
                    return fc.documentElement.firstChild;
                }
                // If there's a parsing error, we just continue on
            }
            catch(e) {
                // If there's a parsing error, we just continue on
            }
        }
        else if (win.ActiveXObject) { // We don't bother with a holder in Explorer as it doesn't support namespaces
            var axo = new ActiveXObject('MSXML2.DOMDocument');
            axo.loadXML(str);
            return axo.documentElement;
        }
        /*else if (win.XMLHttpRequest) { // Supposed to work in older Safari
            var req = new win.XMLHttpRequest;
            req.open('GET', 'data:application/xml;charset=utf-8,'+encodeURIComponent(str), false);
            if (req.overrideMimeType) {
                req.overrideMimeType('application/xml');
            }
            req.send(null);
            return req.responseXML;
        }*/
        // Document fragment did not work with innerHTML, so we create a temporary element holder
        // If we're in XHTML, we'll try to allow the XHTML namespace to be available by default
        //if (d.createElementNS && (d.contentType && d.contentType !== 'text/html')) { // Don't create namespaced elements if we're being served as HTML (currently only Mozilla supports this detection in true XHTML-supporting browsers, but Safari and Opera should work with the above DOMParser anyways, and IE doesn't support createElementNS anyways)
        if (d.createElementNS &&  // Browser supports the method
            d.documentElement.namespaceURI && (d.documentElement.namespaceURI !== null || // We can use if the document is using a namespace
            d.documentElement.nodeName.toLowerCase() !== 'html' || // We know it's not HTML4 or less, if the tag is not HTML (even if the root namespace is null)
            (d.contentType && d.contentType !== 'text/html') // We know it's not regular HTML4 or less if this is Mozilla (only browser supporting the attribute) and the content type is something other than text/html; other HTML5 roots (like svg) still have a namespace
        )) { // Don't create namespaced elements if we're being served as HTML (currently only Mozilla supports this detection in true XHTML-supporting browsers, but Safari and Opera should work with the above DOMParser anyways, and IE doesn't support createElementNS anyways); last test is for the sake of being in a pure XML document
            holder = d.createElementNS(ns, container);
        }
        else {
            holder = d.createElement(container); // Document fragment did not work with innerHTML
        }
        holder.innerHTML = str;
        while (holder.firstChild) {
            parent.appendChild(holder.firstChild);
        }
        return false;
        // throw 'Your browser does not support DOM parsing as required by echo()';
    };


    var ieFix = function (node) {
        if (node.nodeType === 1) {
            var newNode = d.createElement(node.nodeName);
            var i, len;
            if (node.attributes && node.attributes.length > 0) {
                for (i = 0, len = node.attributes.length; i < len; i++) {
                    newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i].nodeName));
                }
            }
            if (node.childNodes && node.childNodes.length > 0) {
                for (i = 0, len = node.childNodes.length; i < len; i++) {
                    newNode.appendChild(ieFix(node.childNodes[i]));
                }
            }
            return newNode;
        }
        else {
            return d.createTextNode(node.nodeValue);
        }
    };

    for (i = 0; i < argc; i++ ) {
        arg = argv[i];
        if (this.php_js && this.php_js.ini && this.php_js.ini['phpjs.echo_embedded_vars']) {
            arg = arg.replace(/(.?)\{\$(.*?)\}/g, function (s, m1, m2) {
                // We assume for now that embedded variables do not have dollar sign; to add a dollar sign, you currently must use {$$var} (We might change this, however.)
                // Doesn't cover all cases yet: see http://php.net/manual/en/language.types.string.php#language.types.string.syntax.double
                if (m1 !== '\\') {
                    return m1+eval(m2);
                }
                else {
                    return s;
                }
            });
        }
        if (d.appendChild) {
            if (d.body) {
                if (win.navigator.appName == 'Microsoft Internet Explorer') { // We unfortunately cannot use feature detection, since this is an IE bug with cloneNode nodes being appended
                    d.body.appendChild(stringToDOM(ieFix(arg)));
                }
                else {
                    var unappendedLeft = stringToDOM(arg, d.body, ns_xhtml, 'div').cloneNode(true); // We will not actually append the div tag (just using for providing XHTML namespace by default)
                    if (unappendedLeft) {
                        d.body.appendChild(unappendedLeft);
                    }
                }
            } else {
                d.documentElement.appendChild(stringToDOM(arg, d.documentElement, ns_xul, 'description')); // We will not actually append the description tag (just using for providing XUL namespace by default)
            }
        } else if (d.write) {
            d.write(arg);
        }/* else { // This could recurse if we ever add print!
            print(arg);
        }*/
    }
}


function print_r (array, return_val) {
    // http://kevin.vanzonneveld.net
    // +   original by: Michael White (http://getsprink.com)
    // +   improved by: Ben Bryan
    // +      input by: Brett Zamir (http://brett-zamir.me)
    // +      improved by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // -    depends on: echo
    // *     example 1: print_r(1, true);
    // *     returns 1: 1

    var output = "", pad_char = " ", pad_val = 4, d = this.window.document;
    var getFuncName = function (fn) {
        var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
        if (!name) {
            return '(Anonymous)';
        }
        return name[1];
    };

    var repeat_char = function (len, pad_char) {
        var str = "";
        for (var i=0; i < len; i++) {
            str += pad_char;
        }
        return str;
    };

    var formatArray = function (obj, cur_depth, pad_val, pad_char) {
        if (cur_depth > 0) {
            cur_depth++;
        }

        var base_pad = repeat_char(pad_val*cur_depth, pad_char);
        var thick_pad = repeat_char(pad_val*(cur_depth+1), pad_char);
        var str = "";

        if (typeof obj === 'object' && obj !== null && obj.constructor && getFuncName(obj.constructor) !== 'PHPJS_Resource') {
            str += "Array\n" + base_pad + "(\n";
            for (var key in obj) {
                if (obj[key] instanceof Array) {
                    str += thick_pad + "["+key+"] => "+formatArray(obj[key], cur_depth+1, pad_val, pad_char);
                } else {
                    str += thick_pad + "["+key+"] => " + obj[key] + "\n";
                }
            }
            str += base_pad + ")\n";
        } else if (obj === null || obj === undefined) {
            str = '';
        } else { // for our "resource" class
            str = obj.toString();
        }

        return str;
    };

    output = formatArray(array, 0, pad_val, pad_char);

    if (return_val !== true) {
        if (d.body) {
            this.echo(output);
        }
        else {
            try {
                d = XULDocument; // We're in XUL, so appending as plain text won't work; trigger an error out of XUL
                this.echo('<pre xmlns="http://www.w3.org/1999/xhtml" style="white-space:pre;">'+output+'</pre>');
            }
            catch (e) {
                this.echo(output); // Outputting as plain text may work in some plain XML
            }
        }
        return true;
    } else {
        return output;
    }
}

function vdie()
{
    var __getType = function( inp ) {
        var type = typeof inp, match;
        if (type == 'object' && !inp) {
            return 'null';
        }
        if (type == "object") {
            if (!inp.constructor) {
                return 'object';
            }
            var cons = inp.constructor.toString();
            if (match = cons.match(/(\w+)\(/)) {
                cons = match[1].toLowerCase();
            }
            var types = ["boolean", "number", "string", "array"];
            for (key in types) {
                if (cons == types[key]) {
                    type = types[key];
                    break;
                }
            }
        }
        return type;
    };
    var content = '';
    for (var i =0; i < arguments.length; i++) {
        content += (i + 1) + ') [' + __getType(arguments[i]) + '] ' + print_r(arguments[i], true) + '\n';
    }
    alert(content);
}