/*global MAGNET, console, window, document, jQuery, setTimeout, clearTimeout, clearInterval, setInterval */ /* for jsLint */

/*
 * Conde Nast Digital Core JavaScript
 * @copyright 2008-2009 Conde Nast Digital except where specified. All rights reserved.
 */


/*
 * Break out of frame
 */
if (top !== self) {
    top.location.replace(location);
}


/*
 * Sets jQuery no conflict;
 */
jQuery.noConflict();


/*
    SECTION: EXTENSIONS TO NATIVE OBJECTS
*/


/*
    Prototypal inheritance, the missing JavaScript method
    Author: Andrea Giammarchi
    Example: newObject = Object.make(oldObject);
    Reference: http://webreflection.blogspot.com/2008/10/big-douglas-begetobject-revisited.html
    New version recycles function constructor to cut down on memory consumption
    and is based on Doug Crockford's original prototypal inheritance function
*/
if (typeof Object.make !== 'function') {
    Object.make = function(F) {
        return function(Object) {
            F.prototype = Object;
            return new F();
        };
    }(function() {});
}


/*
    Memoizes a function - this DOES add to the Function.prototype
    @author    Keith Gaughan
    @see       http://talideon.com/weblog/2005/07/javascript-memoization.cfm
*/
Function.prototype.memoize = function() {
    var memo = {},
        that = this,
        obj  = arguments.length > 0 ? arguments[i] : null, // TODO: fails jslint, references 'i' out of scope...
        memoizedFn;
        
    memoizedFn = function() {
        var args = [],
            i,
            il;
            
        for (i = 0, il = arguments.length; i < il; i++) {
            args[i] = arguments[i];
        }
        
        if (!(args in memo)) {
            memo[args] = that.apply(obj, arguments);
        }
        
        return memo[args];
    };
    
    memoizedFn.unmemoize = function() {
        return that;
    };
    
    return memoizedFn;
};

/*
    Unmemoizes a function
*/
Function.prototype.unmemoize = function() {
    MAGNET.debug.info('Attempted to unmemoize a function that was never memoized in the first place');
    return null;
};


/*
    Sugar Arrays (c) Creative Commons 2006
    http://creativecommons.org/licenses/by-sa/2.5/
    Author: Dustin Diaz | http://www.dustindiaz.com
    Reference: http://www.dustindiaz.com/basement/sugar-arrays.html
*/
if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(fn, thisObj) {
        var scope,
            i,
            j;
        scope = thisObj || window;
        for (i=0, j=this.length; i < j; ++i) {
            fn.call(scope, this[i], i, this);
        }
    };

    Array.prototype.every = function(fn, thisObj) {
        var scope,
            i,
            j;
        scope = thisObj || window;
        for (i=0, j=this.length; i < j; ++i) {
            if (!fn.call(scope, this[i], i, this)) {
                return false;
            }
        }
        return true;
    };

    Array.prototype.some = function(fn, thisObj) {
        var scope,
            i,
            j;
        scope = thisObj || window;
        for (i=0, j=this.length; i < j; ++i) {
            if (fn.call(scope, this[i], i, this)) {
                return true;
            }
        }
        return false;
    };

    Array.prototype.map = function(fn, thisObj) {
        var scope,
            a,
            i,
            j;
        scope = thisObj || window;
        a = [];
        for (i=0, j=this.length; i < j; ++i) {
            a.push(fn.call(scope, this[i], i, this));
        }
        return a;
    };

    Array.prototype.filter = function(fn, thisObj) {
        var scope,
            a,
            i,
            j;
        scope = thisObj || window;
        a = [];
        for (i=0, j=this.length; i < j; ++i) {
            if (!fn.call(scope, this[i], i, this)) {
                continue;
            }
            a.push(this[i]);
        }
        return a;
    };

    Array.prototype.indexOf = function(el, start) {
        var i,
            j;
        start = start || 0;
        for (i=start, j=this.length; i < j; ++i) {
            if (this[i] === el) {
                return i;
            }
        }
        return -1;
    };

    Array.prototype.lastIndexOf = function(el, start) {
        var i;
        start = start || this.length;
        if (start >= this.length) {
            start = this.length;
        }
        if (start < 0) {
            start = this.length + start;
        }
        for (i=start; i >= 0; --i) {
            if (this[i] === el) {
                return i;
            }
        }
        return -1;
    };
}


/*
 * Remove items in an array. Not included in the above because it's not
 * an official part of the upcoming spec, so its implementation should be
 * checked separately.
 * @see http://ejohn.org/blog/javascript-array-remove/ (http://ejohn.org/blog/javascript-array-remove/#comment-296138)
 */
if (!Array.prototype.remove) {
    Array.prototype.remove = function(from, to) {
        this.splice(from, (to || from || 1) + (from < 0 ? this.length : 0));
        return this.length;
    };
}


/*
    SECTION: MAGNET CORE METHODS
*/


if (typeof MAGNET === 'undefined' || !MAGNET)  {
    /**
     * MAGNET global namespace object
     * @namespace MAGNET global namespace object
     */
    var MAGNET = {};
}


/* 
    The following methods are located at the root of MAGNET, because they 
    deal with primitives that jQuery does not identify. No other functions
    should be placed at this level.
*/


/**
 * Determines whether or not the provided object is a boolean
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isBoolean = function(mixed) {
    return typeof mixed === 'boolean';
};

/**
 * Determines whether or not the provided object is a date
 * @param  {mixed}   mixed  The object being tested
 * @return {boolean}        the result
 */
MAGNET.isDate = function(mixed) {
    return Object.prototype.toString.call(mixed) === '[object Date]';
};

/**
 * Determines whether or not the provided string is empty
 * @param  {string}  str    The string being tested
 * @return {boolean}        the result
 */
MAGNET.isEmpty = function(str) {
    return !/\S/.test(str || '');
};

/**
 * Determines whether or not the provided object is null
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isNull = function(mixed) {
    return mixed === null;
};

/**
 * Determines whether or not the provided object is a legal number
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isNumber = function(mixed) {
    return typeof mixed === 'number' && isFinite(mixed);
};

/**
 * Determines whether or not the provided object is of type object
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isObject = function(mixed) {
    return typeof mixed === 'object';
};

/**
 * Determines whether or not the provided object is a string
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isString = function(mixed) {
    return typeof mixed === 'string';
};

/**
 * Determines whether or not the provided object is undefined
 * @param  {mixed}   mixed  The object being testing
 * @return {boolean}        the result
 */
MAGNET.isUndefined = function(mixed) {
    return typeof mixed === 'undefined';
};


/*
    SECTION: MAGNET STATIC CLASSES
*/


/**
 * @class       MAGNET URL Object
 * @description Contains methods for dealing with urls, query and hash params
 * @public
 * @author      Paul Bronshteyn
 */
MAGNET.url = (function($M) {
    var
        /**
         * Path Cache Array.
         * @memberOf    MAGNET.url
         * @private
         * @type        object
         */
        pathCache = [];

    /**
     * @scope MAGNET.url
     */
    return {
        /**
         * Retrieves domain name from the url in the form of domain.com
         * @param   {string} [url]  Url to be parsed
         * @return  {string}        domain.com
         */
        domain : function(url) {
        	var d = ((url) ? url.replace(/^https*:\/\/|(:|\/).*$/g, '') : location.hostname).split('.'),
                dl = d.length;
        	return d.slice(dl - 2, dl).join('.');
        },

        /**
         * Retrieve current site section
         * @return {string} Section name
         */
        section : function() {
             return ((location.pathname.split('/')[1] || '').match(/^[^\.]*$/) || [''])[0];
        },

        /**
         * Get query params as object of key, value pairs or a value of a param passed in.
           If query is not provided, location.search will be used.
           Result will be caches to queryCache variable for faster access on next call.
         * @param   {string}        param     Parameter to lookup
         * @param   {string}        query     Query string to parse
         * @param   {string}        regex     String key representing regular expression in parsers object
         * @return  {object|string}
         */
        params : function(param, query, regex) {
            var result = $M.utils.parseStr((query || location.search), (regex || 'query'));
            return (param) ? result[param] || '' : result;
        },

        /**
         * Retrive current site path
         * @return {array} Path
         */
        path : function() {
            if (!pathCache.length) {
                pathCache = location.pathname.split('/');
            }
            return pathCache;
        },

        /**
         * Retrieve the URL fragment identifier
         * @return {string|boolean} fragment id
         */
        getFragment : function() {
            return location.hash.substring(1) || false;
        },
        
        /**
         * Sets the fragment identifier string
         */
        setFragment : function(value) {
            location.hash = value || '';
            return this;
        }
    };
})(MAGNET);


/**
 * @class           MAGNET Utilities
 * @description     Collection of utility helper functions
 * @public
 * @author          Paul Bronshteyn
 */
MAGNET.utils = (function($M) {
    var
        /**
         * Cache object.
         * @description Contains result objects for all parsed string using parseStr function.
         * @memberOf    MAGNET.utils
         * @private
         * @type        object
         */
        cache = {},

        /**
         * Regular expression parsers
         * @memberOf    MAGNET.utils
         * @private
         * @type        object
         */
        parsers = {
            /**
             * Query, hash parser expression.
             * @description Will parse a url string in the form of ?var=value&var1=value#hash=value&hash1=value1 into
                            key value pair object.
             * @memberOf    MAGNET.utils
             * @private
             * @type        RegEx expression
             */
            query : /([^?=&]+)(=([^&]*))?/g,

            /**
             * Hash parser expression.
             * @description Will parse url hash string in the form of
                            ?var=value&var1=value into key value pair object.
             * @memberOf MAGNET.utils
             * @private
             * @type RegEx expression
             */
            hash: /([^#=&]+)(=([^&]*))?/g,

            /**
             * User cookie hash parser expression.
             * @description  Will parse a cookie value in the form of var=value|var1=value|var2=value into
                             key value pair object.
             * @memberOf     MAGNET.utils
             * @private
             * @type         RegEx expression
             */
            usercookie : /([^=|]+)(=([^|]*))?/g
        },

        /**
        * Takes an argument and a goal length and prepends or appends
          padding character to reach that length.
        * @param {string} str A number or string representing a number
        * @param {integer} total A length to make the return string
        * @param {string} padding A number or string to pad with
        * @param {string} dir Direction to pad on
        * @return {string} Padded string
        */
        pad = function(str, total, padding, dir) {
            str = String(str || '');
            padding = String(padding || ' ');

            var strLen = str.length,
                padLen = padding.length;

            if (strLen >= total) {
                return str;
            }

            while (strLen < total) {
                str = (dir === 'left') ? padding + str : str + padding;
                strLen += padLen;
            }

            return str;
        };

    /**
     * @scope MAGNET.utils
     */
    return {
        /**
         * Parse string using a regular expression and return object of key, value pairs.
         * @param   {string}    query   Query to be parsed
         * @param   {string}    regex   String key representing regular expression in parsers object
         * @return  {object}            Result object of key, value pairs
         */
        parseStr: function(str, regex) {
            if (cache[str]) {
                return cache[str];
            }
            cache[str] = {};

            (str || '').replace(parsers[regex], function($0, $1, $2, $3) {
                cache[str][$1] = $3;
            });

            return cache[str];
        },

        /**
         * Intval - Check if variable is an integer
         * @param    {mixed}     mixed   The scalar value being converted to an integer
         * @param    {integer}   [base]  The base for the conversion, a number (from 2 to 36)
         *                               that represents the numeral system to be used (default is base 10)
         * @return   {integer}           Return a number (default is 0)
         */
        intval: function(mixed, base) {
            if (typeof mixed === 'boolean') {
                return (mixed) ? 1 : 0;
            } else if (typeof mixed === 'string') {
                mixed = parseInt(mixed * 1, (base || 10));
                return (isNaN(mixed) || !isFinite(mixed)) ? 0 : mixed;
            } else if (typeof mixed === 'number' && isFinite(mixed)) {
                return Math.floor(mixed);
            }

            return 0;
        },

        /**
         * Trim string.
         * @description  Remove leading and trailing space, tab and new lines characters
         * @param        {string}    str String to be trimmed
         * @return       {string}    Trimmed string
         * @author                   Ariel Flesler
         * @see                      http://flesler.blogspot.com/2008/11/fast-trim-function-for-javascript.html
         */
        trim: function(str) {
            var start = -1, 
                end = str.length;
            while (str.charCodeAt(--end) < 33); // TODO: fails jslint - no while block
            while (++start < end && str.charCodeAt(start) < 33); // TODO: fails jslint - no while block
            return str.slice(start, end + 1);
        },

        /**
        * Takes an argument and a goal length and prepends
          padding character to reach that length.
        * @param {mixed} str A number or string representing a number
        * @param {integer} total A length to make the return string
        * @param {mixed} padding A number or string to pad with
        * @return {string} Padded string
        * @uses MAGNET.utils.pad
        */
        padLeft: function(str, total, padding) {
            return pad(str, total, padding, 'left');
        },

        /**
        * Takes an argument and a goal length and appends
          padding character to reach that length.
        * @param {mixed} str A number or string representing a number
        * @param {integer} total A length to make the return string
        * @param {mixed} padding A number or string to pad with
        * @return {string} Padded string
        * @uses MAGNET.utils.pad
        */
        padRight: function(str, total, padding) {
            return pad(str, total, padding, 'right');
        }
    };
})(MAGNET);


/**
 * MAGNET Debug Object
 * @requires    jQuery
 * @class       MAGNET Debug Object
 * @public
 * @constructor
 * @author      Paul Bronshteyn
 */
MAGNET.debug = (function($M) {
    var 
        /**
         * Log Types (error, warn, info, log, user)
         * @memberOf    MAGNET.debug
         * @private
         * @type        object
         */
        eType = {
            error : { f: 'error', msg: 'ERROR' },
            warn  : { f: 'warn',  msg: 'WARNING' },
            info  : { f: 'info',  msg: 'INFO' },
            debug : { f: 'debug', msg: 'DEBUG' },
            user  : { f: 'error', msg: 'USER' }
        },

        /**
         * Log Types (DEV, STAG, PREV, PROD)
         * @memberOf    MAGNET.debug
         * @private
         * @type        object
         */
        eEnv = {
            DEV  : 'Development',
            STAG : 'Staging',
            PREV : 'Preview',
            PROD : 'Production'
        },
        
        /**
         * Shows error information in console or alert 
         * @memberOf    MAGNET.debug
         * @private
         * @param       {string}    type    Error Type
         * @param       {string}    msg     Error message
         * @param       {array}     [args]  Error details
         */
        show = function(type, msg, args) {
            var t = eType[type] || eType.debug;

            if ($M.site.env === 'PROD' && !$M.site.debug) {
                if (type === 'error' || type === 'user') {
                    //serverlog(t.msg, msg, args);
                }
                return;
            }
    
            msg  = msg  || '';
            args = args || [];
    
            if (typeof console === 'object' && console[t.f]) {
                console[t.f](t.msg + ': ' + msg + ' :: ' + args.join(' :: '));
            }
        };
        
        /**
         * @description Server Log - This might be a good idea for production environment, stay tuned.
                        Use ajax to log everything to the server text file/db
         * @memberOf    MAGNET.debug
         * @private
         * @param       {string}    m       Error Type Description
         * @param       {string}    msg     Error message
         * @param       {array}     args    Error details
         */
        //serverlog = function(m, msg, args) {
        //};

        if (MAGNET.url.params("debugOff") === 'true') {
        	show = function() { return; }
        }

    /** 
     * @scope MAGNET.debug
     */
    return {
        /**
         * Log error messages
         * @param   {string}    msg     Error message
         * @param   {array}     [args]  Error details
         */
        error : function(msg, args) {
            show('error', msg, args);
            return this;
        },

        /**
         * Log warning messages
         * @param   {string}    msg     Warning message
         * @param   {array}     [args]  Warning details
         */
        warn : function(msg, args) {
            show('warn', msg, args);
            return this;
        },
        
        /**
         * Log info messages
         * @param   {string}    msg     Info message
         * @param   {array}     [args]  Info details
         */
        info : function(msg, args) {
            show('info', msg, args);
            return this;
        },
        
        /**
         * Log debug messages
         * @param   {string}    msg     Debug message
         * @param   {array}     [args]  Debug details
         */
        debug : function(msg, args) {
            show('debug', msg, args);
            return this;
        },
        
        /**
         * Log Try/Catch messages
         * @param   {object}        e Error object
         * @param   {array} [args]  Error details
         */
        user : function(e, args) {
            show('user', e.message, [args, e.fileName, e.lineNumber, e.name, e.stack]);    
            return this;
        },

        /**
         * Speed test your function
         * @param   {function|string}   f           Function name or it's string representation
         * @param   {array}             [args]      Arguments that will be passed to the function
         * @param   {integer}           [cycles]    How many cycles to run the test (default 10000)
         * @return  {console|alert}                 Prints time in ms in console in FF,Safari,Chrome and alert() on IE
         */
        speedtest : function(f, args, cycles) {
            var x, i;
    
            if ($M.isNumber(args)) {
                cycles = args;
                args = [];
            }
    
            if (!jQuery.isArray(args)) {
                args = [];
            }
            
            cycles = cycles || 10000;
            
            if (!jQuery.isFunction(f)) {
                $M.debug.error('Not a function', [f]);
                return this;
            }
            
            if (typeof console === 'object') {
                if (console.time) {
                    x = 'timer' + Math.floor(Math.random() * 1000000);
                    console.time(x);
                    for (i = 0; i < cycles; i++) {
                        f.apply(this, args);
                    }
                    console.timeEnd(x);
                } else {
                    x = new Date() - 0;
                    for (i = 0; i < cycles; i++) {
                        f.apply(this, args);
                    }
                    x = new Date() - x;
                    console.log(x);
                }
            } else {
                x = new Date() - 0;
                for (i = 0; i < cycles; i++) {
                    f.apply(this, args);
                }
                x = new Date() - x;
                alert(x);
            }

            return this;
        },

        /**
         * MAGNET Application Debug Object
         * @class       MAGNET Application Debug Object
         * @constructor
         * @public
         * @author      Paul Bronshteyn
         */
        app: function() {
            var 
                /**
                 * Holds setLevel options
                 * @memberOf    MAGNET.debug.app
                 * @private
                 * @type        object
                 */
                options = {},
                
                /**
                 * Shows error information in console or alert.
                 * @description     Uses setLevel options to display or supress error messages.
                                    Calls parent show() method if setLevel options match
                 * @memberOf        MAGNET.debug.app
                 * @link            MAGNET.debug.show
                 * @private
                 * @param           {string}    type    Error Type
                 * @param           {string}    msg     Error message
                 * @param           {array}     [args]  Error details
                 */
                _show = function(type, msg, args) {
                    if (options[MAGNET.site.env][type]) {
                        show(type, msg, args);
                    }
                };
            
            /** 
             * @scope MAGNET.debug.app
             */
            return {
                /**
                 * Set Levels of debuging messages
                 * @param   {array}     type    Log Types (error, warn, info, debug, user)
                 * @param   {string}    [env]   Enviroment (DEV, STAG, PREV, PROD)
                 */
                setLevel : function(type, env) {
                    if (!type || !jQuery.isArray(type) || type.length === 0) {
                        return this;
                    }
                    env = (env && env in eEnv) ? env : 'DEV';
                    options[env] = type;
                    return this;
                },
        
                /**
                 * Get Levels of debuging messages
                 * @param   {string}        [env]   Enviroment (DEV, STAG, PREV, PROD)
                 * @return  {object|array}          If enviroment not provided returns reporting object, if provided levels array
                 */
                getLevel : function(env) {
                    return (env) ? options[env] || '' : options;
                },

                /**
                 * Log error messages
                 * @link    MAGNET.debug.error
                 * @param   {string}    msg     Error message
                 * @param   {array}     [args]  Error details
                 */
                error : function(msg, args) {
                    _show('error', msg, args);
                    return this;
                },
                
                /**
                 * Log warning messages
                 * @link    MAGNET.debug.warn
                 * @param   {string}    msg     Warning message
                 * @param   {array}     [args]  Warning details
                 */
                warn : function(msg, args) {
                    _show('warn', msg, args);
                    return this;
                },
                
                /**
                 * Log info messages
                 * @link    MAGNET.debug.info
                 * @param   {string}    msg     Info message
                 * @param   {array}     [args]  Info details
                 */
                info : function(msg, args) {
                    _show('info', msg, args);
                    return this;
                },

                /**
                 * Log debug messages
                 * @link    MAGNET.debug.debug
                 * @param   {string}    msg     Debug message
                 * @param   {array}     [args]  Debug details
                 */
                debug : function(msg, args) {
                    _show('debug', msg, args);
                    return this;
                },

                /**
                 * Log Try/Catch messages
                 * @link    MAGNET.debug.user
                 * @param   {object}    e       Error object
                 * @param   {array}     [args]  Error details
                 */
                user : function(e, args) {
                    _show('user', e.message, [args, e.fileName, e.lineNumber, e.name, e.stack]);
                    return this;
                }
            };
        }
    };
})(MAGNET);


/**
 * Intercept window errors, log them quietly.
 * @description      The error will be intercepted on all enviroments and suppresed 
                     on production enviroment (this should be optional).
 * @name    onerror
 * @event
 * @param  {string}  msg Error message
 * @param  {string}  url URL of the error
 * @param  {integer} line Line number
 * @return {boolean}
 */
if (MAGNET.url.params("debugOff")!=='true') {
    window.onerror = function(msg, url, line) {
        MAGNET.debug.error(msg, [url, line]);
        return (MAGNET.site.env === 'PROD') ? true : false;
    };
}


/**
 * MAGNET Site Object
 * @class    MAGNET Site Object
 * @public
 * @author   Paul Bronshteyn
 */
MAGNET.site = (function($M) {
    /** @scope MAGNET.site */
    return {
        /**
         * Site code
         * @type string
         */
        code : '',

        /**
         * Site title
         * @type string
         */
        title : '',

        /**
         * Site name - Lower cased title
         * @type string
         */
        name : '',

        /**
         * Site environment
         * @type string
         */
        env : '',

        /**
         * Site debug.
         * @description If set will console debug messages in any enviroment.
                        Use query parameter magdebug to toggle debuger.
         * @type boolean
         */
        debug : !!$M.url.params('magdebug'),

        /**
         * Site no ads.
         * @description If set will disable ad calls on the page.
         * @type boolean
         */
        noads : !!$M.url.params('magnoads'),

        /**
         * Test ads.
         * @description If not empty we will use this as dart site and zone
         * @type String
         */
        testads : $M.url.params('dartAdOverride'),

        /**
         * Initiate site specific object, sets document.domain
         * @param {object} settings S
         * @type function
         */
        init : function(settings) {
            settings = settings || {};
            for (var s in settings) {
                this[s] = settings[s];
            }

            /**
             * @name        MAGNET.site#dynamicName
             * @description Dynamically generated site object based on the name of the site.
                            All site specific code will be in this object.
             * @memberOf    MAGNET.site
             * @type        object
             * @example     MAGNET.site.glamour
             */
            this[this.name] = {};

            this.domain = $M.url.domain();
            try {
                if (this.domain) {
                    document.domain = this.domain;
                }
                $M.debug.info('Document domain was set', [this.domain]);
            } catch(e) {
                $M.debug.error(e);
            }

            $M.debug.info('MAGNET Started', [this.code, this.title, this.env, this.name, this.debug, this.noads]);
            
            return this;
        }
    };
})(MAGNET);


/*********************************************************************************************************************
 The above namespaces need to be in the order listed
 All namespaces below will follow in alphabetical order
*********************************************************************************************************************/


/**
 * @class    MAGNET Cookie
 * @public
 * @author   Paul Bronshteyn
 */
MAGNET.cookie = (function($M) {
    var
        /**
         * Cookie Cache Object.
         * @description     Contains all the cookies parsed on the page.
         * @memberOf        MAGNET.cookie
         * @private
         * @type            object
         */
        cookieCache = {};

    /**
     * @scope MAGNET.cookie
     */
    return {
        /**
         * Get the value of a cookie with the given name.
         * @param   {string}    name   Cookie name
         * @return  {string}            Cookie value
         *
         * @example
             Get the value of a cookie:
             MAGNET.cookie.get('the_cookie');
         */
        get : function(name) {
            if (cookieCache[name]) {
                return cookieCache[name];
            }

            var cookies = document.cookie.split('; '),
                cookie = [],
                c = 0,
                cl = cookies.length;

            for (; c < cl; c++) {
                cookie = cookies[c].split('=');
                cookieCache[cookie[0]] = decodeURIComponent(cookie.slice(1).join('='));
                if (cookie[0] === name) {
                    return cookieCache[cookie[0]];
                }
            }

            return '';
        },

        /**
        * Delete the cookie with the given name.
        * @param {string} name Cookie name
        * @param {object} [options] Cookie options
        *
        * @example
            Delete the cookie:
            MAGNET.cookie.del('the_cookie');

            Delete the cookie set with path:
            MAGNET.cookie.del('the_cookie', { path: '/' });
        */
        del: function(name, options) {
            options = options || {};
            options.expires = -1;
            return this.set(name, '', options);
        },

        /**
         * Create a cookie with the given name and value and other optional parameters.
         * @param   {string}    name        Cookie name
         * @param   {string}    [value]     Cookie value
         * @param   {object}    [options]   Cookie options
         *
         * @example
             Create or set the value of a cookie:
             MAGNET.cookie.set('the_cookie', 'the_value');
 
             Create a cookie with all available options:
             MAGNET.cookie.set('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'condenast.com', secure: true });
 
             Delete the cookie:
             MAGNET.cookie.set('the_cookie', '', { expires: -1 });
         */
        set : function(name, value, options) {
            options = options || {};
            value = value || '';
            options.expires = $M.isDate(options.expires) ? options.expires.toUTCString() : $M.isNumber(options.expires) ? (new Date(+(new Date) + options.expires * 60 * 60 * 1000)).toUTCString() : '';

            var cookie = [name + '=' + encodeURIComponent(value)];

            cookie.push('expires=' + options.expires);
            if (options.path) cookie.push('path=' + options.path);
            if (options.domain) cookie.push('domain=' + options.domain);
            if (options.secure) cookie.push('secure');

            return document.cookie = cookie.join('; '), cookieCache[name] = value, true;
        }
    };
})(MAGNET);


/**
 * @class        MAGNET Dart Object
 * @description  Contains methods to build and update Dart ad calls
 * @public
 * @author       Paul Bronshteyn
 */
MAGNET.dart = (function($M) {
    var
        /**
         * Url to loocal dart html page which loads within the frame
         * @memberOf    MAGNET.dart
         * @private
         * @type        string
         * @default     /ads/displayad.html
         */
        frameUrl = '/ads/displayad.html',
        
        /**
         * DoubleClick server url
         * @memberOf    MAGNET.dart
         * @private
         * @type        string
         * @default     http://ad.doubleclick.net/adj/
         */
        dcUrl = 'http://ad.doubleclick.net/adj/',
        
        /**
         * DoubleClick site
         * @memberOf    MAGNET.dart
         * @private
         * @type        string
         * @default     .dart
         */
        dcSite = '.' + ($M.site.testads || 'dart'),

        /**
         * Section of the site
         * @memberOf    MAGNET.dart
         * @private
         * @type        string
         * @default     /home
         */
        zone = '/' + ($M.site.testads || $M.url.section().replace(/-/g, '_') || 'home'),
        
        /**
         * A unique identifier for the page required by DoubleClick to be
           sent with every ad call
         * @memberOf    MAGNET.dart
         * @private
         * @type        integer
         */
        ord = Math.random() * 10000000000000000,
    
        /**
         * Index of the advertising placement on the page
         * @memberOf    MAGNET.dart
         * @private
         * @type        integer
         */
        tile = 0,
    
        /**
         * Ad placement collection
         * @memberOf    MAGNET.dart
         * @private
         * @type        object
         */
        ads = {};

    /**
     * @scope MAGNET.dart
     */
    return {
        /**
         * Request ad placement
         * @param   {string}    placement   Dart ad placement name
         * @param   {object}    params      Dart ad params
         * @param   {boolean}   [store]     Store ad placement?
         * @uses                            MAGNET.frame.refresh
         */
        request : function(placement, params, store) {
            if ($M.site.noads) {
                return this;
            }

            ads[placement] = {
                tile   : ++tile,
                params : params,
                width  : jQuery('#' + placement).width()
            };

            ads[placement].url = frameUrl + '?req=' + dcUrl + $M.site.name + dcSite + zone + ';' + ads[placement].params + 'tile=' + ads[placement].tile + ';';
            $M.frame.refresh('#' + placement, ads[placement].url + 'ord=' + ord);
            $M.debug.info('MAGNET Dart Request', [placement, ads[placement], ord]);

            // store placement? no?
            if (store === false) {
                delete ads[placement];
            }
            
            return this;
        },

        /**
         * Reserving for future use
         * @param   {string}    placement Dart ad placement name
         */
        update : function(placement) {
            return this;
        },

        /**
         * Refresh ad placement(s).
         * @description This will refresh all ad placements provided to the function or all the
                        placements currently on the page.
         * @param       {string,array}  [placement]     Placement or Placements to refresh
         * @uses                                        MAGNET.frame.refresh
         */
        refresh : function(placement) {
            var p = ($M.isString(placement)) ? placement.split(/,|\s+/) : (jQuery.isArray(placement)) ? placement : ads;
    
            ord = Math.random() * 10000000000000000;

            // the next line removes any references to a doubleclick frame busting ads    
            jQuery('script[id*="prscr"], .prWrap').remove();

            // refresh frames
            jQuery.each(p, function(i, v) {
                var ad = ($M.isNumber(i)) ? v : i;
                if (ad in ads) {
                    // reset the width, frame busting ads set it to 0px
                    jQuery('#' + ad).width(ads[ad].width);
                    $M.frame.refresh('#' + ad, ads[ad].url + 'ord=' + ord);
                }
            });
            
            return this;
        }
    };
})(MAGNET);


/**
 * @class   MAGNET Date Object
 * @public
 * @author  Eric Shepherd
 * @author  Paul Bronshteyn
 */
MAGNET.date = (function($M) {
    var 
        /**
         * Month name list
         * @memberOf    MAGNET.date
         * @private
         * @type        object
         */
        months = {
            
            /**
             * English month names
             * @memberOf     MAGNET.date
             * @private
             * @type         array
             */
            en : {
                /**
                 * English long month names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _long: 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','),

                /**
                 * English short month names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _short: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(',')
            },
    
            /**
             * Spanish month names
             * @memberOf     MAGNET.date
             * @private
             * @type         array
             */
            es : {
                /**
                 * Spanish long month names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _long: 'enero,febraro,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,deciembre'.split(',')
            }
        },
        
        days = {
            
            /**
             * English day of the week names
             * @memberOf    MAGNET.date
             * @private
             * @type        array
             */
            en : {
                /**
                 * English day of the week long names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _long: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),

                /**
                 * English day of the week short names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _short: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(',')
            },

            /**
             * Spanish day of the week names
             * @memberOf MAGNET.date
             * @private
             * @type object
             */
            es: {
                /**
                 * Spanish day of the week long names
                 * @memberOf MAGNET.date
                 * @private
                 * @type array
                 */
                _long: 'el domingo,el lines,el martes,el mi&eacute;rcoles,el jueves,el viernes,el s&aacute;bado'.split(',')
            }
        },
        
        
        /**
         * Takes an argument and a goal length, and prepends 0 digits to reach that length
         * @param   {mixed}     arg     A number or string representing a number
         * @param   {number}    len     A length to make the return string
         * @return  {string}            A string containing zero digits plus the original number
         */
        zeroPad = function(arg, len) {
            arg = arg.toString();
            var diff = len - arg.length,
                i;
            for (i = 0; i < diff; i++) {
                arg = '0' + arg.slice(0, arg.length);
            }
            return arg;
        },


        /**
         * Flags for formatting dates, parallels Java simple date format.
         * NOTE: Only works in English for now. We'll need a global language identifier probably at the MAGNET.site level
         * @memberOf        MAGNET.date
         * @private
         * @type            object
         */
        formatFlags = {
            
                    // G - Era designator, we assume AD for now
            G : function() {
                return 'AD';
            },
                    // y - Year
            y : function(d, number) {
                var y = d.getFullYear().toString();
                y = (number === 2) ? y.substring(y.length - 2, y.length) : zeroPad(y,number);
                return y;
            },
            
                    // M - Month in year
            M : function(d, number) {
                var m = d.getMonth(),
                    opt = {};
                if (number < 3) {
                    m = zeroPad(m + 1, number);
                } else {
                    opt.form = number === 3 ? 'short' : 'long';
                    m = MAGNET.date.getMonthName(m, opt);
                }
                return m;
            },
            
                    // w - Week in year
            w : function(d, number) {
                var first = new Date(d.getFullYear(), 0, 1), // January first of the current year
                    w;
                w = Math.ceil((((d - first) / 86400000 /* ms in a day */) + first.getDay() + 1) / 7);
                w = zeroPad(w, number);
                return w;
            },
            
            // W - Week in month - not currently supported
            
                    // D - Day in year
            D : function(d, number) {
                var first = new Date(d.getFullYear(), 0, 1),
                    day;
                day = Math.ceil(((d - first) / 86400000 /* ms in a day */) + first.getDay() + 1);
                day = zeroPad(day, number);
                return day;
            },
            
                    // d - Day in month
            d : function(d, number) {
                return zeroPad(d.getDate(), number);
            },
            
            // F - Day of week in month - not currently supported
            
                    // E - Day in week
            E : function(d, number) {
                var opt = {};
                opt.form = number > 3 ? 'long' : 'short';
                return MAGNET.date.getDayName(d.getDay(), opt);
            },
            
                    // a - AM/PM marker
            a : function(d, number) {
                return d.getHours() < 12 ? 'AM' : 'PM';
            },
            
                    // H - Hour in day (0-23)
            H : function(d, number) {
                return zeroPad(d.getHours(), number);
            },
            
                    // k - Hour in day (1-24)
            k : function(d, number) {
                return zeroPad(d.getHours() + 1, number);
            },
            
                    // K - Hour in am/pm (0-11)
            K : function(d, number) {
                var hours = d.getHours();
                return zeroPad(hours - 12 >= 0 ? hours - 12 : hours, number);
            },
            
                    // h - Hour in am/pm (1-12)
            h : function(d, number) {
                var hours = d.getHours();
                return zeroPad((hours - 13 >= 0 ? hours - 12 : hours), number);
            },
            
                    // m - Minute in hour
            m : function(d, number) {
                return zeroPad(d.getMinutes(), number);
            },
            
                    // s - Second in minute
            s : function(d, number) {
                return zeroPad(d.getSeconds(), number);
            },
                    // S - Millisecond
            S : function(d, number) {
                return zeroPad(d.getMilliseconds(), number);
            }
            
            // z - Time zone (general) // Not Supported
            // Z - Time Zone (RFC 822 e.g. -0800) // Not Supported
        };
        
            
    /**
     * @scope MAGNET.date
     */
    return {
        
        /**
         * Determines whether or not the provided year is a leap year
         * @param   {number}    year    The year being tested
         * @return  {boolean}           Whether or not the year is a leap year
         */
        isLeapYear: function(year) {
            return !!(year && (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0));
        },
        
        /**
         * Get the number of days in the given month
         * @param   {number}    month   Month number (0-11) where January is 0
         * @param   {number}    year    The year
         * @return  {number}            The number of days in the month
         */
        getDaysInMonth: function(month, year) {
            return (month === 1 && this.isLeapYear(year)) ? 29 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] || 0;
        },
        
        /**
         * Get month name.
         * @description Returns month name for specified month index and language, the language will 
                        default to English if not provided.
         * @param       {integer}   month   Month number (0-11) where January is 0, February is 1 and so on
         * @param       {object}    options Language, short or long form
         * @option      {string}    lang    ISO 639-1 language code (default "en")
         * @option      {string}    form    Type of form to use (default "long")
         * @return      {string}            Month name or Empty
         * @link                            http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
         */
        getMonthName: function(month, options) {
            options = options || {};
            return MAGNET.date.getMonthNames(options)[month] || '';
        },

        /**
         * Get month names array.
         * @description Returns array of month names for specified language
         * @param       {string}    lang    ISO 639-1 language code
         * @return      {array}             Month names or Empty
         * @link                            http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
         */
        getMonthNames: function(options) {
            options = options || {};
            return months[options.lang || 'en']['_' + (options.form || 'long')] || [];
        },
        
        /**
         * Get day name.
         * @description Returns day name for specified month index and language; the langauge will
                        default to English if not provided.
         * @param       {number}    day     Day number (0-6) where Sunday is 0
         * @param       {object}    options Language, short or long form
         * @option      {string}    lang    ISO 639-1 language code (default "en")
         * @option      {string}    form    Type of form to use (default "long")
         * @return      {string}            Day name or empty
         * @link                            http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
         */
        getDayName : function(day, options) {
            options = options || {};
            return MAGNET.date.getDayNames(options)[((day === 7) ? 0 : day)] || '';
        },
        
        /**
        * Get day of the week names array.
        * @description Returns array of day of week names for specified language
        * @param {object} [options] Language and name options
        * @option {string} lang ISO 639-1 language code (default "en")
        * @option {string} Type of form to use (default "long")
        * @return {array} Month names or Empty array
        * @link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
        */
        getDayNames: function(options){
            options = options || {};
            return days[options.lang || 'en']['_' + (options.form || 'long')] || [];
        },

        /**
         * Format a JavaScript date to a readable format
         * For now, this is only a format method. It can be expanded to parse a string into a date later.
         * @description Returns a formatted date
         * @param   {object}    d       A JavaScript date object
         * @param   {string}    pattern A formatting string, per Java's SimpleDateFormat specification
         * @return  {string}            A formatted date string
         * @link                        http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html
         */
        format: function(d, pattern) {
                    // For now, only accepting date objects.
            if (!MAGNET.isDate(d)) {
                MAGNET.debug.warn('date.format() method requires a JavaScript date object to be passed');
                return d;
            }
            
            var str = '',
                p = function(q) { // Utility function to push into the string we will be returning
                    str += q;
                },
                i,
                il,
                current = '',
                flagLength = 1,
                currentToEnd,
                subPattern;
            /// END VAR BLOCK

            if (MAGNET.isString(pattern)) {
                
                pattern = pattern.split(''); // pattern is now an array since IE can't do String[n]

                for (i = 0, il = pattern.length; i < il; i++) {
                    current = pattern[i];
                    
                            // If there's a flag, call it, else parse literally while accounting for single quotes
                    if (formatFlags[current]) {
                                // Keeps increasing flagLength if letters are repeated and match a flag
                        while (pattern[i + flagLength] === pattern[i]) {
                            flagLength += 1;
                        }
                                // Calls the method for the given flag
                        p(formatFlags[current](d, flagLength));
                        
                    } else {
                                // If we get a single quote
                        if (pattern[i] === '\'') {
                            if (pattern[i + 1] !== '\'') {
                                currentToEnd = pattern.slice(i + 1, pattern.length);
                                subPattern = currentToEnd.slice(0, currentToEnd.indexOf("'"));
                                p(subPattern.join(''));
                                flagLength += (subPattern.length + 1);
                            } else {
                                p("'");
                                flagLength += 1;
                            }
                            
                        } else {
                            p(pattern.slice(i, i + 1).join(''));
                        }
                    }
                    
                    i += (flagLength - 1); // jump i ahead by the number of letters
                    current = ''; // reset
                    flagLength = 1; // reset
                }
            } else { // Fallback to generic toString() representation if no format was passed in
                str = d.toString();
            }

            return str;
        }
    };
})(MAGNET);


/**
 * @class        MAGNET Frame
 * @description  Contains methods for dealing with iFrames.
 * @public
 * @requires     jQuery
 * @author       Paul Bronshteyn
 */
MAGNET.frame = (function($M) {
    var
        /**
         * Resize iFrame height to fit content on load.
         * @description This is a private function that is triggered by the onload
                        event of the iFrame. This will also be triggered by the
                        public resize method.
         * @memberOf    MAGNET.frame
         * @private
         * @event
         */
        _resize = function() {
            try {
                var body = this.contentWindow.document.body;
                jQuery(this).css({
                    border : 'none',
                    margin : 0,
                    height : jQuery(body).css({ overflow: 'hidden', margin: 0, border: 0 }).outerHeight()
                });
            } catch(e) {
                return $M.debug.user(e, [this, this.id]);
            }
        };

    /**
     * @scope MAGNET.frame
     */
    return {
        /**
         * Bind iFframe resize on iFrame load.
         * @description     Binds the load event to the element passed in.
         * @param           {string}    frame   ID or class of the iFrame in jQuery excepted format.
         * @uses            MAGNET.frame._resize
         *
         * @example
             Using element id:
             MAGNET.frame.bindResize('#frame_id');

             Using element class:
             MAGNET.frame.bindResize('.frame_class');

             Using multiple and combinations:
             MAGNET.frame.bindResize('#frame_id, .frame_class');
         */
        bindResize : function(frame) {
            jQuery(frame).bind('load', _resize);
            return this;
        },

        /**
         * Refresh iFrame
         * @description Refreshes an iFrame with the current url or with the url if the
                        param (if provided), resizes the frame onload to fit content.
         * @param       {string,array}  frames      Array, CSV or space-delimitted list of iframe
                                                    classes or ids or mixed
         * @param       {string}        [url]       Url for the iFrame(s) to be refreshed with, defaults to
                                                    refreshing current iFrame url
         * @param       {boolean}       [resize]    Resize iFrame after refresh, default is true
         * @uses        MAGNET.frame._resize
         *
         * @example
             Refresh iFrame:
             MAGNET.frame.refresh('#frame_id');

             Refresh multiple iFrames (comma-separated):
             MAGNET.frame.refresh('#frame_id,.frame1,#frame2');

             Refresh multiple iFrames (space-separated):
             MAGNET.frame.refresh('#frame_id .frame1 #frame2');

             Refresh iFrame with url:
             MAGNET.frame.refresh('#frame_id', 'http://www.example.com');

             Refresh iFrame with url and do not resize:
             MAGNET.frame.refresh('#frame_id', 'http://www.example.com', false);
         */
        refresh : function(frames, url, resize) {
            frames = ($M.isString(frames)) ? frames.split(/,|\s+/) : (jQuery.isArray(frames)) ? frames : [];
            // frames array empty? exit
            if (!frames.length) {
                return this;
            }

            // shift arguments if url was ommited
            if ($M.isBoolean(url)) {
                resize = url;
                url = '';
            }

            // == is intentional and checks for values that are null or undefined
            resize = (resize == null) ? true : resize;

            // update each frame
            jQuery.each(frames, function(i, v) {
                if (!/\S/.test(v)) {
                    return true;
                }

                var frame = jQuery(v);
                if (!frame.length) {
                    return true;
                }

                // bind load event
                if (resize) {
                    frame.bind('load', _resize);
                }
                url = url || frame[0].src;

                // load url provided or refresh
                // adblock extension throws error, catch it, kill it
                try {
                    frame[0].contentWindow.location.replace(url);
	                $M.debug.info('MAGNET Frame Refresh', [v, url, resize]);
                } catch(e) {
	                $M.debug.user(e, [v, url, resize]);
                }
            });
            
            return this;
        },

        /**
         * Resize iFrame height to fit content.
         * @description     Binds the load event to the element passed in and then triggers it.
         * @param           {string}    frame   ID or class of the iFrame in jQuery excepted format.
         * @uses            MAGNET.frame._resize
         *
         * @example
             Using element id:
             MAGNET.frame.resize('#frame_id');

             Using element class:
             MAGNET.frame.resize('.frame_class');

             Using multiple and combinations:
             MAGNET.frame.resize('#frame_id, .frame_class');
         */
        resize : function(frame) {
            jQuery(frame).bind('load', _resize).triggerHandler('load');
            return this;
        }
    };
})(MAGNET);

/**
 * @class       MAGNET Internal Object
 * @description Handles functionality for non-production environments.
 * @public
 * @author      Eric Shepherd
 */
MAGNET.internal = (function($M) {
    var
        servers = {
            PROD: 'samgdedd02',
            STAG: 'samgdedh06',
            DEV: 'samgdedh06'
        };
        
    return {
        /**
         * Returns the correct teamsite server depending on the current 
         * environment variable. Requires MAGNET.platformEnvironment to 
         * be declared.
         * @memberOf MAGNET.internal
         * @public
         * @method getTeamsiteServer
         * @return {String} The teamsite server name
         */
        getTeamsiteServer : function() {
            return servers[$M.site.env] || 'serverNotAvailable';
        }
    };
})(MAGNET);


/**
 * @class       MAGNET.page
 * @description Page level information
 * @public
 * @author      Paul Bronshteyn
 * @author      Eric Shepherd
 */
MAGNET.page = (function($M) {
    return {
        
        /**
         * The section of the site we are in
         * @memberOf MAGNET.page
         * @public
         * @return {String} The current site section, or empty
         */
        section : function() {
             return ((location.pathname.split('/')[1] || '').match(/^[^\.]*$/) || [''])[0];
        },
        
        /**
         * The subsection of the site we are in, if applicable
         * @memberOf MAGNET.page
         * @public
         * @return {String} The subsection of the site, or empty
         */
        subsection : function() {
            return ((location.pathname.split('/')[2] || '').match(/^[^\.|(\d{4})]*$/) || [''])[0];
        },
        
        /**
         * The content slug of the current page, if applicable
         * @memberOf MAGNET.page
         * @public
         * @return {String} The current page's slug, or empty
         */
        slug : function() {
            return ((location.pathname.split('/')[location.pathname.split('/').length-1] || '').match(/^[^\.]*$/) || [''])[0];
        }
    };
})(MAGNET);


/**
 * @description     Reg specific methods.  If your function is used on
 *                  registration, and it doesn't fit anywhere else... then it belongs here.
 * @class           MAGNET Reg
 * @public
 * @author          Paul Bronshteyn
 * @author          Russell Munson
 */
MAGNET.reg = (function($M) {

    var form = {},
        reqClass = "rqrd";

    function formBindings() {
        form.bind('submit', function() {
            var bdayfield = jQuery('#bdayfield', this);
            if (bdayfield.length && jQuery('#birthYear', this).val() != 'YYYY') {
                bdayfield.val([jQuery('#birthMonth', this).val(), jQuery('#birthDay', this).val(), jQuery('#birthYear', this).val()].join('/'));
            }
        });
    };

    return {
    
        /**
         * Set the form context for the usage in  MAGNET.reg
         *
         * @public
         * @param {String} fid Takes jQuery formatted selector pointing to form
         * @returns {Object} Returns MAGNET.reg for easy command chaining. 
         */
        setForm : function(fid){
            form = jQuery(fid);
            formBindings();
            return this;
        },

        /**
         * Return for jQuery object containing the form currently in context
         *
         * @public
         * @returns {Object} jQuery object conaining form currently in context
         */
        getForm : function(){
            return form;
        },
    
        /**
         * Concatenates values of separate birthday fields with a '/' deliminator
         *
         * @public
         */
        setBirthday : function() {
            var bdayfield = jQuery('#bdayfield', form);
            if (bdayfield.length) {
                var fields = bdayfield.val().split('/');
                jQuery('#birthMonth', form).val(fields[0]);
                jQuery('#birthDay', form).val(fields[1]);
                jQuery('#birthYear', form).val(fields[2]);
            }
        },
        
        /**
         * Add css class indicator for required fields. Assumes
         * standard regForm markup, with parent .row containing class defined in reqClass
         *
         * @public
         */
        setReq: function(el) {
            jQuery(el).parents('.row').addClass(reqClass);
        },
        
        /**
         * Remove css class indicator for required fields. Assumes
         * standard regForm markup, with parent .row containing class defined in reqClass
         *
         * @public
         */
        removeReq: function(el) {
            jQuery(el).parents('.row').removeClass(reqClass);
        },
        
        /**
         * Return class name for marking required fields in a reg form 
         *
         * @public
         */
        getReqClass : function() {
            return reqClass;
        }
    };
})(MAGNET);


/**
 * @class    MAGNET Search
 * @public
 * @author   Paul Bronshteyn
 */
MAGNET.search = (function($M) {
    var
        /**
         * RegEx checks to validate search string
         * @memberOf MAGNET.search
         * @private
         */
        checks = {
            alphanum : /[^0-9a-zA-Z\s]/g,
            script   : /<script(.|\s)*?\/script>/g
        };
        
    /**
     * @scope MAGNET.search
     */
    return  {
        /**
         * Generate search path.
         * @description     Sanitizes the string first, replaces all spaces with -
         * @param           {string}    keywords    Search keywords
         * @return          {string}                Sanitized search path
         * @uses            MAGNET.search.sanitize
         */
        path : function(keywords) {
            return this.sanitize(keywords).replace(/\s+/g, '-');
        },
        
        /**
         * Sanitize query string.
         * @description     Remove &lt;script/&gt; tags to prevent XSS and all non-alpha numeric characters.
         * @param           {string}    keywords    Search keywords
         * @return          {string}                Sanitized keywords
         * @uses            MAGNET.utils.trim
         */
        sanitize : function(keywords) {
            return $M.utils.trim(keywords || '').replace(checks.script, '').replace(checks.alphanum, '');
        }
    };
})(MAGNET);


/**
 * @class    MAGNET User
 * @public
 * @author   Paul Bronshteyn
 */
MAGNET.user = (function($M) {
    
    /**
     * @scope MAGNET.user
     */
    return {
        
        /**
         * Determine if the user is logged in?
         * @return  {boolean}
         * @uses    MAGNET.cookie.get
         */
        isLoggedIn : function() {
            return ($M.cookie.get('amg_user_record'));
        },

        /**
         * Get logged in username
         * @return  {string}    Username
         * @uses    MAGNET.utils.parseStr
         * @uses    MAGNET.cookie.get
         */
        username : function() {
            return $M.utils.parseStr($M.cookie.get('amg_user_record'), 'usercookie').username || '';
        },

        /**
         * Get user id
         * @return  {string}    id
         * @uses    MAGNET.utils.parseStr
         * @uses    MAGNET.cookie.get
         */
        userid : function() {
            return $M.utils.parseStr($M.cookie.get('amg_user_record'), 'usercookie').uid || 0;
        }
    };
})(MAGNET);


/**
 * @description     Methods invloving geo-location, states, provinces, countries... anything
 *                  world oriented
 * @class           MAGNET World
 * @public
 * @author          Paul Bronshteyn
 * @author          Russell Munson
 */
MAGNET.world = (function($M) {

    var
        states = {
            msg: 'Select your',

            us: {
                desc: 'state',
                code: 'AL,AK,AZ,AR,CA,CO,CT,DE,DC,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY,AE,AA,AP'.split(','),
                name: 'Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York,North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode Island,South Carolina,South Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming,Armed Forces Europe,Armed Forces Americas,Armed Forces Pacific'.split(',')
            },

            ca: {
                desc: 'province',
                code: 'AB,BC,MB,NB,NL,NT,NS,NU,ON,PE,QC,SK,YT'.split(','),
                name: 'Alberta,British Columbia,Manitoba,New Brunswick,Newfoundland and Labrador,Northwest Territories,Nova Scotia,Nunavuta,Ontario,Prince Edward Island,Quebec,Saskatchewan,Yukon'.split(',')
            }
        },
        
        /**
         * Defaults the form context for the usage in MAGNET.world to the form currently
         * in context for MAGNET.reg.
         *
         * @protected
         * @returns {Object} Returns jQuery object containing a form
         * @see MAGNET.reg.getForm
         */
        form = function() {
            return MAGNET.reg.getForm();
        };

    return {

        /**
         * Set the form context for the usage in  MAGNET.reg
         *
         * @public
         * @param {String} fid Takes jQuery formatted selector pointing to form
         * @returns {Object} Returns MAGNET.reg for easy command chaining. 
         */
        setForm : function(fid) {
            form = jQuery(fid);
            return this;
        },

        /**
         * Event that handles coordination between the field containing the currently
         * selected country value, and the field #state which lists the states/provinces
         * currently supported by CN Digital reg forms.  State field is disabled for non-supported
         * nations
         *
         * @type Event
         * @public
         */
        setState : function() {
            var stateField = jQuery('#state', form),
                zipField = jQuery("#zip", form),
                selection = this.value.toLowerCase();
                
            if (!(selection in states)) {
                stateField.attr({disabled: 'true'});
                zipField.attr({disabled: 'true'}).data("val",zipField[0].value).val("");
                stateField[0][0].selected = true;
                $M.reg.removeReq("#zip, #state");
            } else {
                $M.reg.setReq("#zip, #state");
                zipField[0].value = (zipField.attr({disabled: ''}).data("val") || zipField[0].value);
                var choice = stateField.children("[selected]").val() || false;
                stateField.empty();
                stateField.attr('disabled', '')[0][0] = new Option(states.msg + ' ' + states[selection].desc, '');
                jQuery.each(states[selection].code, function(i) {
                    stateField[0][i + 1] = new Option(states[selection].name[i], this);
                    if (choice && choice == this) { // TODO: is this == intentional?
                        stateField[0][i + 1].selected = true;
                    }
                });
            }
        }
    };
})(MAGNET);


/*
    SECTION: MAGNET INSTANTIABLE CLASSES
*/


/**
 * Interface creation and verification methods
 * @class Interface
 * @constructor
 * @author Ross Harmes and Dustin Diaz, from Pro JavaScript Design Patterns
 *
 * @param name    {String} The name of the interface. Preferable to use IName convention.
 * @param methods {Array}  The methods which need to be implemented by the child classes.
 */
MAGNET.Interface = function(name, methods) {
    var i,
        il;
    if (arguments.length !== 2) {
        throw new Error('MAGNET.Interface constructor called with ' + arguments.length + ' arguments, but expected exactly 2');
    }
    this.name = name;
    this.methods = [];
    for (i = 0, il = methods.length; i < il; i++) {
        if (typeof methods[i] !== 'string') {
            throw new Error('MAGNET.Interface constructor expects method names to be passed in as strings');
        }
        this.methods.push(methods[i]);
    }
};


/**
 * Verifies that a class implements a given interface.
 * @method ensureImplements
 * @static
 * 
 * @param object {Object} Any object to verify
 */
MAGNET.Interface.ensureImplements = function(object) {
    var i,
        il,
        inter,
        j,
        jl,
        method;
    if (arguments.length < 2) {
        throw new Error('Static method MAGNET.Interface.ensureImplements called with ' + arguments.length + ' arguments, but expected at least 2');
    }
    for (i = 1, il = arguments.length; i < il; i++) {
        inter = arguments[i];
        if (inter.constructor !== MAGNET.Interface) {
            if (jQuery.browser.safari && jQuery.browser.version < 500) {
                // do nothing - safari 2 can't handle this constructor check, this is a patch fix for now
            } else {
                throw new Error('Static method MAGNET.Interface.ensureImplements expects arguments two and above to be instances of MAGNET.Interface.');
            }
        }
        for (j = 1, jl = inter.methods.length; j < jl; j++) {
            method = inter.methods[j];
            if (!object[method] || typeof object[method] !== 'function') {
                throw new Error('Static method MAGNET.Interface.ensureImplements: object does not implement the ' + inter.name + ' interface. Method ' + method + ' was not found.');
            }
        }
    }
};


/**
 * Creates an observable object
 * @class Observer
 * @constructor
 */
MAGNET.Observer = function() {
    /**
     * @property
     * @static
     */
    this.fns = [];
};

MAGNET.Observer.prototype = {
    
    /**
     * Subscribes to an observable event
     * @method subscribe
     *
     * @param fn {Function} A function to execute when the subscribed event fires
     */
    subscribe : function(fn) {
        this.fns.push(fn);
    },
    
    /**
     * Unsubscribes to an observable event
     * @method unsubscribe
     *
     * @param fn {Function} A function to remove from those executed when the subscribed event fires
     */
    unsubscribe : function(fn) {
        this.fns = this.fns.filter(
            function(el) {
                if (el !== fn) {
                    return el;
                }
            }
        );
    },
    
    /**
     * Executes the functions bound to the observable
     * @method fire
     *
     * @param o     {Object} Parameters to pass to the functions when they are called
     * @param scope {Object} Optional scope to execute function within, defaults to window 
     */
    fire : function(o, thisObj) {
        var scope = thisObj || window;
        this.fns.forEach(function(el) {
            el.call(scope, o);
        });
    }
};


/**
 * Creates a timer
 * Adapted from GNU licensed JavaScript Timer
 * Original API Docs: <http://abcoder.com/wp-content/uploads/2008/05/jstimer-api.html>
 * Pass in the milliseconds to wait and the callback function to assign.
 * Timer functions are chainable, and can be started, stopped, paused, resumed and restarted.
 * @class Timer
 * @constructor
 *
 * @param millis   {Number}   Milliseconds for the timer
 * @param callback {Function} A callback to execute each time the interval is reached
 */
MAGNET.Timer = function(millis, callback) {
    this.interval = millis;
    this.timer = null;
    this.callbacks = [];
    this.multipliers = [];
    this.tickCounts = [];
    this.canRun = [];
    this.stoppedThreads = 0;
    this.shouldRunOnce = false;
    this.startedAt = -1;
    this.pausedAt = -1;
    this.addCallback(callback);        
    return this;
};

MAGNET.Timer.prototype = {

    preset : function() { // called from start()
        this.stoppedThreads = 0;
        this.startedAt = -1;
        this.pausedAt = -1;
        for (var i = 0, il = this.callbacks.length; i < il; i++) {
            this.canRun[i] = true;
            this.tickCounts[i] = 0;
        }
    },
    
    ticks : function(initInterval) {
        var that = this,
            i,
            il;
        for (i = 0, il = this.callbacks.length; i < il; i++) {
            if (typeof this.callbacks[i] === 'function' && this.canRun[i]) {
                this.tickCounts[i]++;
                if (this.tickCounts[i] === this.multipliers[i]) {
                    this.tickCounts[i] = 0;
                    if (this.runOnce()) {
                        this.canRun[i] = false;
                        this.stoppedThreads++;
                    }
                    window.setTimeout(that.callbacks[i], 0);
                }
            }
        }
        if (this.runOnce() && this.stoppedThreads === this.callbacks.length) {
            this.stop();
        }
        if (typeof initInterval === 'number') {
            this.stop().start(null, true);
        }
    },
    
    runOnce : function(isRunOnce) {
        if (typeof isRunOnce === 'undefined') {
            return this.shouldRunOnce;
        } else if (typeof isRunOnce === 'boolean') {
            this.shouldRunOnce = isRunOnce;
        } else {
            MAGNET.logger.getInstance().log.error('Invalid argument for runOnce');
        }
        return this;
    },
    
    /**
     * Resets the interval to the specified time or returns the current interval setting
     * @method getSetInterval
     *
     * @param millis {Number} Milliseconds to change the timer's interval to
     * 
     * @return {Mixed} Either the current interval or the timer object itself after resetting the interval
     */
    getSetInterval : function(millis) {
        if (typeof millis === 'undefined') {
            return this.interval;
        } else if (typeof millis === 'number') {
            this.interval = Math.floor(millis);
        }
        return this;
    },
    
    /**
     * Stops the timer.
     * @method stop
     */
    stop : function(isPausing) {
        if (this.timer) {
            if (!isPausing) {
                this.pausedAt = -1;
            }
            try {
                window.clearInterval(this.timer);
            } catch(e) {
            }
            this.timer = null;
        }
        return this;
    },
    
    isStopped : function() {
        return ((this.timer === null) && !this.isPaused());
    },
    
    /**
     * Starts the timer
     * @method start
     */
    start : function(initialInterval, withoutPreset) { // don't use params when calling
        var tempInterval,
            that = this;
            
        if (this.isPaused()) {
            return this.resume();
        }
        if (!this.isStopped()) {
            return this;
        }
        if (!withoutPreset) {
            this.preset();
        }
        tempInterval = this.interval;
        if (typeof initialInterval === 'number') {
            tempInterval = initialInterval;
        }
        this.timer = window.setInterval(function() {
            that.ticks(initialInterval);
        }, tempInterval);
        this.startedAt = new Date().getTime();
        this.startedAt -= (this.interval - tempInterval);
        return this;
    },
    
    /**
     * Pauses the timer, without resetting. Use resume() to restart playing.
     * @method pause
     */
    pause : function() {
        if (this.timer) {
            this.pausedAt = new Date().getTime();
            this.stop(true);
        }
        return this;
    },
    
    isPaused : function() {
        return (this.pausedAt >= 0);
    },
    
    /**
     * Resumes playing a paused timer
     * @method resume
     */
    resume : function() {
        if (this.isPaused()) {
            var tempInterval = this.interval - ((this.pausedAt - this.startedAt) % this.interval);
            this.pausedAt = -1;
            this.start(tempInterval, true);
        }
        return this;
    },
    
    restart : function() {
        return this.stop().start();
    },
    
    /**
     * Adds a callback to the array to be executed at the timer's interval
     * @method addCallback
     */
    addCallback : function(callback, n) {
        if (typeof callback === 'function') {
            this.callbacks.push(callback);
            if (typeof n === 'number') {
                n = Math.floor(n);
                this.multipliers.push(n < 1 ? 1 : n);
            } else {
                this.multipliers.push(1);
            }
            this.tickCounts.push(0);
            this.canRun.push(true);
        }
        return this;
    },
    
    clearCallbacks : function() {
        this.callbacks.length = 0;
        this.multipliers.length = 0;
        this.canRun.length = 0;
        this.tickCounts.length = 0;
        this.stoppedThreads = 0;
        return this;
    }
};


/**
 * DOM-related methods
 * @class dom
 * @static
 */
MAGNET.dom = MAGNET.dom || {};


/**
 * Temporary storage for DOM elements
 * @property storage
 * @static
 */
MAGNET.dom.storage = {
    activeClass   : 'active',
    inactiveClass : 'inactive',
    innerTag      : 'span'
};


/**
 * Removes an element temporarily from the document tree ('activating' a tab, for example, by removing its link)
 * @method activateElement
 *
 * @param el      {Node}   A standard DOM element
 * @param storage {Object} A temporary storage variable
 * @param klass   {String} An optional class name to apply
 *
 * @return {Object} The storage variable, now with the element added
 */
MAGNET.dom.storage.activateElement = function(el, storage, klass) {
    var oldLink,
        newLink,
        newEl;
    
            // Uses the default class name unless one is provided
    klass = klass || this.activeClass;
   
            // If there is a link present in the element
    if (el.getElementsByTagName('a').length > 0) {
        
                // Stores the link - Note that the cloning of the element is 
                // because of an IE bug where the links get frozen on the hover
                // state. Creating a new element altogether fixes this bug. For
                // good browsers, we can just remove the link to storage.
        if (jQuery.browser.msie || jQuery.browser.safari) {
            oldLink = el.getElementsByTagName('a')[0];
            newLink = jQuery(oldLink).clone(true); // gets link AND event handlers
            storage = jQuery(newLink).get()[0];
            oldLink.parentNode.removeChild(oldLink);
        } else {
            storage = el.removeChild(el.getElementsByTagName('a')[0]); 
        }
        
        jQuery(el).addClass(klass);
        
                // Creates a new span to hold the contents and aid in styling
        newEl = document.createElement(this.innerTag);
        newEl.innerHTML = storage.innerHTML;
        
                // Appends the span to the element and returns the storage variable reference
        el.appendChild(newEl);
        return storage;
    }
};


/**
 * Reinserts an element temporarily from the document tree
 * @method deactivateElement
 * @static
 * 
 * @param el      {Node}   A standard DOM element
 * @param storage {Object} A temporary storage variable
 * @param klass   {String} An optional class name to apply
 */
MAGNET.dom.storage.deactivateElement = function(el, storage, klass) {
    
            // Uses the default class name unless one is provided
    klass = klass || this.activeClass;
    
            // If there is a span inside the element
    if (el.getElementsByTagName(this.innerTag).length > 0) {
        
                // Gets the span element, remove it and the class name, and add back what is in storage
        var children = el.getElementsByTagName(this.innerTag);
        el.removeChild(children[0]);
        el.appendChild(storage);

        jQuery(el).removeClass(klass);

    }
};
