///////////////////
// 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._isEmbedded = false;
    this._disabled = false;
    this._isClickToClose = false;  // 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 = 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 : null;
}

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

MDetailsContext.prototype.findContainingFetcherDiv = function(node)
{
    while (node != null) {
        if (node.tagName == 'DIV') {
            var detailsUrlTag = findChildTagWithId(node, "a", "rawDetailsUrl");
            if (detailsUrlTag != null) {
                return node;
            }
        }
        node = node.parentNode;
    }
    return null;
}

// Used by pages that present a "static" details view instead of using the DetailsPopup
MDetailsContext.prototype.installStaticDetails = function(detailsFetcherDiv)
{
    this._currentDetailsFetcherDiv = detailsFetcherDiv;
    this._fakeDetailsInstalled = true;
}

MDetailsContext.prototype.positionAndDisplayDetailsPopup = function(detailsFetcherDiv, event)
{
    // trace("positionAndDisplayDetailsPopup");
    var detailsDiv = this.getDetailsDiv(detailsFetcherDiv);
    if (detailsDiv == null && detailsFetcherDiv._isFetching == null) {
        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 = getResponseText(xmlhttp);
            if (!handleAjaxExceptionPage(dialogContents)) {
                //dialogContents = tx_stripHeadTagFromAjaxResponse(dialogContents);
                tempDiv.innerHTML = dialogContents;
                findAndEvalJavascript(dialogContents);
                detailsDiv = 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
        };
        var rawDetailsUrlTag = findChildTagWithId(detailsFetcherDiv, "a", "rawDetailsUrl");
        var url = rawDetailsUrlTag.href;
        new Ajax.Request(url, optionsDict);
    }
    else if (detailsDiv != null) {
        this._positionAndDisplayDetailsPopup(detailsFetcherDiv, detailsDiv);
    }
}

MDetailsContext.prototype._positionAndDisplayDetailsPopup = function(detailsFetcherDiv, detailsDiv)
{
    // Note: cannot cache these values as they change when user scrolls the CellBrowser
    var cellTop = absoluteTop(detailsFetcherDiv);
    var cellLeft = 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._isEmbedded) {
        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 = 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 = availableSpaceTop(detailsDiv, popupTop);
            if (availableTop < 0) {
                topAdjustment = -availableTop;
                popupTop += topAdjustment;
            }
        }
    }
    var isOverRightEdge = isOffscreenRight(detailsDiv, popupLeft);
    if (isOverRightEdge) {
        if (this._isEmbedded) {
            popupLeft += (availableSpaceRight(detailsDiv, popupLeft) - 2);
        }
        else {
            popupLeft = cellLeft - (detailsDiv.offsetWidth + megaphoneWidth) + (horizontalOffset * cellWidth);
            var availableLeft = availableSpaceLeft(detailsDiv, popupLeft);
            if (availableLeft < 0) {
                popupLeft -= availableLeft;
            }
        }
    }
    
    // show or hide the close button
    var closeButton = findChildWithId(detailsDiv, 'closeDetailsButton');
    if (closeButton) {
        closeButton.style.display = this._isClickToClose ? 'block' : 'none';
    }

    // show megaphone
    var megaphoneImage = this.findMegaphoneImage(detailsFetcherDiv, isOverBottomEdge, isOverRightEdge);
    if (megaphoneImage != null) {
        if (topAdjustment > 0) {
            var newMegaphoneTop = (parseInt(megaphoneImage.initialTop) - 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 newMegaphoneTop = (parseInt(megaphoneImage.initialTop) - (bottomAdjustment * .5)) + "px";
            megaphoneImage.style.top = newMegaphoneTop;
        }
        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 (isIE6()) {      // for widget popup; see MCell.adjustTwoLineDivHeight
        var twoLineDiv = findChildTagWithId(detailsDiv, 'DIV', 'twoLineDiv');
        if (twoLineDiv != null) {
            twoLineDiv.style.height = "";
            if (twoLineDiv.offsetHeight > 24)
                twoLineDiv.style.height = "24px";
        }
    }
    
    this.observers.notify("show");
}

MDetailsContext.prototype.showDetailsPopup = function(detailsFetcherDiv, event, clickToClose)
{
    //trace("showDetailsPopup " + detailsFetcherDiv + " " + clickToClose);
    if (this._disabled) {
        return;
    }

    // if this._currentDetailsFetcherDiv == null, we're not showing details.
    var normalDelay = clickToClose ? 10 : 300;
    var delay = this._currentDetailsFetcherDiv == null ? 500 + this._extraDelay : normalDelay;
    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;
            if (!detailsContext._isClickToClose) {
                Event.observe(document.body, "mouseover", details_handleMouseOverBody, false);
            }
            detailsContext.positionAndDisplayDetailsPopup(detailsFetcherDiv, event);
        }
        this._pendingShowDetailsTimeout = window.setTimeout(handlerFunction, delay);
        this._pendingTimeoutPurpose = detailsFetcherDiv;
        
        if (document.popupContext != null) {
            // hide the generic popup when we kick off
            document.popupContext.closePopup();
        }
    }
}

// general routine to close the details, closes the popup and takes care of outstanding timeouts
MDetailsContext.prototype.closeDetailsPopup = function()
{
    //trace("closeDetailsPopup start " + this._isClickToClose);
    if (this._pendingShowDetailsTimeout != 0 && this._pendingTimeoutPurpose == null) {
        // 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", details_handleMouseOverBody, false);
        }
        else {
            // reset to default
            detailsContext._isClickToClose = false;
        }
    }
    if (this._isClickToClose) {
        // do work immediately
        handlerFunction();
    }
    else {
        this._pendingShowDetailsTimeout = window.setTimeout(handlerFunction, 300);
        this._pendingTimeoutPurpose = null;
    }
}

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

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

MDetailsContext.prototype.handleMouseOverBody = function(event)
{
    this.closeDetailsPopup();
}

function details_handleMouseOverBody(event)
{
    document.detailsContext.handleMouseOverBody(event);
}

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

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

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

MDetailsContext.prototype.handleMouseOutDetailsFetcher = function(detailsFetcherDiv, event)
{
    //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 != null) {
        if (link.onclick != null) {
            link.onclick();
        }
        else {
            window.location.href = link.href;
        }
    }
    Event.stop(event);
}

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

document.detailsContext = new MDetailsContext();
