/*jslint devel: false, browser: true, undef: true, unparam: true, sloppy: true, vars: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
/*global $, Element, Event, Tx, Effect, SSData, SSApp, Ajax, SSDialog, MArray, Position */

////////////////
// MRect
////////////////
function MRect(x, y, width, height)
{
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}

////////////////
// MPoint
////////////////
function MPoint(x, y)
{
    this.x = x;
    this.y = y;
}

////////////////
// MSize
////////////////
function MSize(width, height)
{
    this.width = width;
    this.height = height;
}

/********************
  Search text Field
*********************/

var Store = {};

Store.searchFocusHandler = function(element,initialValue)
{
    if (element.value === initialValue && !Element.hasClassName(element, 'hilite')) {
        element.className = element.className + ' hilite';
    }
};

Store.searchClickHandler = function(element,initialValue)
{
    if (element.value === initialValue) {
        element.value = '';
    }
};

Store.selectSearchField = function(onlyIfEdited)
{
    var searchField = document.getElementById('searchFld');
    if (searchField !== null) {
        var isDefault = (searchField.value === Store.defaultSearchFieldValue && Store.defaultSearchFieldValue.length !== 0);
        if (!onlyIfEdited || !isDefault) {
            SSApp.selectTextField(searchField);
        }
    }
};

Store.submitFormNamed = function(formId)
{
    var formElement = document.getElementById(formId);
    formElement.submit();
};

Store.handleFreeTextSearchClicked = function(event)
{
    var searchField = document.getElementById('searchFld');
    var trimmedValue = searchField.value.trim();
    if (trimmedValue !== Store.defaultSearchFieldValue || Store.defaultSearchFieldValue.length === 0) {
        var lastSearch = Tx.getParameter('fts');
        if (trimmedValue === '' && (lastSearch === null || lastSearch === '')) {
            // Empty search with no existing search is a no-op.
            searchField.value = '';
        } else {
            var searchType = $('searchType').value;
            Store.userDidSearch();
            if (searchType === 'all') {
                Store.submitFormNamed('searchFrm');
            } else {
                var newSearch = encodeURIComponent(searchField.value).replace(/\%20/g, "+");
                // for a 'within' search, avoid repeating the same term, and allow blank to clear search
                if (searchType === 'within' && lastSearch !== null && lastSearch.trim() === newSearch) {
                    window.location.reload();
                } else if (searchType === 'within' && document.browseContext !== null) {
                    // Ajax browsing: the current browse URL must be pulled from the cell browser context
                    window.location.href = Tx.updateParam(document.browseContext.currentQuery, 'fts', newSearch);
                } else {
                    var linkName = 'search_' + searchType;
                    window.location.href = Tx.updateParam($(linkName).href, 'fts', newSearch);
                }
            }
        }
    }
    if (event) {
        Event.stop(event);
    }
};

Store.handleSearchFrmKeyPress = function(event)
{
    if (event.keyCode === Event.KEY_RETURN) {
        Store.handleFreeTextSearchClicked(event);
    }
};

Store.searchMenuClicked = function(event, type, prompt)
{
    $('searchPrompt').innerHTML = prompt;
    $('searchType').value = type;
    if (Tx.popupContext) {
        Tx.popupContext.closePopup();
    }
    Store.selectSearchField(false);

    Event.stop(event);
};

Store.setupAutoCompleter = function(category)
{
    var showMatches = function(element, update) {
        if (!update.style.position || update.style.position === 'absolute') {
            update.style.position = 'absolute';
            Position.clone(element, update, {
                setHeight: false,
                setWidth: false,
                offsetTop: element.offsetHeight
            });
            if (Tx.popupContext) {
                // close any open drop-down menu when showing auto-completer
                Tx.popupContext.closePopup();
            }
        }
        Effect.Appear(update, {duration:0.02});
    };
    var hideMatches = function(element, update) {
        Effect.Fade(update, {duration:0.02});
    };
    var submitForm = function(element, sel) {
        Store.handleFreeTextSearchClicked();
    };
    var popupObserver = function() {
        if (document.autoCompleter) {
            document.autoCompleter.hide();
        }
    };
    if (Tx.popupContext) {
        // hide the auto-completer if another menu is opened
        Tx.popupContext.observers.addObserver("openPopup", popupObserver);
    }
    var updateQuery = function(element, entry) {
        // update the query to add the domain parameters
        return entry + "&cat=" + category;
    };

    // Instant search driven by auto-complete callback if enabled
    var doInstantSearch = function(text) {
    };

    document.observe('dom:loaded', function() {
        document.autoCompleter = new Ajax.Autocompleter('searchFld', 'searchAutoComplete', '/action/autoComplete',
            { paramName: 'search', minChars: 3, onValueUpdated: doInstantSearch,
            onShow: showMatches, onHide: hideMatches, afterUpdateElement: submitForm, method: 'get', callback:updateQuery });
    });
};



/*****************/
/* SSO Support   */
/*****************/

Store.ssoCheckRemoteLogin = function(loginUrl)
{
    // (Kevin 7/28/11) Allow the document.write
    /*jslint evil: true */

    var doCheck = Store._ssoRefreshCookie();
    if (doCheck) {
        Tx.setCookie('sc22', encodeURIComponent(document.referrer), 30/(24*60*60), SSData.cookieDomain);
        document.write('<script src="' + loginUrl + '" type="text/javascript"><\/script>');
    }
};

Store._ssoRefreshCookie = function()
{
    var checkCount = Tx.getCookie('sc13');
    var checkLogin = false;
    if (checkCount !== null) {
        checkCount = parseInt(checkCount, 10);
        if (isNaN(checkCount)) {
            checkCount = null;
        }
    }
    if (!checkCount) {
        checkCount = 0;
        checkLogin = true;
    }

    var nCheckSeconds = 6;
    if (checkCount < (5*60)/nCheckSeconds) {
        var ckdata = checkCount+1;
        Tx.setCookie('sc13', ckdata, 15/(24*60*60), SSData.cookieDomain);
        var cookieCheck = Tx.getCookie('sc13');
        if (cookieCheck === null) {
            // could not set cookie?!  Chrome has this problem if we're accessed with numeric ip address
            return false;
        }
        setTimeout(Store._ssoRefreshCookie, 1000*nCheckSeconds);
    }
    else {
        Tx.clearCookie('sc13', SSData.cookieDomain);
    }

    return checkLogin;
};

// notify, via a script request, across domains
// used in sso but is actually general functionality
Store.ssoNotify = function(href)
{
    // (Kevin 7/28/11) Allow the document.write
    /*jslint evil: true */

    document.write('<script src="' + href + '" type="text/javascript"><\/script>');
};

/*****************/
/* utility dialogs  */
/*****************/

//show the email list signup dialog on the home page
Store.showEmailListSignupDialog = function()
{
    var url = "/action/emailListSignupDialog";
    var theCloseFunction = {
            setMyCookie: function(){
                Tx.setCookie('sc34', 1, 365, SSData.cookieDomain);
            }
        };
    SSDialog.openDialog(null, url, null, null, false, {closeOnBackgroundClick:true, backgroundDivCSSClass:'modalDialogDarker', closeFunction: theCloseFunction.setMyCookie});
};

//show the home feature popup (a dialog) on the home page
Store.showHomeFeaturePopup = function(nameOfFeature)
{
    var url = "/action/homeFeaturePopup";
    var theCloseFunction = {
            setMyCookie: function(){
                Tx.setCookie('sc36', nameOfFeature, 365, SSData.cookieDomain);
            }
        };
    SSDialog.openDialog(null, url, null, null, false, {closeOnBackgroundClick:true, closeFunction: theCloseFunction.setMyCookie});
};

// bring up the confirmation dialog with the given title and message
// if the confirm argument exists, that will be the text of the confirm button (the dismiss button will then say "cancel")
// if the pending function argument exists, then the dialog will execute that function on confirmation, rather than submitting the confirmation form
Store.showConfirmation = function(title, message, confirm, pendingFunction)
{
    var url = "/action/confirmationDialog?title="+encodeURIComponent(title) + "&message=" + encodeURIComponent(message);
    if (confirm) {
        url += "&confirmButton=" + encodeURIComponent(confirm);
    }
    if (pendingFunction) {
        url += "&submit=0";
    }
    SSDialog.openDialog(null, url, '', null, false, {pendingFunction:pendingFunction});
};

/*****************/
/* internationalization  */
/*****************/

Store.strings = {};

Store.getMessage = function(key)
{
    var msg = Store.strings[key];
    if (!msg) {
        return '[' + key + ']';
    }
    else {
        return msg;
    }
};

/*****************/
/* User Icons  */
/*****************/

Store.showDeleteButton = function(iconDiv)
{
    var buttons = Element.select(iconDiv, '.userIconDeleteButton');
    if (buttons.length > 0) {
        buttons[0].style.visibility = "visible";
    }
};

Store.hideDeleteButton = function(iconDiv)
{
    var buttons = Element.select(iconDiv, '.userIconDeleteButton');
    if (buttons.length > 0) {
        buttons[0].style.visibility = "hidden";
    }
};

Store.hiliteIconLabels = function(iconDiv)
{
    var labels = Element.select(iconDiv, '.clickable');
    var i;
    for (i = 0; i < labels.length; i++) {
        labels[i].style.color = "black";
    }
};

Store.unhiliteIconLabels = function(iconDiv)
{
    var labels = Element.select(iconDiv, '.clickable');
    var i;
    for (i = 0; i < labels.length; i++) {
        labels[i].style.color = "";
    }
};


Store.scrollToVisible = function(elementId)
{
    var selectedLook = document.getElementById(elementId);
    var selectedLookTop = Tx.absoluteTop(selectedLook);
    window.scrollTo(0, selectedLookTop - 20);
};

Store.elementHeight = function(element)
{
    var height = Tx.isIE() ? element.offsetHeight : element.clientHeight;
    return height;
};

function MHintContext(hintName, alwaysDoHint, viewerContext)
{
    this.viewerContext = viewerContext;
    var cookieValue = Tx.getCookie('sc4');
    var hintVersion = cookieValue ? parseInt(cookieValue, 10) : 0;
    if (alwaysDoHint || hintVersion < 1) {
        this.hintVersion = hintVersion;

        this.isHintVisible = false;
        this.hasScrolled = false;

        this.hintBubble = document.getElementById(hintName);
        if (this.hintBubble && viewerContext) {
            window.setTimeout(this.beginShowingAnimation.bind(this), 4000);
        }
    }
}

MHintContext.prototype.beginShowingAnimation = function()
{
    if (!this.hasScrolled && this.viewerContext.canScroll()) {
        new Effect.Opacity(this.hintBubble, {from:0.0, to:1.0, duration:1.2, fps:16, transition:Effect.Transitions.linear,
                                             afterFinish:this.finishShowingAnimation.bind(this) });
    }
};

MHintContext.prototype.finishShowingAnimation = function()
{
    this.isHintVisible = true;
    if (this.hasScrolled) {
        // they scrolled while the hint was animating into view
        this.beginHidingAnimation(this);
    }
};

MHintContext.prototype.didScroll = function()
{
    if (!this.hasScrolled) {
        Tx.setCookie('sc4', 1, 365*100, SSData.cookieDomain);
        this.hasScrolled = true;
        if (this.isHintVisible) {
            this.beginHidingAnimation(this);
        }
    }
};

MHintContext.prototype.beginHidingAnimation = function()
{
    this.isHintVisible = false;
    new Effect.Opacity(this.hintBubble, { from:1.0, to:0.0, duration:0.4, fps:12, transition:Effect.Transitions.linear,
                                          afterFinish:this.finishHidingAnimation.bind(this) });
};

MHintContext.prototype.finishHidingAnimation = function()
{
    // Safari doesn't completely erase the hint unless you give it time to redraw after
    // the animation, but before the hint is hidden with display:none.
    window.setTimeout(this.andReallyFinishHidingAnimation.bind(this), 500);
};

MHintContext.prototype.andReallyFinishHidingAnimation = function()
{
    this.hintBubble.style.visibility = "hidden";
    // important to display:none, see CellBrowser.html
    this.hintBubble.style.display = "none";
};


/**
  Used to record explicit user searches in Omniture, which got
  harder once we cleaned up our urls and made them canonical
*/
Store.userDidSearch = function()
{
    Tx.setCookie('sc2', 'search|', null, SSData.cookieDomain);
};

/**
  Used to record diff nav types in Omniture without mucking up our urls.
*/
Store.userDidNavType = function(type)
{
    Tx.setCookie('sc2', 'navt|' + type, null, SSData.cookieDomain);
};

// get the user signin state:
// 0 - not signed in
// 1 - credentialed
// 2 - registered
Store.getSignInState = function()
{
    var signInState = 0;
    var handleAjaxResponse = function(xmlhttp) {
        var responseText = xmlhttp.responseText;
        signInState = +responseText; // convert to number
    };
    var optionsDict = {
            asynchronous: false,
            method: 'get',
            onSuccess: handleAjaxResponse
    };
    Tx.ajax('/action/isUserSignedIn', optionsDict);
    return signInState;
};

Store.openSignInDialog = function(anchorElement, signInUrl, pendingUrl, isPendingUrlForDialog)
{
    var signInState = Store.getSignInState();
    if (signInState === 2) {
        // registered user, just go
        if (isPendingUrlForDialog) {
            SSDialog.openDialog(anchorElement, pendingUrl);
        }
        else {
            window.location.href = pendingUrl;
        }
    }
    else {
        var baseElement = null;
        SSDialog.openDialog(anchorElement, signInUrl, pendingUrl, baseElement, isPendingUrlForDialog);
    }
    return false;
};

Store.setShippingCountry = function(country)
{
    var handleAjaxResponse = function(xmlhttp) {
        // refresh the page, but strip any dialog invoking or shipping country parameters (so we don't cycle)
        var url = window.location.href;
        url = Tx.removeParam(url, "dialog");
        url = Tx.removeParam(url, "shipping");
        if (url.charAt(url.length-1) === '#') {
            url = url.substr(0, url.length-1);
        }
        window.location = url;
    };

    // remember that we've set the shipping country, for analytics
    Tx.setCookie('sc2', 'shipC|'+country, null, SSData.cookieDomain);

    var optionsDict = {
            method: 'get',
            onSuccess: handleAjaxResponse
    };
    var url = '/action/changeShippingCountry?country='+country;
    Tx.ajax(url, optionsDict);

    return false;
};

Store.setPartnerIdFromAnchor = function()
{
    var anchor = window.location.hash;
    if (anchor && anchor.indexOf('#pid=') === 0) {
        var pid = Tx.getParameterFromUrl('pid', anchor);
        var pdata = Tx.getParameterFromUrl('pdata', anchor);
        Tx.fireDirectActionAjax('/action/setPartnerId?pid=' + pid + (!pdata ? '' : '&pdata=' + pdata));
    }
};


///////////////////
// CellBrowser sizes
///////////////////

/* all sizes copied in store.css too, and dependent on bo.Image's sizes too */
Store._smallProductCellSize = new MSize(70, 84);
Store._mediumProductCellSize = new MSize(138, 188);
Store._largeProductCellSize = new MSize(184, 255);
Store._xlargeProductCellSize = new MSize(348, 460);
Store._departmentCellSize = new MSize(315, 440);
Store._departmentSmallCellSize = new MSize(163, 194);
Store._mediumLookCellSize = new MSize(351, 230);
Store._largeLookCellSize = new MSize(351, 301);
Store._tinyLookWidgetCellSize = new MSize(150, 130);
Store._smallLookWidgetCellSize = new MSize(280, 260);
Store._largeLookWidgetCellSize = new MSize(480, 430);
Store._homeLookCellSize = new MSize(330, 265);
Store._promotionCellSize = new MSize(341, 162);
Store._userCellSize = new MSize(240, 130);
Store._priceAlertQueryCellSize = new MSize(245, 120);
Store._priceAlertProductCellSize = new MSize(148, 202);

Store.computeProductCellSize = function()
{
    var isSmallWindow = Tx.windowSizeForCookie() < Store.cellSizeThreshold;
    return isSmallWindow ?Store._mediumProductCellSize : Store._largeProductCellSize;
};

Store.smallProductCellSize = function()
{
    return Store._smallProductCellSize;
};

Store.mediumProductCellSize = function()
{
    return Store._mediumProductCellSize;
};

Store.largeProductCellSize = function()
{
    return Store._largeProductCellSize;
};

Store.xlargeProductCellSize = function()
{
    return Store._xlargeProductCellSize;
};

Store.departmentCellSize = function()
{
    return Store._departmentCellSize;
};

Store.departmentSmallCellSize = function()
{
    return Store._departmentSmallCellSize;
};

Store.computeLookCellSize = function()
{
    // magic constant also exists in the server
    var isSmallWindow = Tx.windowSizeForCookie() < Store.lookCellSizeThreshold;
    return isSmallWindow ? Store._mediumLookCellSize : Store._largeLookCellSize;
};

Store.tinyLookWidgetSmallCellSize = function()
{
    return Store._tinyLookWidgetCellSize;
};

Store.smallLookWidgetSmallCellSize = function()
{
    return Store._smallLookWidgetCellSize;
};

Store.largeLookWidgetSmallCellSize = function()
{
    return Store._largeLookWidgetCellSize;
};

Store.homeLookCellSize = function()
{
    return Store._homeLookCellSize;
};

Store.promotionCellSize = function()
{
    return Store._promotionCellSize;
};

Store.computeUserCellSize = function()
{
    return Store._userCellSize;
};

Store.priceAlertQueryCellSize = function()
{
    return Store._priceAlertQueryCellSize;
};

Store.priceAlertProductCellSize = function()
{
    return Store._priceAlertProductCellSize;
};

////////////////////////
// Analytics / Omniture
////////////////////////

Store.ajaxTrackingEnabled = false;

Store.setAjaxTrackingEnabled = function(flag)
{
    Store.ajaxTrackingEnabled = flag;
};

Store.isAjaxTrackingEnabled = function()
{
    return Store.ajaxTrackingEnabled;
};

Store.reportSuiteName = null;
Store.omniHostName = null;

Store.setReportSuiteName = function(name)
{
    Store.reportSuiteName = name;
};

Store.setOmniHostName = function(name)
{
    Store.omniHostName = name;
};

// linkElement should be the widget clicked on (not sure how it's used, but null doesn't
// work).  gestureName is a constant that will show up as a value under the Omniture AJAX prop.
Store.emitOmnitureAjaxPacket = function(linkElement, gestureName)
{
    Store.emitOmnitureMicroPacket(11, gestureName);
};

// A "micro" omni packet is one that just holds a few property values, but doesn't imply
// a full-on page view and visit.  This is useful for AJAX ops that happen within a page.
// The arguments are pairs of prop number then property value.
Store.emitOmnitureMicroPacket = function(/* ... */)
{
    if (Store.reportSuiteName) {
        // need to make the url different every time to defeat browser caching
        // pe_lnk stuff needed or else the packet will cause a page view
        var random = new Date().getTime();
        var im = new Image();
        var url = 'http://' + Store.omniHostName + '/b/ss/' + Store.reportSuiteName + '/1/H.7-pdv-2/' + random + '?ns=shopstyle';
        var i;
        for (i = 0; i < arguments.length-1; i += 2) {
            url += '&c' + arguments[i] + '=' + encodeURIComponent(arguments[i+1]);
        }
        url += '&pe=lnk_o&pev2=AJAX%20Link';
        im.src = url;
    }
/*
    // standard method, but it was too slow
    if (Store.reportSuiteName) {
        var s2=makeOmnitureObject(Store.reportSuiteName, "shopstyle");
        // linkTrackVars determines what values are sent to Omniture, and we only want to send the one prop
        // we set, since this isn't a full page view.
        s2.linkTrackVars="prop11";
        s2.prop11 = gestureName;
        s2.tl(linkElement, 'o', 'AJAX Link');
    }
*/
};

Store.emitComScorePacket = function(currentUrl)
{
    window.setTimeout(function() {
        if (!currentUrl) {
            currentUrl = window.location.hostname + window.location.pathname;
        }
        var im = new Image();
        im.src = "http://beacon.scorecardresearch.com/scripts/beacon.dll?c1=2&c2=6035900" +
            "&c3=&c4=" + currentUrl +
            "&c5=&c6=&c7=" + encodeURIComponent(document.location.href) +
            "&c8=" + encodeURIComponent(document.title) +
            "&c9=" + encodeURIComponent(document.referrer) +
            "&c10=" + encodeURIComponent(screen.width+'x'+screen.height) +
            "&c15=&rn=" + (new Date()).getTime();
    }, 1);
};

Store.emitComScoreScrollPacket = function(isBigScroll)
{
    if (isBigScroll) {
        Store.emitComScorePacket(null);
    }
};

Store.emitQuantcastScrollPacket = function(isBigScroll)
{
    if (isBigScroll) {
        Store.emitQuantcastAjaxPacket();
    }
};

Store.emitQuantcastAjaxPacket = function()
{
    if (quantserve !== undefined) {
        // (Kevin 7/28/11) _qoptions is global quantcast variable?
        _qoptions = {
            qacct:"p-36POJYHTosuxU",
            media:"webpage",
            event:"refresh"
        };
        quantserve();
    }
};

Store._previousExplanationParentDiv = null;

// debug hack for showing/hiding an explanation div
Store.showExplanation = function(parentDiv)
{
    Store.hideCurrentExplanation();
    Store._previousExplanationParentDiv = parentDiv;
    var explanation = Tx.findChildWithId(parentDiv, 'explanation');
    document.body.appendChild(explanation);
    explanation.id = "currentExplanation";
    explanation.style.display = "block";
};

Store.hideCurrentExplanation = function()
{
    Store.hideExplanation(Store._previousExplanationParentDiv);
};

Store.hideExplanation = function(parentDiv)
{
    var explanation = Tx.findChildWithId(document.body, 'currentExplanation');
    if (explanation && parentDiv) {
        explanation.style.display = "none";
        parentDiv.appendChild(explanation);
        explanation.id = "explanation";
    }
};

//
// Ad rotation
//
Store._lastRotateTime = (new Date()).getTime();
Store._rotateTimeoutId = null;
Store._adFrames = null;

Store.rotateAds = function(isBigScroll)
{
    // cache the list of ad iframes needing rotation.  This will allow us
    // to short out of this work in the case where nothing needs rotating.
    if (Store._adFrames === null) {
        Store._adFrames = [];
        var iframes = document.getElementsByTagName('iframe');
        var frameIndex;
        for (frameIndex = 0; frameIndex < iframes.length; frameIndex++) {
            var frame = iframes[frameIndex];
            if (frame.id && frame.id.startsWith('rotatingAd')) {
                Store._adFrames.push(frame);
            }
        }
    }

    if (Store._adFrames.length > 0) {
        // no matter what kind of scroll is going on, if a rotate was scheduled,
        // we want to defer it (meaning cancel and reschedule).  Also, if its a big
        // scroll and the current ad has been up long enough, then schedule it);
        if (Store._rotateTimeoutId || (isBigScroll && ((new Date()).getTime() - Store._lastRotateTime > 6000))) {
            if (Store._rotateTimeoutId) {
                window.clearTimeout(Store._rotateTimeoutId);
            }
            Store._rotateTimeoutId = window.setTimeout(Store.doRotateAds, 500);
        }
    }
};

Store.doRotateAds = function()
{
    var frameIndex;
    for (frameIndex = 0; frameIndex < Store._adFrames.length; frameIndex++) {
        var frame = Store._adFrames[frameIndex];
        var adSrc = Tx.replaceParam(frame.src, 'showNow', 't');
        if (frame.contentWindow) {
            frame.contentWindow.location.replace(adSrc);
        }
        else {
            frame.src = adSrc;
        }
        // track the ad, position is based on the id
        /*
        var i = frame.id.indexOf("rotatingAd-");
        if (i > -1) {
            Store.emitOmnitureMicroPacket(26, frame.id.substring(i+11));
        }
        */
    }
    Store._lastRotateTime = (new Date()).getTime();
    Store._rotateTimeoutId = null;
};

Store.manualAdRotation = function()
{
    Store.rotateAds(false);
    Store.doRotateAds();
};

//
// Locale flag selection feedback
//
Store.selectLocale = function(hoverId, hoverColor, selectedColor)
{
    //trace('SET');
    // first locale in the list is always the selected locale
    var selectedLocale = document.getElementById('locale0');
    selectedLocale.style.color = selectedColor;

    var hoverLocale = document.getElementById(hoverId);

    if (hoverLocale !== selectedLocale || hoverColor !== 'black') {
        // need to set the color
        hoverLocale.style.color = hoverColor;
    }
};

// If we have JS enabled, some product cells will have a details popup available, so we can
// convert their links to go straight to the retailer instead of the product page, which would
// just be redundant info wrt the details popup.
Store.fixAjaxProductLinks = function(parent)
{
    // find replacement items
    var productLinks = $(parent).select(".productLink");
    var i;

    for (i = productLinks.length-1; i >= 0; i--) {
        var productLink = productLinks[i];
        // we only change the product link if the domdata element is present
        var data = Tx.getDOMData(productLink);
        if (data) {
            productLink.href = data;
            productLink.rel = "nofollow";
            productLink.onclick = Tx.getDOMFunction(productLink);
        }
    }
};

Store.ajaxUpdateDiv = function(url, parentDiv, hideFirst, waitForResponse)
{
    var handleAjaxResponse = function(xmlhttp) {
        var dialogContents = Tx.getResponseText(xmlhttp);
        if (!Tx.handleAjaxExceptionPage(dialogContents)) {
            $(parentDiv).update(dialogContents);
            Tx.findAndEvalJavascript(dialogContents);
            var showParent = function() {
                // Hide until element can paint itself to avoid flicker
                $(parentDiv).show();
            };
            if (hideFirst) {
                window.setTimeout(showParent, 10);
            }
        }
    };
    if (hideFirst) {
        $(parentDiv).hide();
    }

    var optionsDict = {
        asynchronous: !waitForResponse,
        method: 'get',
        onSuccess: handleAjaxResponse
    };
    Tx.ajax(url, optionsDict);

    return false;
};

// check for cookie support
Store.supports_cookies = false;
Tx.setCookie('sc16', '1');
if (Tx.getCookie('sc16')) {
    Store.supports_cookies=true;
}

Store.gatherCheckboxIds = function(container)
{
    var ids = [];
    var inputs = container.getElementsByTagName('input');
    var i;
    for (i = 0; i !== inputs.length; i++) {
        var input = inputs[i];
        if (input.type === "checkbox" && input.checked) {
            ids.push(input.id);
        }
    }

    return ids;
};

Store.disableButton = function(button)
{
    Store.setEnabledButton(button, false);
};

Store.enableButton = function(button)
{
    Store.setEnabledButton(button, true);
};

Store.setEnabledButton = function(buttonId, enabled)
{
    // our buttons have outer <a> with onclick and inner div with class
    var button = $(buttonId);
    if (button !== null) {
        if (enabled) {
            if (button.stashedOnClick) {
                button.onclick = button.stashedOnClick;
            }
            button.stashedOnClick = null;
        }
        else {
            if (button.onclick) {
                button.stashedOnClick = button.onclick;
            }
            button.onclick = null;
        }
        // figure out the disabled class name, which is the normal buttonCell class name with Disabled appended
        var theDiv = button.select('div')[0];
        var classNames = Element.classNames(theDiv);
        var disabledClassName = "buttonCellDisabled";  //default
        var bcells = classNames.grep(/ButtonCell$/);
        if (bcells && bcells.length > 0) {
            disabledClassName = bcells[0] + 'Disabled';
        }

        if (enabled) {
            Element.removeClassName(theDiv, disabledClassName);
        }
        else {
            Element.addClassName(theDiv, disabledClassName);
        }
    }
};

Store.shareLink = function(url)
{
    var newWindow = window.open(
        url, "ShopStyleShare",
        // see note in productdetails.js on forcing new window in Firefox:
        "height=450,width=550,menubar=no,toolbar=no,location=no,directories=no,resizable=yes,scrollbars=yes");
    if (window.focus) {
        newWindow.focus();
    }
    return false;
};

/////////////////////////////
//Compatibility
//This section should get
//replaced by Prototype.js
/////////////////////////////

MProductImpressionTracker.trackers = [];

// We track impressions with a min/max range that we want to send to the server, and another range
// for what has already been sent.  We queue up what we want to send because we hope that it will be
// sent as a side-effect of another request (like /action/rawProducts), but if we have to just send
// it as a separate request we'll do that after not waiting very long.
function MProductImpressionTracker(viewerContext, callbackUrl)
{
    this.viewerContext = viewerContext;
    this.callbackUrl = callbackUrl + (callbackUrl.indexOf('?') > -1 ? '&' : '?') + 'imps=';
    this.scheduledTimer = 0;

    this.minIndexToSend = -1;   // range of uberIds we'd like to send
    this.maxIndexToSend = -1;
    this.minIndexSent = -1;     // range of uberIds we've already managed to send
    this.maxIndexSent = -1;

    var impressionTrackerCallback = this.noteImpressionsToSend.bind(this);
    viewerContext.observers.addObserver("scroll", impressionTrackerCallback);
    viewerContext.observers.addObserver("resize", impressionTrackerCallback);
    // The complete initialization of the viewerContext is delayed. Hence we have to delay
    // our own initialization as well
    viewerContext.observers.addObserver("init", impressionTrackerCallback);

    MProductImpressionTracker.trackers.push(this);
}

// Look at all trackers we know about, return a query string that one of them would
// like to have sent to the server.
MProductImpressionTracker.getImpressionsQueryString = function()
{
    var i;
    for (i = MProductImpressionTracker.trackers.length-1; i >= 0; i--) {
        var paramValue = MProductImpressionTracker.trackers[i].getImpsParamValue();
        if (paramValue) {
            return "imps=" + paramValue;
        }
    }
    return null;
};

MProductImpressionTracker.prototype.noteImpressionsToSend = function()
{
    var viewerContext = this.viewerContext;
    var lastIndex = viewerContext.getLastVisibleCellIndex();
    var firstIndex = viewerContext.getFirstVisibleCellIndex();

    if (this.minIndexSent >= 0) {
        // Exclude any indexes that have already been sent.
        // Because the routine we're calling is a NOP if startIndex > lastIndex, these lines work for all cases
        // 1 - new range is within the range already sent
        // 2 - range already sent is within the new range (and we have to do two sends)
        // 3 - new range is disjoint from range already sent, or only overlaps on one side
        this.noteImpressionIndexesToSend(firstIndex, Math.min(lastIndex, this.minIndexSent-1));
        this.noteImpressionIndexesToSend(Math.max(firstIndex, this.maxIndexSent+1), lastIndex);
    } else {
        // nothing sent yet, just work with the new range
        this.noteImpressionIndexesToSend(firstIndex, lastIndex);
    }
};

// Should be called with a range that does not include indexes that were already set.
MProductImpressionTracker.prototype.noteImpressionIndexesToSend = function(firstIndex, lastIndex)
{
    if (lastIndex < firstIndex) {
        return;
    }

    var foundNewData = false;
    if (this.minIndexToSend === -1) {
        // Nothing queued up to send, so new range becomes the queued range
        this.minIndexToSend = firstIndex;
        this.maxIndexToSend = lastIndex;
        foundNewData = true;
    } else if (firstIndex > this.maxIndexToSend+1 || lastIndex < this.minIndexToSend-1) {
        // The new range is disjoint from what we've already got queued up, so try to send
        // what we have queued now, and then we'll keep this new range as what's queued up.
        this.emitImpressionsData();
        this.minIndexToSend = firstIndex;
        this.maxIndexToSend = lastIndex;
        foundNewData = true;
    } else {
        // The new range overlaps what's queued, just expand that region as needed
        if (lastIndex > this.maxIndexToSend) {
            this.maxIndexToSend = lastIndex;
            foundNewData = true;
        }
        if (firstIndex < this.minIndexToSend) {
            this.minIndexToSend = firstIndex;
            foundNewData = true;
        }
    }

    if (foundNewData && this.scheduledTimer === 0) {
        // If we don't have a timer already scheduled, set one up.  Either this timer will fire and
        // we'll send the impression, or if we're lucky a rawProducts will happen and carry our
        // data for us.  We want to wait a bit longer than the faultCells(0) and faultCells(1)
        // calls in pan.js.
        this.scheduledTimer = window.setTimeout(this.emitImpressionsData.bind(this), 1700);
    }
};

MProductImpressionTracker.prototype.getImpsParamValue = function()
{
    if (this.minIndexToSend < 0) {
        return null;
    }

    var result = "";
    var maxIndex = -1;
    var collectUberIds = function(index, cell){
        if (!cell) {
            // not sure why this is happening
            return;
        }
        var rawCell = cell.getCellDomElement();
        if (!rawCell) {
            // this can happen if we haven't faulted yet
            return MArray.breakPerformMarker;
        }

        // fish the uberId out of the details URL
        var detailsFetcherDiv = Tx.findChildWithClass(rawCell, 'detailsFetcher');
        var detailsUrl = Tx.getDOMData(detailsFetcherDiv);
        var uberId = Tx.getParameterFromUrl('uberId', detailsUrl);
        result += ',' + uberId;
        if (index > maxIndex) {
            maxIndex = index;
        }
    };

    this.viewerContext.cells.performRange(this.minIndexToSend, this.maxIndexToSend + 1, collectUberIds);
    if (maxIndex > -1) {
        // update what we've sent
        var firstIndex = this.minIndexToSend;
        this.minIndexSent = (this.minIndexSent === -1) ? firstIndex : Math.min(firstIndex, this.minIndexSent);
        this.maxIndexSent = (this.maxIndexSent === -1) ? this.maxIndexToSend : Math.max(this.maxIndexToSend, this.maxIndexSent);
        if (maxIndex < this.maxIndexToSend) {
             // didn't send everything, update queued range
            this.minIndexToSend = maxIndex+1;
        } else {
            this.minIndexToSend = -1;
            this.maxIndexToSend = -1;
        }

        // add the geometry info to the front of the results
        return this.viewerContext.getNumRows() + ':' + (this.viewerContext.getPageCountLabel()-1) + ':'
                + this.viewerContext.indexToRow(firstIndex) + ':' + this.viewerContext.indexToCol(firstIndex)
                + result;
    } else {
        return null;
    }
};

MProductImpressionTracker.prototype.emitImpressionsData = function()
{
    var paramValue = this.getImpsParamValue();
    if (this.minIndexToSend >= 0) {
        // Still more work to do, push out another try.  Presumably this is because some cell hasn't
        // faulted in yet, so we don't know its uberId.
        this.scheduledTimer = window.setTimeout(this.emitImpressionsData.bind(this), 750);
    } else {
        this.scheduledTimer = 0;
    }
    if (paramValue) {
        var url = this.callbackUrl + paramValue;
        Tx.ajax(url, {method: 'get'});
    }
};

Store.removeSavedSearchParam = function(shouldRemoveParam)
{
    // This is called upon deletion of a saved search so the browse page refreshes with the same product query but without the saved search parameter.
    var url = window.location.href;
    if (shouldRemoveParam) {
        url = Tx.removeParam(url, "savedSearchId");
    }
    window.location = url;
};

Store.saveNewSavedSearch = function(event)
{
    Event.stop(event);
    var searchName = document.getElementById('searchNameField').value;
    var searchString = document.getElementById('searchStringField').value;
    var errorDiv = document.getElementById('saveSearchErrorDiv');
    if (!searchName || searchName.length === 0) {
        return false;
    }

    var handleAjaxResponse = function(xmlhttp) {
        var errorOrId = Tx.getResponseText(xmlhttp);
        var numericChars = "0123456789";
        if (numericChars.indexOf(errorOrId.charAt(0)) === -1) {
            // We have an error, write it and show it
            errorDiv.innerHTML = errorOrId;
            errorDiv.style.visibility = "";
        }
        else {
            var url = window.location.href;
            url = Tx.replaceParam(url, "savedSearchId", errorOrId);
            window.location = url;
        }
    };

    var optionsDict = {
            method: 'get',
            onSuccess: handleAjaxResponse
    };
    var urlAction = '/action/saveSearch?searchName=' + encodeURIComponent(searchName) + '&searchString=' + encodeURIComponent(searchString);
    Tx.ajax(urlAction, optionsDict);
    return false;
};

Store.resizeSavedSearchMenu = function(ssMenuBox, offsetbox, showingSaveSearchLink, originatingMenuLine, ssMenuTable)
{
    // First calculate the height
    var height = $(ssMenuTable).getHeight();
    var top = $(originatingMenuLine).viewportOffset().top - height;
    // Cutdown below black page header
    var cutdown = Math.max(0, (130 - top));
    if (cutdown !== 0) {
        height = height - cutdown;
        $(ssMenuBox).setStyle({height: height+'px'});
        if (showingSaveSearchLink) {
            // Scroll to bottom where save search link is, if its being displayed
            ssMenuBox.scrollTop = ssMenuBox.scrollHeight;
        }
    }
    else {
        $(ssMenuBox).setStyle({overflow:'hidden', overflowY: 'hidden', height: height+'px'});
    }
    // Position
    var offset = -1 * height;
    offset -= 10;
    if (Tx.isIE()) {
        offset += 2;
    }
    if (Tx.isFirefox()) {
        offset--;
    }
    $(offsetbox).setStyle({top: offset+"px"});

    if(Tx.isIE()) {
        //IE doesnt resize properly so we have to set the width explicitly
        var width = parseInt(ssMenuTable.firstDescendant().getWidth(), 10);
        if (cutdown === 0) {
            $(ssMenuBox).setStyle({width: width+'px'});
        }
        else {
            $(ssMenuBox).setStyle({width: (width + 17)+'px'});
        }
    }
};

Store.showMoreRelatedSearches = function()
{
    var expandedBox = $('expandedRelatedSearches');
    var oldList =  $('searchList1');
    var newList = $('searchList2');
    newList.update(oldList.innerHTML);
    expandedBox.show();
    if ($('expandedFeaturedProducts')) {           // won't exist for IE6
        $('expandedFeaturedProducts').hide();
    }
};

Store.hideMoreRelatedSearches = function()
{
    $('expandedRelatedSearches').hide();
};

Store.handleResizeRelatedSearches = function()
{
    // hide more... links for relates pieces if the span with all the link text is too wide for container
    var searchList = $('searchList1');
    if (searchList) {           // won't exist for IE6
        var listRight = searchList.positionedOffset().left + searchList.getWidth();
        var moreLink = $('moreSearchesLink');
        moreLink.show();
        var moreRight = moreLink.positionedOffset().left + moreLink.getWidth();
        if (listRight <= moreRight) {
            moreLink.hide();
        }
    }

    searchList = $('featuredProductList1');
    if (searchList) {           // won't exist for IE6
        listRight = searchList.positionedOffset().left + searchList.getWidth();
        moreLink = $('moreProductsLink');
        moreLink.show();
        moreRight = moreLink.positionedOffset().left + moreLink.getWidth();
        if (listRight <= moreRight) {
            moreLink.hide();
        }
    }
};

Store.showMoreFeaturedProducts = function()
{
    var expandedBox = $('expandedFeaturedProducts');
    var oldList =  $('featuredProductList1');
    var newList = $('featuredProductList2');
    newList.update(oldList.innerHTML);
    expandedBox.show();
    if ($('expandedRelatedSearches')) {
        $('expandedRelatedSearches').hide();
    }
};

Store.hideMoreFeaturedProducts = function()
{
    $('expandedFeaturedProducts').hide();
};

Store.expandLookByline = function(event, elem, height)
{
    Event.stop(event);
    elem.style.height = height + 'px';
    elem.onclick = null;
    return false;
};

