var Analytics = Class.create({
    subscribers : {},
    listeners : {},
    fromConfigListeners : [],
    seenMemoWithTag : {},
    isEnabled: false,
    enabledModules: new Array(),
    bvLoaded: 0,
    uniqueEvents: [],
    lastQVloc: '',
    lastQVkey: '',
    currentTags: [],
    jsEventsWaited: [],

    RPC_METHODS_ALLOWED: new Hash({
        "prodcat" : 1,
        "generic" : 1,
        "cart" : 1,
        "rpc.form" : 1,
        "search" : 1,
        "email.signup" : 1
    }),

    initialize: function(modules,enabled){
        this._addStaticListeners();
    },
    addPendingTags: function(newTags ){
        this.pendingTags = newTags;
        this.execTags();
        return this;
    },
    _addStaticListeners: function(){
        var self = this;
        document.observe("dom:loaded", function(){
            self.isEnabled = (typeof ANALYTICS_ENABLED != "undefined") ? ANALYTICS_ENABLED : false;
            self.enabledModules = (typeof ANALYTICS_MODULES != "undefined" ) ? ANALYTICS_MODULES : [];

            // CONVERSION EVENTS - If conversion events exist add handler as directed and form input for location 
            self.jsEvents = (typeof CONVERSION_EVENTS != "undefined" ) ? CONVERSION_EVENTS : [];
            self.jsEvents = self.jsEvents.concat( (typeof JS_EVENTS != "undefined" ) ? JS_EVENTS : [] );  
            self.jsPixelEvents = (typeof JS_PIXEL_EVENTS != "undefined" ) ? JS_PIXEL_EVENTS : new Object();

            if (Object.keys(self.jsPixelEvents).length > 0){
                Object.keys(self.jsPixelEvents).each(function(module){
                    self.jsPixelEvents[module].each(function(CEVENT){
                        self.jsEvents = self.jsEvents.concat( (typeof CEVENT != "undefined" ) ? CEVENT : [] );
                    });
                });
            }

            if (typeof self.jsEvents != "undefined"){
                self.jsEventsWaited = [];

                self.jsEvents.each(function(CEVENT){
                    CEVENT.hookIds = [];  // allow for mapping one event to many elements
                    //console.log("event on ", CEVENT); 
                    //Check for events to wait on, loading iframes, bv etc to be custom handled in static listeners
                    //if the ele is expected to exist on load, then the event handlers can be set up right away in addFrontendEvent

                    if (CEVENT.after){
                        self.jsEventsWaited.push(CEVENT);
                    }else{                    
                        self._addFrontendEvent(CEVENT); 
                    } 
                    
                }); 
            }

        });
        //Search hook for frontend and soon Endeca
        document.observe("search:results", function(event){
            var res = event.memo;
            self.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[res.pageid,res.keywords,res.cat,'"' + res.count +'"',null],"tag":"cmCreatePageviewTag"}]}});
        });

        // BV loaded and ready this is just a place to handle the event, tags come from the backend config. 
        document.observe("bv:loaded", function(){
            if (self.bvLoaded == 1){
               return;
            }
            self.bvLoaded = 1;
            
            // console.log("BV IS LOADED",self.jsEventsWaited);
            self.jsEventsWaited.each(function(EVENT){
                if (EVENT.after && EVENT.after == 'bv:loaded'){
                    self._addFrontendEvent(EVENT); 
                } 
            }); 

        });
        document.observe("bv:created", function(){
              // console.log("BV CREATED IS LOADED",self.jsEventsWaited);
            self.jsEventsWaited.each(function(EVENT){
               if (EVENT.after && EVENT.after == 'bv:created'){
                   self._addFrontendEvent(EVENT);
               }
            });

        });
         document.observe("MPP:productQV", function(){
              // console.log("QV IS LOADED",self.jsEventsWaited);
              setTimeout("Analytics._pushBV()", 4000);
        });
        
        // our RPC hook
        document.observe('RPC:RESULT', function(obj){
            var rpcRequestArray, rpcResponseArray;
            var requestMethod, requestId;
            if (typeof obj.memo.request != "undefined") {
                rpcRequestArray = (obj.memo.request.parameters.JSONRPC != null) ?
                    obj.memo.request.parameters.JSONRPC.evalJSON() :
                    null;

                if (rpcRequestArray) {
                    rpcResponseArray = obj.memo.responseText.evalJSON();
                    if (rpcResponseArray) {
                        rpcRequestArray.each(function(rpcRequest){
                            requestMethod = rpcRequest.method;
                            requestId = rpcRequest.id;
                            // console.log("Analytics handling RPC request:  ", requestMethod, " with id: ", requestId);

                            // We can do special handlers for requests here or filter out non handled requests... 
                            if (!self.RPC_METHODS_ALLOWED.get(requestMethod)) {
                                // console.log("Analytics skipped ", requestMethod);
                            } else {
                                // Make sure we have the response for this request (id's must match).
                                var myRpcResponse = rpcResponseArray.find(function(rpcResponse){
                                    return rpcResponse.id == requestId
                                });
                                if (myRpcResponse && myRpcResponse.result != null) {
                                    //console.log("Analytics will handle ", myRpcResponse.result.data.Analytics);
                                    var newTags = myRpcResponse.result.data.Analytics;
                                    self.addPendingTags(newTags);
                                }
                            }
                        });
                    }
                }
            }
        });
    

        //Page data already exists hook
        document.observe('PAGEDATA:RESULT', function(obj){

            // console.log("GOT PAGE DATA ",obj);
            var catalog_path = obj.memo;

            try {

                var catalog = generic.page_data(catalog_path);
                if (catalog) {
                    var rpcdata = catalog.get("rpcdata.result.Analytics");
                    if (rpcdata) {
                        self.addPendingTags(rpcdata);
                    }
                }

                // var newTags = eval ('page_data.' + catalog_path + '.rpcdata.result.Analytics');
                // console.log("page data ",newTags);
                // self._handleIO(catalog_path); //assumed page data exists in method
                // self.addPendingTags(newTags);
            }
            catch(err) {
                 console.log('PAGEDATA:RESULT error: ', err);
            }

        }); 
       
        //Pageview and Productview for SPP pages
        document.observe('PAGEDATA:CMSPP',  function(obj){
            
            var catalog_path = obj.memo;
            var newTags = eval ('page_data.'+ catalog_path + '.rpcdata.result.Analytics');
            self.addPendingTags(newTags);

        });

        //Pageview and Productview for SPP pages
        document.observe('PAGEDATA:CMMPP',  function(obj){

            var catalog_path = obj.memo;
            var newTags = eval ('page_data.'+ catalog_path + '.rpcdata.result.Analytics');
            self.addPendingTags(newTags);

        });

    },
    _pushBV: function(){
       // console.log("push BV");
       Analytics.jsEventsWaited.each(function(EVENT){
               if (EVENT.after && EVENT.after == 'bv:loaded'){
                   Analytics._addFrontendEvent(EVENT);
               }
       });
   },


  
    _handleIO: function(catalog_path){
       // console.log("IO PATH ",catalog_path);

       if (catalog_path == "catalog.spp.recommendedProducts"){
           // console.log("IO SPP RUNNING");
           //cm rec request
           rec_products_default = eval ('page_data.' + catalog_path + '.rpcdata.products');
           main_product = page_data.catalog.spp.rpcdata.products[0];
           //console.log("Default rec products",rec_products_default); 
           //console.log("Main product ",main_product);
           //cmRecRequest("ProdPage",main_product.PRODUCT_ID,main_product.DEFAULT_CAT_ID);
           //cmDisplayRecs();
       }
    },

    addDynamicListener: function(taggingModule, myEvent, TagBlocks){
        var self = this;
        var id = "default";
        TagBlocks.each(function(tagBlock){
            if (typeof tagBlock.memo != "undefined") {
                id = tagBlock.memo;
            }
            if (!self.subscribers[id]) { self.subscribers[id] = {}; }
            if (!self.subscribers[id][taggingModule]) { self.subscribers[id][taggingModule] = {}; };
            if (!self.subscribers[id][taggingModule][myEvent]) {
                self.subscribers[id][taggingModule][myEvent] = new Array();
            } else {
                 //console.log( "Event is defined ", self.subscribers[id][taggingModule][myEvent] );
            }

            if (!self.seenMemoWithTag[id]) { self.seenMemoWithTag[id] = {}; }
            if (!self.seenMemoWithTag[id][tagBlock.tag]) {
                self.seenMemoWithTag[id][tagBlock.tag] = 1;
                self.subscribers[id][taggingModule][myEvent].push(tagBlock);
                //console.log( "pushed Event for: id: ", id, " taggingModule: ", taggingModule, " event: ", myEvent, " -> ", self.subscribers[id][taggingModule][myEvent] );
            }

        });

        // Attach the listener for this event type.
        if (!self.listeners[myEvent]) {
            self.listeners[myEvent] = 1;
            //Event.observe(window, myEvent, function(evt){
            document.observe(myEvent, function(evt){
                // console.log("running window event: ", myEvent, " with memo: ", evt.memo);

                var myId = evt.memo;
                self.enabledModules.each(function(taggingModule){
                     //console.log("for tagging module: ", taggingModule);
                    if (typeof self.subscribers[myId][taggingModule] != "undefined"){
                       if (self.subscribers[myId][taggingModule][myEvent]) {
                          if ( taggingModule.match("CoreMetrics") ) {
                             self.execEventTagBlocks( self.subscribers[myId][taggingModule][myEvent] );
                          }else{ 
                             self.execImageBlocks(  self.subscribers[myId][taggingModule][myEvent] ); 
                          } 
                       }
                    }
                });
            });
        }
    },
    execTags: function (){
        var self = this;
        if (typeof self.pendingTags == "object") {
            Object.keys(self.pendingTags).each(function(taggingModule){
                if (typeof taggingModule != "object" && self.pendingTags[taggingModule] == 'notag') {
                        return;
                }
                Object.keys(self.pendingTags[taggingModule]).each(function(myEvent){
                    // console.log("Analytics: module / event ", taggingModule, myEvent);
			        if (myEvent != 'dom:loaded'){
                        // register this event with our collection of listeners.
                        self.addDynamicListener(taggingModule, myEvent, self.pendingTags[taggingModule][myEvent]);
                    
                    } else {
                         // incoming tagging events under the 'dom:loaded' label can be executed straightaway.
                         if ( taggingModule.match("CoreMetrics") ) {
                            self.execEventTagBlocks( self.pendingTags[taggingModule][myEvent] );
                        }else{
                            self.execImageBlocks( self.pendingTags[taggingModule][myEvent] );
                        }
                    } 
                }); 
            });
        }
    },

    execImageBlocks: function(tagBlocks){
        // these often have a rnd number, gen that here to make sure it isn't used over and over as a global
        var axel = Math.random() + "";
        var num = axel * 1000000000000000000;
        tagBlocks.each(function(tagBlock){
            var tag_url = new Image();
            tagBlock = tagBlock.replace("rndnum",num);
            // console.log("exec tag image ",tagBlock);
            tag_url.src = tagBlock;
        });

    },

    execEventTagBlocks: function(tagBlocks){
        tagBlocks.each(function(tagBlock){
            if (!tagBlock.params || !tagBlock.tag) { return; }
                 console.log( "Analytics.execEventTagBlocks about to execute tag: ", tagBlock.tag, " with params: ", tagBlock.params );
            if (typeof window[tagBlock.tag] == "undefined") {
                 //console.log( "The Tagging Module function is not found: ", tagBlock.tag );
                return;
            }
            window[tagBlock.tag].apply(this, tagBlock.params);
        });
    },

    _addFrontendEvent: function(EVENT){
        var self = this;

        if ( $(EVENT.domID) == null && EVENT.event != "dom:loaded" ){
            //console.log("CM ELEMENT does not EXIST! ",EVENT.domID);
            // removed by not deleted yet EF 
            //return;
        } 

        if (EVENT.event == "dom:loaded"){
            self.execTagsbyType(EVENT);
        }else{
            if (EVENT.domID || (EVENT.attachAttr && EVENT.attachValue) ){
               self._attachFrontendEvent(EVENT);  
            }
        }
    },

    _attachFrontendEvent: function(EVENT){
        var self = this; 
            // EVENT.attachAttr && EVENT.attachValue are required unless EVENT.domID is specified(for legacy cases with email signups)

            if (EVENT.domID){
                 EVENT.attachAttr = 'id';
                 EVENT.attachValue = EVENT.domID;
            }  
            if (typeof EVENT.attachTag == "undefined"){
                 EVENT.attachTag = '';
            }
            if (EVENT.attachAttr && EVENT.attachValue){
               //console.log ( "got ",EVENT.attachTag,EVENT.attachAttr,EVENT.attachValue,EVENT.event);
               var theseListeners = [];
               $$(''+ EVENT.attachTag + '[' + EVENT.attachAttr + '="' + EVENT.attachValue + '"]').each(function (ele){
                    var dups = EVENT.hookIds.indexOf( $(ele).identify() );
                    if ( dups < 0 ){
                        EVENT.hookIds.push( $(ele).identify() ); // if id doesn't exist give it one to use for matching in event handlers  
                        self.fromConfigListeners.push(ele); // keep a full current list for ref
                        theseListeners.push(ele); // but don't dupilicate events
                    }
               });

                 theseListeners.each( function(ele){
                     //console.log("DOM ID IS ",EVENT.domID,EVENT,ele);
                     if (self.uniqueEvents.indexOf(ele) != -1){
                         return;
                     }
                     self.uniqueEvents.push(ele);

                     ele.observe(EVENT.event, function(evt){  
                        // console.log("running event ",evt);
                    
                        // which events match in the current list?  
                        var cevents;
                        self.jsEvents.each(function(CEVENT){
                             CEVENT.hookIds.each(function(hooks){
                                var nodecheck = null;
                                if (hooks == evt.target.parentNode.id){
                                   nodecheck = evt.target.parentNode.id; 
                                }
                                if (hooks == evt.target.id){
                                   nodecheck = evt.target.id;
                                } 

                                // check for all parents as event may have been down from the configured event
                                if (evt.target && typeof evt.target.up == "function") {
                                    var i=1;
                                    while (arr = evt.target.up(i)){
                                        if (hooks == arr.id){
                                            nodecheck = arr.id;
                                            break;
                                        }
                                        i = i+1;
                                    }
                                }
                                if (evt.target.parentNode && typeof evt.target.parentNode.up == "function") {
                                    var i=1;
                                    while (arr = evt.target.parentNode.up(i)){
                                        if (hooks == arr.id){
                                            nodecheck = arr.id;
                                            break;
                                        }
                                        i = i+1;
                                    }
                                }
                                if (nodecheck){
                                    cevents = CEVENT;
                                    // console.log("HOOK MATCHED ",hooks,CEVENT);
                                    // now we know the event and have the data, exec the tag according to type
                                    self.execTagsbyType(CEVENT);
                                }
                             });
                        });
                        //ele.stopObserving();
                     });
               });
            }
     },


    // tags specificly connected to user actions and tags more closely connected to frontend events vs backend data, like prodcat    
     /*
         cmCreatePageElementTag(elementID, elementCategory,attributes) 
         cmCreateManualPageviewTag(pageID, categoryID,DestinationURL,ReferringURL) 
         cmCreateManualLinkClickTag(href,name,pageID) 
         cmCreateManualImpressionTag(pageID, trackSP, trackRE) 
         cmCreateErrorTag(pageID, categoryID) 
     */
    execTagsbyType: function(CEVENT){

         // now config can use any JS var as param value.
        Object.keys(CEVENT).each(function(param){
                if ( param.match("_") ){
                    // console.log("match eval type param");
                    // console.log ( CEVENT[param]  );
                    newParam = param.replace(/_/,'');
                    CEVENT[newParam] = eval( CEVENT[param] );
                }
        });

        if (CEVENT.type == 'conversion_event'){
            if (CEVENT.points < 1){
                CEVENT.points = '"0"';
            }
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.eventID, CEVENT.actionType, CEVENT.cat, CEVENT.points,CEVENT.attributes],"tag":"cmCreateConversionEventTag"}]}});
        }
        if (CEVENT.type == 'element'){
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.elementID,CEVENT.elementCategory,CEVENT.attributes],"tag":"cmCreatePageElementTag"}]}});
        }
        if (CEVENT.type == 'mpageview'){
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.pageID,CEVENT.categoryID,CEVENT.DestinationURL,CEVENT.ReferringURL],"tag":"cmCreateManualPageviewTag"}]}});
        }
        if (CEVENT.type == 'mlinkclick'){
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.href,CEVENT.name,CEVENT.pageID],"tag":"cmCreateManualLinkClickTag"}]}});
        }
        if (CEVENT.type == 'mimpression'){
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.pageID,CEVENT.trackSP,CEVENT.trackRE],"tag":"cmCreateManualImpressionTag"}]}});
        }
        if (CEVENT.type == 'error'){
            this.addPendingTags({"CoreMetrics":{"dom:loaded":[{"params":[CEVENT.pageID,CEVENT.categoryID],"tag":"cmCreateErrorTag"}]}});
        }
        if (CEVENT.type == 'img'){
            // console.log("IMG EXISTS",CEVENT);
            this.execImageBlocks([CEVENT.src]);
        }

    },

    // Possible special funtions for front end (reserved for future use)
    onDivShow: function() {},
    onFrameUpdate: function() {},
    onJsRedirect: function() {},

    // find an attribute up from evt.target
    findAttrUp : function(element,attr) {

        if (element && element != "undefined"){
            current_element = element;
            if ( current_element.parentNode ){
                if ( current_element.parentNode.hasAttribute(attr) ){
                    if ( current_element.parentNode.readAttribute(attr) ){
                        console.log("Analytics CURRENT node HAS ATTR ");
                        return current_element.parentNode.readAttribute(attr);
                    }
                }
            }
            // find element that contains attr
            var nodecheck;
            var i=1;
            while (arr = current_element.up(i)){
                if ( arr.readAttribute(attr) ){
                    nodecheck = arr;
                    break;
                }
                i = i+1;
           }
           i=1;
           while (arr = current_element.parentNode.up(i)){
               if ( arr.readAttribute(attr) ){
                   nodecheck = arr;
                   break;
               }
               i = i+1;
           }
       }
       console.log("Analytics: loc node",nodecheck);
       return nodecheck;
    },

     // find element that contains attr
    findAttrDown : function(element,attr) {
        var nodecheck = [];
        i=0;
        while (arr = element.down(i)){
            if ( arr.readAttribute(attr) ){
                nodecheck.push(arr);
            }
            i = i + 1;
        }
        return nodecheck;
    },

    
    crossSellCallback : function() {
         // empty function
    },
    getCrossSellData : function(args) {
        // console.log("cross sell call data called");
        var zone = '';
        if (typeof args.callback === "function") {
            this.crossSellCallback = args.callback;
        }
        if ( args.pageContext){
            if (args.pageContext == 'spp'){
                zone = 'ProdPage';
            }
            if (args.pageContext == 'cart'){
                zone ='CartPg1';
            }
        }else{
            return false;
        } 
       // commented out for Brx  
        //cmRecRequest("ProdPage",PRODUCT_ID,CATEGORY_ID);
        cmRecRequest(zone,"PROD2224","CAT1186");
        //cmRecRequest(zone,PRODUCT_ID,CATEGORY_ID);
        //cmRecRequest("CartPg1","PROD4195","CAT688");
        cmDisplayRecs(); // calls either _ProdPage_zp or 

    },
    parseCrossSellData: function(rawCmDataArray) {
        // console.log("============rawCmDataArray=============");
        // console.dir(rawCmDataArray);
        // rawCmData --> parsedData (convert CM data to PG/BX JSON format)
        if (!Object.isArray(rawCmDataArray)) {
            return null;
        }
        var parsedProductData = {products:[]};
        for (var i=0, len=rawCmDataArray.length; i<len; i++) {
            var rawProductDataArray = rawCmDataArray[i];
            if (!Object.isArray(rawProductDataArray)) {
                continue;
            }
            var prod = { skus: [ {} ] };
            var rawProd = rawProductDataArray[4].split("|");
            var rawSku = rawProductDataArray[5].split("|");
            var rawUrl = rawProductDataArray[6].split("---");
            prod.url = rawUrl[1];
 
            for (var j=0, jlen=rawProd.length; j<jlen; j++) {
                var keyVal = rawProd[j].split("---");
                prod[keyVal[0]] = keyVal[1];
            }

            for (var j=0, jlen=rawSku.length; j<jlen; j++) {
                var keyVal = rawSku[j].split("---");
                prod["skus"][0][keyVal[0]] = keyVal[1];
            }

            parsedProductData.products[i] = prod;
        }
        
        return parsedProductData;
    },
 
    /* var _ProdPage_zp = function(rawCmData) {
    var parsedData = analytics.parseCallbackData(rawCmData);
    analytics.crossSellCallback(parsedData);
    };
    */

     _ProdPage_zp: function(a_product_ids, 
                  zone,
                  symbolic,
                  target_id,
                  category,
                  rec_attributes,
                  target_attributes,
                  target_header_txt) {

        if (symbolic !== '_NR_'){
            var parsedData = this.parseCrossSellData(rec_attributes);
            this.crossSellCallback(parsedData);
        }else{
            this.crossSellCallback();// no recs use defaults 
        }
                      
                      
        var PRODUCTS_OUT = new Array();
        var SKU_OUT = new Array();

        prod_fields = new Array("blank","PRODUCT_ID","PARENT_CAT_ID","DEFAULT_CAT_ID","PROD_RGN_NAME","PROD_RGN_SUBHEADING","SUB_LINE","SHORT_DESC","PRODUCT_USAGE","LARGE_IMAGE","PROD_CAT_IMAGE_NAME","SMALL_IMAGE","THUMBNAIL_IMAGE","sized","shaded"); 
        sku_fields = new Array ( "blank","SKU_ID","SKU_BASE_ID","PRODUCT_SIZE","SHADE_DESCRIPTION","SMOOSH_PATH_STRING","SHADENAME","SKIN_TYPE_TEXT","ATTRIBUTE_COLOR_FAMILY","HEX_VALUE","HEX_VALUE_STRING","UNDERTONE","STRENGTH","FINISH","SKIN_TONE", "formattedPrice" );

        // console.log("ZONE POP CALLED",a_product_ids, zone,symbolic,target_id,category,rec_attributes,target_attributes,target_header_txt);
        var html = zone + "_zp: No recommendations returned";

        if (symbolic !== '_NR_'){
            var n_recs = a_product_ids.length;
            for (var ii=0; ii < n_recs; ii++){
                var rec_prod_id       = a_product_ids[ii];
                PRODUCTS_OUT[ii] = new Array();
                attributes = rec_attributes[ii]; // returns array
                prod_values = attributes[4].split("|"); // 5th element of response is prod field string, this is hard coded. 
                sku_values =  attributes[5].split("|"); // 6th element is optional sku
                a=0; 
                prod_fields.each(function(field){
                    PRODUCTS_OUT[ii][field] = prod_values[a];
                    a++; 
                }); 
                a=0;
                sku_fields.each(function(field){
                    //console.log("Sku Field is ", field, sku_values[a]);
                    SKU_OUT[field] =  sku_values[a];
                    a++;
                });
                PRODUCTS_OUT[ii]['skus'] = SKU_OUT;
            }
         page_data.catalog.spp.recommendedProducts.rpcdata.products = PRODUCTS_OUT;
         // console.log(PRODUCTS_OUT);
         }

   },

     _CartPg1_zp: function(a_product_ids,
                  zone,
                  symbolic,
                  target_id,
                  category,
                  rec_attributes,
                  target_attributes,
                  target_header_txt) {

         // console.log("ZONE POP CALLED",a_product_ids, zone,symbolic,target_id,category,rec_attributes,target_attributes,target_header_txt);
        if (symbolic !== '_NR_'){
            var parsedData = this.parseCrossSellData(rec_attributes);
            this.crossSellCallback(parsedData);
        }else{
            this.crossSellCallback();
        }
     }


});
Analytics = new Analytics();


