
// METROQUEST VERSION 6
// COPYRIGHT ENVISION SUSTAINABILITY TOOLS


// Create top level object (mq) and immediately create some global variables

mq.nodes = {}; // Node set
mq.struct = {}; // Node structure
mq.collections = {}; // Nodes by node type
mq.details = {};
mq.managers = {};



mq.ui = {}; // Global ui objects - nodeCount, ModalBackground


mq.s.mqInitialized = false;
mq.s.initialScenario = 0;
mq.s.isValidated = true;

// Promoted session variables
mq.s.sessionId = 0;
mq.s.hostId = 0;
mq.s.platform = "Web";


/* ---- PSEUDOCODE ---------

mq.f.getMqConfig(); // called from containing page

    - mq.f.getUrlParams(); // gets parameters from URL
    - mq.f.getSessionId();, mq.f.getHostId(); // get id's from init string
    - mq.f.createSessionVariables(); // sets global session variables
    - mq.f.setAjaxMqConfig(); // called if url params set test file
    - mq.f.processMqConfig(); // process existing or test config
        -- mq.f.setRootNodes(); // sets up root nodes
        -- mq.f.checkPlatform(); // sets platform type
        -- mq.f.idContentConfig(); // adds config/content/node id's to div's
        -- processes the configs of all nodes
        -- if valid configs, recursively call intialization fn of all nodes
    - mq.f.revealInterface(); 
        -- triggers top-level event, if necessary
        -- miscellaneous housekeeping
        -- validate session

*/

// ------------------------------- Get & Process Config ----------------------------------------------------------------------

mq.f.getMqConfig = function () { // called as initial function by containing page


    mq.f.jqueryFix(); // TODO_INIT upgrade and remove

    mq.ui.div = $("#mq");

    // - Get parameters from URL
    mq.f.getUrlParams();

    // - Session + Host Id's
    mq.f.getSessionId();
    mq.f.getHostId();

    // - Set up further top-level variables
    mq.f.createSessionVariables();

    mq.s.initReady = (mq.ui.div.children().filter(".MQInitialization").size() == 1);

    if (mq.s.initReady) {

        if (mq.s.configOverride) { // set in createSessionVariables
            mq.f.getAjaxMqConfig(mq.s.initFile); // get test file
        } else {
            mq.f.processMqConfig(); // process what is in containing page already
        }

    } else {
        // do nothing
    }
}

mq.f.getAjaxMqConfig = function (u) { // Get test file
    mq.ui.div.html("");
    $.ajaxSetup({ cache: false });
    $.ajax({
        type: "GET",
        url: u,
        error: mq.f.configError,
        processData: false,
        success: function (data, status) {
            mq.ui.div.html(data);
            mq.f.processMqConfig();
        }
    });
}


mq.f.configError = function () {
    // Error
}

mq.f.checkPlatform = function () {

    // - Get platform from config
    mq.s.platform = mq.nodes.RootNode.config.targetPlatform || "Web";

    // - Check for url override
    if (mq.s.urlVars.platformoverride) {
        mq.s.platform = mq.s.urlVars.platformoverride;
    }

    mq.ui.rootNode.addClass(mq.s.platform);

    // - Check for kiosk mode flag for web/kiosk hybrids
    mq.s.isKioskMode = ((mq.s.platform == "Kiosk") || (mq.s.urlVars.kioskmode == "true"));

    if (mq.s.platform != "Web") {
        mq.s.getAllContent = true;
    }
}

mq.f.setRootNodes = function () {

    // add ids to make selectors faster
    // process root configs

    $(".RootNode").attr({ id: "RootNode", objectname:"RootNode" });
    mq.ui.rootNode = $("#RootNode");
    mq.f.processNodeDiv(mq.ui.rootNode);

    $(".RootDataNode").attr({ id: "RootDataNode", objectname:"RootDataNode"  });
    mq.ui.rootDataNode = $("#RootDataNode");
    mq.f.processNodeDiv(mq.ui.rootDataNode);

    $(".RootDemNode").attr({ id: "RootDemNode", objectname:"RootDemNode"  });
    mq.ui.rootDemNode = $("#RootDemNode");
    mq.f.processNodeDiv(mq.ui.rootDemNode);

}

mq.f.idContentConfig = function () {
    $(".Content").each(function () {
        var nodeId = $(this).parent().parent().attr("nodeid");
        if (nodeId > 0) { $(this).attr({ "nodeid": nodeId }); }
    });
    $(".Config").each(function () {
        var nodeId = $(this).parent().parent().attr("nodeid");
        if (nodeId > 0) { $(this).attr({ "nodeid": nodeId }); }
    });
}

mq.f.processMqConfig = function () {

    // - Container of initialization string

    mq.ui.div.show(); // ??
    mq.ui.div.css({ visibility: "hidden" });

    mq.f.setRootNodes();
    mq.f.checkPlatform();
    mq.f.idContentConfig();

    // reset node counter
    mq.ui.nodeCount = 0;

    // ---- Process Nodes -----------------------------------------------   

    // Process Data Nodes
    $(".Node", mq.ui.rootDataNode).each(function () {
        mq.f.processNodeDiv($(this));
    });

    // Process Dem Nodes
    $(".Node", mq.ui.rootDemNode).each(function () {
        mq.f.processNodeDiv($(this));
    });

    // Process Managers and Calculators first
    $(".Node", mq.ui.rootNode).each(function () {
        if (mq.f.isManager($(this))) { mq.f.processNodeDiv($(this)); }
    });

    // Process UI Controls
    $(".Node", mq.ui.rootNode).each(function () {
        if (mq.f.isManager($(this)) == false) { mq.f.processNodeDiv($(this)); }
    });

    // ----- Initialize Interface ---------------------------------------

    if (mq.s.isValidated) { // set to false if any node config fails

        mq.f.initializeNode(mq.nodes.RootNode); // recursively initialize all interface nodes

        mq.f.revealInterface();

    } else {
        // allow for config editing if configs not validated
        if (mq.s.performConfigEditing) {
            var em = mq.managers.EditManager;
            if (em) {
                em.div.insertAfter(mq.ui.rootNode);
                em.initialize();
            }
        }
        mq.ui.rootNode.hide();
    }
}

mq.f.processNodeDiv = function (d) { // instantiate a js node object based on the div

    mq.ui.nodeCount += 1;

    // the node type is the second string in the class attribute as the first is "Node"
    var nodeType = d.attr("class").split(" ")[1];
    var newObj;
    try {

        if (mq.nodeTypes[nodeType]) { // check that constructor exists for this note type
            newObj = new mq.nodeTypes[nodeType](d); // instantiate node type
        } else {
            newObj = new mq.nodeTypes.GenericNode(d); // instantiate default node
        }
    } catch (err) {
        mq.console("---");
        mq.console("Error processing div!");
        mq.console("Potential code problem with " + nodeType);
        mq.console("div:");
        mq.console(d);
        mq.console("---");
    }
}

mq.f.initializeNode = function (n) {

    var initSuccess = true;
    var codeSuccess = true;
    var status = "";

    try {
        if (n.initialize) {
            n.initialize();
            status = n.config.isInitialized;
            if (status == false) {
                mq.console("Warning: " + n.objectName + " (" + n.nodeType + ") did not initialize.");
                initSuccess = false;
            }
        } else {
            mq.console(n.objectName + " does not have an initialization function.");
        }
    }
    catch (err) {
        mq.console("Caught initialiation error " + n.objectName + " (" + n.nodeType + ")");
        codeSuccess = false;
    }

    if (codeSuccess) {
        var allNodes = n.nodes.all();
        for (cn in allNodes) {
            var c = allNodes[cn];
            mq.f.initializeNode(c); // recursive
        }
    }
}


mq.f.getAllContent = function () {
    $("[loadtype=2]").each(function () {
        if ($(this).hasClass("ContentLeaf")) {

        } else {
            mq.f.loadContent(this);
        }

    });
}

mq.f.revealInterface = function () { // TODO_INIT this is a miscellaneous grab-bag

    // - Reveal interface
    $(".InfoPages").hide();
    //$(".Restart").hide();
    $(".AboutThis").hide();
    $(".MiniNav").hide(); // TODO!

    //mq.ui.div.show();
    $(".LoadingScreen").hide();
    $("#mq_loading").hide();

    mq.ui.div.css({ visibility: "visible" }); 

    // - Check to get all content right away
    if (mq.s.getAllContent) { mq.f.getAllContent(); }

    // - Focus keyboard on Header - TODO_INIT accessibility
    $(".Header").focus();

    // - Intitialization has completed
    mq.s.mqInitialized = true;

    // - Move all Details to BTF, set current details
    //$(".Details", mq.ui.div).hide();
    //mq.trigger("checkToMoveDetails", mq.ui.div);

    // - If screens exist, open screen 1
    if (mq.f.nodeClassExists("ScreenContainer")) {
        mq.openScreen(1);
    }

    var config = mq.nodes.RootNode.config;

    // - Title
    var title = config.titleText || "MetroQuest";
    document.title = title;

    // - For custom triggers, add code to CustomScript in RootNode config
    if (config.initialTrigger) {
        mq.trigger(config.initialTrigger);
    }

    mq.f.validateSession();

    mq.trigger("MqInitializationComplete");

    mq.f.checkUrlTriggers();

    mq.trigger("displayDemVars");

}

// ---------------- GET VARIABLES FROM URL --------------------------------------------------------------------

mq.f.getUrlParams = function () {

    // - Get any variables that might be in URL
    mq.s.urlVars = mq.f.getUrlVars();

    // - Testing mode - true as long as test is present and not false - ?test=true or ?test=testfile
    mq.s.isTestMode = mq.f.booleanParameter(mq.s.urlVars.test, false);

    // - Write to console iff in test mode
    mq.s.writeToConsole = mq.s.isTestMode;

    // - Set initialization URL ?remote=[subdomain]
    mq.s.configOverride = (mq.s.urlVars.remote != undefined);
    if (mq.s.configOverride) {
        var remoteServer = "http://" + mq.s.urlVars.remote + ".metroquest.com/";
        mq.s.initFile = remoteServer + "initialize.aspx?platformid=1&technologyid=2";
        mq.s.dataURL = remoteServer + "getdata.aspx";
    } else {
        mq.s.dataURL = "getdata.aspx";
    }

    // - Check for override to cancel tracking - ?t=[false]
    mq.s.performTracking = true; // set true by default

    // - Check for shared URL - ?share=[true]
    mq.s.isShare = mq.f.booleanParameter(mq.s.urlVars.share, false);

    // - Check for initial scenario - ?s=[scenarioid]
    var s = mq.s.urlVars.s;
    mq.s.isDefaultScenario = (s == undefined);
    if ((mq.s.isDefaultScenario != true) || (mq.s.isShare)) {
        mq.trigger("changeScenarioFromURL", s); // will set initial scenario to shared scenario
    }

    // - Check to turn off deep linking
    mq.s.deepLinking = false;

    // - Editing flags - ?edit=true&config=true
    mq.s.performContentEditing = mq.f.booleanParameter(mq.s.urlVars.edit, false);
    if (mq.s.performContentEditing) {
        mq.s.performConfigEditing = mq.f.booleanParameter(mq.s.urlVars.config, false);
    } else {
        mq.s.performConfigEditing = false;
    }
    mq.s.hideEditingDots = mq.f.booleanParameter(mq.s.urlVars.hidedots, false);

    // - Get all content - ?getallcontent=true
    mq.s.getAllContent = mq.f.booleanParameter(mq.s.urlVars.getallcontent, false);

    // - Monitor tracking - ?trackstatus=true
    mq.s.trackStatus = mq.f.booleanParameters([mq.s.urlVars.trackstatus, mq.s.urlVars.tracking, mq.s.urlVars.track, mq.s.urlVars.trackingtest], false);

    // - Keyboard mode
    mq.s.keyboardEnabled = mq.f.booleanParameter(mq.s.urlVars.keyboard, true); 

}


// -------------------------------- TOP-LEVEL SESSION VARIABLES ------------------------------------------------------------------------

mq.f.getSessionId = function () {
    mq.s.sessionId = Number($(".SessionID", mq.ui.div).html());
    if (mq.s.sessionId > 1) {
        mq.s.trackingReady = true;
    } else {
        mq.s.sessionId = 1;
    }
}

mq.f.getHostId = function () {
    mq.s.hostId = Number($(".HostID", mq.ui.div).html());
}

mq.f.createSessionVariables = function () {

    // TODO
    mq.s.technologyId = 2; // required for some server queries

    // - Ensure this function is only run once 
    mq.s.mqActive = true;

    // - Track number of changes that affect URL
    mq.s.sessionUrlChangeCount = 0;

    mq.s.editorLocation = $("#EditorLocation").html();
    mq.s.dataLocation = $("#EditorLocation").html(); 

    // ----------------- MISC VARIABLES - TODO_INIT : CLEAN UP ------------------
    mq.s.isStartup = true;
    mq.s.screenClickCount = 0;

    mq.s.viewingScenario = false;
    mq.s.isRandomizingScenario = false;

    // - Metrolinx specific - move?
    mq.s.lastInvestmentLevel = 1;
    mq.s.nextInvestmentLevel = 1;
    mq.s.investmentLevel = 1;
}

mq.f.getNewSessionId = function () {

    var url = "GetSessionId.aspx";
    url += "?technologyId="+mq.s.technologyId;

    $.ajaxSetup({ cache: false });
    $.ajax({
        type: "GET",
        url: url,
        error: function (data, success) {

        },
        processData: false,
        success: function (data, success) {
            mq.s.sessionId = $(".SessionID",$(data)).text();
            mq.f.validateSession();
        }
    });

}

mq.f.validateSession = function () {
    var url = "ValidateSession.aspx?sessionid=" + mq.s.sessionId;
    url += "&technologyId=" + mq.s.technologyId;
    var trackId = mq.s.trackId;
    mq.trigger("trackStatus", ["NewSessionId", mq.s.sessionId, url, trackId]);
    $.ajax({
        type: "GET",
        url: url,
        success: function (data, success) {
            mq.trigger("trackStatusSuccess", trackId);
        }
    });

}



// -------------------------------- LOADING CONTENT (AJAX) ------------------------------------------------------------------------------


// converts a div containing an image address into that image - NOT CURRENTLY USED!
mq.f.populateImageContent = function (d, dims) { 

    var theDiv = $(d);
    var imgSrc = theDiv.html();
    theDiv.css({
        "background-image": "url(" + imgSrc + "')",
        border: "none",
        position: "absolute",
        left: dims.left,
        top: dims.top,
        height: dims.height,
        width: dims.width
    });
    theDiv.html(" ");

}

mq.f.loadContent = function (obj) {

    var contentId = $(obj).attr("contentid");
    var loadType = $(obj).attr("loadtype");

    var getDataUrl = mq.s.dataURL + "?id=" + contentId + "&type=Content&format=AJAX";

    if (loadType === "2") {
        if ($(obj).attr("override")) {
            // will be true if content id = 1 and overidden
        } else {
            $.ajax({
                type: "GET",
                url: getDataUrl,
                contentObj: obj,
                error: mq.f.loadContentError,
                success: mq.f.populateDiv
            });
        }
    }
}

mq.f.updateContent = function (obj) {
    $('[loadtype=2]', obj).each(function () {
        var loadingStatus = $(this).attr("loading");
        if (loadingStatus) {
            // already loading or loaded
        } else {
            if ($(this).attr("override")) {
                // will be true if content id = 1 and overidden
            } else {
                $(this).attr("loading", "true");
                mq.f.loadContent(this);
            }
        }
    });
}


mq.f.loadContentError = function (data, success) {
    mq.console("Load content error");
    mq.console(data);
}

mq.f.populateDiv = function (data, success) { // fill DOM object with content

    var div = $(this.contentObj)

    div.html($.trim(data)).attr("loadtype", "1").removeAttr("loading");
    mq.trigger("addInlineEditButton", div);


    // Check for demographic variables
    if ($("[demvar],[variable_name]", div).size() > 0) {
        mq.trigger("displayDemVars", div);
    }
    
    // Check for feedback form // TODO_DEM clunky
    if ($("[name=feedback]", div).size() > 0) {
        mq.trigger("setCurrentFeedback", div);
    }

    // Handle standard clicks
    $(".SendFeedback", div).bind("click", function () { // TODO_DEM remove this
        mq.trigger("openFeedback");
    })

    var contentLoadedTrigger = "contentloaded_" + div.attr("contentid");
    mq.trigger(contentLoadedTrigger);
}

// -------------------------------- UTILITIES ---------------------------------------------------------------------------------

mq.f.getUrlVars = function() {
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for (var i = 0; i < hashes.length; i++) {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

function trim(stringToTrim) {
    return stringToTrim.replace(/^\s+|\s+$/g, "");
}

mq.console = function (msg, c) {
    if (mq.s.writeToConsole) {
        if ($.browser.msie) {
            // window.console.log(msg);
        } else {
            if (c) { console.log(c); }
            console.log(msg);
        }
    }
}

mq.f.nodeClassExists = function (className) {
    return ($("." + className).size() > 0);
}

mq.f.nodeIdByClass = function (c) { // This is sloppy - fix
    var o = $("." + c);
    var nodeId = o.attr("nodeid");
    return (nodeId);
}

mq.f.isManager = function (n) { // TODO
    return ((n.attr("class").indexOf("Manager") > -1) || (n.attr("class").indexOf("Calculator") > -1))
}

mq.f.isNotIE = function () {
    return jQuery.support.cssFloat;
}

mq.f.append = function (o) { // add to f?

    // o is an object with several properties
    // o must have properties o.type and o.targ
    // o can have optional properties o.id, o.content 
    // also one additional optional attribute o.attr (name/value pair);
    // switch element type with o.elementType

    // node id
    mq.ui.nodeCount += 1;
    o.id ? id = o.id : id = "Node" + mq.ui.nodeCount;

    // node content
    o.content ? content = o.content : content = "";

    // element type
    o.elementType ? elementType = o.elementType : elementType = "div";

    // build tag
    var tag = "<" + elementType + " class='" + o.type + "' id='" + id + "' >" + content + "</" + elementType + ">";

    // return jquery object
    var appendObject;
    if (o.mode) {
        if (o.mode == "prepend") {
            appendObject = $(tag).prependTo(o.targ);
        } else {
            appendObject = $(tag).appendTo(o.targ);
        }
    } else {
        appendObject = $(tag).appendTo(o.targ);
    }

    if (o.css) {
        appendObject.css(o.css);
    }
    if (o.attr) {
        appendObject.attr(o.attr);
    }

    // return jquery object
    return (appendObject);
}

mq.f.getNode = function (div, defaultConfig, defaultContent) {

    // ----------- config ---------------

    var config = mq.f.processConfig(div, defaultConfig);

    // ----------- contents -------------

    var contentsObj = mq.f.processContents(div, defaultContent);
    var contentsDiv = div.children().filter(".Contents");

    // ------------- nodes -----------------

    var nodesDiv = div.children().filter(".Nodes");
    var nodeSet = nodesDiv.children().filter(".Node");

    // --- parent object ----
    var parentDiv = mq.ui.div;
    div.parentsUntil(".Node").each(function () {
        parentDiv = $(this).parent();
    });

    var parentId = parentDiv.attr("objectname") || 0; //"id") || 0;
    var parentNode = mq.nodes[parentId];

    // ---- attributes -----
    var nodeId = div.attr("nodeid");
    if (nodeId == 0) {
        nodeId = mq.f.newNodeId();
    }

    var objectName = "";
    var nodeType = div.attr("class").split(" ")[1];

    var isManager = (nodeType.indexOf("Manager") > -1);
    config.isManager = isManager;

    if (div.attr("id")) {
        objectName = div.attr("id");
    } else if (nodeType.indexOf("Manager") > -1) {
        objectName = nodeType;
        div.attr("id", objectName);
    } else {
        if (config.objectName) {
            objectName = config.objectName;
        } else {
            objectName = nodeType + nodeId;
        }
        div.attr("id", objectName);
    }
    div.attr("objectname", objectName);

    /*if (config.details) {
    mq.details[objectName] = contentsObj[config.details];
    contentsObj[config.details].getDiv().attr("details", objectName).attr("detailstype", nodeType);
    }*/

    var ui = {
        nodes: nodesDiv,
        nodeSet: nodeSet,
        contents: contentsDiv
    }

    // -------------- universal initialziation -----------------

    // apply styles
    div.css(config.css || {});

    if (config.siblingOrder) {
        switch (config.siblingOrder) {
            case "toFront":
                div.appendTo(div.parent());
                break;
            case "toBack":
                div.prependTo(div.parent());
                break;
        }
    }

    // apply displayStyle class
    if (config.displayStyle) {
        // do nothing
    } else {
        config.displayStyle = "Regular";
    }
    div.addClass(config.displayStyle);

    config.isInitialized = false;
    config.isBuilt = false;

    // -------------- nodes (children) functions ------------

    hideNodes = function (c) {
        if (c) {
            divsByType(c).each(function () {
                $(this).hide();
            });
        } else {
            node.ui.nodeSet.each(function () {
                $(this).hide();
            });
        }
    }
    showNodes = function (c) {
        if (c) {
            divsByType(c).each(function () {
                $(this).show();
            });
        } else {
            node.ui.nodeSet.each(function () {
                $(this).show();
            });
        }
    }
    addNode = function (n) {
        node.nodes[n.objectName] = n;
    }
    nodesByType = function (c) {
        var ns = [];
        node.ui.nodeSet.filter("." + c).each(function () {
            var dv = $(this);
            var id = dv.attr("objectname"); //"id");
            var nd = mq.nodes[id];
            ns.push(nd);
        });
        return (ns);
    }
    allNodes = function () {
        var ns = [];
        node.ui.nodeSet.each(function () {
            var dv = $(this);
            var id = dv.attr("objectname"); //"id");
            var nd = mq.nodes[id];
            if (nd) {
                ns.push(nd);
            } else {
                mq.console("MISSING ID!");
            }
        });
        return (ns);
    }
    divsByType = function (c) {
        var cn = {};
        if (node.ui.nodeSet) {
            cn = node.ui.nodeSet.filter("." + c)
        }
        return (cn);
    }
    screenNum = function () {
        // finds number of containing screen, or zero
        var sd = div.parentsUntil(".SlidingScreen").parent();
        var sn = sd.attr("screennum") || 0;
        return (sn);
    }

    var nodeObj = {
        add: addNode,
        all: allNodes,
        byType: nodesByType,
        divsByType: divsByType,
        hideNodes: hideNodes,
        showNodes: showNodes,
        screenNum: screenNum
    }


    var node = {
        config: config,
        contents: contentsObj,
        div: div,
        nodeId: nodeId,
        nodes: nodeObj,
        objectName: objectName,
        nodeType: nodeType,
        parentNode: parentNode,
        ui: ui
    }



    // ---------------- finshed. store and return node ------------------

    // store node in global list (mq.node)
    mq.nodes[node.objectName] = node;

    // store node in collection of this type
    var setName = node.nodeType;
    if ((mq.collections[setName]) || (isManager)) {
        // already built
    } else {
        mq.collections[setName] = []; // create empty collection to hold all nodes of this type
    }
    if (isManager) {
        mq.managers[objectName] = node;
    } else {
        mq.collections[setName].push(node); // [objectName] = node;
    }

    // place node in global structure (mq.struct)
    if (parentId == 0) {
        mq.struct[objectName] = node; // add to root node
    } else {
        var p = mq.nodes[parentId];
        if (p) {
            p.nodes.add(node);
        }
    }

    return (node);

}

mq.f.processConfig = function (o, d) {

    var config = {};

    var vars = {};
    vars.error = "noError";

    var configFound = false;

    var configs = $(o).children().filter(".Configs");
    if (configs.size() == 1) {
        configFound = true;
        config = configs.children().filter(".Params");
    }

    if (configFound) {
        var configId = config.attr("configid");
        var c = $.trim(config.html());

        if (c.indexOf("{") == 0) { // whole config is json
            var v = {};
            try {
                v = eval('(' + c + ')');
                v.error = "noError";
            }
            catch (err) {
                v = { error: c };
            }
            vars = v;
        } else { // at least one var tag

            $(".Var", config).each(function () {

                var varDiv = $(this);
                var varName = varDiv.attr("class").split(" ")[1];
                c = $.trim(varDiv.html());

                try {
                    if (c.indexOf("{") == 0) { // var is json
                        vars[varName] = eval('(' + c + ')');
                    } else {
                        vars[varName] = Number(c) || c;
                    }
                }
                catch (err) {
                    vars.error = c;
                }
            });
        }

        // handle legacy variable names with first letter capitalized
        for (varName in vars) {
            var f = varName.substring(0, 1); // first character
            var lowerCasef = f.toLowerCase();
            if (f != lowerCasef) { // first char is uppercase
                var newName = lowerCasef + varName.substring(1, varName.length);
                vars[newName] = vars[varName];
            }
            if ((varName.toLowerCase() == "css") && (varName != "css")) {
                vars.css = vars[varName];
            }
        }

        // check for default config - at minimum there is an isActive flag
        if (d) { d.isActive = true; } else { d = { isActive: true} }

        // update default config with platform appropriate values, if any
        if ((d.kiosk) && (mq.s.platform == "Kiosk")) {
            for (theVar in d.kiosk) {
                d[theVar] = d.kiosk[theVar];
            }
        }
        if ((d.workshop) && (mq.s.platform == "Workshop")) {
            for (theVar in d.workshop) {
                d[theVar] = d.workshop[theVar];
            }
        }

        // overwrite default config with custom config, if any
        for (theVar in d) { // cycle through default parameters
            var defaultVal = d[theVar];

            if (vars[theVar]) {

                if ((typeof (defaultVal) == "boolean") && (typeof (vars[theVar]) == "string")) {
                    // if custom var is string and default var is boolean, 
                    // check for strings "true"/"false" and convert to boolean
                    vars[theVar] = mq.f.booleanParameter(vars[theVar], defaultVal);
                } else {
                    // otherwise, leave custom var as is
                }
            } else {
                // if no custom var, use default var
                vars[theVar] = defaultVal;
            }
        }

        if ((vars.error != "noError") && mq.s.isValidated) {

            mq.s.isValidated = false;

            var classType = o.attr("class").split(" ")[1];
            var errMessage = "Problem in " + classType + "<br/>Node Id:" + o.attr("nodeid") + "<br/>Config Id:" + vars.configId + "<br/>Config:<Br/><Br/>" + vars.error;

            var loadingPane = $("#mq_loading");
            mq.f.append({ targ: loadingPane, type: "ConfigErrorMessage", content: errMessage });

        }


        vars.configId = config.attr("configid");
    } else {
        mq.console("NO CONFIG FOUND!");
        mq.console(o);
    }
    return vars;
}

mq.f.parameter = function (configVal, defaultVal) {
    return (configVal || defaultVal);
}

mq.f.booleanParameters = function (configList, defaultVal) {

    var cVal = (defaultVal == "true");
    for (cId in configList) {

        var config = configList[cId];

        if (config) { // not undefined
            cVal = mq.f.booleanParameter(config, defaultVal);
        } else {
            // do nothing
        }

    }
    return (cVal);
}

mq.f.booleanParameter = function (configVal, defaultVal) {
    // checks for strings "true" and "false", converts to boolean
    // if no parameter present, return default

    if (defaultVal) { // default equals true
        if (configVal) {
            return (configVal.toLowerCase() != "false");
        } else {
            return true;
        }
    } else { // default equals false
        if (configVal) {
            return (configVal.toLowerCase() == "true");
        } else {
            return false;
        }
    }
}


mq.f.getNodeFromDiv = function (div) {
    //var id = div.attr("id");
    var id = div.attr("objectname");
    return (mq.nodes[id]);
}


/* --------------- FORMATTING -------------- */

mq.f.parseEmbeddedVarString = function (s, t) { // TODO_INIT replace with mq.f.buildString();
    // utility for embedding global variables into a string
    // form is "initial text {{varName}} more text {{varName2}} etc. "
    // NB there must be some text before first var and after last var
    // object t contains values

    var delim1 = "{{";
    var delim2 = "}}";

    // create array based on beginning delimiter
    var s1 = s.split(delim1);

    // take text before first variable as starting point
    var r = s1[0];

    for (n = 1; n < s1.length; n++) { // for each variable
        var s2 = s1[n].split(delim2); // break each array element into two parts
        var varName = s2[0]; // variable is in first part,
        var v = t[varName] || "";  // evaluate variable
        var varSpan = "<span class='" + varName + "' >" + v + "</span>"; // surround with span for styling
        r += varSpan + s2[1]; // add variable and the remainder
    }

    return r;
}

mq.f.commaFormat = function (n) {

    // input: 3532.23
    // output: 3,532.23
    var nStr = n.toString();
    nStr += '';
    var x = nStr.split('.');
    var x1 = x[0];
    var x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;

}

// --------------------- TRIGGERS ---------------------

mq.f.stopNodes = function (div) { // not used
    $(".Node", div).each(function () {
        var n = $(this);
        var c = n.attr("class").split(" ")[1];
        var t = "stop" + c;
        n.trigger(t);
    });
}

mq.f.preUpdateNodes = function (div) { // not used
    $(".Node", div).each(function () {
        var n = $(this);
        var c = n.attr("class").split(" ")[1];
        var t = "preUpdate" + c;
        n.trigger(t);
    });
}

mq.f.updateNodes = function (d) {
    $(".Node", d).each(function () {

        // udpate this node by id
        // (class trigger disabled)

        var n = $(this);
        var id = n.attr("objectname"); //"id");

        var idTrigger = "update" + id;
        n.trigger(idTrigger);
    });
}
mq.hideObjByName = function (objName) {
    $("#" + objName).hide();
}
mq.hideObjByClass = function (className) {
    $("." + className).hide();
}
mq.showObjByName = function (objName) { 
    $("#" + objName).show();
}
mq.showObjByClass = function (className) {
    $("." + className).show();
}
mq.updateObjByClass = function (className) {
    $.event.trigger("update" + className);
}
mq.updateObjByName = function (objName) { 
    $.event.trigger("update" + objName);
}
mq.trigger = function (t, p) {
    if (p) {
        if (typeof (p) == "object") {
            $.event.trigger(t, p);
        } else {
            $.event.trigger(t, [p]);
        }
    } else {
        $.event.trigger(t);
    }
}

mq.triggerClick = function (id) {
    $("#" + id).trigger("click");
}

mq.f.trackEvent = function (eventName, nodeId, eventParameter, commentString) { // not used, use trigger directly instead
    mq.trigger("trackEvent", [eventName, nodeId, eventParameter, commentString]);
}

// --------------------------- misc utilities ------------------------

/*mq.f.hasDetails = function () {
    return (mq.f.nodeClassExists("DetailsContainer"));
}*/

mq.f.addModalBackground = function () { // TODO
    if (mq.f.nodeClassExists("ModalBackground")) {
        // already created, do nothing
    } else {
        mq.ui.ModalBackground = mq.f.append({ targ: $("body"), type: "ModalBackground" });
        mq.ui.ModalBackground.hide();
        if (mq.f.isNotIE()) {

        } else {
            mq.ui.ModalBackground.css({ visibility: "hidden" });
        }
    }
}

mq.f.newNodeId = function () {
    mq.ui.nodeCount += 1;
    var newId = mq.ui.nodeCount + 10000000;
    return (newId);
}

function isiPad() {
    return (navigator.platform.indexOf("iPad") != -1);
}

mq.f.matchPosition = function (o, target) {
    var t = target.position().top;
    var l = target.position().left;
    o.css({ top: t, left: l });
}

mq.f.contentAttr = function (o) {
    return ({ "contentid": o.attr("contentid"), "nodeid": o.attr("nodeid") });
}

mq.f.newBackground = function (div, config) { // object, config

    var theBg;

    if (config.cornerStyle == "rounded") {

        var cornerWidth = config.cornerWidth || 10;
        var cornerHeight = config.cornerHeight || 10;
        var bgStyle = config.bgStyle || "white";

        theBg = mq.f.append({ targ: div, type: "RoundedCorners", mode: "prepend" });

        //theBg.addClass("RoundedCorners");
        theBg.addClass(bgStyle);

        var w = div.width() - (2 * cornerWidth);
        var h = div.height() - (2 * cornerHeight);

        var topRow = mq.f.append({ targ: theBg, type: "RoundedCornerTopRow" });
        var bodyRow = mq.f.append({ targ: theBg, type: "RoundedCornerBodyRow BG" });
        var bottomRow = mq.f.append({ targ: theBg, type: "RoundedCornerBottomRow" });

        var tl = mq.f.append({ targ: topRow, type: "RoundedCorner TL" });
        var t = mq.f.append({ targ: topRow, type: "RoundedCornerTop BG" });
        var tr = mq.f.append({ targ: topRow, type: "RoundedCorner TR" });

        var body = mq.f.append({ targ: bodyRow, type: "RoundedCornerBody" });

        var bl = mq.f.append({ targ: bottomRow, type: "RoundedCorner BL" });
        var b = mq.f.append({ targ: bottomRow, type: "RoundedCornerBottom BG" });
        var br = mq.f.append({ targ: bottomRow, type: "RoundedCorner BR" });

        bodyRow.css({ height: h });
        body.append($(".StaticContent", div));
        t.css({ width: w });
        b.css({ width: w });

    } else { // old style - TODO_INIT remove

        var nodeId = mq.f.newNodeId();

        var bgTemplate = "";
        bgTemplate += "<div class='mqBG' nodeid='" + nodeId + "'>";
        bgTemplate += "<div class='TopRow'><div class='Corner Top Left' /><div class='Edge Top Center' /><div class='Corner Top Right' /></div>";
        bgTemplate += "<div class='MidRow'><div class='Edge Mid Left' /><div class='Edge Mid Center ContentArea' /><div class='Edge Mid Right' /></div>";
        bgTemplate += "<div class='BotRow'><div class='Corner Bot Left' /><div class='Edge Bot Center' /><div class='Corner Bot Right' /></div>";
        bgTemplate += "</div>";

        var r = c.cornerRadius || 10;
        var edgeWidth = div.width() - (2 * r);
        var edgeHeight = div.height() - (2 * r);

        var theStyle = config.bgStyle || "white";

        $(bgTemplate).prependTo(div);

        $(".Center", div).css({ width: edgeWidth, left: r });
        $(".Mid", div).css({ height: edgeHeight, top: r });
        $(".Bot", div).css({ height: r, top: (div.height() - r) });
        $(".Top", div).css({ height: r, top: 0 });
        $(".Left", div).css({ width: r, left: 0 });
        $(".Right", div).css({ width: r, left: (div.width() - r) });

        var bg = $("[nodeid=" + nodeId + "]", div);

        bg.addClass(theStyle).addClass("Radius" + r);

        theBg = $(bg);
    }

    return (theBg);






}

mq.f.randomList = function (n) {

    var randomOrder = [];
    var list = [0];
    for (bn = 1; bn <= n; bn++) {
        randomOrder[bn] = { seed: Math.floor(Math.random() * 100), idx: bn };
    }
    randomOrder.sort(function (a, b) {
        return a.seed - b.seed
    });
    for (bn = 0; bn < n; bn++) {
        list.push(randomOrder[bn].idx);
    }
    return (list);
}

mq.nodeTypes.GenericNode = function (d) {

    // default config
    var dc = {

    };

    // default content
    var dt = {

    };

    var n = mq.f.getNode(d, dc, dt); // node (object)
    var c = n.config; // config variables
    var i = n.objectName;

    var v = {}; // local variables
    var u = {}; // local ui variables

    function build() {
        c.isBuilt = true;
    }

    function reset() {

    }

    function update() {
        if (c.isBuilt == false) {
            build();
        }
    }

    function hide() {
        d.hide();
    }

    function show() {
        d.show();
        update();
    }

    function initialize() {

        c.isInitialized = true;
    }

    d.bind("update" + i, update);
    d.bind("reset" + i, reset);
    d.bind("hide" + i, hide);
    d.bind("show" + i, show);

    n.initialize = initialize;
    return (n);

}

mq.HideDiv = function (d) {
    $("#" + d).hide();
}

mq.ShowDiv = function (d) {
    $("#" + d).show();
}

// --- processing contents (new)

mq.f.contentObj = function (cDiv) { // input: content node
    var co = {};
    co.getText = function () {
        return (cDiv.text());
    }
    co.getDiv = function () {
        return (cDiv);
    }
    co.getContent = function () {
        return (cDiv.html());
    }
    co.getClone = function () {
        return (cDiv.clone());
    }
    co.contentId = cDiv.attr("contentid");
    co.loadType = cDiv.attr("loadtype");
    // etc.
    return (co);
}

mq.f.processContents = function (div, defaults) {

    var contentsObj = {};

    var contentsDiv = div.children().filter(".Contents");
    if (contentsDiv.size() == 0) {
        contentsDiv = mq.f.append({ targ: div, type: "Contents" });
    }

    // add additional content
    if (defaults) {
        for (c in defaults) {
            var defaultContent = defaults[c];
            var dataContent = $("." + c, contentsDiv);
            if (dataContent.size() == 0) {
                var newContent = mq.f.append({ targ: contentsDiv, type: "Content " + c, content: defaultContent });
                newContent.attr({ "contentid": 1 });
            }
        }
    }

    // cycle through content
    contentsDiv.children().filter(".Content").each(function () {

        var contentDiv = $(this);
        var contentName = contentDiv.attr("class").split(" ")[1];
        var contentId = contentDiv.attr("contentid");

        // swap in default config, if any, if content has not been set (contentid = 1)
        if ((defaults) && (contentId == 1)) {
            if (defaults[contentName]) { // swap default content in, set override flag to true
                contentDiv.html(defaults[contentName]).attr("override", "true");
            }
        }

        // add object
        contentsObj[contentName] = mq.f.contentObj(contentDiv);

    });


    return (contentsObj);
}


// --- processing data types ----

mq.f.processDataSets = function (dt) { // input: data type, output: set of data sets

    var sets = [];

    var sNum = 0;

    var typeSetCollection = mq.collections[dt + "Set"];
    for (ssIdx in typeSetCollection) { // cycle through sets

        var dataSet = typeSetCollection[ssIdx];

        if (dataSet.config.isActive) {

            sNum += 1;
            var setId = dataSet.config[dt + "SetId"] || sNum;

            dataSet.div.attr(dt + "setid", setId);

            sets[setId] = []; // dataSet;
            sets[setId].contents = dataSet.contents;

            var dNum = 0;

            //for (sIdx in dataSet.childNodes) { // cycle through data in each set
            for (sIdx in dataSet.nodes.byType(dt)) { // cycle through data in each set

                //var d = dataSet.childNodes[sIdx];
                var d = dataSet.nodes[sIdx];

                if (d.config.isActive) {

                    dNum += 1;

                    var dataId = d.config[dt + "Id"] || dNum;

                    var id = dt + "_" + setId + "_" + dataId;

                    d.div.attr(dt + "setid", setId).attr(dt + "id", dataId).attr("id", id);

                    if (d.config.scenarioId) {
                        dataSet.div.attr({ "scenarioid": d.config.scenarioId });
                    }

                    // add to set
                    sets[setId][dataId] = d;

                    // look for details
                    var det = d.contents[dt + "Details"];
                    if (det) {
                        det.getDiv().addClass("Details").attr("detailsid", id);
                    }
                }
            }

            // look for details
            var detC = d.contents[dt + "SetDetails"];
            if (detC) {
                detC.getDiv().addClass("Details").attr("detailsid", setId);
            }
        }
    }

    return (sets);

}

mq.f.insertNodeType = function (nodeType, parentNodeType) {

    var parentNode = $("." + parentNodeType);
    if (parentNode.size() == 1) {
        var pNode = parentNode;
        var nodesNode = pNode.children().filter(".Nodes");

        var newNode = "<div class='Node " + nodeType + "' nodeid='" + mq.f.newNodeId() + "' >";
        newNode += "<div class='Configs'><div class='Config Params' /><div class='Config CSS' /></div>";
        newNode += "<div class='Contents' />";
        newNode += "</div>";

        nodesNode.prepend(newNode);
    }

}


mq.f.getMetaTag = function (o) { // o = { tag : "val", defaultValue = "val" }
    var tag = o.tag;
    var def = o.defaultValue || "";

    var meta = $("meta[property=" + tag + "]");
    var returnValue = def;
    var metaExists = (meta.size() == 1);
    if  (metaExists){
        returnValue = meta.attr("content");
    }
    return (returnValue);
}

mq.f.registerTriggerSet = function (triggerSet) {
    //var triggerSet = [{ abbr : "p", triggerName : "openMqInfo" }]; // example

    for (sIdx in triggerSet) {
        var st = triggerSet[sIdx];
        mq.f.registerUrlTrigger(st.abbr, st.triggerName);
    }
}

mq.f.registerUrlTrigger = function (abbr, triggerName) {
    mq.s.urlTriggers[abbr] = triggerName;
}

mq.f.checkUrlTriggers = function () { // example ?p=Configurations = mq.trigger('openMqInfo','Configurations');
    mq.s.urlTriggers = {};

    var starndardTriggers = [{ abbr: "s", triggerName: "openScreen"}]; // TODO_INIT ADD some standard triggers
    mq.f.registerTriggerSet(starndardTriggers);

    var customTrigers = mq.nodes.RootNode.config.customTriggers || [];
    mq.f.registerTriggerSet(customTrigers);


    for (abbr in mq.s.urlTriggers) {
        if (mq.s.urlVars[abbr]) {
            var trigger = mq.s.urlTriggers[abbr];
            var triggerTarget = mq.s.urlVars[abbr];
            mq.trigger(trigger, triggerTarget);
        }
    }
}

mq.f.buildString = function (template, vars) {

    var delim1 = "_%";
    var delim2 = "%_";

    var t1 = template.split(delim1);

    var t = "";

    for (tIdx in t1) {

        var t2 = t1[tIdx];

        var t3 = t2.split(delim2);

        var inside = t3[0];

        if (inside) {
            var varName = trim(inside);
            if (vars[varName]) {
                t += vars[varName];
            } else {
                t += inside;
            }

            var outside = t3[1]

            if (outside) {
                t += outside;
            }
        } else {
            t += t2;
        }


    }

    return (t);
}

mq.f.buildTemplate = function (t, v) {

    var r = "";
    var type = t.type;
    if (type) {

        r += "<div class='" + type + "' ";

        if (t.attr) {
            for (tId in t.attr) {
                r += tId + "='" + mq.f.buildString(t.attr[tId],v) +"' ";
            }
        }

        r += " >";

        if (t.content) {
            r += mq.f.buildString(t.content, v);
        }

        var children = t.children;
        if (children) {

            for (cId in children) {
                var child = children[cId];
                r += mq.f.buildTemplate(child, v);
            }

        }


        r += "</div>";
    }

    return (r);
}

// fixes a problem with older versions of jquery. 
// TODO_INIT upgrade to latest version of jquery and remove call to this fn
mq.f.jqueryFix = function () { 
    (function () {
        // remove layerX and layerY
        var all = $.event.props,
        len = all.length,
        res = [];
        while (len--) {
            var el = all[len];
            if (el != 'layerX' && el != 'layerY') res.push(el);
        }
        $.event.props = res;
    } ());
}

/*
* jQuery outside events - v1.1 - 3/16/2010
* http://benalman.com/projects/jquery-outside-events-plugin/
* 
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
//(function ($, c, b) { $.map("click dblclick mousemove mousedown mouseup mouseover mouseout change select submit keydown keypress keyup".split(" "), function (d) { a(d) }); a("focusin", "focus" + b); a("focusout", "blur" + b); $.addOutsideEvent = a; function a(g, e) { e = e || g + b; var d = $(), h = g + "." + e + "-special-event"; $.event.special[e] = { setup: function () { d = d.add(this); if (d.length === 1) { $(c).bind(h, f) } }, teardown: function () { d = d.not(this); if (d.length === 0) { $(c).unbind(h) } }, add: function (i) { var j = i.handler; i.handler = function (l, k) { l.target = k; j.apply(this, arguments) } } }; function f(i) { $(d).each(function () { var j = $(this); if (this !== i.target && !j.has(i.target).length) { j.triggerHandler(e, [i.target]) } }) } } })(jQuery, document, "outside");
