/* Sitebrand Relevance v7.0 $Revision: 6491 $
 * Copyright 2001-2009 Sitebrand Inc. All rights reserved
 * http://www.sitebrand.com
 */
/*global Sitebrand, _sb */
// FOR BETA ONLY - Future versions will do browser detection server-side
var BrowserDetect = {
    init: function () {
        this.browser =
            this.searchString(this.dataBrowser) ||
            "An unknown browser";

        this.version =
            this.searchVersion(navigator.userAgent) ||
            this.searchVersion(navigator.appVersion) ||
            "an unknown version";

        this.OS =
            this.searchString(this.dataOS) ||
            "an unknown OS";
    },
    searchString: function (data) {
        for (var i=0;i<data.length;i++) {
            var dataString = data[i].string;
            var dataProp = data[i].prop;
            this.versionSearchString = data[i].versionSearch || data[i].identity;
            if (dataString) {
                if (dataString.indexOf(data[i].subString) != -1) {
                    return data[i].identity;
                }
            }
            else if (dataProp) {
                return data[i].identity;
            }
        }
        return null;
    },
    searchVersion: function (dataString) {
        var index = dataString.indexOf(this.versionSearchString);
        if (index == -1) {
            return null;
        }
        return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },
    dataBrowser: [
        {
            string: navigator.userAgent,
            subString: "Chrome",
            identity: "Chrome"
        },
        {   string: navigator.userAgent,
            subString: "OmniWeb",
            versionSearch: "OmniWeb/",
            identity: "OmniWeb"
        },
        {
            string: navigator.vendor,
            subString: "Apple",
            identity: "Safari",
            versionSearch: "Version"
        },
        {
            prop: window.opera,
            identity: "Opera"
        },
        {
            string: navigator.vendor,
            subString: "iCab",
            identity: "iCab"
        },
        {
            string: navigator.vendor,
            subString: "KDE",
            identity: "Konqueror"
        },
        {
            string: navigator.userAgent,
            subString: "Firefox",
            identity: "Firefox"
        },
        {
            string: navigator.vendor,
            subString: "Camino",
            identity: "Camino"
        },
        {       // for newer Netscapes (6+)
            string: navigator.userAgent,
            subString: "Netscape",
            identity: "Netscape"
        },
        {
            string: navigator.userAgent,
            subString: "MSIE",
            identity: "Explorer",
            versionSearch: "MSIE"
        },
        {
            string: navigator.userAgent,
            subString: "Gecko",
            identity: "Mozilla",
            versionSearch: "rv"
        },
        {       // for older Netscapes (4-)
            string: navigator.userAgent,
            subString: "Mozilla",
            identity: "Netscape",
            versionSearch: "Mozilla"
        }
    ],
    dataOS : [
        {
            string: navigator.platform,
            subString: "Win",
            identity: "Windows"
        },
        {
            string: navigator.platform,
            subString: "Mac",
            identity: "Mac"
        },
        {
               string: navigator.userAgent,
               subString: "iPhone",
               identity: "iPhone/iPod"
        },
        {
            string: navigator.platform,
            subString: "Linux",
            identity: "Linux"
        }
    ]

};
BrowserDetect.init();

/**
 * Returns element if the provided pattern matches an encestor's ID
 *
 * @param regex pattern
 *
 * @returns element if found, null if not found
 */
jQuery.fn.extend({
    getAncestorMatchingPattern: function( pattern ) {

        // If this element is <html> we've reached the end of the path and can't
        // find an element that matches ID with pattern.
        if ( this.is( 'html' ) ) {
	        return null;
        }
        
        // We've found element, return it.
        if ( this.parent()[0].id.match( pattern ) ) {
            return this.parent()[0];
        }

        // Have yet to find it, recurse up the DOM.
        return this.parent().getAncestorMatchingPattern( pattern );
    }
});

(function($) {
    /** jQuery.toJSON( json-serializble )
        Converts the given argument into a JSON respresentation.

        If an object has a "toJSON" function, that will be used to get the representation.
        Non-integer/string keys are skipped in the object, as are keys that point to a function.

        json-serializble:
            The *thing* to be converted.
     **/
    $.toJSON = function(o)
    {
        if (typeof(JSON) == 'object' && JSON.stringify)
            return JSON.stringify(o);
        
        var type = typeof(o);
    
        if (o === null)
            return "null";
    
        if (type == "undefined")
            return undefined;
        
        if (type == "number" || type == "boolean")
            return o + "";
    
        if (type == "string")
            return $.quoteString(o);
    
        if (type == 'object')
        {
            if (typeof o.toJSON == "function") 
                return $.toJSON( o.toJSON() );
            
            if (o.constructor === Date)
            {
                var month = o.getUTCMonth() + 1;
                if (month < 10) month = '0' + month;

                var day = o.getUTCDate();
                if (day < 10) day = '0' + day;

                var year = o.getUTCFullYear();
                
                var hours = o.getUTCHours();
                if (hours < 10) hours = '0' + hours;
                
                var minutes = o.getUTCMinutes();
                if (minutes < 10) minutes = '0' + minutes;
                
                var seconds = o.getUTCSeconds();
                if (seconds < 10) seconds = '0' + seconds;
                
                var milli = o.getUTCMilliseconds();
                if (milli < 100) milli = '0' + milli;
                if (milli < 10) milli = '0' + milli;

                return '"' + year + '-' + month + '-' + day + 'T' +
                             hours + ':' + minutes + ':' + seconds + 
                             '.' + milli + 'Z"'; 
            }

            if (o.constructor === Array) 
            {
                var ret = [];
                for (var i = 0; i < o.length; i++)
                    ret.push( $.toJSON(o[i]) || "null" );

                return "[" + ret.join(",") + "]";
            }
        
            var pairs = [];
            for (var k in o) {
                var name;
                var type = typeof k;

                if (type == "number")
                    name = '"' + k + '"';
                else if (type == "string")
                    name = $.quoteString(k);
                else
                    continue;  //skip non-string or number keys
            
                if (typeof o[k] == "function") 
                    continue;  //skip pairs where the value is a function.
            
                var val = $.toJSON(o[k]);
            
                pairs.push(name + ":" + val);
            }

            return "{" + pairs.join(", ") + "}";
        }
    };

    /** jQuery.evalJSON(src)
        Evaluates a given piece of json source.
     **/
    $.evalJSON = function(src)
    {
        if (typeof(JSON) == 'object' && JSON.parse)
            return JSON.parse(src);
        return eval("(" + src + ")");
    };
    
    /** jQuery.secureEvalJSON(src)
        Evals JSON in a way that is *more* secure.
    **/
    $.secureEvalJSON = function(src)
    {
        if (typeof(JSON) == 'object' && JSON.parse)
            return JSON.parse(src);
        
        var filtered = src;
        filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
        filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
        filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
        
        if (/^[\],:{}\s]*$/.test(filtered))
            return eval("(" + src + ")");
        else
            $(document).trigger('PersonalizationJsonError', src);
    };

    $.quoteString = function(string)
    {
        if (string.match(_escapeable))
        {
            return '"' + string.replace(_escapeable, function (a) 
            {
                var c = _meta[a];
                if (typeof c === 'string') return c;
                c = a.charCodeAt();
                return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
            }) + '"';
        }
        return '"' + string + '"';
    };
    
    var _escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
    
    var _meta = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"' : '\\"',
        '\\': '\\\\'
    };
})(jQuery);

// Create the public-scope accessable namespace for Sitebrand
if ('undefined' == typeof Sitebrand) {
    var Sitebrand = {};
}

// Create the public-scope accessable options object, if it hasn't been
if ('undefined' == typeof _sb) {
    var _sb = {};
}
if ('undefined' == typeof _sb.c) {
    _sb.c={};
}
if ('undefined' == typeof _sb.s) {
    _sb.s={};
}
if ('undefined' == typeof _sb.test) {
    _sb.test={};
}
if ('undefined' == typeof _sb.conf) {
    _sb.conf={};
}
if ('undefined' == typeof _sb.conv) {
    _sb.conv={};
}
if ('undefined' == typeof _sb.item) {
    _sb.item={};
}

// Set a default timeout of 500ms, if no other has been set
_sb.conf.timeout = ('undefined' == typeof _sb.conf.timeout) 
                ? 500 
                : _sb.conf.timeout;

// Set a default personalize URL if no other has been set
_sb.conf.sbUrl = ('undefined' == typeof _sb.conf.sbUrl) 
                        ? 'http://1.rt.sitebrand.com/personalize' 
                        : _sb.conf.sbUrl;

// Set a default content space id pattern
_sb.conf.contentSpaceIdPattern = ('undefined' == typeof _sb.conf.contentSpaceIdPattern) 
                                ? /^_sb_/
                                : _sb.conf.contentSpaceIdPattern;


/**
 * Returns true if the provided option is set to false or not defined.
 *
 * @param string optionName The name of the option to check.
 *
 * @returns false
 */
optFalse = function(optionName) {
    return (('undefined' == typeof _sb[optionName]) || (false === _sb[optionName]));
};

/**
 * Returns true if the provided option is defined and set to true.
 *
 * @param string optionName The name of the option to check.
 *
 * @returns false
 */
optTrue = function(optionName) {
    return (('undefined' !== typeof _sb[optionName]) && (true === _sb[optionName]));
};

// Create Snippet object to hold Realtime and Personalization objects
Sitebrand.Snippet = {};

Sitebrand.preObj = {};

/**
 * Initialize Realtime and Personalization objects
 */
Sitebrand.init = function() {

    Sitebrand.Snippet.Realtime.init();
    Sitebrand.Snippet.Personalization.init();
};

/**
 * Realtime Object
 */
Sitebrand.Snippet.Realtime = (function() {
    // Internal shortcut to jQuery
    var $ = Sitebrand.jQuery;
    
    // Private members ========================================================
    var self = {};
    
    /**
     * Triggered when ajax call does not complete.
     */
    self.onFailure = function() {
    };
    
    /**
     * Triggered when ajax call completes.
     *
     * @param {object} response The response from the AJAX call.
     */
    self.onSuccess = function(response) {
        $(document).trigger('Personalize', [response]);
    };
    
    // Public members =========================================================
    var pub = {};
    
    /**
     * Initializes controls and data for this module.
     *
     * Called when jQuery is finished loading.
     */
    pub.init = function() { };
    
    /**
     * Sends information to Sitebrand.
     *
     * @param {object} messageData Associated data with this message.
     */
    pub.send = function(messageData) {
        $.ajax({
            url:            _sb.conf.sbUrl,
            dataType:       'jsonp',
            data:           messageData,
            success:        self.onSuccess,
            failure:        self.onFailure
        });
    };
    
    return pub;
}) ();

/**
 * Personalization Object
 */
Sitebrand.Snippet.Personalization = (function() {
    // Internal shortcut to jQuery
    var $ = Sitebrand.jQuery;
    
    // Private members ========================================================
    var self = {};
    
    // Number of pieces of content remaining to personalize
    self.contentRemaining = 0;
    
    // Data used for personalization
    self.personalizationData = {};
    
    /**
     * Collects data about the visitor that can be used to personalize on.
     */
    self.collectVisitorData = function() {
        var vd = {};
        
        // Try to get session cookie
        vd.sid = self.getCookie('sb_session_id');
        
        // If this is the first impression of the session
        if (null === vd.sid) {
            // If session id is null, we'll let the server assign one for us
           
            // Visitor Sticky Session Data
            vd.s={};
            
            // Browser
            vd.s.bt   = BrowserDetect.browser;
            vd.s.bv   = BrowserDetect.version;
            
            // Language
            vd.s.l   = navigator.language;
            
            // Referrer URL
            vd.s.ur  = document.referrer;
            
            // Timezone offset
            vd.s.tz  = new Date().getTimezoneOffset() / 60 * -1;
        }
        
        // Collect common traits for all impressions
        
        // Try to get permanent cookie
        vd.cid = self.getCookie('sb_permanent_id');
        
        for ( var param in _sb ) {
            if ('object' == typeof(_sb[param])) {
                if (param != 'conf') {
                    eval("vd."+param+"='"+$.toJSON(_sb[param])+"';");
                }
            }
            else {
                eval("vd."+param+"=_sb."+param+";");
            }
        }
           
        return vd;
    };
    
    /**
     * Creates a browser cookie.
     *
     * @param {string}  name  The name of the cookie.
     * @param {string}  value The value of the cookie.
     * @param {integer} days  The number of days to keep the cookie around for.
     */
    self.createCookie = function (name, value, days) {
        var date, expires;
        
        if (days) {
            date = new Date();
            date.setTime(date.getTime()+(days*24*60*60*1000));
            expires = "; expires="+date.toGMTString();
        }
        else {
            expires = "";
        }
        document.cookie = name+"="+value+expires+"; path=/";
    };
    
    /**
     * Erases a browser cookie.
     *
     * @param {string} name The name of the cookie to erase.
     */
    self.eraseCookie = function(name) {
        self.createCookie(name,"",-1);
    };
    
    /**
     * Gets a browser cookie.
     *
     * @param {string} name The name of the cookie to get.
     *
     * @return {string/null}
     */
    self.getCookie = function (name) {
        var nameEQ, ca, i, c;
        
        nameEQ = name + "=";
        ca = document.cookie.split(';');
        for(i=0;i < ca.length;i++) {
            c = ca[i];
            while (' ' == c.charAt(0)) {
                c = c.substring(1,c.length);
            }
            if (0 === c.indexOf(nameEQ)) {
                return c.substring(nameEQ.length,c.length);
            }
        }
        return null;
    };
    
    /**
     * Gets a jQuery compatible DOM path string of an element
     *
     * @param {string} elPath The relevance DOM path string to convert
     *
     * @return {string/null}
     */
    self.convertToJQueryPath = function(elPath) {

      // this is the returned element path string
      var jQueryPath='';
      // this is the pattern to extract tokens from the string
      var tokenPat= /([\w-]+\[?"?[\w-]+\"?]?)/g;
      // this is the array of found tokens
      var elTokens = elPath.match(tokenPat);
      // this is the pattern to extract the DOM tag and index from the token
      var elPat= /([\w-]+)\["?([\w-]+)"?\]/;

      for (var i = 0; i < elTokens.length; i++) {

        elTypeIdx = elPat.exec(elTokens[i]);

        // if this is null and on first loop then it's an element ID string
        // else, it's a DOM tag and index
        if (elTypeIdx == null && i == 0) {
            jQueryPath = jQueryPath + "$('#" + elTokens[i] + "')";
        }
        else {
          // if it's not null but is on first loop, then it's a base ID
          if ( elTypeIdx[2] != parseInt(elTypeIdx[2]) ){
            jQueryPath = jQueryPath + "$('#" + elTypeIdx[2] + "')";
          }
          else {

            if (i == 0) {
              jQueryPath = jQueryPath + "$(document.body)";
            }

            // the tag
            jQueryPath = jQueryPath + ".find('" + elTypeIdx[1].toLowerCase() + "')";
            // the index
            jQueryPath = jQueryPath + ".eq(" + (parseInt(elTypeIdx[2]) -1) + ")";

          }
        }
      } // for loop

      return jQueryPath;
    }
    
    /**
     * Personalizes the page, given the data received
     *
     * @param {string} event    The event that triggered the personalization.
     * @param {object} actions  An array of actions to perform to personalize.
     */
    self.personalize = function(event, actions) {
        $(document).trigger('BeforePersonalize', actions);
    
       
        if ('undefined' !== typeof(actions)) {

            // If any page redirects to perform
            if ('undefined' !== typeof(actions.data.rw)) {
                window.location = actions.data.rw[0].s;
            }

            // If any headers to append
            if ('undefined' !== typeof(actions.data.he)) {
               // Loop through the actions we've been given
                $.each(actions.data.he, function(action) {
                    pub.appendContent('head', action.s);
                });
            }

            // If any HTML replacement to perform
            if ('undefined' !== typeof(actions.data.sh)) {
               // Loop through the actions we've been given
                $.each(actions.data.sh, function(path, action) {
                    pub.replaceContent(action.d, action.s);
                });
            }
            
            // If any freefloat divs to append
            if ('undefined' !== typeof(actions.data.ff)) {
               // Loop through the actions we've been given
                $.each(actions.data.ff, function(action) {
                    pub.appendContent('body', action.s);
                });
            }

        }
        
        $(document).trigger('AfterPersonalize', actions);

        if ('undefined' !== typeof(actions.sid) && actions.sid !== null) {
            // Write session cookie using id provided by server
            self.createCookie('sb_session_id', actions.sid);
        }
        
        if ('undefined' !== typeof(actions.cid && actions.cid !== null)) {
            // Write permanent cookie using id provided by server (10 years)
            self.createCookie('sb_permanent_id', actions.cid, 3650);
        }

        // Append a new click handler to all links using live to make sure that
        // any links dynamically added are also handled.
        $('a').live('click', function () {
        
            // Click Data
            var cd = {};

            // The target 
            if ( 'undefined' !== typeof(this.href) ) {
                cd.chref=this.href;
            }

            if ( 'undefined' !== typeof( $(this).children('img')[0] ) ) {
                cd.csrc=$(this).children('img')[0].src;
            }

            // Get the content space element
            var csDiv=$(this)
                        .getAncestorMatchingPattern(_sb.conf.contentSpaceIdPattern );

            // Get the ID and filter out the pattern from the it
            if ( csDiv !== null ) {
                cd.csid=csDiv.id.replace( _sb.conf.contentSpaceIdPattern , '' );
            }

            // Send user data to Sitebrand
            $(document).trigger('BeforeClick', cd);
            Sitebrand.Snippet.Realtime.send(cd);
            $(document).trigger('AfterClick', cd);

        });
    };
    
    // Public members =========================================================
    var pub = {};
    
    /**
     * Initializes controls and data for this module.
     *
     * Called when jQuery is finished loading.
     */
    pub.init = function() {
        // Respond to receipt of personalization data
        $(document).bind('Personalize', self.personalize);
        
        // Collect visitor data
        var visitorData = self.collectVisitorData();
        
        // Send user data to Sitebrand
        $(document).trigger('BeforeVisit', visitorData);
        Sitebrand.Snippet.Realtime.send(visitorData);
        $(document).trigger('AfterVisit', visitorData);
    };
    
    /**
     * When the HEAD or BODY elements provided are available, append contents 
     * with the provided HTML.
     *
     * Must be public because we need to use string-parameter'd settimeout() for
     * IE6 which must call globally-available functions.
     *
     * @param {string} element  The element type to append the content with.
     * @param {string} html     The HTML content to append to the element.
     * @param {integer} elapsed The amount of time we've already spent trying to append this content.
     */
    pub.appendContent = function(element, html, elapsed) {
        // If no time has elapsed yet, make note of that
        elapsed = ('undefined' === typeof(elapsed)) ? 0 : elapsed;
        
        var appended    = false;
        
        // See if the element is available
        if ($(element).length > 0) {
            $(document).trigger('BeforeAppend', { 'element': element, 'html': html });
            
            // Append the content
            $(element).append(html);
            appended = true;
            
            $(document).trigger('AfterAppend', { 'element': element, 'html': html });
        }
        
        // If we were unable to append the content
        if ( ! appended) {
            // If we've spent too much time waiting for the element to be available
            if (elapsed > _sb.conf.timeout) {
                // Raise an event indicating this
                $(document).trigger('PersonalizationTimeout', element);
            }
            // If we still have time left
            else {
                // Wait a bit and try appending again
                setTimeout('Sitebrand.Snippet.Personalization.appendContent("' + element + '", "' + html.replace(/"/g, '\\"') + '", ' + (elapsed + 50) +')', 50);
            }
        }
    };
    
    /**
     * When the DOM element provided is available, replace its contents with the
     * provided HTML.
     *
     * Must be public because we need to use string-parameter'd settimeout() for
     * IE6 which must call globally-available functions.
     *
     * @param {string} path       The path of the DOM element to replace the content of.
     * @param {string} html     The HTML to replace the DOM element's content with.
     * @param {integer} elapsed The amount of time we've already spent trying to replace this content.
     */
    pub.replaceContent = function(path, html, elapsed) {
        // If no time has elapsed yet, make note of that
        elapsed = ('undefined' === typeof(elapsed)) ? 0 : elapsed;
        
        var pathType = path.match(/\[("?(\d|[\w\d-])+"?)\]/g);

        // Get the element to replace
        var element     = eval(self.convertToJQueryPath(path));
        var replaced    = false;
        
        // If the content is available in the DOM
        if (element.length > 0) {
            $(document).trigger('BeforeContent', { 'path': path, 'html': html });
            
            // Replace the content
            element.html(html);
            replaced = true;
            
            // One less content to replace
            self.contentRemaining--;
            
            $(document).trigger('AfterContent', { 'path': path, 'html': html });
        }
        
        // If we were unable to replace the content
        if ( ! replaced) {
            // If we've spent too much time looking for this element
            if (elapsed > _sb.conf.timeout) {
                // Raise an event indicating this
                $(document).trigger('PersonalizationTimeout', path);
            }
            // If we still have time left
            else {
                // Wait a bit and try replacing again
                setTimeout('Sitebrand.Snippet.Personalization.replaceContent("' + path + '", "' + html.replace(/"/g, '\\"') + '", ' + (elapsed + 50) +')', 50);
            }
        }
    };
    
    return pub;
}) ();

// If we don't need to wait to initialize
if (optFalse('waitForInit')) {
    // Do it!
    Sitebrand.init();
}


