/**
 * Start the CPM Main Application.
 * 
 * If a page parameter is supplied as follows this will be the view that
 * we show. The two ways a view can be specified is either by a reference name, or by id.
 * If not supplied, the default view will be shown.
 * 
 * http://{web context}/apps/main/index.html?view=Executives
 * http://{web context}/apps/main/index.html?id=402
 * 
 * This application presumes the presence of mxweb and jquery js libraries.
 */

/*extern CPM, document, jQuery, mx, mx_mkns, mxSuite, mxutils, YAHOO, window */

/**
 * A globally available symbol that refers to jQuery
 */
var $j = jQuery.noConflict();

/**
 * A globally available symbol that refers to a single instance of a running
 * MxEngine
 */
var mxEngine = new mx.engine.Engine();

/**
 * A globally available symbol that retains state and functions used by this
 * application. Only used for easier syntax.
 */
var MxMain;
mx_mkns("CPM.apps.main");
MxMain = CPM.apps.main;

/*
 * Public symbols that are well known hooks.
 *
 * CPM.apps.main.viewLookup
 *     {Object} That uses property names as view reference names, and values as the numberic view
 *              id to map to.
 *              For example a property like "Executives" : 23 Would have view 23 shown when
 *              Executives was designated.
 */
CPM.apps.main.viewLookup = {};

// Load the Suite Module. This contains bundled, core code to be shared
// across MxWeb Applications and can be accessed by its well known name
// "mxSuite"
mxEngine.loadModule("suite/suite.js");

/*
 * Symbols that are shared between functions.
 *
 * MxMain.elCore
 *     {HMLElement} The parent display area in main's index.html. This is the
 *                  root of everything we add.
 *
 * MxMain.oModuleContext
 *     {ModuleContext} A custom built ModuleContext for this app.
 *
 * MxMain.elContent
 *     {HTMLElement} The display area for content on the page. This is the root
 *                   of all content being displayed.
 *
 * MxMain.sInvalidElementHtml
 *     {string} HTML to be displayed when a user encounters an invalid
 *              presentation node.
 *
 * MxMain.sInaccessibleView
 *     {string} HTML to be displayed when a user encounters a view referencing a view that either
 *     doesn't exist or that they don't have access to.
 * 
 * MxMain.sModuleErrorHtml
 *     {string} HTML to be displayed when a user encounters a presentation node
 *              that cannot be initialized for interaction.
 *
 * MxMain.sNoChildrenHtml
 *     {string} HTML to be displayed when a user encounters a presentation node
 *              that has no children or content.
 *
 * MxMain.fnPresSvc
 *     {function} A function that returns the presentations service.
 *
 * MxMain.fnPresViewer
 *     {function} A function that returns the presentation viewer module.
 * 
 */

MxMain.oModuleContext = mxEngine.createContext("MainApp", "");

// Invoke MxMain.begin after the associated HTML page has finished loading.
$j(document).ready(function () {

    try {
        MxMain.elCore = $j("#cpm-core")[0];
        MxMain.begin();
    } catch (eCantBegin) {
        MxMain.handleGlobalError(eCantBegin);
    }

});

/**
 * Standard log wrapper.
 */
MxMain.log = function (sMsg) {
    mxutils.logDebug("[MxMain] " + sMsg);
};

/**
 * This function should only be invoked when a fatal error has occurred that
 * causes the application to shutdown.
 *
 * @param {string | Error} A string or an Error object associated with the error.
 */
MxMain.handleGlobalError = function (error) {

    // Hide the main application
    $j("#cpm-core").hide();
    // Display general "Bad Error Occurred" message
    $j("#cpm-error-area").show();

    // If more specific information is available, show it.
    var sMsg = error.message || error;
    if (sMsg) {
        mxutils.logDebug("Global Error: " + sMsg);
        $j("#cpm-error-msg-area").show();
        $j("#cpm-error-msg").html(sMsg);
    }

};

MxMain.sPlatformUnavailable = "Please insure that the Production Server has started.";

/**
 * This function is invoked when one of the "browser" modules fails to start as
 * when it is initialized.
 *
 * @param {string} sType The type of item that couldn't be viewed: metric,
 *     scorecard, etc.
 * @param {string | Error} A string or an Error object associated with the
 *     error.
 */
MxMain.handleModuleError = function (sType, error) {

    MxMain.fnShowHTML(MxMain.sModuleErrorHtml);
    $j("#cpm-module-item-type").html(sType);

    // If more specific information is available, show it.
    var sMsg;
    if (error.message && error.message.indexOf("Unable to access XmlWebStore URL") >= 0) {
        // The platform is down. Supply a "better" message
        sMsg = MxMain.sPlatformUnavailable;
    } else {
        sMsg = error.message || error;
    }
    if (sMsg) {
        mxutils.logDebug("Module Error: " + sMsg);
        $j("#cpm-module-error-msg-area").show();
        $j("#cpm-module-error-msg").html(sMsg);
    }

};

/**
 * XXX Copy Paste code from lite.js
 * Gets a page parameter.
 * 
 * @param {string} sKey That the value should be returned for.
 * @returns {string} The value in the URL for the <code>sKey</code>.
 */
MxMain.fnGetQueryVariable = function (sKey) {
    var sQuery = window.location.search.substring(1);
    var a_sVars = sQuery.split("&");
    var i;
    var a_sPair;
    for (i = 0; i < a_sVars.length; i = i + 1) {
        a_sPair = a_sVars[i].split("=");
        if (a_sPair[0] === sKey) {
            return a_sPair[1];
        }
    }
};

MxMain.fnPresSvc = function () {
    return CPM.service.view();
};

MxMain.fnPresViewer = function () {
    return mxEngine.loadModule("view/view.js");
};

/**
 * Insures that a user authenticates themselves and then passes control to
 * the main next phase of the application.
 */
MxMain.begin = function () {

    // {MxWebModule} The login module
    var oLoginMod = mxEngine.requireModule("login/login.js");

    // {Object} Has methods for callbacks from login module
    var oLoginCallback = {
        onLoggedIn : function () {
            try {
                MxMain.startApp();
            } catch (eCantStart) {
                MxMain.handleGlobalError(eCantStart);
            }
        }
    };

    // {string} String holding HTML describing skeleton layout
    var sLoginHtml = MxMain.oModuleContext.loadContent("loginTemplate.html");
    $j(MxMain.elCore).html(sLoginHtml);
    var sLoginBottomHtml = MxMain.oModuleContext.loadContent("loginBottom.html");
    $j("#cpm-login-bottom").html(sLoginBottomHtml);
    oLoginMod.doLogin("login", oLoginCallback);

};

/**
 * Function invoked after a user has authenticated themselves.
 */
MxMain.startApp = function () {

    // {string} string holding HTML describing skeleton layout
    var sMainHtml;

    // {string} string holding HTML that will be available when the application
    // is first viewed.
    var sWelcomeHtml;

    // {string} string holding HTML displayed when no presentations are
    // available or when we have trouble starting up.
    var sContactAdminHtml;

    // {boolean} Should we display the application as normal or should we
    // display a template indicating that the user should contact an
    // administrator
    var bContactAdmin = false;

    // {number} The id of the context named 'Main' underneath a context named
    // 'System'
    var nContextId;
    try {
        nContextId = mxSuite.getMainContextId();
    } catch (cantGetMainId) {
        bContactAdmin = true;
    }

    // {HTMLElement} The main display area in the page.
    MxMain.elCore = $j("#cpm-core")[0];
    var xFirstPres = MxMain.getFirstPresentation();
    if (xFirstPres === null || bContactAdmin) {

        sContactAdminHtml = MxMain.oModuleContext.loadContent("contactAdminTemplate.html");
        $j(MxMain.elCore).html(sContactAdminHtml);
        MxMain.setupUsername();
        MxMain.setupLogout();

    } else {

        // Load templates to be displayed at various times
        MxMain.sInvalidElementHtml = MxMain.oModuleContext.loadContent("invalidElementTemplate.html");
        MxMain.sInaccessibleView = MxMain.oModuleContext.loadContent("inaccessibleView.html");
        MxMain.sModuleErrorHtml = MxMain.oModuleContext.loadContent("moduleErrorTemplate.html");
        MxMain.sNoChildrenHtml = MxMain.oModuleContext.loadContent("noChildrenTemplate.html");

        // Load and display the main template
        sMainHtml = MxMain.oModuleContext.loadContent("mainTemplate.html");
        $j(MxMain.elCore).html(sMainHtml);
        $j(".cpm-wrap").resizable({
            minHeight: 100,
            minWidth: 1
        });

        // Show and hide the navigation on double click.
        var sExpandedWidth;
        $j(".cpm-wrap .ui-resizable-e").bind("dblclick", function () {
            var sCurrentWidth = $j(".cpm-wrap").css("width");
            if (sCurrentWidth === "1px") {
                $j(".cpm-wrap").css("width", sExpandedWidth);
            } else {
                sExpandedWidth = $j(".cpm-wrap").css("width");
                $j(".cpm-wrap").css("width", "1px");
            }
        });

        // Set the height of the left nav to be the height of the whole window.
        var reHeightLeftNav = function () {
            var nWindowHeight = window.innerHeight ||
                document.documentElement && document.documentElement.clientHeight ||
                document.body.clientHeight;
            var nScrollOffset = window.pageYOffset ||
                document.documentElement && document.documentElement.scrollTop ||
                document.body.scrollTop;
            var nNavOffset = $j(".cpm-wrap").offset().top;
            $j(".cpm-wrap").height(nWindowHeight - nNavOffset + nScrollOffset);
        };

        // Bind window events.
        $j(window).bind("resize", reHeightLeftNav);
        $j(window).bind("scroll", reHeightLeftNav);

        // Fix #374 by setting the nav div to the same width as its parent for IE6.
        if ($j.browser.msie && $j.browser.version === "6.0") {
            $j(".cpm-wrap").bind("resize", function () {
                $j(".nav").width($j(this).width());
            });
        }

        // Turn off the bottom are bottom right corner of the resize bar.
        $j(".cpm-wrap .ui-resizable-s").css({display: "none"});
        $j(".cpm-wrap .ui-resizable-se").css({display: "none"});

        // Decorate main template via JavaScript: Add logout handler, prepare
        // trees, etc
        MxMain.prepareTemplate();

        // Display introductory text on the main page
        sWelcomeHtml = MxMain.oModuleContext.loadContent("welcomeTemplate.html");
        MxMain.fnShowHTML(sWelcomeHtml);

        // Initialize the width of the left navigation.
        $j(".cpm-wrap").css("width", "200px");

        // Setup the primary presentation viewer. Its clicks may motivate the
        // creation of a secondary Presentation viewer or may change the main
        // content.
        MxMain.setupFirstPresentation(xFirstPres);

        // Initialize the height of the left navigation.
        reHeightLeftNav();

    }

};

/**
 * This method is run after we have a skeletal template. It decorates and
 * enhances the markup from this template with JavaScript.
 */
MxMain.prepareTemplate = function () {

    MxMain.setupUsername();

    MxMain.setupLogout();

    MxMain.elContent = $j("#mainContent")[0];

};

/**
 * Modify page so that username is displayed
 */
MxMain.setupUsername = function () {

    var sUserName = mx.engine.userCredential.userName;
    $j("#cpm-username").html(sUserName);

};

/**
 * Insure the logout link has appropriate click handlers or is hidden if
 * the user came into the page via an Auth Token and cannot (effectively) logout.
 *
 * NOTE: If the user came in using an Auth Token, the login module, login.js has
 * set isSSO.
 */
MxMain.setupLogout = function () {

    var sIsSSO = mx.env.getClientProperty("isSSO");

    if (sIsSSO === "true") {
        $j("#cpm-logout-area").hide();
    } else {
        $j("#cpm-logout").bind("click", function () {
            mxEngine.logoutUser();

            // Unbind window events.
            $j(window).unbind("resize");
            $j(window).unbind("scroll");

            MxMain.begin();
            return false;
        });
    }

};

/**
 * Interprets page parameters to determine which presentation to retrieve.
 *
 * Retrieves a presentation and returns it as XML. This method will update the
 * page and it will interact with handleGlobalError if it cannot determine which
 * presentation to retrieve or cannot retrieve that presentation. It will not
 * throw an Error in this instance, but it will return "null" as a way of
 * signalling that it could not proceed.
 *
 * @returns {XML|null} The XML for the primary presentation to be displayed
 *     or null if none could be found.
 */
MxMain.getFirstPresentation = function () {

    var nContextId;

    // {XML} XML representing the primary presentation
    var xPrimaryPresentation;

    // {string} The URL page parameter supplied view id
    var sViewId = MxMain.fnGetQueryVariable("id");
    // {string} The URL page parameter supplied view reference name
    var sView = MxMain.fnGetQueryVariable("view");
    var nViewId;
    if (sViewId) {
        nViewId = parseInt(sViewId, 10);
        if (isNaN(nViewId)) {
            MxMain.handleGlobalError("Unable to translate '" + sViewId + "' into a number.");
            xPrimaryPresentation = null;
        } else {
            try {
                xPrimaryPresentation = MxMain.fnPresSvc().getView(nViewId);
            } catch (eCantGet) {
                MxMain.log(eCantGet.message);
                MxMain.handleGlobalError("Unable to retrieve view '" + nViewId + "'");
                xPrimaryPresentation = null;
            }
        }
    } else {
        if (sView) {
            nViewId = MxMain.viewLookup[sView];
            if (nViewId) {
                try {
                    xPrimaryPresentation = MxMain.fnPresSvc().getView(nViewId);
                } catch (eCantGet) {
                    MxMain.log(eCantGet.message);
                    MxMain.handleGlobalError("Unable to retrieve view '" + nViewId + "'");
                    xPrimaryPresentation = null;
                }
            } else {
                MxMain.handleGlobalError("Unable to retrieve view associated with '" + sView + "'");
                xPrimaryPresentation = null;
            }
        } else {
            nContextId = mxSuite.getMainContextId();
            xPrimaryPresentation = MxMain.fnPresSvc().getDefaultPresentation(nContextId);
        }
    }
    return xPrimaryPresentation;

};

/**
 * Given a presentationNode, create a link back to a new instance of this
 * application that focuses on the contents of that piece of a presentation.
 * 
 * @param (XML} xPresNode XML corresponding to an individual presentationNode
 * @returns {string} URL to main that focuses on that piece of the presentation.
 */
MxMain.fnHrefBuilder = function (xPresNode) {
    var nViewId = CPM.service.view().getId(xPresNode);
    var sViewUrl = mxutils.getViewUrl(nViewId);
    return sViewUrl;
};

/**
 * Display a message in the main portion of the app indicating that the requested view cannot be
 * retrieved.
 */
MxMain.fnShowBadView = function () {
    MxMain.fnShowHTML(MxMain.sInaccessibleView);
};

/**
 * Modify page so that an initial Presentation is retrieved and drawn. If the
 * primary presentation is composed of more than one presentationNode, we'll
 * draw the primary tree on the left. If not, we'll hide the left-hand
 * navigation bar and display only the presentationNode.
 *
 * @param {XML} xPres The "first" or "primary" presentation. This is the
 *     presentation that should be displayed in the first (upper left) Tree
 *     Control.
 */
MxMain.setupFirstPresentation = function (xPres) {

    // To start, see if the presentation we're showing has a landing page.
    // If so, display it.
    var oRootData = MxMain.fnPresSvc().getData(xPres);
    if (oRootData.sType !== "empty") {
        // Immediately clear whatever was present in the template
        MxMain.fnShowHTML("");
        if (oRootData.sType === "view") {
            // Show a view as if someone had clicked it in the presentationViewer.
            MxMain.updateSecondViewer(oRootData.nRefId, oRootData.sName);
        } else {
            // Display the landing page of the presentation.
            MxMain.showPresentationNode(oRootData);
        }
    }

    // {PresentationViewer} A tree control viewer for the presentation
    var oFirstViewer;
    var a_xChildren = MxMain.fnPresSvc().getChildren(xPres);
    if (a_xChildren.length === 0 && oRootData.sType !== "view") {
        // If the specified presentation has no children and is not a view, then hide the navigation
        // bar on the left and display "it" on the main area.
        MxMain.fnHideNav();
    } else {
        // If the presentation has children or is a view, display the children or referenced view
        // in a presentationViewer on the left-hand side.
        $j("#metaPres").show();
        oFirstViewer = MxMain.fnPresViewer().newInstance("metaPres", xPres, {
            fnHrefBuilder : MxMain.fnHrefBuilder
        });

        oFirstViewer.addClickHandler(function (oData) {
            if (oData.sType === "view") {
                if (MxMain.fnPresSvc().isViewValid(oData.nId)) {
                    MxMain.updateSecondViewer(oData.nRefId, oData.sName);
                } else {
                    MxMain.fnShowBadView();
                }
            } else {
                MxMain.showPresentationNode(oData);
                MxMain.fnHideSecondNav();
            }
        });
    }

};

/**
 * Display a presentationNode in the main area of the page. If the
 * presentationNode points to a metric, scorecard, mde or contains XHTML this
 * will be displayed as expected.
 *
 * If the presentationNode points to another presentation, HTML that allows a
 * user to open the presentation (in another window) will be displayed on the
 * main part of the page.
 *
 * If there's any problem "showing a presentation node" this method will display
 * an error message in place of the "presentation node."
 *
 * @param {object} oNodeData Data describing the piece of the presentation
 *     to display.
 */
MxMain.showPresentationNode = function (oNodeData) {

    // Is this view piece invalid? If so, display an error message
    // and stop.
    var bIsValid = CPM.service.view().isViewValid(oNodeData.nId);
    if (!bIsValid) {
        if (oNodeData.sType === "view") {
            MxMain.fnShowBadView();
        } else {
            MxMain.fnShowHTML(MxMain.sInvalidElementHtml);
        }
        return;
    }

    // If valid, display whatever we're supposed to. Trap any errors, and display
    // them the user.
    var sItemTypeForError = oNodeData.sType;
    try {
        switch (oNodeData.sType) {
        case "metric":
            MxMain.fnShowMetric(oNodeData.nRefId, oNodeData.nId);
            break;
        case "scorecard":
            MxMain.fnShowScorecard(oNodeData.nRefId, oNodeData.nId);
            break;
        case "mde":
            MxMain.fnShowMDE(oNodeData.nRefId, oNodeData.nId);
            break;
        case "innerHTML":
            sItemTypeForError = "item";
            MxMain.fnShowHTML(oNodeData.sHtml);
            break;
        case "metricFolder":
        case "scorecardFolder":
        case "mdeFolder":
        case "empty":
            sItemTypeForError = "item";
            MxMain.fnShowHTML("");
            break;
        case "view":
            MxMain.fnShowHTML("<p>Open '<a target='_blank' href='" +
                mxutils.getApplicationUrl(
                "index.html") +
                "?id=" + oNodeData.nRefId +
                "'>" +
                oNodeData.sName +
                "</a>' <span style='color: #999;'>(in a new window)</span></p>");
            break;
        default:
            // Do nothing.
            break;
        }
    } catch (eCantShowNode) {
        MxMain.handleModuleError(sItemTypeForError, eCantShowNode);
    }

};
 
/**
 * Modify the page so that a presentation represented by <code>sPresId</code> is
 * displayed in a Presentation Viewer in the bottom left part of the screen. If
 * the presentation has no children then hide the Presentation Viewer in the
 * bottom left part of the screen, and display either the presentation's content
 * or an error message in the <code>MxMain.sNoChildrenHtml</code> template.
 *
 * @param {string} sPresId The id of a presentation to view.
 * @param {sName} The name of that presentation.
 */
MxMain.updateSecondViewer = function (sPresId, sName) {

    // {number} The id of a presentation to view.
    var nPresId = parseInt(sPresId, 10);

    // {PresentationViewer} A Presentation Viewer that we'll build
    var xPres = MxMain.fnPresSvc().getView(nPresId);

    var oSecondViewer = MxMain.fnPresViewer().newInstance("pres", xPres, {
        fnHrefBuilder : MxMain.fnHrefBuilder
    });

    // Listen for label clicks..
    oSecondViewer.addClickHandler(function (oData) {
        MxMain.showPresentationNode(oData);
    });


    var oRootData = MxMain.fnPresSvc().getData(xPres);
    var bNoChildren = (MxMain.fnPresSvc().getChildren(xPres).length === 0);
    if (bNoChildren) {

        // Hide second nav box.
        MxMain.fnHideSecondNav();

        if (oRootData.sType === "empty") {
            // Show the no children or content template.
            MxMain.fnShowHTML(MxMain.sNoChildrenHtml);
        } else {
            // Show the presentation's landing page.
            MxMain.showPresentationNode(oRootData);
        }

    } else {

        // Reveal second nav box.
        MxMain.fnShowSecondNav();

        // Show the presentation's heading.
        $j("#presName").html(sName);

        // Show the presentation's landing page.
        MxMain.showPresentationNode(oRootData);

    }

};

/**
 * Display the text "Loading..." in the main content area and then call the
 * <code>fnCallback</code> which has been wrapped in error handling code. The
 * intention is to change the main content area before a synchronous
 * function may freeze the browser.
 *
 * @param {string} sType The type of item that couldn't be viewed: metric,
 *     scorecard, etc.
 * @param {function} fnCallback The function that will be run after the
 *     "Loading..." has been displayed.
 */
MxMain.fnLoadAndDisplay = function (sType, fnCallback) {

    MxMain.fnShowHTML("Loading...");

    var fnWrapCallback = function () {
        try {
            fnCallback();
        } catch (oError) {
            MxMain.handleModuleError(sType, oError);
        }
    };

    // The following timeout is to allow the "Loading..." to be displayed before
    // a synchronous browser freezing function is called.
    window.setTimeout(fnWrapCallback, 0);

};

/**
 * Display a Scorecard with id <code>sId</code>in the main portion of the
 * page.
 *
 * @param {string} sId The xObject Store id of a Scorecard to be displayed.
 * @param {number} nAuthorizationId The identifier for authorization.
 */
MxMain.fnShowScorecard = function (sId, nAuthorizationId) {

    var oScorecardMod = mxEngine.loadModule("scorecards/view/scorecardViewer.js");

    var oParams = {
        nScorecardId : parseInt(sId, 10),
        nAuthorizationId : nAuthorizationId
    };

    MxMain.fnLoadAndDisplay("scorecard", function () {
        oScorecardMod.initDisplay(MxMain.elContent, oParams);
    });

};

/**
 * Display a Metric with id <code>sId</code>in the main portion of the
 * page.
 *
 * @param {string} sId The xObject Store id of a Metric to be displayed.
 * @param {number} nAuthorizationId The identifier for authorization.
 */
MxMain.fnShowMetric = function (sId, nAuthorizationId) {

    var oMetricMod = mxEngine.loadModule("metric/table/metricTable.js");

    var oParams = {
        nMetricId : parseInt(sId, 10),
        nAuthorizationId : nAuthorizationId
    };

    MxMain.fnLoadAndDisplay("metric", function () {
        oMetricMod.initDisplay(MxMain.elContent, oParams);
    });

};


/**
 * Display an MDE with id <code>sId</code>in the main portion of the page.
 *
 * @param {string} sId The xObject Store id of a MDE to be displayed.
 * @param {number} nAuthorizationId The identifier for authorization.
 */
MxMain.fnShowMDE = function (sId, nAuthorizationId) {

    var oMdeMod = mxEngine.loadModule("manual/entry/manualEntry.js");

    var oParams = {
        nContextId: mxSuite.getMainContextId(),
        nMdeId: parseInt(sId, 10),
        nAuthorizationId: nAuthorizationId
    };

    MxMain.fnLoadAndDisplay("mde", function () {
        oMdeMod.initDisplay(MxMain.elContent, oParams);
        oMdeMod.show();
    });

};

/**
 * Display HTML content <code>sHtml</code> in the main portion of the page.
 *
 * @param {string} A string containing HTML to be displayed.
 */
MxMain.fnShowHTML = function (sHtml) {
    $j(MxMain.elContent).html(sHtml);
};

MxMain.fnHideNav = function () {
    $j(".cpm-wrap").hide();
    // TODO determine if the resizable navigation events need to be unbound here.
};

MxMain.fnHideSecondNav = function () {
    $j("#presHolder").hide();
};

MxMain.fnShowSecondNav = function () {
    $j("#presHolder").show();
};

MxMain.alive = true;
