/*
    Mixpanel, Inc. (http://mixpanel.com/)
*/

var MixpanelLib = function(token, callback_obj, config) {
    var metrics = {},
        super_props_loaded = false,
        dom_loaded = false,
        dom_loaded_queue = [];
 
    metrics.config = {
        cross_subdomain_cookie: true,
        cookie_name: "mp_super_properties",
        test: false,
        store_google: true,
        save_referrer: true,
        debug: false,
        track_links_timeout: 300,
        cookie_expiration: 30,
        img: false
    };

    metrics.jsonp_callback = function() {}; 
    metrics.super_properties = {"all": {}, "events": {}, "funnels": {}};
    
    // predefined funnels; deprecated.
    metrics.funnels = {};

    var console = {
        log: function() {
            if(typeof(window.console) !== 'undefined' && window.console && metrics.config.debug) {
                try {
                    window.console.log.apply(window.console, arguments);
                } catch(err) {
                    var args = Array.prototype.slice.call(arguments);
                    window.console.log(args.join('\n'));
                }
            }
        },
        error: function() {
            if(typeof(window.console) !== 'undefined' && window.console && metrics.config.debug) {
                try {
                    window.console.error.apply(window.console, arguments);
                } catch(err) {
                    var args = Array.prototype.slice.call(arguments);
                    window.console.error(args.join('\n'));
                }
            }
        }
    };
    
    //  Globally useful functions

    function each(arr, fn, context) {
        /*  Iterate over an array-like 
         *  object, calling fn(item, position) 
         *  for each element.
         *
         *  Also allows iteration over 
         *  a dictionary, calling 
         *  fn(key, value) for each element.
         */ 
        
        if (Object.prototype.toString.call(arr) === '[object Array]') {
            var i;
            for (i=0; i < arr.length; i++) {
                fn.call(context || fn, arr[i], i);
            }
        } else if (typeof(arr) == 'object') {
            var k;
            for (k in arr) {
                if (Object.hasOwnProperty.call(arr, k)) {
                    fn.call(context || fn, arr[k], k);
                }
            }
        }
    }

    function merge(dict1, dict2, condition) {
        condition = condition || function() { return true; };
        each(dict2, function(val, key) {
            if (condition(val)) {
                dict1[key] = val;
            }
        });
        return dict1;
    }

    function isundef(variable) {
        return typeof variable === "undefined";
    }

    function inherit(subclass, superclass) {
        subclass.prototype = new superclass();
        subclass.prototype.constructor = subclass;
        subclass.superclass = superclass.prototype;
        return subclass;
    }

    function is_list(obj) {
        if(!obj) { return false; }
        var obj_str = Object.prototype.toString.call(obj);
        return  (obj_str === '[object Array]') ||
                (obj_str === '[object HTMLCollection]') ||
                (obj_str === '[object NodeList]') ||
                (typeof(obj.jquery) !== "undefined" && typeof(obj.length) !== "undefined") ||

                // this is for ie
                (typeof(obj.length) === 'number' &&
                typeof(obj.item) === 'function') ||
                (typeof(obj.length) === 'number' &&
                typeof(obj.item) === 'string') ||
                (typeof(obj.length) === 'number' &&
                typeof(obj.item) === 'object');
    }

    function is_object(obj) {
        return (typeof(obj) === "object" && !is_list(obj));
    }

    // tries to convert obj to an array
    function to_array(obj) {
        var l, i = 0, ret = [];
        if(obj === null || obj === undefined) { return []; }
        try {
            return Array.prototype.slice.call(obj, 0);
        } catch (err) {
            if (typeof(obj.length) === "number") {
                for (l = obj.length; i < l; i++) {
                    ret[i] = obj[i];
                }
            } else {
                while (obj[i] !== undefined) {
                    ret[i] = obj[i];
                    i++;
                }
            }
            return ret;
        }
    }

    function is_dom_query(possible_query) {
        if (!possible_query) { return false; }
        return  (possible_query.charAt(0) === '.') ||
                (possible_query.charAt(0) === '#');
    }
    
    function parse_dom_query(query) {
        if (!query) { return {'type':"error", "query": ""}; }
        var tmp = query.charAt(0);
        var type = (tmp === '.' ? 'class' : (tmp === '#' ? 'id' : 'error'));
        return {
            'type': type,
            'query': (type === 'error') ? "" : query.slice(1)
        };
    }

    function get_elements_by_class_name(className) {
        if (typeof(document.getElementsByClassName) === "undefined") {
            var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
            var allElements = document.getElementsByTagName("*");
            var results = [];

            var element, i;
            for (i = 0; (element = allElements[i]) != null; i++) {
                var elementClass = element.className;
                if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass)) {
                    results.push(element);
                }
            }
            return results;   
        } else {
            var elements = document.getElementsByClassName(className);
            return to_array(elements);
        }
    }

    function get_element_by_id(q) {
        if (typeof(q) !== 'string') { return q; }
        if (document.getElementById === undefined) {
            if (document.all !== undefined) { return document.all[q]; }
            if (document.layers !== undefined) { return document.layers[q]; }
            return null;
        } else {
            return document.getElementById(q);
        }
    }

    function register_event(element, event_name, callback) {
        if (element !== undefined  && element !== null && typeof(event_name) === 'string') {
            var old_event_handler = (element[event_name] ? element[event_name] : function(e){});
            element[event_name] = function(e) { old_event_handler(e); return callback(e); };
        }
    }

    function get_dom_elements(query) {
        var parsed = parse_dom_query(query),
            elements = null;
        if (parsed.type === 'class') {
            elements = get_elements_by_class_name(parsed.query);
        } else if (parsed.type === 'id') {
            var element = get_element_by_id(parsed.query);
            elements = (element !== null) ? [element] : [];
        }
        return elements;
    }

    function register_tracking_event(element, override_event, event_name, properties, callback) {
        // override_event = onclick or onsubmit, etc.
        register_event(element, override_event, function(e) {
            var callback_fired = false;

            e = e || window.event;

            var options = {
                new_tab_click: is_newtab_click(e)
            };

            var timeout_callback = function() {
                if (callback_fired) { return; }
                callback_fired = true;
                callback(false, properties, options);
            };
            var t = window.setTimeout(timeout_callback, metrics.config.track_links_timeout);

            var metrics_callback = function() {
                if (callback_fired) { return; }
                callback_fired = true;
                window.clearTimeout(t);
                callback(true, properties, options);
            };

            metrics.track(event_name, properties, metrics_callback);

            if (!options.new_tab_click) {
                return prevent_default(e);
            }
        });
    }

    function is_newtab_click(e) {
        // this function tries to determine if the user is opening a
        // link in a new tab
        if (e && e.type === "click") {
            var button = 0,
                meta = e.metaKey || e.ctrlKey;
            if (e.which === null && e.button !== null) {
                // IE
                button = (e.button <  2) ? 0 :
                        ((e.button == 4) ? 1 : 2);
            } else if (e.which !== null) {
                // Other
                button = (e.which <  2) ? 0 :
                        ((e.which == 2) ? 1 : 2);
            }
            return (button == 1 || (meta && button == 0));
        }
        return false;
    }

    // add return prevent_default(e) to event callbacks in which you
    // want to prevent the browser default handler from firing
    function prevent_default(e) {
        if (e) {
            e.returnValue = false;
            if (e.preventDefault !== undefined) { e.preventDefault(); }
        }
        return false;
    }

    // Abstract out much of the logic for tracking links/forms (basically, 
    // normal browser events that change the page.
    var DomTracker = function() {};
    DomTracker.prototype.track = function() {
        if (!dom_loaded) {
            dom_loaded_queue.push([this.function_name].concat(to_array(arguments)));
            return true;
        }

        var arg = (arguments.length > 0) ? arguments[0] : undefined;
        if (arg === undefined) {
            console.error("Invalid arguments for track_forms:", arguments);
        } else if (typeof(arg) === 'string' && is_dom_query(arg)) {
            return this.track_query.apply(this, arguments);
        } else if (is_list(arg)) {
            arguments[0] = to_array(arg);
            return this.track_dom.apply(this, arguments);
        } else {
            console.error("Invalid arguments for track_links:", arguments);
        }
        return false;
    };
    DomTracker.prototype.track_query = function(query, event_name, properties, callback) {
        // parse query, select and pass to track_dom_links
        var elements = get_dom_elements(query);
        if (elements === null) { return false; }

        return this.track_dom(elements, event_name, properties, callback);
    };
    DomTracker.prototype.track_dom = function(element_list, event_name, properties, callback) {
        if (!event_name) {
            console.error("No event name provided to mpmetrics." + this.function_name);
            return false;
        }

        properties = properties || {};

        // attach tracking function to all elements in element_list
        each(element_list, function(element) {
            if ((typeof(element) !== 'object') || (typeof(element.nodeName) === 'undefined')) {
                console.error("Invalid element provided to " + this.function_name, element);
                return false;
            }

            var p = this.update_properties(element, properties);

            register_tracking_event(element, this.override_event, event_name, p, this.callback_generator(element, callback));
        }, this);

        return true;
    };
    DomTracker.prototype.update_properties = function(element, properties) {
        var props = {};
        each(properties, function(val, key) {
            props[key] = val;
        });
        return props;
    };

    function LinkTracker() {
        this.function_name = 'track_links';
        this.override_event = 'onclick';
    }
    inherit(LinkTracker, DomTracker);
    LinkTracker.prototype.callback_generator = function(element, callback) {
        return function (skipped_timeout, properties, options) {
            if (callback && (callback(skipped_timeout, properties) === false)) {
                // user can prevent the window from actually changing 
                // location by returning false from their callback
                return; 
            }
            if (options.new_tab_click) { return; }
            if (element.href !== undefined && element.href !== null) {
                setTimeout(function() {
                    window.location = element.href;
                }, 0);
            }
        };
    };

    LinkTracker.prototype.update_properties = function(element, properties) {
        var props = LinkTracker.superclass.update_properties.call(this, element, properties);

        // add href element as url property
        if (element.href) { props["url"] = element.href; }

        return props;
    }

    function FormTracker() {
        this.function_name = 'track_forms';
        this.override_event = 'onsubmit';
    }
    inherit(FormTracker, DomTracker);
    FormTracker.prototype.callback_generator = function(element, callback) {
        return function (skipped_timeout, properties, options) {
            if (callback && (callback(skipped_timeout, properties) === false)) {
                // user can prevent the window from actually changing 
                // location by returning false from their callback
                return; 
            }
            setTimeout(function() {
                // prevent the submit attribute from being overwritten 
                // by an <input type="submit"> inside the actual form
                document.createElement('form').submit.call(element);
            }, 0);
        };
    };

    metrics.track_links = function() {
        var lt = new LinkTracker();
        return lt.track.apply(lt, arguments);
    };

    metrics.track_forms = function() {
        var ft = new FormTracker();
        return ft.track.apply(ft, arguments);
    };

    metrics.send_request = function(url, data) {
        if (is_blocked_ua()) { return; }
        if (metrics.config.test) { data.test = 1; }
        if (metrics.config.img) { data.img = 1; }

        data['_'] = new Date().getTime().toString();
        url += '?' + metrics.http_build_query(data);

        if (metrics.config.img) {
            var img = document.createElement("img");
                img.src = url;
            document.body.appendChild(img);
        } else {
            var script = document.createElement("script"); 
                script.type = "text/javascript"; 
                script.async = true;
                script.defer = true;
                script.src = url;
            var s = document.getElementsByTagName("script")[0]; 
            s.parentNode.insertBefore(script, s);
        }
    };

    metrics.get_query_param = function(url, param) {
        // Expects a raw URL
    
        param = param.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
        var regexS = "[\\?&]" + param + "=([^&#]*)",
            regex = new RegExp( regexS ),
            results = regex.exec(url);
        if (results === null || (results && typeof(results[1]) !== 'string' && results[1].length)) {
            return '';
        } else {
            return unescape(results[1]).replace(/\+/g, ' ');
        }
    };

    metrics.track = function(event_name, properties, callback, type) {
        if (!event_name) {
            console.error("No event name provided to mpmetrics.track");
            return;
        }

        metrics.load_super_once();
        type = type || 'events';
        properties = properties || {};
        properties.token = properties.token || metrics.token;

        var randomized_cb = Math.floor(Math.random() * 10000),
            callback_fn = metrics.callback_fn;

        if (callback !== undefined) { 
            metrics.jsonp_callback['' + randomized_cb] = callback; 
            callback_fn += '["' + randomized_cb + '"]';
        }

        properties.time = metrics.get_unixtime();
        metrics.save_campaign_params();
        metrics.save_search_info(document.referrer);
        if (metrics.config.save_referrer) {
            metrics.save_referrer_info(document.referrer);
        }

        var props_merge = {};
        // get default props (if they are valid, e.g. length > 0)
        merge(props_merge, get_properties(), function(val) { return val.length; });
        // then add global super properties
        merge(props_merge, metrics.super_properties.all);
        // then add specific properties
        if (type != "all") {
            merge(props_merge, metrics.super_properties[type]);
        }
        // then add the passed in properties
        merge(props_merge, properties);
    
        var data = {
            'event' : event_name,
            'properties' : props_merge
        };

        var encoded_data = metrics.base64_encode(metrics.json_encode(data)); // Security by obscurity
        
        if (metrics.config.debug) {
            console.log("-------------- REQUEST --------------");
            console.log(data);
        }
        
        metrics.send_request(
            metrics.api_host + '/track/', 
            {
                'data' : encoded_data, 
                'ip' : 1,
                'callback': callback_fn
            }
        );
        metrics.track_predefined_funnels(event_name, properties);
        return data;
    };

    metrics.track_funnel = function(funnel, step, goal, properties, callback) {
        properties = properties || {};
        properties.funnel = funnel;
        properties.step = parseInt(step, 10);
        properties.goal = goal;
    
        return metrics.track('mp_funnel', properties, callback, "funnels");
    };

    metrics.track_pageview = function() {
        metrics.track("mp_page_view", metrics.get_pageview_info());
    };

    // Valid metadata are desc, url, img
    metrics.track_top = function(collection, item_id, metadata) {
        if (typeof collection == 'undefined') {
            console.error("No data_store provided to track_top");
            return;
        }
        if (typeof item_id == 'undefined') {
            console.error("No item_id provided to track_top");
            return;
        }

        var data = {
            'token': metrics.token,
            'collection': collection,
            'item_id': item_id
        };
        if (typeof metadata != 'undefined') {
            if (typeof metadata['desc'] != 'undefined') {
                data['desc'] = metadata['desc'];
            }
            if (typeof metadata['img'] != 'undefined') {
                data['img'] = metadata['img'];
            }
            if (typeof metadata['url'] != 'undefined') {
                data['url'] = metadata['url'];
            }
        }

        metrics.send_request('http://lift.mixpanel.com/lift', data);

        return data;
    };

    metrics.identify = function(person) {
        // Will bind a unique identifer to the user via a cookie (super properties)
        metrics.register_once({'distinct_id': person}, 'all', null, 30);
    };
    
    metrics.name_tag = function(name_tag) {
        metrics.register({'mp_name_tag': name_tag}, 'all', 30);
    };

    metrics.register_once = function(props, type, default_value, days) {
        // register properties without overriding
        if (is_object(props)) {
            metrics.load_super_once();
            type = type || 'all';
            if (default_value === undefined) { default_value = "None"; }
            if (days === undefined) { days = metrics.config.cookie_expiration; }

            if (metrics.super_properties.hasOwnProperty(type)) {
                var sp = metrics.super_properties[type];
                each(props, function(val, prop) {
                    if (!sp[prop] || sp[prop] === default_value) {
                        metrics.super_properties[type][prop] = val;
                    }
                });
            }
            if (metrics.config.cross_subdomain_cookie) { metrics.clear_old_cookie(); }
            metrics.set_cookie(metrics.config.cookie_name, metrics.json_encode(metrics.super_properties), days, metrics.config.cross_subdomain_cookie);
            return true;
        } else {
            return false;
        }
    };

    metrics.register = function(props, type, days) {
        // register a set of super properties to be included in all events and funnels
        if (is_object(props)) {
            metrics.load_super_once();
            type = type || 'all';
            if (days === undefined) { days = metrics.config.cookie_expiration; }
            var p;

            if (metrics.super_properties.hasOwnProperty(type)) {
                each(props, function(val, prop) {
                    metrics.super_properties[type][prop] = val;
                });
            }

            if (metrics.config.cross_subdomain_cookie) { metrics.clear_old_cookie(); }
            metrics.set_cookie(metrics.config.cookie_name, metrics.json_encode(metrics.super_properties), days, metrics.config.cross_subdomain_cookie);
            return true;
        } else {
            return false;
        }
    };

    metrics.http_build_query = function(formdata, arg_separator) {
        var key, use_val, use_key, tmp_arr = [];

        if (!arg_separator) {
            arg_separator = '&';
        }

        each(formdata, function(val, key) {
            use_val = encodeURIComponent(val.toString());
            use_key = encodeURIComponent(key);
            tmp_arr[tmp_arr.length] = use_key + '=' + use_val;
        });

        return tmp_arr.join(arg_separator);
    };

    metrics.get_unixtime = function() {
        return parseInt(new Date().getTime().toString().substring(0,10), 10);
    };

    metrics.json_encode = function(mixed_val) {    
        var indent;
        var value = mixed_val;
        var i;

        var quote = function (string) {
            var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
            var meta = {    // table of character substitutions
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '"' : '\\"',
                '\\': '\\\\'
            };

            escapable.lastIndex = 0;
            return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
        };

        var str = function(key, holder) {
            var gap = '';
            var indent = '    ';
            var i = 0;          // The loop counter.
            var k = '';          // The member key.
            var v = '';          // The member value.
            var length = 0;
            var mind = gap;
            var partial = [];
            var value = holder[key];

            // If the value has a toJSON method, call it to obtain a replacement value.
            if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }
        
            // What happens next depends on the value's type.
            switch (typeof value) {
                case 'string':
                    return quote(value);

                case 'number':
                    // JSON numbers must be finite. Encode non-finite numbers as null.
                    return isFinite(value) ? String(value) : 'null';

                case 'boolean':
                case 'null':
                    // If the value is a boolean or null, convert it to a string. Note:
                    // typeof null does not produce 'null'. The case is included here in
                    // the remote chance that this gets fixed someday.

                    return String(value);

                case 'object':
                    // If the type is 'object', we might be dealing with an object or an array or
                    // null.
                    // Due to a specification blunder in ECMAScript, typeof null is 'object',
                    // so watch out for that case.
                    if (!value) {
                        return 'null';
                    }

                    // Make an array to hold the partial results of stringifying this object value.
                    gap += indent;
                    partial = [];

                    // Is the value an array?
                    if (Object.prototype.toString.apply(value) === '[object Array]') {
                        // The value is an array. Stringify every element. Use null as a placeholder
                        // for non-JSON values.

                        length = value.length;
                        for (i = 0; i < length; i += 1) {
                            partial[i] = str(i, value) || 'null';
                        }

                        // Join all of the elements together, separated with commas, and wrap them in
                        // brackets.
                        v = partial.length === 0 ? '[]' :
                        gap ? '[\n' + gap +
                        partial.join(',\n' + gap) + '\n' +
                        mind + ']' :
                        '[' + partial.join(',') + ']';
                        gap = mind;
                        return v;
                    }

                    // Iterate through all of the keys in the object.
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }

                    // Join all of the member texts together, separated with commas,
                    // and wrap them in braces.
                    v = partial.length === 0 ? '{}' :
                    gap ? '{' + partial.join(',') + '' +
                    mind + '}' : '{' + partial.join(',') + '}';
                    gap = mind;
                    return v;
            }
        };

        // Make a fake root object containing our value under the key of ''.
        // Return the result of stringifying the value.
        return str('', {
            '': value
        });
    };

    metrics.base64_encode = function(data) {        
        var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc="", tmp_arr = [];

        if (!data) {
            return data;
        }

        data = metrics.utf8_encode(data+'');
    
        do { // pack three octets into four hexets
            o1 = data.charCodeAt(i++);
            o2 = data.charCodeAt(i++);
            o3 = data.charCodeAt(i++);

            bits = o1<<16 | o2<<8 | o3;

            h1 = bits>>18 & 0x3f;
            h2 = bits>>12 & 0x3f;
            h3 = bits>>6 & 0x3f;
            h4 = bits & 0x3f;

            // use hexets to index into b64, and append result to encoded string
            tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
        } while (i < data.length);
    
        enc = tmp_arr.join('');
    
        switch( data.length % 3 ){
            case 1:
                enc = enc.slice(0, -2) + '==';
                break;
            case 2:
                enc = enc.slice(0, -1) + '=';
                break;
        }

        return enc;
    };

    metrics.utf8_encode = function (string) {
        string = (string+'').replace(/\r\n/g, "\n").replace(/\r/g, "\n");

        var utftext = "",
            start, 
            end;
        var stringl = 0,
            n;

        start = end = 0;
        stringl = string.length;

        for (n = 0; n < stringl; n++) {
            var c1 = string.charCodeAt(n);
            var enc = null;

            if (c1 < 128) {
                end++;
            } else if((c1 > 127) && (c1 < 2048)) {
                enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128);
            } else {
                enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128);
            }
            if (enc !== null) {
                if (end > start) {
                    utftext += string.substring(start, end);
                }
                utftext += enc;
                start = end = n+1;
            }
        }

        if (end > start) {
            utftext += string.substring(start, string.length);
        }

        return utftext;
    };

    metrics.set_cookie = function(name, value, expiredays, cross_subdomain) {
        var expiration = new Date(),
            domain = ((cross_subdomain) ? metrics.parse_domain(document.location.hostname) : ""),
            cookiestring = name + "=" + escape(value);
        
        expiration.setDate(expiration.getDate()+expiredays);
        cookiestring += ((expiredays===null) ? "" : ";expires=" + expiration.toGMTString());
        cookiestring += "; path=/";
        cookiestring += ((domain) ? ";domain=." + domain : ""); 
        document.cookie = cookiestring;
    };

    metrics.get_cookie = function(name) {
        var c_start, c_end;
        
        if (document.cookie.length > 0) {
            if (document.cookie.match('^' + name + '=')) {
                c_start = 0;
            } else {
                c_start = document.cookie.search('; ' + name + '=');
                if (c_start !== -1) { c_start += 2; }
            }
            if (c_start !== -1) {
                c_start = c_start + name.length + 1;
                c_end = document.cookie.indexOf(";", c_start);
                if (c_end == -1) { c_end = document.cookie.length; }
                return unescape(document.cookie.substring(c_start, c_end));
            }
        }
        return "";
    };

    metrics.delete_cookie = function(name, cross_subdomain) {
        metrics.set_cookie(name, '', -1, cross_subdomain);
    };
    
    metrics.parse_domain = function(url) {
        var matches = url.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i);
        return matches ? matches[0] : '';
    };
    
    metrics.get_super = function() {
        var cookie_props = eval('(' + metrics.get_cookie(metrics.config.cookie_name) + ')') || {};
        each(cookie_props, function(val, key) {
            metrics.super_properties[key] = val;
        });
    
        return metrics.super_properties;
    };
    
    metrics.load_super_once = function() {
        if (!super_props_loaded) {
            try {
                metrics.get_super();
                super_props_loaded = true;
            } catch(err) {}
        }
    };
    
    metrics.register_funnel = function(funnel_name, steps) {
        metrics.funnels[funnel_name] = steps;
    };
    
    // deprecated...
    metrics.track_predefined_funnels = function(event_name, properties) {
        var funnel, i;
        if (event_name && metrics.funnels) {
            for (funnel in metrics.funnels) {
                if (metrics.funnels.hasOwnProperty(funnel)) {
                    for (i = 0; i < metrics.funnels[funnel].length; ++i) {
                        if (metrics.funnels[funnel][i]) {
                            if (metrics.funnels[funnel][i] == event_name) {
                                // Somewhat inefficient, todo: batch requests one day?
                                metrics.track_funnel(funnel, i+1, event_name, properties);
                            }
                        }
                    }
                }
            }
        }
    };

    metrics.get_campaign_params = function(url) {
        var campaign_keywords = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'],
            kw = '',
            params = {};
        each(campaign_keywords, function(kwkey) {
            kw = metrics.get_query_param(url, kwkey);
            if (kw.length) {
                params[kwkey] = kw;
            }
        });
        return params;
    };
    
    metrics.save_campaign_params = function(url) {
        // Save AdWords campaign information as properties
        metrics.campaign_params_saved = metrics.campaign_params_saved || false;
        if (metrics.config.store_google && !metrics.campaign_params_saved) {
            metrics.register_once(metrics.get_campaign_params(document.URL));
            metrics.campaign_params_saved = true;
        }
    };
    
    metrics.save_search_info = function(referrer) {
        var search = get_search_engine(referrer),
            param = (search != "yahoo") ? "q" : "p";
        if (search.length) {
            var keyword = metrics.get_query_param(referrer, param);
            if (keyword.length) {
                metrics.register({
                    'mp_keyword' : keyword,
                    '$search_engine': search
                }, 'all');
            }
        }
    };

    metrics.save_referrer_info = function(referrer) {
        metrics.register_once({ 
            $initial_referrer: referrer,
            $initial_referring_domain: get_referring_domain(referrer) 
        }, 'all', '');
    };

    // The following bots have a probable chance of executing js...block them
    function is_blocked_ua() {
        var a = navigator.userAgent;
        if (/google web preview/i.test(a) || /baiduspider/i.test(a) || /yandexbot/i.test(a)) {
            return true;
        }
        return false;
    }

    function get_search_engine(referrer) {
        if (referrer.search('https?://(.*)google.([^/?]*)') === 0) {
            return 'google';
        } else if (referrer.search('https?://(.*)bing.com') === 0) {
            return 'bing';
        } else if (referrer.search('https?://(.*)yahoo.com') === 0) {
            return 'yahoo';
        } else if (referrer.search('https?://(.*)duckduckgo.com') === 0) {
            return 'duckduckgo';
        } else {
            return ''
        }
    }
    
    function browser() {
        var a = navigator.userAgent;
        if (window.opera) {
            return "Opera";
        } else if (/chrom/i.test(a)) {
            return "Chrome"; 
        } else if (/msie/i.test(a)) {
            return "Internet Explorer";
        } else if (/AppleWebKit/.test(navigator.appVersion)) {
            return "Safari";
        } else if (/mozilla/i.test(a) && !/compatible|webkit/i.test(a)) {
            return "Firefox";
        } else {
            return "";
        }
    }

    function os() {
        var a = navigator.userAgent;
        if (/Windows/i.test(a)) {
            return 'Windows';
        } else if (/iPhone/.test(a)) {
            return 'iPhone';
        } else if (/Android/.test(a)) {
            return 'Android';
        } else if (/Mac/i.test(a)) {
            return 'Mac OS X';
        } else if (/X11/.test(a) || /Linux/.test(a)) {
            return 'Linux';
        } else {
            return '';
        }
    }

    function get_referring_domain(referrer) {
        var split = referrer.split("/");
        if (split.length >= 3) {
            return split[2];
        }
        return "";
    }

    function get_properties() {
        return {
            $os: os(),
            $browser: browser(),
            $referrer: document.referrer,
            $referring_domain: get_referring_domain(document.referrer)
        }
    }

    metrics.get_pageview_info = function() {
        // mp_keyword we handle automatically.
        var mp_referrer = document.referrer,
            mp_browser = browser(),
            mp_platform = os(),
            mp_page = document.location.href,
            props = {};

        if (mp_referrer.length) { props.mp_referrer = mp_referrer; } 
        if (mp_browser.length) { props.mp_browser = mp_browser; } 
        if (mp_platform.length) { props.mp_platform = mp_platform; } 
        if (mp_page.length) { props.mp_page = mp_page; } 

        return props;
    };
    
    metrics.clear_old_cookie = function() {
        // Delete old non-crossdomain cookie
        metrics.delete_cookie(metrics.config.cookie_name, false);
        // Save the new cookie with domain=.example.com (works across subdomains)
        metrics.set_cookie(metrics.config.cookie_name, metrics.json_encode(metrics.super_properties), 7, true);
    };

    metrics.set_config = function(configuration) {
        if (is_object(configuration)) {
            each(configuration, function(val, key) {
                metrics.config[key] = val;
            });
        }
    };

    function process_dom_loaded_queue() {
        if (dom_loaded) { return; }
        dom_loaded = true;
        if (document.removeEventListener) {
            document.removeEventListener("DOMContentLoaded", process_dom_loaded_queue, false);
            document.removeEventListener("load", process_dom_loaded_queue, false);
        }
        while(dom_loaded_queue.length > 0) {
            var item = dom_loaded_queue.shift();
            metrics[item[0]].apply(metrics, item.slice(1));
        }
    }


    // From http://www.kryogenix.org/days/2007/09/26/shortloaded
    (function(i) {
    var u = navigator.userAgent.toLowerCase();
    var ie = /*@cc_on!@*/false;
    if (/webkit/.test(u)) {
        // safari
        timeout = setTimeout(function(){
                if ( document.readyState === "loaded" || 
                    document.readyState === "complete" ) {
                    i();
                } else {
                setTimeout(arguments.callee,10);
                }
            }, 10); 
    } else if ((/mozilla/.test(u) && !/(compatible)/.test(u)) ||
                (/opera/.test(u))) {
        // opera/moz
        document.addEventListener("DOMContentLoaded",i,false);
    } else if (ie) {
        // IE
        (function (){ 
        var tempNode = document.createElement('document:ready'); 
        try {
            tempNode.doScroll('left'); 
            i(); 
            tempNode = null; 
        } catch(e) { 
            setTimeout(arguments.callee, 0); 
        } 
        })();
    } else {
        register_event(window, 'onload', i);
    }
    })(process_dom_loaded_queue);

    metrics._private = {
        dom_loaded: function(value) { if (typeof(value) !== 'undefined') { dom_loaded = value; } else { return dom_loaded; } },
        is_list: is_list,
        is_object: is_object,
        is_dom_query: is_dom_query,
        parse_dom_query: parse_dom_query,
        to_array: to_array,
        get_elements_by_class_name: get_elements_by_class_name,
        get_element_by_id: get_element_by_id,
        register_event: register_event,
        register_tracking_event: register_tracking_event,
        prevent_default: prevent_default,
        process_dom_loaded_queue: process_dom_loaded_queue
    };

    metrics.set_config(config || {});
    
    // Initiation
    var mp_protocol = (("https:" == document.location.protocol) ? "https://" : "http://");
    metrics.token = token;
    metrics.api_host = mp_protocol + 'api.mixpanel.com';
    
    if (callback_obj) {
        metrics.callback_fn = callback_obj + '.jsonp_callback';
    } else {
        metrics.callback_fn = 'mpmetrics.jsonp_callback';
    }

    // Track pageviews
    metrics.track_pageview();
    
    return metrics;
};
if (typeof mpq != 'undefined' && mpq && mpq[0] && mpq[0][0] == 'init') {
    (function(mpq) {
        // [0][2] may not exist, but it's fine to pass 'undefined' here
        mpq.metrics = new MixpanelLib(mpq[0][1], "mpq.metrics", mpq[0][2]);
        mpq.push = function(item) {
            if (item) {
                if (typeof item == 'function') {
                    item();
                } else if (item.constructor == Array) {
                    var f = mpq.metrics[item[0]];
                    if (typeof f == 'function') {
                        f.apply(mpq.metrics, item.slice(1));
                    }
                }
            }
        };
        var i;
        for (i = 1; i < mpq.length; i++) {
            mpq.push(mpq[i]);
        }
        mpq.length = 0;
    })(mpq);
}


