/**
 * @author Adam McIntyre
 * Wrapper module for Omniture tagging business logic.
 */


OmnitureHelper = (function() {
    var DELIMETER = "|",
            bcRegex = /\//g,
            commandQueue = [],
            undefined;

    var allProps = [2,3,4,5,8,9,10,12,13,14,16,17,18,19,20];  // All active sProp values. Used to reset to null after data transmissions.
    var allEvars = [6,7,8,9,10,11,20,21,22,23];  // All active sProp values. Used to reset to null after data transmissions.
    var propLength = 100;  // Maximum length of a "prop" string

    return{
        _getUrlPath : function(sUrl) {
            if(! sUrl) {
                sUrl = window.location.pathname;
            }
            var h = document.location.hostname;
            if(document.location.port) {
                h += ':' + document.location.port;
            }

            return (sUrl.indexOf(h) != -1 ? sUrl.substr(sUrl.indexOf(h) + h.length) : sUrl).replace(/^\//, '');
        },
        _getFileName : function(type, sUrl) {
            var remainingChars = propLength - type.length;
            var path = this._getUrlPath(sUrl);

            if(path.length > remainingChars) {
                return path.substr(path.length - remainingChars);
            }
            else {
                return path;
            }
        },
        /***
         * Walks up crumb's parent tree, constructing a string based on parent Elements' innerHTML.
         * Assumes HTMLElement is a ListItem.
         * @param {HTMLElement} breadcrumb Base-level Element
         */
        _buildBreadCrumb : function(breadcrumb) {
            return this._getUrlPath().replace(bcRegex, ' &gt; ');
        },
        /***
         * Queues up a given setProp event if s (Omniture) is not available or fires it immediately.
         * @param {String} propNumber Number of sProp we'd like to call
         * @param {String|Array} args String or Array of strings to add to set sProp's value to
         */
        setProp : function(propNumber, args) {
            if(typeof s == 'undefined') {
                var o = this;
                o.queueCommand(function() {
                    o._configureProp(propNumber, args);

                    if(nikon.inArray('prop' + propNumber, o.linkTrackVars) == -1) {
                        o.linkTrackVars.push('prop' + propNumber);
                    }
                });
            }
            else {
                this._configureProp(propNumber, args);

                if(nikon.inArray('prop' + propNumber, this.linkTrackVars) == -1) {
                    this.linkTrackVars.push('prop' + propNumber);
                }
            }

            return this;
        },
        /***
         * Sets a given "sProp" variable, given by propNumber, to a DELIMTER-delimted string of vals
         * @param {String} propNumber Number of sProp we'd like to call
         * @param {String|Array} args String or Array of strings to add to set sProp's value to
         */
        _configureProp : function(propNumber, args) {
            var prop = s['prop' + propNumber];

            if(typeof prop == 'undefined') {
                s['prop' + propNumber] = '';
            }

            if((typeof s['prop' + propNumber]) != 'undefined') {
                if(typeof args === 'string' || typeof args === 'number' || args === null) {
                    s['prop' + propNumber] = args;
                }
                else {
                    s['prop' + propNumber] = args.join('|');
                }
            }
            return this;
        },
        /***
         * Queues up a given setEvar event if s (Omniture) is not available or fires it immediately.
         * @param {String} varNumber Number of eVar we'd like to call
         * @param {String|Array} args String or Array of strings to add to set eVar's value to
         */
        setEvar : function(varNumber, args) {
            if(typeof s == 'undefined') {
                var o = this;
                YAHOO.util.Event.addListener(window, 'load', function() {
                    if(typeof s == 'undefined') {
                        setTimeout(function() {
                            o._configureEvar(varNumber, args);
                        }, 1600);
                    }
                    else {
                        o._configureEvar(varNumber, args);

                        if(nikon.inArray('eVar' + varNumber, o.linkTrackVars) == -1) {
                            o.linkTrackVars.push('eVar' + varNumber);
                        }
                    }
                });
            }
            else {
                this._configureEvar(varNumber, args);

                if(nikon.inArray('eVar' + varNumber, this.linkTrackVars) == -1) {
                    this.linkTrackVars.push('eVar' + varNumber);
                }
            }

            return this;
        },
        /***
         * Sets a given "eVar" variable, given by varNumber, to a DELIMTER-delimted string of vals
         * @param {String} varNumber Number of eVar we'd like to call
         * @param {String|Array} args String or Array of strings to add to set eVar's value to
         */
        _configureEvar : function(varNumber, args) {
            var sEVar = s['eVar' + varNumber];

            if(typeof sEVar == 'undefined') {
                s['eVar' + varNumber] = '';
            }

            if((typeof s['eVar' + varNumber]) != 'undefined') {
                if(typeof args === 'string' || typeof args === 'number' || args === null) {
                    s['eVar' + varNumber] = args;
                }
                else {
                    s['eVar' + varNumber] = args.join('|');
                }
            }
            return this;
        },
        /***
         * Queues up a given setProducts event if s (Omniture) is not available or fires it immediately.
         * @param {String} prod_id is the product ID to set
         * @param {String} prod_name is the product Name to set
         */
        setProductIdAndName: function(prod_id, prod_name) {
            this.setProducts(prod_id + '|' + prod_name);

            return this;
        },
        /***
         * Queues up a given setProducts event if s (Omniture) is not available or fires it immediately.
         * @param {String} val Value we'd like to set s.products to
         */
        setProducts : function(val) {
            var cVal = val;
            var o = this;
            if(typeof s == 'undefined') {
                this.queueCommand(function() {
                    o._addProduct(cVal);
                });
            }
            else {
                o._addProduct(cVal);
            }
            return this;
        },
        /***
         * Adds a product in a non-destructive manner.
         * @param {String|Array} ars Name/ID of product we're adding, i.e. "D80" or ["25343","D2000"]
         */
        _addProduct : function(args) {
            if(args === null) {
                s.products = null;
            }
            else if(typeof s.products === "string") {
                if(typeof args === 'string' || typeof args === 'number') {
                    s.products += ";" + args;
                }
                else {
                    s.products += ";" + args.join('|');
                }
            }
            else {
                if(typeof args === 'string' || typeof args === 'number') {
                    s.products = ";" + args;
                }
                else {
                    s.products = ";" + args.join('|');
                }
            }

            if(s.products !== null) {
                if(s.products.indexOf(',') == 0) {
                    s.products = s.products.substr(1);
                }
                s.products += ',';
            }

            if(s !== undefined && nikon.inArray('products', this.linkTrackVars) == -1 && s.products) {
                this.linkTrackVars.push('products');
            }
        },
        _cleanUpProducts: function() {
            if(s.products && s.products.lastIndexOf(',') == (s.products.length - 1)) {
                s.products = s.products.substr(0, s.products.length - 1);
            }
        },
        /***
         * Sets the s.events tag to eventName
         * @param {String} eventName Name of event we're triggering i.e. "event13"
         */
        setEvents : function(eventName) {
            var eName = eventName;
            if(typeof s == 'undefined') {
                this.queueCommand(function() {
                    if(typeof s.events == 'undefined') {
                        s.events = eName;
                    }
                    else if(s.events && s.events.indexOf(eName) < 0) {
                        s.events = eName;
                    }
                });
            } else {
                if(!s.events) {
                    s.events = eName;
                } else if(s.events && s.events.indexOf(eName) < 0) {
                    s.events = eName;
                }
            }

            if(nikon.inArray('events', this.linkTrackVars) == -1) {
                this.linkTrackVars.push('events');
            }
            
            return this;
        },
        /***
         * Sets the s.events tag to eventName in a non-destructive manner.
         * If you're setting more than one on a page, use this method.
         * @param {String} eventName Name of event we're triggering i.e. "event13"
         */
        setEvents_safe : function(eventName) {
            var eName = eventName;
            var o = this;
            if(typeof s == 'undefined') {
                this.queueCommand(function() {
                    o._addEvent(eName);

                    if(nikon.inArray('events', o.linkTrackVars) == -1) {
                        o.linkTrackVars.push('events');
                    }

                    o.linkTrackEvents.push(eName);
                });
            }
            else {
                o._addEvent(eName);

                if(nikon.inArray('events', this.linkTrackVars) == -1) {
                    o.linkTrackVars.push('events');
                }

                o.linkTrackEvents.push(eName);
            }

            return this;
        },
        /***
         * Adds an event tags in a non-destructive manner.
         * @param {String} eventName Name of event we're triggering i.e. "event13"
         */
        _addEvent : function(eventName) {
            if(typeof s.events === "string") {
                if(s.events.indexOf(eventName) < 0) {
                    s.events += "," + eventName;
                }
            }
            else {
                s.events = eventName;
            }
        },
        /***
         * Removes event tags. Use after an s.tl() call or data transmit.
         */
        _removeEvents : function() {
            s.events = null;
        },
        /***
         * Clears all data, resetting all sProps and eVars identified in the arrays above.
         */
        _clearAllData : function() {
            try {
                s.test = 'test';
            } catch(e) {
                return;
            }

            for(var i = 0, max = Math.max(allProps.length, allEvars.length); i < max; i++) {
                if(i < allProps.length) {
                    s['prop' + allProps[i]] = null;
                }
                if(i < allEvars.length) {
                    s['eVar' + allEvars[i]] = null;
                }
            }
            s.products = null;
            s.pageName = null;
            this.linkTrackVars = [];
            this.linkTrackEvents = [];
            this._removeEvents();
        },
        /***
         * Sets the Omniture variables required to track interactions with the navigation.
         * Must be transmitted immediately.
         * @param {String} navName The "name" of this navigation element (i.e. "top_nav", "side_nav");
         * @param {String} linkName The text-portion of this link.
         */
        setNavEvents : function(navName, linkName) {
            this.setProp('19', [this._buildBreadCrumb(),navName,linkName]).sendLinkData("Breadcrumb Click");
            return this;
        },
        /***
         * Sets the "Products You've Viewed" interaction events
         * @param {String} pId The product ID of the product we're tracking
         * @param {String} pName The name of the product we're tracking
         */
        setPYVEvents : function(pId, pName) {
            this.setEvents('event14').setProductIdAndName(pId, pName).sendLinkData("Products You've Viewed");
            return this;
        },
        /***
         * Sets the "Articles You've Viewed" interaction events
         * @param {String} aId The article ID of the article we're tracking
         * @param {String} aName The name of the article
         * @param {String} aCategory The category this article belongs to (i.e. "Nikon World")
         * @param {String} aLevel The level (beginner/advanced) of this article's content
         */
        setAYVEvents : function(aId, aName, aCategory, aLevel) {
            this.setEvents('event15').setProp('14', [aId,aName,aCategory,aLevel]).setProp('13',
                    'Articles You\'ve Viewed|').setEvar('10',
                    [aId,aName,aCategory,aLevel]).sendLinkData("Articles You've Viewed");
            return this;
        },
        /***
         * Sets the promo box link interaction events
         * @param {HTMLElement} linkEl The HTMLElement interacted with. Used to figure out "rank"/order/position that
         *   this link's parent box occurs in inside a row of promos (1, 2, 3...)
         * @param {String} boxTitle The title of this link's parent box
         * @param {String} linkUrl The URL of the given link
         * @param {String} linkTitle The title/text of this link
         */
        setPromoBoxEvents : function(linkEl, boxTitle, linkUrl, linkTitle) {
            var ancestorPromo = YAHOO.util.Dom.getAncestorByClassName(linkEl, 'promo_bw');

            var promos = YAHOO.util.Dom.getElementsByClassName('promo_bw');
            var boxRank = 0;
            for(var i = 0; i < promos.length; i++) {
                if(promos[i] == ancestorPromo) {
                    boxRank = i;
                    break;
                }
            }
            this.setEvents('event16').setEvents_safe('event13').setProp('12', boxRank + '').setProp('13',
                    ['Promo Box',boxTitle]).setProp('16', [linkUrl,linkTitle]).setProp('20', boxTitle)
                    .setEvar('11', boxTitle).sendLinkData("Promobox Click");
            return this;
        },
        /***
         * Sets the Page View event
         */
        setPageViewEvent: function() {
            s.pageName = s.getPageName && s.getPageName() || '';
            this.setEvents_safe('event8').sendData();
            this._clearAllData();
            return this;
        },
        /***
         * Sets the overview/Component 5 link interaction events
         * @param {String} ovTitle The title used for component 5
         * @param {String} ovUrl The URL of the given link
         * @param {String} ovLinkText The text/copy of the given link
         */
        setOverviewEvents : function(ovTitle, ovUrl, ovLinkText) {
            var pn = "";
            if(s.pageName) {
                pn = s.pageName;
            }
            else {
                pn = document.title;
            }
            this.setEvents('event13').setProp('13', ["Section Overview Header",ovTitle,pn]).setProp('16',
                    [ovUrl,ovLinkText]).sendLinkData("Component 5 Click");
            return this;
        },
        /***
         * Sets up the events fired by clicking on articles under the "related content" tabs
         * @param {String} aId The ID of the parent article clicked on
         * @param {String} aName The name of the given article
         * @param {String} aLevel The level of the given article
         * @param {String} tabTitle The title of the currently active tab.
         */
        setRelatedTabEvents : function(aId, aName, aLevel, tabTitle) {
            this.setEvents('event13').setProp('14', [aId,aName,aLevel]).setProp('13',
                    ['Related Content Tab',tabTitle]).setProp('16', tabTitle).sendLinkData('Related Content Tab');
            return this;
        },
        /***
         * Toggles the state of the in-page glossary
         * @param {Boolean} bState The on/off state of the glossary: true = on, false = off
         */
        setIPGStateEvent : function(bState) {
            s.eVar16 = bState;
            this.sendLinkData("Glossary Toggle");
            return this;
        },
        /***
         * Sets the events associated with clicking on an in-page glossary term
         * @param {String} gTerm The term clicked on.
         */
        setIPGTermClickEvents : function(gTerm) {
            this.setEvents('event13').setProp('13', ['Glossary Term',gTerm]).sendLinkData('Glossary Term Click');
            return this;
        },
        /***
         * Sets the events associated with clicking on a Media Center thumbnail.
         * @param {String} tabTitle The title of the currently selected tab
         * @param {String} aType The "type" (Image, Video, Audio, Document) of the clicked item
         * @param {String} aUrl The URL this item's asset.
         * @param {String} aTitle The Title of the item, if available.
         */
        setMCThumbClickEvents : function(tabTitle, aType, aUrl, aTitle) {
            /*FIXIT?, needs productID & productName */
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents('event12').setProp('13', ['Media Center',tabTitle]).setProp('18',
                    [aType,this._getFileName(aType, aUrl),aTitle]).sendLinkData('Media Center');

            return this;
        },
        /***
         * Sets the events associated with enlarging a Media Center Image/playing Video or Audio/linking to a document.
         * @param {String} aType The "type" (Image, Video, Audio, Document) of the clicked item
         * @param {String} aUrl The URL this item's asset.
         * @param {String} aTitle The Title of the item, if available.
         */
        setMCEnlargeClickEvents : function(aType, aUrl, aTitle) {
            /*FIXIT, needs productID & productName */
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents('event14').setProp('18',
                    [aType,this._getFileName(aType, aUrl),aTitle]).sendLinkData("Media Center Image Click");

            return this;
        },
        /***
         * Sets the events associated with clicking on a Media Bar thumbnail. At some point, I'd imagine this will match
         * what we're tracking above.
         * @param {String} aType The "type" (Image, Video, Audio, Document) of the clicked item
         * @param {String} aUrl The URL this item's asset.
         * @param {String} aTitle The Title of the item, if available.
         */
        setMediaBarThumbClickEvents : function(aType, aUrl, aTitle) {
            /*FIXIT, needs productID & productName */
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents('event12').setProp('18',
                    [aType,this._getFileName(aType, aUrl),aTitle]).sendLinkData("Media Bar thumbnail Click");

            return this;
        },
        /***
         * Sets the events associated with clicking on a product view Media Bar thumbnail.
         */
        setMBProdView : function() {
            /*FIXIT, needs productID & productName */
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents_safe('event10').sendLinkData("Media Bar thumbnail Click");

            return this;
        },
        /***
         * Sets the events associated with clicking on a product view.
         */
        setProdViewClick : function() {
            /*FIXIT, needs productID & productName */
            this.setEvents_safe('event10').sendLinkData("Product View Click");

            return this;
        },
        /***
         * Flags whether or not we've altered the AddThis core functions on this page.
         *
         */
        _addThisSet : false,
        /***
         * Sets and immediately sends all events/props associated with mousing over or clicking "AddThis" widget
         */
        setAddThisEvents : function() {
            /*FIXIT, needs productID & productName */
            this.setEvents('event13').sendLinkData("AddThis Widget Click");
            var o = this;

            if(typeof addthis_sendto === 'function' && ! this._addThisSet) {
                var f = addthis_sendto;
                addthis_sendto = function(str) {
                    o.setAddThisShareEvent(str);
                    f(str);
                    return false;
                }

                this._addThisSet = true;
            }

            return this;
        },
        /***
         * Sets and immediately sends the events/props associated with sharing an item through "AddThis" widget.
         * @param {String} str Name of service used to share
         */
        setAddThisShareEvent : function(str) {
            /*FIXIT, needs productID & productName */
            this.setProp('13', ['AddThis',str]).sendLinkData("AddThis Widget Click");
            return this;
        },
        /***
         * Sets and immediately sends the events/props associated with printing a page.
         * @param {String} s Name of service used to share
         */
        setPrintEvent : function() {
            /*FIXIT, needs productID & productName */
            this.setEvents('event13').setProp('13', ['Print Page']).sendLinkData("Print Page Click");
            return this;
        },
        /***
         * Sets the events/props associated with filtering on the Nikon World Past Issue page.
         * @param {String} cat Current category filter
         * @param {String} subcat Current subcategory filter.
         */
        setWorldFilterEvent : function(cat, subcat) {
            this.setProp('17', [cat,subcat]);
            return this;
        },
        /***
         * Sets up the events fired by clicking on articles under the "related content" tabs
         * @param {String} aId The ID of the parent article clicked on
         * @param {String} aName The name of the given article
         * @param {String} aCategory The category of the given article
         * @param {String} aLevel The level of the given article
         */
        setArticleDetailEvent : function(aId, aTitle, aCategory, aLevel) {
            this.setProp('14', [aId,aTitle,aCategory,aLevel]);
        },
        /***
         * Sets up the events fired by performing a search
         * @param {String} term The value of the term being searched for
         * @param {String} type (Optional) Optional "type" of search conducted, i.e. "product" for a product search,
         *     "noResults" for a search yielding no results.
         */
        setSearchEvent : function(term, type) {
            var o = this;
            this.addPixel(function() {
                var tmpTerm = term.replace(/'/g, "&#39;").replace(/"/g, "&quot;");
                var sChannel = "";
                if(s.channel) {
                    sChannel = s.channel;
                }

                if(type && type == 'noResults') {
                    o.setEvents('event5').setProp('3', [tmpTerm,sChannel]);
                }
                else if(type && type == 'product') {
                    o.setEvents('event5').setProp('8', [tmpTerm,sChannel]).setEvar('7',
                            [tmpTerm,sChannel]).setEvents_safe('event11');
                }
                // This data will be captured from search forms originally.
                // If there is a search string, the user has performed another search on the search results page.
                else if(document.location.search) {
                    o.setEvents('event5').setProp('2', [tmpTerm,sChannel]);
                }

                return o;
            });
            return true;    // Return true here in case this is bound to a search form.
        },
        /***
         * Sets up the events fired by performing a search via a form
         * @param {HTMLElement} f The form used to submit the search
         */
        setSearchFormEvent : function(f) {
            var o = this;

            var inputs = f.getElementsByTagName('input');
            var term;
            for(var i = 0; i < inputs.length; i++) {
                if(inputs[i].name == 'q') {
                    term = inputs[i].value;
                    break;
                }
            }

            var tmpTerm = term.replace(/'/g, "&#39;").replace(/"/g, "&quot;");
            var sChannel = "";
            if(s.channel) {
                sChannel = s.channel;
            }
            o.setEvents('event5').setProp('2', [tmpTerm,sChannel]).sendLinkData("Search Form Click");

            return true;    // Return true here in case this is bound to a search form.
        },
        /***
         * Sets the events associated with the various tracking points on the WTB page
         * @param {String} type The type of event that we'd like to trigger.
         * @param {Array|String|Object} vals (Optional) The value(s) we'd like to set type to.
         */
        setWTBEvent : function(type, vals) {
            switch(type) {
                case "checkout":
                    this.setEvents_safe('scCheckout').sendData();
                    break;
                case "add":
                    this.setEvents_safe('scAdd').sendData();
                    break;
                case "zip":
                    this.setProp('9', vals).setEvar('8', vals).sendData().setProp('9', null).setEvar('8', null);
                    break;
                case "category":
                    this.setProp('10', vals).setEvar('9', vals).sendData().setProp('10', null).setEvar('9', null);
                    break;
                case "product":
                    this.setProducts(vals).setEvents_safe('scAdd').sendData().setProducts(null);
            }
            return this;
        },
        /***
         * Sets the events associated with "viewing" a product on a given page. Note that these are "non-detailed" views, like those associated with
         * a component 1 or category page view.
         * @param {String|Array} p ID/name of product that's been viewed, i.e. "D80" or ["25443","D200"]
         */
        setProdViewEvent : function(p) {
            this.setEvents_safe('prodView').setProducts(p);
            return this;
        },
        /***
         * Queues up all of the various products we see on a product listing page
         * @param {String|Array} p ID/name of product that's been viewed, i.e. "D80" or ["25443","D200"]
         */
        setProdViewListEvent : function(p) {
            this.setProducts(p);
        },
        /***
         * Sends data for the various products we see on a product listing page
         */
        sendProdViewList : function(p) {
            this.setEvents_safe('prodView').sendLinkData("Product View Click");
        },
        /***
         * Sets the events associated with "viewing" a product detail page.
         * @param {String} pId ID of product that's been viewed, i.e. "25443"
         * @param {String} pName Name of product that's been viewed, i.e. "D80"
         */
        setProdDetailEvents : function(pId, pName) {
            this.setEvents_safe('event7').setProdViewEvent([pId,pName]);
            return this;
        },
        /***
         * Sets the events associated with initiating a download.
         * @param {HTMLElement} el The link element we're clicking on to initiate the download.
         */
        setDownloadEvent : function(el) {
            var pn = s.pageName || document.title.replace(' ', '-');
            var fileName = el.pathname || el.href;
            var path = this._getUrlPath(fileName);
            this.setEvents_safe('event4').setProp('4', path).setProp('5', pn).setEvar('6', path).sendData().setProp('4', null).setProp('5', null).setEvar('6', null);
            return true;
        },
        /***
         * Invokes Omniture's s.t function, transmitting all data to Omniture
         */
        sendData : function() {
            if(this.tmpOmnitureVar) {
                this.tmpOmnitureVar.t();
                this._clearAllData();
                this._removeEvents();
            } else {
                if(typeof s !== 'undefined' && s.t) {
                    if(s.channel) {
                        s.channel = s.channel.toLowerCase();
                    }
                    s.linkTrackVars = this.linkTrackVars.join(',');
                    s.linkTrackEvents = this.linkTrackEvents.join(',');
                    this._cleanUpProducts();
                    s.t();
                    this._clearAllData();
                    this._removeEvents();
                }
            }
            return this;
        },
        /***
         * Invokes Omniture's s.tl function, transmitting all data to Omniture
         */
        sendLinkData : function(text) {
            if(this.tmpOmnitureVar) {
                this.tmpOmnitureVar.tl(true, 'o', text);
                this._clearAllData();
                this._removeEvents();
            } else {
                if(typeof s !== 'undefined' && s.tl) {
                    if(s.channel) {
                        s.channel = s.channel.toLowerCase();
                    }
                    s.linkTrackVars = this.linkTrackVars.join(',');
                    s.linkTrackEvents = this.linkTrackEvents.join(',');
                    this._cleanUpProducts();
                    s.tl(true, 'o', text);
                    this._clearAllData();
                    this._removeEvents();
                }
            }
            return this;
        },
        /***
         *  Initializes Omniture, writing it to the page, etc.
         */
        init : function() {
            var o = this;

            nikon(window).load(function() {
                s.server = location.hostname;
                o.processQueue();
            });

        },
        /***
         *   Safely adds a JavaScript tracking pixel to the page
         *   @param f {Function} Function used to set pixel
         */
        addPixel : function(f) {
            if(typeof s === 'undefined') {
                this.queueCommand(f);
            } else {
                f();
            }
        },
        /***
         *   Adds a command to the queue, which will be processed on Omniture's load
         *   @param f {Function} Function we'd like to queue
         */
        queueCommand : function(f) {
            commandQueue.push(f);
        },
        /***
         *   Processes any commands queued up while Omniture was loading
         */
        processQueue : function() {
            for(var i = 0; i < commandQueue.length; i++) {
                if(typeof commandQueue[i] == 'function') {
                    commandQueue[i]();
                }
            }
        },
        linkTrackEvents: [],
        linkTrackVars: []
    }
})();

