/*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, Form, Tx, TxEventObserver, Element, alert */

//////////////////////
// MDialog
//////////////////////

var SSDialog = {};

SSDialog._createDeadBackgroundDiv = function(owner, className, zIndexHint, closeDialogOnClick)
{
    var modalDiv = Tx.createCoverDiv(className, zIndexHint, false);

    // Make sure no events get passed the modal div.  This is what deadens the background.
    var boundStopEvent = owner.stopEvent.bindAsEventListener(owner);
    // optionally we allow a click on the dead background to close the dialog
    if (closeDialogOnClick) {
        $(modalDiv).observe('click',
                function(event)
                {
                    // eat the event and close the dialog
                    Event.stop(event);
                    owner.close();
                }
        );
    }
    else {
        modalDiv.onclick = boundStopEvent;
    }
    modalDiv.onmouseover = boundStopEvent;
    modalDiv.onmouseout = boundStopEvent;
    modalDiv.onmousedown = boundStopEvent;
    modalDiv.onmouseup = boundStopEvent;
    modalDiv.onkeydown = boundStopEvent;
    modalDiv.onkeyup = boundStopEvent;
    modalDiv.onkeypress = boundStopEvent;

    modalDiv.style.zIndex = 0;
    document.body.appendChild(modalDiv);
    modalDiv.style.zIndex = zIndexHint;
    return modalDiv;
};

//dialogContents can either be a div, in which case we use that div as the dialog,
//or else html text, in which case we create a div with the text
function MDialog(dialogContents, zIndexHint, baseElement, options)
{
    this.isValid = true;

    var inlineDialog = (typeof(dialogContents) === 'object');

    // 99999's are to ensure the dialogs and background get above partner ads and other elements

    // Create a big transparent div to force modality of the dialog
    var closeOnBackgroundClick = (options && options.closeOnBackgroundClick) ? true : false;
    var backgroundDivCSSClass = (options && options.backgroundDivCSSClass) ? options.backgroundDivCSSClass : 'modalDialog';
    // (Kevin 8/4/11) - note that this is a separate background div than the one that enableUI creates and deletes
    // for a moment the two of them both exist
    this.modalDiv = SSDialog._createDeadBackgroundDiv(this, backgroundDivCSSClass, 2 * zIndexHint + 99999, closeOnBackgroundClick);

    // Optional function to call upon close
    this.closeFunction = (options && options.closeFunction) ? options.closeFunction : null;

    // Create the div for the actual dialog.
    var allowFixedPositioning = false;
    var useIE6Hack = Tx.isIE6() && !baseElement && allowFixedPositioning;
    var dialogDiv;
    if (inlineDialog) {
        dialogDiv = dialogContents;
    } else {
        dialogDiv = document.createElement('div');
        Element.update(dialogDiv, dialogContents);
        try {
            Tx.findAndEvalJavascript(dialogContents);
        }
        catch (err) {
            alert("Error creating dialog, please contact support: " + err);
            Tx.serverLog(null, "error constructing dialog " + err);
            // error evaluating javascript.  Bad included javascript (ga.js once had a problem) or some hackery by evil browser plugin
            // mark dialog as not valid so that we bail out
            this.isValid = false;
            // get rid of dialog's dead background div
            if (this.modalDiv.parentNode) {
                this.modalDiv.parentNode.removeChild(this.modalDiv);
            }
            // and get rid of the other background div (and its event blocking)
            SSDialog.dialogContext.enableUI(true);
            return;
        }
    }
    dialogDiv.style.zIndex = 2 * zIndexHint + 999999 + 1;
    if (useIE6Hack) {
        dialogDiv.id = "fixedPosDivIE6";
        document.body.appendChild(dialogDiv);
        if (!SSDialog._elementFitsInsideWindow(dialogDiv)) {
            // if the dialogDiv cannot display on the screen, allow it to scroll.
            document.body.removeChild(dialogDiv);
            dialogDiv.id = "normalDiv";
            dialogDiv.style.position = "absolute";
            document.body.appendChild(dialogDiv);
        }
    }
    else {
        if (!inlineDialog) {
            dialogDiv.style.position = "absolute";
            dialogDiv.style.top = '-1000px';
            dialogDiv.style.left = '-1000px';
        } else {
            dialogDiv.style.display = 'block';
        }
        // We must first put it into the dom to get layout performed so we can then center it.
        // This does not appear to result in any noticable flicker.
        document.body.appendChild(dialogDiv);

        var dialogHeight = dialogDiv.clientHeight;
        var dialogWidth = dialogDiv.clientWidth;
        var newLeft;
        var newTop;
        var elementFitsInWindow = SSDialog._elementFitsInsideWindow(dialogDiv);
        if (!baseElement) {
            newLeft = Math.floor((Tx.windowInnerWidth() - dialogWidth) / 2);
            newTop = Math.floor((Tx.windowInnerHeight() - dialogHeight) * 0.40);
            if (elementFitsInWindow && allowFixedPositioning) {
                dialogDiv.style.position = "fixed";
            }
            else {
                newLeft += Tx.windowScrollLeft();
                newTop += Tx.windowScrollTop();
                if (Tx.isTouch()) {
                    // adjust for scrolling inside viewport which is larger than window
                    newTop += window.scrollY;
                }
            }
        } else {
            newLeft = Tx.absoluteRight(baseElement);
            newTop = Tx.absoluteTop(baseElement);
        }

        // Fixup bottom first, then fixup top, so that at worst the top part is visible
        if (!elementFitsInWindow || baseElement) {
            var overhang = -Tx.availableSpaceBottom(dialogDiv, newTop);
            if (overhang >= 0) {
                newTop -= overhang + 10;
            }
            overhang = -Tx.availableSpaceTop(newTop);
            if (overhang >= 0) {
                newTop += overhang + 10;
            }
        }

        dialogDiv.style.left = newLeft + 'px';
        newTop = Math.max(newTop, 5);
        dialogDiv.style.top = newTop + 'px';
    }

    this.dialogDiv = dialogDiv;
    this.pendingFunction = (options && options.pendingFunction) ? options.pendingFunction : null;
    this.pendingOperationUrl = null;
    this.isPendingUrlForDialog = false;

    SSDialog.dialogContext.observers.notify("load", this);

    // Need to cache this exact function so we can stopObserving when the dialog closes.
    this.handleResizeObserver = this.handleResize.bindAsEventListener(this);
    Event.observe(window, 'resize', this.handleResizeObserver, false);
    this.handleKeyDownObserver = this.handleKeyDown.bindAsEventListener(this);
    Event.observe(document.documentElement, 'keydown', this.handleKeyDownObserver, false);
}

SSDialog._elementFitsInsideWindow = function(element)
{
    if (Tx.isTouch()) {
        // ipad expando-view fits any dialog
        return true;
    }
    var docElement = document.documentElement;
    var doesFit = element.clientHeight < docElement.clientHeight && element.clientWidth < docElement.clientWidth;
    return doesFit;
};

MDialog.prototype.close = function()
{
    //trace("mdialog close");
    if (this.closeFunction !== null) {
        this.closeFunction();
    }

    if (this.dialogDiv.parentNode) {
        this.dialogDiv.parentNode.removeChild(this.dialogDiv);
    }
    if (this.modalDiv.parentNode) {
        this.modalDiv.parentNode.removeChild(this.modalDiv);
    }

    Event.stopObserving(window, 'resize', this.handleResizeObserver, false);
    Event.stopObserving(document.documentElement, 'keydown', this.handleKeyDownObserver, false);
};

MDialog.prototype.handleResize = function()
{
    // This makes sure the modal div fills the browser as it is resized
    // We first set the modalDiv to 1px so that it shrinks and we can determine
    // what size the window wants to be (this handles the case of shrinking the windwo
    // after expanding it)
    this.modalDiv.style.width = '1px';
    this.modalDiv.style.height = '1px';

    // On FF we can immediately see the effects of the relayout from the above, but on IE we
    // have to have a small delay, so we wait 20ms to actually resize.
    setTimeout(function() {
        this.modalDiv.style.width = Tx.coverDivWidth();
        this.modalDiv.style.height = Tx.coverDivHeight();
    }.bind(this), 20);
};

MDialog.prototype.handleKeyDown = function(event)
{
    if (event.keyCode === Event.KEY_ESC) {
        // close dialog, making sure context knows to pop this dialog, etc.
        //trace("escape key closer");
        SSDialog.dialogContext.close();
        Event.stop(event);
    }
};

MDialog.prototype.stopEvent = function(event)
{
    Event.stop(event);
};

// finish the work of signin, update profile, or reset password
MDialog.prototype.finishSecureDialog = function(errorCode)
{
    if (!errorCode) {
        SSDialog.goToPendingUrl(this, false);
    }
    else {
        // Clear all password fields, set error value and re-submit form
        // to return a validation error page. The re-submit is forced over
        // http to trigger the standard ajax dialog action.
        var inputs = document.getElementsByTagName('input');
        var i;
        for (i = 0; i < inputs.length; i++) {
            if (inputs[i].type === 'password') {
                inputs[i].value = "";
            }
        }
        //var status_array = status.split('|');
        document.getElementById('errorStatus').value = errorCode;

        var formElement = document.getElementById(this.secureFormId);
        var index = formElement.action.indexOf('/action');
        if (index > -1) { // reset to http
            formElement.action = formElement.action.substring(index);
        }
        SSDialog.postFormAndOpen(this.secureFormId);
    }
};

////////////////////
// MDialogContext
////////////////////

function MDialogContext()
{
    this.activeDialogs = [];
    // for notification on load, close event
    this.observers = new TxEventObserver();
    this.enableUITimeoutId = null;
    this.enableUI(true);
}

MDialogContext.prototype.open = function(dialogContents, baseElement, options)
{
    var dialog = new MDialog(dialogContents, this.activeDialogs.length + 20, baseElement, options);
    if (dialog.isValid) {
        this.enableUI(true);
        this.activeDialogs.push(dialog);
        return dialog;
    }
    else {
        return null;
    }
};

MDialogContext.prototype.close = function()
{
    //trace("dialogcontext close");
    var dialog = this.activeDialogs.pop();
    this.observers.notify("close", dialog);
    dialog.close();
    this.enableUI(true);
};

MDialogContext.prototype.getActiveDialog = function()
{
    var activeDialogsLength = this.activeDialogs.length;
    var dialog = activeDialogsLength > 0 ? this.activeDialogs[activeDialogsLength - 1] : null;
    return dialog;
};

/**
    When a dialog is being opened, this must be called (with isEnabled = false) to
    prevent further operations from going on.  It immediately puts up a dead
    background which prevents further mouse events from being processed.
    This dead background is removed when this is called with false or after
    10 seconds.  When the dialog is actually open, this is called with false
    to re-enable opening.  However, by that time a new dead background is
    provided by the dialog itself.
*/
MDialogContext.prototype.enableUI = function(isEnabled)
{
    this.isUIEnabled = isEnabled;
    if (isEnabled) {
        if (this.enableUITimeoutId) {
            window.clearInterval(this.enableUITimeoutId);
            this.enableUITimeoutId = null;
        }
        var deadBackground = this.deadBackground;
        if (deadBackground) {
            if (deadBackground.parentNode) {
                deadBackground.parentNode.removeChild(deadBackground);
            }
            this.deadBackground = null;
        }
    }
    else {
        this.deadBackground = SSDialog._createDeadBackgroundDiv(this, 'deadBackground', 100 + 99999, false);
        this.enableUITimeoutId = window.setTimeout("SSDialog.dialogContext.enableUI(true)", 10000);
    }
};

MDialogContext.prototype.stopEvent = function(event)
{
    Event.stop(event);
};

// Install one global MDialogContext
SSDialog.dialogContext = new MDialogContext();

////////////////////////////////////////////////
// Convenience Functions for Users of this API
////////////////////////////////////////////////

// Invokes a dialog using contents received from the url
// via an ajax request.  Recommended usage:
// <a href="http://some-url-that-returns-dialog-content"
//    onclick="return SSDialog.openDialog(this, this.href);">Open</a>
//
// baseElement is optional, and is used for initial positioning.
// isPendingUrlForDialog==true means that url should be used in another
//    SSDialog.openDialog() call, to open a subsequent dialog
// options contains rarer options:
//     pendingFunction, if it exists, executes in goToPendingUrl
//     closeOnBackgroundClick, if true we close the dialog if the user clicks on the background div
SSDialog.openDialog = function(element, url, pendingOperationUrl, baseElement, isPendingUrlForDialog, options)
{
    if (!SSDialog.dialogContext.isUIEnabled) {
        return false;
    }
    // Bug #222: if the dialog being opened doesn't have any text fields on it
    // which take the focus the focus will stay on the element that was clicked
    // and subsequent "enter" key events will cause another dialog to be opened.
    // We prevent that by explcitly  blur'ing the element which was clicked.
    if (element) {
        element.blur();
    }
    SSDialog.dialogContext.enableUI(false);
    var handleAjaxResponse = function(xmlhttp) {
        //var startTime = new Date().getTime();
        var dialogContents = Tx.getResponseText(xmlhttp);
        if (dialogContents === "") {
            // empty content from server indicates an error, don't show dialog
            window.location.reload();
        }
        else if (dialogContents.startsWith("url=")) {
            window.location = dialogContents.substring(4);
        }
        else if (!Tx.handleAjaxExceptionPage(dialogContents)) {
            //dialogContents = Tx.stripHeadTagFromAjaxResponse(dialogContents);
            var dialog = SSDialog.dialogContext.open(dialogContents, baseElement, options);
            if (dialog !== null) {
                // note that null and "" both evaluate to false and are valid values for pendingOperationUrl
                dialog.pendingOperationUrl = (pendingOperationUrl || pendingOperationUrl === null || pendingOperationUrl === "") ? pendingOperationUrl : null;
                dialog.isPendingUrlForDialog = isPendingUrlForDialog ? true : false;
                //var totalTime = new Date().getTime() - startTime;
                //alert("Total client time: " + totalTime + " msec");
            }
        }
    };

    var optionsDict = {
            method: 'get',
            onSuccess: handleAjaxResponse
    };
    Tx.ajax(url, optionsDict);
    return false;
};

/**
 * Submits the form and uses the response to open a new dialog.
 */
SSDialog.postFormAndOpen = function(formId, isSecure)
{
    var formElement = document.getElementById(formId);
    var url = formElement.action;
    if (isSecure) {
        return SSDialog._submitSecureDialog(formId);
    }

    if (!SSDialog.dialogContext.isUIEnabled) {
        return false;
    }
    SSDialog.dialogContext.enableUI(false);
    var handleAjaxResponse = function(xmlhttp) {
        var dialogContents = Tx.getResponseText(xmlhttp);
        if (Tx.handleAjaxExceptionPage(dialogContents)) {
            return;
        }
        var scriptMarker = "<!-- script: ";
        if (dialogContents.indexOf(scriptMarker) === 0) {
            var endIndex = dialogContents.indexOf(" -->");
            var scriptString = dialogContents.substring(scriptMarker.length, endIndex);
            SSDialog.closeDialog();
            Tx.exec(scriptString);
        }
        else if (dialogContents === "" || dialogContents === null) {
            SSDialog.goToPendingUrl(SSDialog.dialogContext.getActiveDialog(), false);
        }
        else if (dialogContents === "refresh") {
            SSDialog.goToPendingUrl(SSDialog.dialogContext.getActiveDialog(), true);
        }
        else {
            // carry forward the pendingOperationUrl and close the current dialog
            var activeDialog = SSDialog.dialogContext.getActiveDialog();
            var pendingOperationUrl = activeDialog.pendingOperationUrl;
            var isPendingUrlForDialog = activeDialog.isPendingUrlForDialog;
            SSDialog.closeDialog();
            var dialog = SSDialog.dialogContext.open(dialogContents);
            if (dialog !== null) {
                dialog.pendingOperationUrl = pendingOperationUrl;
                dialog.isPendingUrlForDialog = isPendingUrlForDialog;
            }
        }
    };

    var formValuesString = Form.serialize(formElement);
    var optionsDict = {
        method: 'post',
        contentType:  'application/x-www-form-urlencoded',
        postBody: formValuesString,
        onSuccess: handleAjaxResponse
    };
    Tx.ajax(url, optionsDict);
    return false;
};

// Closes the currently active dialog.  Recommended usage:
// <a href="." onclick="return SSDialog.closeDialog();">Close</a>
SSDialog.closeDialog = function()
{
    SSDialog.dialogContext.close();
    return false;
};

SSDialog.closeDialogIfExists = function()
{
    //trace("Closing Dialog If Exists");
    if (SSDialog.dialogContext.getActiveDialog()) {
        SSDialog.closeDialog();
    }
    return false;
};


// Closes the current dialog but follows the pending url
SSDialog.okDialog = function()
{
    SSDialog.goToPendingUrl(SSDialog.dialogContext.getActiveDialog(), false);
    return false;
};

SSDialog.goToPendingUrl = function(activeDialog, mustAtLeastRefresh)
{
    // if the dialog has a pending function, it may handle the finish - signaled with a return of true
    var pendingFunction = activeDialog ? activeDialog.pendingFunction : null;
    if (pendingFunction !== null && pendingFunction()) {
        // the function handled the job
        SSDialog.closeDialog();
        return;
    }
    // pendingOperation could be a string (should be a url), a null, or an empty string
    var pendingOperationUrl = !activeDialog ? null : activeDialog.pendingOperationUrl;
    if (pendingOperationUrl !== null) {
        activeDialog.pendingOperationUrl = null;
        // Empty string for pending operation url means: simply close the dialog -- do not refresh the page.
        if (pendingOperationUrl === "") {
            if (mustAtLeastRefresh) {
                Tx.windowRefresh();
            } else {
                SSDialog.closeDialog();
            }
        }
        else {
            if (activeDialog.isPendingUrlForDialog === true) {
                SSDialog.closeDialog();
                SSDialog.openDialog(null, pendingOperationUrl);
            }
            else {
                window.location = pendingOperationUrl;
            }
        }
    }
    else {
        Tx.windowRefresh();
    }
};

/**
 * Submits the form without closing dialog or refreshing page and waits for response before returning.
 */
SSDialog.submitDialogQuietly = function(formId)
{
    var formElement = document.getElementById(formId);
    var url = formElement.action;

    var formValuesString = Form.serialize(formElement);
    var optionsDict = {
        method: 'post',
        asynchronous: false,
        contentType: 'application/x-www-form-urlencoded',
        postBody: formValuesString
    };
    Tx.ajax(url, optionsDict);
    return false;
};

// SSL support: submits form directly to an iframe which sets
// a cookie which the main window polls to extract status.
SSDialog._submitSecureDialog = function(formId)
{
    document[formId].submit();

    var activeDialog = SSDialog.dialogContext.getActiveDialog();
    activeDialog.secureFormId = formId;
};

SSDialog.addDialogLoadObserver = function(observerFunction)
{
    SSDialog.dialogContext.addLoadObserver(observerFunction);
};


