function isIE()
{
    return navigator.appName.indexOf("Internet Explorer") >= 0;
}

function isIE7()
{
    return isIE() && !isIE6();
}

var IsSafari = null;

function tx_isSafari()
{
    if (IsSafari == null) {
        IsSafari = navigator.userAgent.indexOf("Safari") >= 0;
    }
    return IsSafari;
}

function isFirefox()
{
    return navigator.userAgent.indexOf("Firefox") >= 0;
};

/*
 * Client side handler for DA ajax requests which update the dom.
 * Application must include prototype.js for this to work.
 */

function fireDirectActionAjax(element, url, afterEffect)
{
    var ajax = new Ajax.Request(
        url,
        {method: 'get',
         onSuccess: function(xmlhttp) {
                    var responseText = getResponseText(xmlhttp);
                    if (!handleAjaxExceptionPage(responseText)) {
                        //responseText = tx_stripHeadTagFromAjaxResponse(responseText);
                        var domUpdates = eval(responseText);

                        for (var elementId in domUpdates) {
                            $(elementId).innerHTML = domUpdates[elementId];
                        }
                        
                        if (afterEffect != undefined) {
                            afterEffect();
                        }            
                    }
                }
        }
    );
    return false;
}

/* Trick to execute embedded JS code in an AJAX response.  Norton sometimes strips <script> tags. */
function findAndEvalJavascript(reponseContents)
{
    /*  // XXX Make this work -- see Bug #355
    if (document.isDevMode && reponseContents.indexOf("<script") != -1) {
        alert("Inline script detected in dialog response.  Norton security prevents the use of such script tags -- use @@script@@");
        alert(reponseContents);
    }
    */
    var scriptMarker = "@@script@@";
    var indexOfStart = 0;
    var indexOfMarker = 0;
    while ((indexOfMarker = reponseContents.indexOf(scriptMarker, indexOfStart)) != -1) {
        indexOfStart = indexOfMarker + scriptMarker.length;
        var indexOfEnd = reponseContents.indexOf(scriptMarker, indexOfStart);
        var substring = reponseContents.substring(indexOfStart, indexOfEnd);
        eval(substring);
        indexOfStart = indexOfEnd + 1;
    }
}


/*
 * Some geometry utils.  This section should get replaced by Prototype.js
 */


// Returns the viewport height, not including any horiz scrollbar.
function windowInnerHeight()
{
    if (tx_isSafari()) {
        return window.innerHeight;
    }
    return document.documentElement.clientHeight;
}

// Returns the viewport width, not including any vertical scrollbar.
function windowInnerWidth()
{
    return document.documentElement.clientWidth;
}

// position.js has alternative versions of these
function absoluteTop(element)
{
    var absoluteTop = 0;
    var parentElement = element;
    do {
        absoluteTop += parentElement.offsetTop;
        // See bug 1165.  Never adjust for scrolXXX on the topmost (HTML or BODY) because that is document scroll
        if (parentElement.offsetParent != null) {
            absoluteTop -= parentElement.scrollTop;
        }
    } while ((parentElement = parentElement.offsetParent) != null)
    return absoluteTop;
}

function absoluteLeft(element)
{
    var absoluteLeft = 0;
    var parentElement = element;
    do {
        absoluteLeft += parentElement.offsetLeft;
        // See bug 1165.  Never adjust for scrolXXX on the topmost (HTML or BODY) because that is document scroll
        if (parentElement.offsetParent != null) {
            absoluteLeft -= parentElement.scrollLeft;
        }
    } while ((parentElement = parentElement.offsetParent) != null)
    return absoluteLeft;
}

function absoluteRight(element)
{
    return absoluteLeft(element) + element.offsetWidth;
}

function absoluteBottom(element)
{
    return absoluteTop(element) + element.offsetHeight;
}

function tx_getDocumentElement()
{
    return tx_isSafari() ? document.body : document.documentElement;
}

function tx_windowScrollTop()
{
    var documentElement = tx_getDocumentElement();
    return documentElement.scrollTop;
}

function tx_windowScrollLeft()
{
    var documentElement = tx_getDocumentElement();
    return documentElement.scrollLeft;
}

function getWindowTopEdge()
{
    var topEdge = tx_windowScrollTop();
    return topEdge;
}

function getWindowBottomEdge()
{
    var topEdge = tx_windowScrollTop();
    var windowHeight = windowInnerHeight();
    var windowBottomEdge = windowHeight + topEdge;
    return windowBottomEdge;
}

function getWindowLeftEdge()
{
    var leftEdge = tx_windowScrollLeft();
    return leftEdge;
}

function getWindowRightEdge()
{
    var leftEdge = tx_windowScrollLeft();
    var windowWidth = windowInnerWidth();
    var windowRightEdge = windowWidth + leftEdge;
    return windowRightEdge;
}

function availableSpaceRight(element, leftPos)
{
    var windowRightEdge = getWindowRightEdge();
    var elementRightEdge = element.clientWidth + leftPos;
    var availableSpace = windowRightEdge - elementRightEdge;
    return availableSpace;
}

function availableSpaceBottom(element, topPos)
{
    var windowBottomEdge = getWindowBottomEdge();
    var elementBottomEdge = element.clientHeight + topPos;
    var availableSpace = windowBottomEdge - elementBottomEdge;
    return availableSpace;
}

function availableSpaceLeft(element, leftPos)
{
    var windowLeftEdge = getWindowLeftEdge();
    var availableSpace = leftPos - windowLeftEdge;
    return availableSpace;
}

function availableSpaceTop(element, topPos)
{
    var windowTopEdge = getWindowTopEdge();
    var availableSpace = topPos - windowTopEdge;
    return availableSpace;
}

function isOffscreenRight(element, leftPos)
{
    var availableSpace = availableSpaceRight(element, leftPos);
    var isOffscreenRight = availableSpace < 0;
    return isOffscreenRight;
}

function isOffscreenBottom(element, topPos)
{
    var availableSpace = availableSpaceBottom(element, topPos);
    var isOffscreenBottom = availableSpace < 0;
    return isOffscreenBottom;
}

/**
*  The coverDivHeight/Width are used for computing the max width/height of a given document
*  and will use the windowInnerHeight/Width is that's greater than the document's width/height.
*/
function coverDivHeight()
{
    return Math.max(windowInnerHeight(), tx_getDocumentElement().scrollHeight) + 'px';
}

function coverDivWidth()
{
    return Math.max(windowInnerWidth(), tx_getDocumentElement().scrollWidth) + 'px';
}

function createCoverDiv(className, zIndex)
{
    var coverDiv = document.createElement('div');
    var coverDivStyle = coverDiv.style;
    if (className == null) {
        coverDivStyle.backgroundColor = 'white';
    }
    else {
        coverDiv.className = className;
    }
    coverDivStyle.position = 'absolute';
    coverDivStyle.width = coverDivWidth();
    coverDivStyle.height = coverDivHeight();
    coverDivStyle.top = '0px';
    coverDivStyle.left = '0px';
    coverDivStyle.zIndex = zIndex;
    
    return coverDiv;
}

/**
*  This function is used by Ajax hanlder routines to detect and display an ExceptionPage response.
*  If an ExceptionPage response is detected, this covers the existing page with a div and puts the
*  body of the ExceptionPage into this div.  You cannot viewSource to see the exception, so you can
*  shift-click in the upper-right corner of the page to see the exception.
*/
function handleAjaxExceptionPage(dialogContents)
{
    var isExceptionPage = false;
    var exceptionMarker = "<!-- ExceptionPage -->";
    var prefixSubstring = dialogContents.substring(0, exceptionMarker.length);
    if (prefixSubstring == exceptionMarker) {
        isExceptionPage = true;
        var bodyMarker = "<body>";
        var indexOfBodyStart = dialogContents.indexOf(bodyMarker);
        var indexOfBodyEnd = dialogContents.indexOf("</body>", indexOfBodyStart);
        var bodyContents = dialogContents.substring(indexOfBodyStart + bodyMarker.length, indexOfBodyEnd);
        var coverDiv = createCoverDiv(null, 1000);
        document.body.appendChild(coverDiv);
        coverDiv.innerHTML = bodyContents;
        window.scrollTo(0,0);
    }
    return isExceptionPage;
}

/**
 * ALWAYS use this to get the responseText from an XMLHttpRequest.
 * as it turns out safari sometimes returns null rather than empty string
 * for empty responses.  See bug 319.
 */
function getResponseText(xmlhttp)
{
    if (xmlhttp != null && xmlhttp.responseText != null) {
        return xmlhttp.responseText;
    }
    return "";
}
 
/**
 * Cookie utilities
 */
function setCookie(name, value, daysToExpire, domain)
{
    var cookieStr = name + "=" + value;
    var expires;
    if (daysToExpire) {
        var date = new Date();
        date.setTime(date.getTime() + daysToExpire*24*60*60*1000);
        cookieStr += "; expires="+date.toGMTString();
    }
    cookieStr += "; path=/";
    if (domain) {
        cookieStr += "; domain="+domain;
    }
    document.cookie = cookieStr;
}

function getCookie(name)
{
    var start = document.cookie.indexOf(name + "=")
    if (start > -1) {
        start += name.length + 1;
        var end = document.cookie.indexOf(";", start);
        if (end == -1)
            end = document.cookie.length;
        return unescape(document.cookie.substring(start, end));
    }
    return null;
}

function clearCookie(name, domain)
{
    setCookie(name, "", -365, domain)
}

// removes duplicates from a SORTED array
Array.prototype.unique = function(valueFunc)
{
    if (this.length < 2) {
        return;
    }

    if (valueFunc == null) {
        valueFunc = function(x) {return x;};
    }

    var prev = valueFunc(this[0]);
    for (var i = 1; i < this.length; ) {
        var curr = valueFunc(this[i]);
        if (prev == curr) {
            this.splice(i, 1);
        } else {
            prev = curr;
            i++;
        }
    }
}

// Ported from http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html.
// NOTE this assumes distinct values.  If there are duplicates, is might not return the first one.
Array.prototype.binarySearch = function(key)
{
    var low = 0;
    var high = this.length - 1;

    while (low <= high) {
        var mid = (low + high) >>> 1;
        var midVal = this[mid];

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}

// Returns the first text field in a form element or null if none exists.
function findFirstTextField(formElement)
{
    var firstTextField = null;
    var inputElementsArray = formElement.getElementsByTagName('INPUT');
    var arrayLength = inputElementsArray.length;
    for (var index = 0; index < arrayLength; index++) {
        var inputElement = inputElementsArray[index];
        var inputElementType = inputElement.type;
        if (inputElementType == 'text') {
            firstTextField = inputElement;
            break;
        }
    }
    return firstTextField;
}

function findChildTagWithId(element, tagName, targetId)
{
    var childArray = element.getElementsByTagName(tagName);
    if (childArray !== null && childArray !== undefined) {
        var childArrayLength = childArray.length;
        for (var index = 0; index < childArrayLength; index++) {
            var child = childArray[index];
            if (child.id == targetId) {
                return child;
            }
        }
    }
    return null;
}

function findChildWithId(element, targetId)
{
    var targetChild = null;
    var childArray = element.childNodes;
    if (childArray !== null && childArray !== undefined) {
        var childArrayLength = childArray.length;
        for (var index = 0; index < childArrayLength; index++) {
            var child = childArray[index];
            var childId = child.id;
            if (childId == targetId) {
                targetChild = child;
                break;
            }
            else {
                // recurse to the lext level
                targetChild = findChildWithId(child, targetId);
                if (targetChild != null) {
                    break;
                }
            }
        }
    }
    return targetChild;
}

function findChildWithClass(element, className)
{
    var targetChild = null;
    var childArray = element.childNodes;
    if (childArray !== null && childArray !== undefined) {
        var childArrayLength = childArray.length;
        for (var index = 0; index < childArrayLength; index++) {
            var child = childArray[index];
            if (child.className == className) {
                targetChild = child;
                break;
            }
            else {
                // recurse to the lext level
                targetChild = findChildWithClass(child, className);
                if (targetChild != null) {
                    break;
                }
            }
        }
    }
    return targetChild;
}

function findNontextChildren(element)
{
    var nontextChildren = new Array();
    var children = element.childNodes;
    var childrenLength = children.length;
    for (var index = 0; index < childrenLength; index++) {
        var child = children[index];
        if (child.nodeName != "#text") {
            nontextChildren.push(child);
        }
    }
    return nontextChildren;
}

function findFirstNontextChild(element)
{
    var children = element.childNodes;
    var childrenLength = children.length;
    for (var index = 0; index < childrenLength; index++) {
        var child = children[index];
        if (child.nodeName != "#text") {
            return child;
        }
    }
    return null;
}

/**
* This function works for getting the window height info when setting the cookie.  The function windowInnerHeight()
* does not work properly for the inline js code which is emitted in BrowsePage.html.  We must use this same function
* for all comparisons of window height where the product cell size is involved.
*/
function tx_windowSizeForCookie()
{
    // Note that windowInnerHeight() doesn't work properly when used in this context (probably too early for things to be setup properly).
    return isIE() ? document.documentElement.offsetHeight : window.innerHeight;
}

function tx_resetWindowSizeCookie(domain)
{
    var innerHeight = tx_windowSizeForCookie();
    // Note that "win.h='" must be kept in sync with same token in UserState.java
    // Note that closing token ';' must be kept in sync with same token in UserState.java
    // Note that cookie name 'sc5' must be kept in sync with same token in StoreUtil.WindowInfoCookieName
    setCookie('sc5', "win.h='" + innerHeight + "'", 0, domain);
}

function tx_stripHeadTagFromAjaxResponse(responseText)
{
    if (responseText == null) {
        return responseText;
    }
    
    // To avoid searches on big strings
    var responsePrefix = responseText.length > 30 ? responseText.substring(0, 30) : responseText;
    var openHeadTagIndex = responsePrefix.indexOf('<head>');
    if (openHeadTagIndex == -1) {
        openHeadTagIndex = responsePrefix.indexOf('<HEAD>');
    }
    
    if (openHeadTagIndex == -1) {
        return responseText;
    }
    
    var closeHeadTagIndex = responseText.indexOf('</head>');
    if (closeHeadTagIndex == -1) {
        closeHeadTagIndex = responseText.indexOf('</HEAD>');
    }
    
    if (closeHeadTagIndex == -1) {
        return responseText;
    }
    
    closeHeadTagIndex += 7;
    
    var strippedResponse = responseText.substring(0, openHeadTagIndex) + responseText.substring(closeHeadTagIndex);
    return strippedResponse;
}

function tx_setLocationAnchor(newAnchor)
{
    if (tx_isSafari()) { // See bug 1132, 1464 re safari anchor support
        return;
    }
    var url = window.location.href;
    var hashIndex = url.indexOf("#");
    var newUrl = hashIndex == -1 ? url : url.substring(0, hashIndex);

    // We never remove the hash because this will cause a reload.
    newUrl += "#";
    if (newAnchor && newAnchor.length > 0) {
        newUrl += newAnchor;
    }
    window.location.replace(newUrl);
}

function tx_setLocationToChildLink(aparent,event)
{
    var links = aparent.getElementsByTagName("a");
    if (links !== null && links !== undefined && links.length > 0) {
        window.location = links[0].href;
    }
    // in case the click was on the contained anchor, don't let it do normal action
    Event.stop(event);
}

function tx_getParameter(param)
{
    var i = window.location.search.indexOf(param + "=");
    if (i > -1) {
        i += param.length + 1;
        var j = window.location.search.indexOf("&", i);
        if (j == -1)
            j = window.location.search.length;
        return window.location.search.substring(i, j);
    }
    return null;
}

// Replace a URL param, appending to existing value if present, or clearing if new value is empty
// E.g. given 'shopstyle.com?a=123&b=456', 'a', 'xyz', returns 'shopstyle.com?a=xyz+123&b=456'
function tx_updateParam(url, param, value)
{
    var start = url.indexOf(param + '=');
    if (start > -1) {
        var end = url.indexOf('&', start);
        var paramLen = param.length + 1;
        var newValue = value == '' ? '' : value + '+' + (end > -1 ? url.substring(start + paramLen, end) : url.substring(start + paramLen));
        return url.substring(0, start) + param + '=' + newValue + (end > -1 ? url.substring(end) : '');            
    }
    else {
        return url + (url.indexOf('?') > -1 ? '&' : '?') + param + '=' + value;
    }
}

// Replace a URL param, replacing an existing value if present, or clearing if new value is empty
// E.g. given 'shopstyle.com?a=123&b=456', 'a', 'xyz', returns 'shopstyle.com?a=xyz&b=456'
function tx_replaceParam(url, param, value)
{
    var start = url.indexOf(param + '=');
    if (start > -1) {
        var end = url.indexOf('&', start);
        return url.substring(0, start) + param + '=' + value + (end > -1 ? url.substring(end) : '');            
    }
    else {
        return url + (url.indexOf('?') > -1 ? '&' : '?') + param + '=' + value;
    }
}

function TX_PopupContext()
{
    this._popupParent = null;
    this._timeout = null;
    this._popupChildId = null;
    this._popEnterTime = 0;
    this._topOffset = 0;
    this._leftOffset = 0;    
}

TX_PopupContext.prototype.mdescription = "TX_PopupContext class";

TX_PopupContext.prototype.initiatePopup = function(popupParent, event, childId, topOffset, leftOffset, popupDelay)
{
    //trace('tx_ initiate popup on ' + event.target);
    if (this._popupParent != null && popupDelay != 0) {
        if (this._popupParent == popupParent) {
            Event.stop(event);
            return;
        } else {
            this.closePopup();
        }
    }
    if ($(childId) == null) {
        return;
    }
    this._popupParent = popupParent;
    this._popupChildId = childId;
    this._topOffset = topOffset;
    this._leftOffset = leftOffset;    
    
    if (popupDelay > 0) {
        Event.observe(document.body, "mouseover", popup_handleMouseOverBody, false);
        Event.observe(this._popupChildId, "mouseover", popup_handleMouseOverPopup, false);
    }
    
    var handlerFunction = function() {
        //trace('tx_ handler');
        document.popupContext.openPopup();
        document.popupContext._timeout = null;
    }
    if (popupDelay > 0) {
        this._timeout = window.setTimeout(handlerFunction, popupDelay);
    } else {
        document.popupContext.openPopup();
    }
    
    Event.stop(event);
}

/**
    Make the child popup div visible, and position directly beneath the parent.
*/
TX_PopupContext.prototype.openPopup = function()
{
    var parentElement = this._popupParent;
    menuElement = $(this._popupChildId);
    if (menuElement == null) {
        return;
    }
    //trace('tx_ show popup child');
    menuElement.style.position = 'absolute';
    var isAbsolute = (this._topOffset == 0 && this._leftOffset == 0);
    
    menuElement.style.top = (isAbsolute ? absoluteBottom(parentElement) : parentElement.offsetTop + parentElement.offsetHeight + this._topOffset) + 'px';
    var parentLeft = (isAbsolute ? absoluteLeft(parentElement) : parentElement.offsetLeft + this._leftOffset);
    var windowEdgeRight = windowInnerWidth() + tx_windowScrollLeft();
    var elementWidth = absoluteRight(menuElement) - absoluteLeft(menuElement);
    if (parentLeft + elementWidth < windowEdgeRight) {
        menuElement.style.left = parentLeft+'px';
    }
    else {
        menuElement.style.left = (windowEdgeRight - elementWidth - 2)+'px';
    }
        
    menuElement.style.visibility='visible';
}

TX_PopupContext.prototype.closePopup = function()
{
    //trace('txp.closePopup');
    
    // cancel the timeout
    var popupContext = this;
    if (popupContext._timeout != null) {
        window.clearTimeout(popupContext._timeout);
        popupContext._timeout = null;
    }
    
    if (popupContext._popupParent != null) {
        // hide the menu
    
        menuElement = $(this._popupChildId);
        Event.stopObserving(this._popupChildId, "mouseover", popup_handleMouseOverPopup, false);
        popupContext._popupParent = null;
        popupContext._popupChildId = null;
        if (menuElement != null) {
            menuElement.style.visibility='hidden';
        }
    }
}

function popup_handleMouseOverBody(event)
{
    var tDelta = new Date().getTime() - document.popupContext._popEnterTime;
    //trace('tx_ mouseoverbody ' + tDelta);
    document.popupContext._popEnterTime = 0;
    // if the body mouse-over happens immediately after popup mouse-over we want to ignore it because it is just a move from one item to another
    if (tDelta > 20) {
        Event.stopObserving(document.body, "mouseover", popup_handleMouseOverBody, false);
        document.popupContext.closePopup();
    }
}

function popup_handleMouseOverPopup(event)
{
    //trace('tx_ mouseoverpopup');
    document.popupContext._popEnterTime = new Date().getTime();
}

document.popupContext = new TX_PopupContext();

function tx_addEventListenerToElement(element, eventName, handlerFunction, useCapture)
{
    if (element.addEventListener == undefined) {
        element.attachEvent("on" + eventName, handlerFunction);
    }
    else {
        element.addEventListener(eventName, handlerFunction, useCapture);
    }
}

// Utility that schedules a function, or calls it immediately if timeout=0
function tx_scheduleFunction(func, delay)
{
    if (delay > 0) {
        setTimeout(func, delay);
    } else {
        func();
    }
}
