/**
 * @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.href;
            var h = document.location.hostname;
            if(document.location.port){
                h += ':' + document.location.port;
            }

            return sUrl.substr(sUrl.indexOf(h) + h.length);                            
        },
        _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);
                });
            }
            else{
                this._configureProp(propNumber,args);    
            }
            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);    
                    }
                });    
            }
            else{
                this._configureEvar(varNumber,args);    
            }
            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} 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.indexOf(',') == 0){
                s.products = s.products.substr(1);    
            }
            s.products += ',';
        },        
        /***
         * 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(typeof s.events == 'undefined'){
                    s.events = eName;
                }
                else if(s.events && s.events.indexOf(eName) < 0){
                    s.events = eName;
                }
            }            
            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);
                });    
            }
            else{
                o._addEvent(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(){
            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;
            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().setProp('19',null);    
            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').setProducts(pId).sendLinkData().setProducts(null);
            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()
                .setProp('14',null).setProp('13',null).setEvar('10',null);    
            /*
              this.setEvents('event15').setEvar('20',aId).setEvar('21',aName).setEvar('22',aCategory).setEvar('23',aLevel).sendData()
                .setEvar('20',null).setEvar('21',null).setEvar('22',null).setEvar('23',null);
            */
            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().setProp('12',null).setProp('13',null).setProp('16',null).setProp('20',null).setEvar('11',null);
            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()
                .setProp('13',null).setProp('16',null);
            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()
                .setProp('14',null).setProp('13',null).setProp('16',null);
            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.sendData();
            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().setProp('13',null);
            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){
            // 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]).sendData().setProp('13',null).setProp('18',null);                       

            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){
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents('event14').setProp('18',[aType,this._getFileName(aType,aUrl),aTitle]).sendData().setProp('18',null);

            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){
            // Transmit these events immediately, as clicks on multiple elements would clobber them.
            this.setEvents('event12').setProp('18',[aType,this._getFileName(aType,aUrl),aTitle]).sendData().setProp('18',null);                       

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

            return this;
        },    
        /***
         * Sets the events associated with clicking on a product view. 
         */
        setProdViewClick : function(){
            this.setEvents_safe('event10').sendLinkData();                       

            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(){
            this.setEvents('event13').sendData();
            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){
            this.setProp('13',['AddThis',str]).sendData().setProp('13',null);
            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(){
            this.setEvents('event13').setProp('13',['Print Page']).sendData().setProp('13',null);
            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().setProp('2',null);

            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');
        },        
        /***
         * 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;
             this.setEvents_safe('event4').setProp('4',this._getUrlPath(fileName)).setProp('5',pn).setEvar('6',this._getUrlPath(fileName)).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._removeEvents();
            }
            else{
                if(typeof s !== 'undefined'){
                    s.t();                     
                    this._removeEvents(); 
                }               
            }
            return this;
        },
        /***
         * Invokes Omniture's s.tl function, transmitting all data to Omniture
         */
        sendLinkData : function(){       
            if(this.tmpOmnitureVar){
                this.tmpOmnitureVar.tl();
                this._removeEvents();
            }
            else{
                if(typeof s !== 'undefined'){
                    s.tl();                     
                    this._removeEvents();   
                }              
            }
            return this;
        },        
        /***
        *  Initializes Omniture, writing it to the page, etc.
        */
        init : function(siteName){
            var o = this;
            if(typeof YAHOO != 'undefined' && !(window.location.href.indexOf('iw-cc')>0)){
                if(YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 7){
                    YAHOO.util.Event.addListener(window,'load',function(){
                        setTimeout(function(){
                          // Standard sitecatalyst script. Version R.14.  
                          YAHOO.util.Get.script('/static/js/omniture/s_code_' + siteName + '.js',
                          {
                              onSuccess : function(){ 
                                  s.server=location.hostname;                               
                                  o.processQueue();
                                  var s_code=s.t();
                                  if(s_code) {
                                    var div = document.createElement('div');
                                    div.style.display = 'none';
                                    div.innerHTML = s_code;
                                    document.body.appendChild(div)
                                  }
                                  o._clearAllData();                              
                              },
                              onFailure : function(){}
                          });
                        },1500);
                   });                     
                }
                else{
                    YAHOO.util.Event.onDOMReady(function(){
                      // Standard sitecatalyst script. Version R.14.  
                      YAHOO.util.Get.script('/static/js/omniture/s_code_' + siteName + '.js',
                      {
                          onSuccess : function(){ 
                              s.server=location.hostname;                               
                              o.processQueue();
                              var s_code=s.t();
                              if(s_code) {
                                var div = document.createElement('div');
                                div.style.display = 'none';
                                div.innerHTML = s_code;
                                document.body.appendChild(div)
                              }
                              o._clearAllData();                              
                          },
                          onFailure : function(){}
                      });
                   });                
                }
            }
            
        },
        /***
        *   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]();
                }
            }
        }
    }   
})();
