/*jslint devel: false, browser: true, undef: true, unparam: true, sloppy: true, vars: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
/*global Event, Tx, TxEventObserver, $, Store */

///////////////////
// MDetailsContext
///////////////////

function MDetailsContext()
{
    this._currentDetailsFetcherDiv = null;
    this._pendingShowDetailsTimeout = 0;
    this._pendingTimeoutPurpose = null;  // if a div, we are showing.  if null, we are hiding
    this._extraDelay = 0;
    this._isSmallWidget = false;
    this._disabled = false;
    this._handleMouseOverBody = this.handleMouseOverBody.bind(this);
    this._isClickToClose = Tx.isTouch();  // if true, there are buttons for open and close instead of mouseover-ing
    // for observing hide and show events
    this.observers = new TxEventObserver();
}

MDetailsContext.prototype.mdescription = "MDetailsContext class";

MDetailsContext.prototype.findMegaphoneImage = function(detailsFetcherDiv, isOverBottomEdge, isOverRightEdge)
{
    var detailsDiv = this.getDetailsDiv(detailsFetcherDiv);
    var megaphoneId = (isOverBottomEdge ? "b" : "t") + (isOverRightEdge ? "r" : "l");
    var megaphoneImage = Tx.findChildTagWithId(detailsDiv, "IMG", megaphoneId);
    if (megaphoneImage !== null && megaphoneImage.initialTop === undefined) {
        megaphoneImage.initialTop = megaphoneImage.style.top;
    }
    return megaphoneImage;
};

MDetailsContext.prototype.clearPendingTimeout = function()
{
    if (this._pendingShowDetailsTimeout !== 0) {
        //trace("clearTimeout");
        window.clearTimeout(this._pendingShowDetailsTimeout);
        this._pendingShowDetailsTimeout = 0;
    }
    this._pendingTimeoutPurpose = null;
};

MDetailsContext.prototype.getDetailsDiv = function(detailsFetcherDiv)
{
    return (detailsFetcherDiv && detailsFetcherDiv._detailsDiv) ? detailsFetcherDiv._detailsDiv : null;
};

MDetailsContext.prototype.clearCachedDetailsDiv = function(detailsFetcherDiv)
{
    if (detailsFetcherDiv) {
        if (detailsFetcherDiv === this._currentDetailsFetcherDiv) {
            this.clearPendingTimeout();
            this.hideCurrentDetails();
        }
        detailsFetcherDiv._detailsDiv = null;
        detailsFetcherDiv._isFetching = false;
    }
};

// Used by pages that present a "static" details view instead of using the DetailsPopup
MDetailsContext.prototype.installStaticDetails = function(detailsFetcherDiv)
{
    this.clearPendingTimeout();
    if (detailsFetcherDiv !== this._currentDetailsFetcherDiv) {
        this.hideCurrentDetails();
        this._currentDetailsFetcherDiv = detailsFetcherDiv;
        this._staticDetailsInstalled = true;
        this.observers.notify("show");
    }
};

MDetailsContext.prototype.positionAndDisplayDetailsPopup = function(detailsFetcherDiv)
{
    // trace("positionAndDisplayDetailsPopup");
    if (detailsFetcherDiv.offsetParent === null) {
        // We may have been removed from the DOM, e.g. after an ajax browser update.
        return;
    }
    var detailsDiv = this.getDetailsDiv(detailsFetcherDiv);
    if (detailsDiv === null && !detailsFetcherDiv._isFetching) {
        detailsFetcherDiv._isFetching = true;
        // We cache the detailsDiv on the detailsFetcher object.  If not there, we haven't been down
        // this path yet for this instance.  Locate the detailsDiv, yank it out of the detailsPopupHolder
        // and attach it to the document.body for document-relative positioning.
        var detailsContext = this;
        var tempDiv = document.createElement("div");
        var handleAjaxResponse = function(xmlhttp) {
            // trace("delayed positionAndDisplayDetails");
            var dialogContents = Tx.getResponseText(xmlhttp);
            if (!Tx.handleAjaxExceptionPage(dialogContents)) {
                //dialogContents = Tx.stripHeadTagFromAjaxResponse(dialogContents);
                tempDiv.innerHTML = dialogContents;
                Tx.findAndEvalJavascript(dialogContents);
                detailsDiv = Tx.findFirstNontextChild(tempDiv);
                detailsFetcherDiv._detailsDiv = detailsDiv;
                detailsDiv.detailsFetcherDiv = detailsFetcherDiv;
                document.body.appendChild(detailsDiv);
                // check to make sure that the user hasn't moved their mouse to another cell while
                // we were waiting for the reponse from the server (this check prevents multiple popups from showing
                // at the same time with some being stuck on the screen).
                if (detailsContext._pendingTimeoutPurpose === detailsFetcherDiv) {
                    detailsContext._positionAndDisplayDetailsPopup(detailsFetcherDiv, detailsDiv);
                }
            }
        };
        var optionsDict = {
                asynchronous: true,
                method: 'get',
                onSuccess: handleAjaxResponse
        };
        Tx.ajax(Tx.getDOMData(detailsFetcherDiv), optionsDict);
    }
    else if (detailsDiv) {
        this._positionAndDisplayDetailsPopup(detailsFetcherDiv, detailsDiv);
    }
};

// connect prefetched details, in the same way that fetched details are set up in positionAndDisplayDetailsPopup
MDetailsContext.prototype.setupPrefetched = function(productId)
{
    var detailsDiv = $('detailsdiv' + productId);
    var detailsFetcherDiv = $('detailsFetcher' + productId);
    if (detailsDiv && detailsFetcherDiv) {
        detailsFetcherDiv._detailsDiv = detailsDiv;
        detailsDiv.detailsFetcherDiv = detailsFetcherDiv;
    }
};

MDetailsContext.prototype._positionAndDisplayDetailsPopup = function(detailsFetcherDiv, detailsDiv)
{
    // Note: cannot cache these values as they change when user scrolls the CellBrowser
    var cellTop = Tx.absoluteTop(detailsFetcherDiv);
    var cellLeft = Tx.absoluteLeft(detailsFetcherDiv);
    var cellWidth = detailsFetcherDiv.offsetWidth;
    var cellHeight = detailsFetcherDiv.offsetHeight;

    //var megaphoneHeight = 50;
    var megaphoneWidth = 48;
    var lowerMegaphoneOffset = 15;
    var horizontalOffset = 0.33;
    var verticalOffset = 0.50;
    var isSmallCell = cellHeight < 45;
    if (isSmallCell) {
        horizontalOffset = 0.28;
        verticalOffset = 0.38;
    }
    var popupTop = cellTop + ((1 - verticalOffset) * cellHeight);
    var popupLeft = cellLeft + ((1 - horizontalOffset) * cellWidth) + megaphoneWidth;

    // position offscreen and make visible so we can get width/height info
    detailsDiv.style.top = "-1000px";
    detailsDiv.style.left = "0px";
    detailsDiv.className = "visible";

    if (this._isSmallWidget) {
        popupTop = cellTop + cellHeight - 18;
        popupLeft = cellLeft + (cellWidth - detailsDiv.offsetWidth) / 2;
        if (popupLeft <= 2) {
            popupLeft = 3;
        }
    }

    var topAdjustment = 0;
    var bottomAdjustment = 0;
    var isOverBottomEdge = false;
    var availableBottom = Tx.availableSpaceBottom(detailsDiv, popupTop) - 8; // gives extra margin at bottom of window
    if (availableBottom < 0) {
        // we only allow another 35% on top of the already 50% so we leave at least 15% of the cell showing.
        // also, if we only allow for a max movement of 25% of the height of the details to avoid placing the megaphone in weird place.
        if ((-availableBottom) < (0.35 * cellHeight) && ((-availableBottom) < (0.25 * detailsDiv.clientHeight))) {
           bottomAdjustment = availableBottom;
           popupTop += bottomAdjustment;
        }
        else {
            isOverBottomEdge = true;
            // if the bottomAdjustment leaves to little space of the cell showing, we throw it to the top.
            popupTop = cellTop + (verticalOffset * cellHeight) - detailsDiv.offsetHeight + lowerMegaphoneOffset;
            var availableTop = Tx.availableSpaceTop(popupTop);
            if (availableTop < 0) {
                topAdjustment = -availableTop;
                popupTop += topAdjustment;
            }
        }
    }
    var isOverRightEdge = Tx.isOffscreenRight(detailsDiv, popupLeft);
    if (isOverRightEdge) {
        if (this._isSmallWidget) {
            popupLeft += (Tx.availableSpaceRight(detailsDiv, popupLeft) - 2);
        }
        else {
            popupLeft = cellLeft - (detailsDiv.offsetWidth + megaphoneWidth) + (horizontalOffset * cellWidth);
            var availableLeft = Tx.availableSpaceLeft(popupLeft);
            if (availableLeft < 0) {
                popupLeft -= availableLeft;
            }
        }
    }

    // show or hide the close/hide/restore button
    var detailsButton = this.getDetailsButton(detailsFetcherDiv);
    if (this._isClickToClose) {
        var closeButton = Tx.findChildWithClass(detailsDiv, 'closeDetailsButton');
        closeButton.style.display = 'block';
    }
    else {
        var hideButton = Tx.findChildWithClass(detailsDiv, 'hideDetailsButton');
        if (hideButton !== null) {
            hideButton.style.display = detailsButton !== null ? 'block' : 'none';
        }
    }

    // show megaphone
    var megaphoneImage = this.findMegaphoneImage(detailsFetcherDiv, isOverBottomEdge, isOverRightEdge);
    if (megaphoneImage !== null) {
        if (topAdjustment > 0) {
            var newMegaphoneTop = (parseInt(megaphoneImage.initialTop, 10) - topAdjustment) + "px";
            megaphoneImage.style.top = newMegaphoneTop;
        }
        else if (bottomAdjustment < 0) {
            // we multiply the bottomAdjustment * 0.5 to allow the megphone to move slightly with the details
            // otherwise it looks a bit funny coming in from the bottom half of the details popup
            var newMegaphoneTopb = (parseInt(megaphoneImage.initialTop, 10) - (bottomAdjustment * 0.5)) + "px";
            megaphoneImage.style.top = newMegaphoneTopb;
        }
        else {
            // this is required in case we altered it in a previous cycle
            megaphoneImage.style.top = megaphoneImage.initialTop;
        }
        megaphoneImage.className = "visible";
    }
    // stash the megaphoneImage for later hiding in hideCurrentDetails()
    detailsDiv.megaphoneImage = megaphoneImage;

    detailsDiv.style.top =  popupTop + "px";
    detailsDiv.style.left = popupLeft + "px";

    if (Tx.isIE6()) {      // for widget popup; see MCell.adjustTwoLineDivHeight
        var twoLineDiv = Tx.findChildTagWithId(detailsDiv, 'DIV', 'twoLineDiv');
        if (twoLineDiv !== null) {
            twoLineDiv.style.height = "";
            if (twoLineDiv.offsetHeight > 24) {
                twoLineDiv.style.height = "24px";
            }
        }
    }

    this.observers.notify("show");

    document.detailsContext.hideCurrentDetailsButton();
};

// touchend on a department image
MDetailsContext.prototype.touchDepartmentImage = function(img)
{
    if (document.focusedViewerContext && document.focusedViewerContext.touchScroll) {
        // if we were scrolling via touch, then don't browse department
        // note that the touch events come to the image before they come to the scroll pane
        var ts = document.focusedViewerContext.touchScroll;
        if (ts.didScroll) {
            return;
        }
    }
    // simulate click on image
    var anchor = $(img).up('a');
    window.location = anchor.href;
};

//touchend on a look cell
MDetailsContext.prototype.touchLookCell = function(lookDiv)
{
    if (document.focusedViewerContext && document.focusedViewerContext.touchScroll) {
        // if we were scrolling via touch, then don't browse department
        // note that the touch events come to the image before they come to the scroll pane
        var ts = document.focusedViewerContext.touchScroll;
        if (ts.didScroll) {
            return;
        }
    }
    // find the look link
    var lookLink = Tx.findChildWithClass(lookDiv, 'menuText');
    if (lookLink !== null) {
        // go to the look
        window.location = lookLink.href;
    }
};

// touchend on a details fetcher
MDetailsContext.prototype.touchDetailsPopup = function(detailsFetcherDiv, event)
{
    if (document.focusedViewerContext && document.focusedViewerContext.touchScroll) {
        // if we were scrolling via touch, then don't bring up details
        // note that the touch events come to the detailsFetcher before they come to the scroll pane
        var ts = document.focusedViewerContext.touchScroll;
        if (ts.didScroll) {
            return;
        }
        else {
            // tell the scroller that the event is done
            ts.end(event);
        }
    }
    this.showDetailsPopup(detailsFetcherDiv, event, true, true);
};

MDetailsContext.prototype.showDetailsPopup = function(detailsFetcherDiv, event, noDelay, clickToClose)
{
    if (event) {
        Event.stop(event);
    }
    //trace("showDetailsPopup " + detailsFetcherDiv + " " + clickToClose);
    if (this._disabled) {
        return;
    }
    // if this._currentDetailsFetcherDiv == null, we're not showing details.
    var delay = noDelay ? 10 : (!this._currentDetailsFetcherDiv ? 500 + this._extraDelay : 300);
    this._extraDelay = 0;
    var detailsContext = this;
    detailsContext._isClickToClose = clickToClose;

    if (detailsContext._pendingShowDetailsTimeout !== 0 && detailsContext._pendingTimeoutPurpose === detailsFetcherDiv) {
        // The work we would do is already scheduled, so nothing for us to do
        return;
    }

    detailsContext.clearPendingTimeout();

    if (detailsFetcherDiv !== this._currentDetailsFetcherDiv) {
        var handlerFunction = function() {
            //trace("DELAYED showDetailsPopup");
            detailsContext._pendingShowDetailsTimeout = 0;
            detailsContext.hideCurrentDetails();
            detailsContext._currentDetailsFetcherDiv = detailsFetcherDiv;
            detailsContext._staticDetailsInstalled = false;
            if (!detailsContext._isClickToClose) {
                Event.observe(document.body, "mouseover", detailsContext._handleMouseOverBody, false);
            }
            detailsContext.positionAndDisplayDetailsPopup(detailsFetcherDiv);
        };
        this._pendingShowDetailsTimeout = window.setTimeout(handlerFunction, delay);
        this._pendingTimeoutPurpose = detailsFetcherDiv;

        if (Tx.popupContext) {
            // hide the generic popup when we kick off
            Tx.popupContext.closePopup();
        }
    }
};

// general routine to close the details, closes the popup and takes care of outstanding timeouts
MDetailsContext.prototype.closeDetailsPopup = function(noDelay)
{
    //trace("closeDetailsPopup start " + this._isClickToClose);
    if (this._pendingShowDetailsTimeout !== 0 && !this._pendingTimeoutPurpose) {
        // The work we would do is already scheduled, so nothing for us to do
        return;
    }
    this.clearPendingTimeout();
    var detailsContext = this;
    var handlerFunction = function() {
        //trace("closeDetailsPopup handler " + detailsContext._isClickToClose);
        detailsContext._pendingShowDetailsTimeout = 0;
        detailsContext.hideCurrentDetails();
        if (!detailsContext._isClickToClose) {
            Event.stopObserving(document.body, "mouseover", detailsContext._handleMouseOverBody, false);
        }
        else {
            // reset to default
            detailsContext._isClickToClose = false;
        }
    };
    if (noDelay || this._isClickToClose) {
        // do work immediately
        handlerFunction();
    }
    else {
        this._pendingShowDetailsTimeout = window.setTimeout(handlerFunction, 300);
        this._pendingTimeoutPurpose = null;
    }
};

MDetailsContext.prototype.isDetailsHidden = function()
{
    return Tx.getCookie('sc31') === '1';
};

MDetailsContext.prototype.showDetailsPopupClicked = function(detailsFetcherDiv, event)
{
    Tx.setCookie('sc31', '2', 30); // toggle into restore popups mode

    this.showDetailsPopup(detailsFetcherDiv, event, true, false);

    Store.emitOmnitureAjaxPacket(detailsFetcherDiv, "Details Shown");
};

MDetailsContext.prototype.hideDetailsClicked = function()
{
    Tx.setCookie('sc31', '1', 3); // hide for 3 days

    this.closeDetailsPopup(true);

    Store.emitOmnitureAjaxPacket(this._currentDetailsFetcherDiv, "Details Hidden");
};

MDetailsContext.prototype.closeDetailsPopupClicked = function()
{
    this.closeDetailsPopup(true);
};

MDetailsContext.prototype.hideCurrentDetails = function()
{
    //trace("hiding details ");
    this.observers.notify("hide");
    var detailsFetcherDiv = this._currentDetailsFetcherDiv;
    if (detailsFetcherDiv && !this._staticDetailsInstalled) {
        var currentDetailsDiv = this.getDetailsDiv(detailsFetcherDiv);
        if (currentDetailsDiv !== null) {
            var megaphoneImage = currentDetailsDiv.megaphoneImage;
            if (megaphoneImage) {
                megaphoneImage.className = "hidden";
                currentDetailsDiv.megaphoneImage = null;
            }
            currentDetailsDiv.className = "hidden";
            this._currentDetailsFetcherDiv = null;
        }
    }
};

//////////////////////////////
// Cell-Based Mouse Handling
//////////////////////////////

MDetailsContext.prototype.handleMouseOverBody = function()
{
    this.hideCurrentDetailsButton();
    this._currentDetailsButton = null;

    this.closeDetailsPopup(false);
};

MDetailsContext.prototype.handleMouseOutDetails = function(event)
{
    Event.stop(event);
};

MDetailsContext.prototype.handleMouseOverDetails = function(detailsDiv, event)
{
    //trace("handleMouseOverDetails" + detailsDiv.detailsFetcherDiv);
    if (!this._isClickToClose) {
        this.showDetailsPopup(detailsDiv.detailsFetcherDiv, event, false, false);
    }
    Event.stop(event);
};

MDetailsContext.prototype.handleMouseOverDetailsFetcher = function(detailsFetcherDiv, event)
{
    //trace("handleMouseOverDetailsFetcher" + detailsFetcherDiv + " " + this._isClickToClose);
    var detailsButton;
    if (this.isDetailsHidden() && (detailsButton = this.getDetailsButton(detailsFetcherDiv)) !== null) {
        if (this._currentDetailsButton !== detailsButton) {
            this.hideCurrentDetailsButton();
            this.hideCurrentDetails();
            this._currentDetailsButton = detailsButton;
            Event.observe(document.body, "mouseover", this._handleMouseOverBody, false);

            var detailsContext = this;
            var showDetailsButton = function() {
                if (detailsContext._currentDetailsButton === detailsButton) {
                    detailsButton.style.display = '';
                }
            };
            window.setTimeout(showDetailsButton, 300);
        }
    }
    else if (!this._isClickToClose) {
        this.showDetailsPopup(detailsFetcherDiv, event, false, false);
    }
    Event.stop(event);
};

MDetailsContext.prototype.handleMouseOutDetailsFetcher = function(detailsFetcherDiv, event)
{
    // (Kevin 7/28/11) allow unused param for possible tracing */
    /*jslint unparam: true */
    //trace("handleMouseOutDetailsFetcher" + detailsFetcherDiv);
    this.clearPendingTimeout();
    Event.stop(event);
};

MDetailsContext.prototype.handleMegaphoneClick = function(megaphoneImg, event)
{
    //trace("handleMegaphoneClick" + detailsFetcherDiv);
    var detailsFetcherDiv = this._currentDetailsFetcherDiv;
    var links = detailsFetcherDiv.getElementsByTagName('A');
    // note: the 0th link is always the rawDetailsUrl
    var link = links[1];
    if (link) {
        if (link.onclick) {
            link.onclick();
        }
        else {
            window.location.href = link.href;
        }
    }
    Event.stop(event);
};

MDetailsContext.prototype.getDetailsButton = function(detailsFetcherDiv)
{
    // may be null, if ProductCell's hidableDetails is false
    return Tx.findChildWithClass(detailsFetcherDiv, 'showDetailsButtonWrapper');
};

MDetailsContext.prototype.hideCurrentDetailsButton = function()
{
    if (this._currentDetailsButton) {
        this._currentDetailsButton.style.display = 'none';
    }
};

//////////////////////////////////////////////////////////////////////////////////////
// Below this point code executes upon parsing.  Above are all functions definitions.
//////////////////////////////////////////////////////////////////////////////////////

document.detailsContext = new MDetailsContext();

