
( function( $ ) {

    /* attempts to execute a callback on page load against the specified selector(s) */
    $.attempt = function( selector, callback ) {
        $( function() {
            var selection = $( selector );
            if ( selection.length ) {
                $.each( selection, function( i, element ) {
                    callback.call( $( element ) );
                } );
            }
        } );
    };

    /* gets or sets the specified cookie */
    $.cookie = function( name, value, options ) {
        if ( typeof value != 'undefined' ) {
            options = $.extend({}, {
                path_prefix: tippr.path_prefix,
                domain: tippr.cookie_domain
            }, options);
            if ( value === null ) {
                value = '';
                options.expires = -1;
            }
            var expires = '';
            if ( options.expires && ( typeof options.expires == 'number' || options.expires.toUTCString ) ) {
                var date;
                if ( typeof options.expires == 'number' ) {
                    date = new Date();
                    date.setTime( date.getTime() + ( options.expires * 24 * 60 * 60 * 1000 ) );
                } else {
                    date = options.expires;
                }
                expires = '; expires=' + date.toUTCString();
            }
            var path = ( options.path ? '; path=' + (options.path) : '' );
            var domain = ( options.domain ? '; domain=' + (options.domain) : '' );
            var secure = ( options.secure ? '; secure' : '' );
            document.cookie = [ name, '=', encodeURIComponent( value ), expires, path, domain, secure ].join( '' );
        } else if ( document.cookie ) {
            var cookies = document.cookie.split( ';' );
            for( var i = 0; i < cookies.length; i++ ) {
                var cookie = $.trim( cookies[ i ] );
                var prefix = name + '=';
                if ( cookie.substring( 0, prefix.length ) == prefix ) {
                    return decodeURIComponent( cookie.substring( prefix.length ) );
                }
            }
        }
    };

} )( jQuery );


jQuery.fn.absbottom = function() {
    return Math.ceil( this.offset().top + this.outerHeight() );
};

jQuery.fn.attach_local_overlay = function(content) {
    content = content || "";
    var existing_overlay = this.children('.local-overlay');
    if (! existing_overlay.length) {
        this.append('<div class="local-overlay">' +
            '<div class="local-overlay-content"></div></div>');
    }
    else {
        existing_overlay.show();
    }
    this.find('.local-overlay-content').html(content).center_vertically();
    return this;
};

jQuery.fn.remove_local_overlay = function() {
    return this.children('.local-overlay').empty().remove(); 
}

jQuery.fn.center_vertically = function() {
    return this.each(function(index, element) {
        (function() {
            this.css('margin-top', Math.ceil((this.parent().height() - this.height()) / 2));
        }).call($(element));
    });
}

jQuery.fn.expandable = function( minimum, maximum ) {
    var hcheck = !( jQuery.browser.msie || jQuery.browser.opera );
    var resize = function( e ) {
        e = e.target || e;
        var hdiff = 0;
        if ( jQuery.browser.webkit ) {
            hdiff = $( e ).innerHeight() - $( e ).height();
        }
        var vlen = e.value.length, ewidth = e.offsetWidth;
        if ( vlen != e.value_length || ewidth != e.box_width ) {
            if ( hcheck && ( vlen < e.value_length || ewidth != e.box_width ) ) {
                e.style.height = '0px';
            }
            var h = Math.max( minimum, Math.min( e.scrollHeight, maximum ) );
            e.style.overflow = ( e.scrollHeight > h ? 'auto' : 'hidden' );
            e.style.height = ( h - hdiff ) + 'px';
            e.value_length = vlen;
            e.box_width = ewidth;
        }
        return true;
    };
    this.each( function() {
        if ( this.nodeName.toLowerCase() == 'textarea' ) {
            resize( this );
            if ( !this.initialized ) {
                this.initialized = true;
                jQuery( this ).bind( 'keyup', resize ).bind( 'focus', resize );
            }
        }
    } );
    return this;
};

jQuery.fn.once = function() {
    var self = this;
    if ( !self.length ) return self;
    jQuery.each( arguments, function( index, method ) {
        method.call( self );
    } );
    return self;
};

jQuery.fn.highlight = function( color ) {
    color = color || '#FFF8AF';
    var self = $( this );
    var original = self.css( 'background-color' );
    self.css( 'background-color', color );
    setTimeout( function() { self.css( 'background-color', original ); }, 500 );
    return this;
};

jQuery.initialize = function( selector, callback ) {
    jQuery( function() {
        jQuery( selector ).once( callback );
    } );
};


jQuery.fn.currencyToFloat = function() {
    return parseFloat( this.text().slice( 1 ) );
};

tippr = {

    modal_cache: {},
    mobile_dialog_cache: {},

    /* animates the offer acceleration scale */
    animate_acceleration: function( element ) {
        var counter = element.find( '.counter' );
        var width = element.width() - counter.width();
        var start = parseFloat( element.find( '.start-value strong' ).text().slice( 1 ) );
        var end = parseFloat( element.find( '.end-value strong' ).text().slice( 1 ) );
        var value = parseFloat( element.find( '.counter .value' ).text().slice( 1 ) );
        var delta = ( value - start ) / ( end - start );
        counter.animate( { 'left': width * delta }, 'slow' );
    },

    /* attaches clipboard support to the specified element
    :param inputid: a jQuery ID selector (eg, #input-id) for a text input box containing the data
    :param linkid: a jQuery ID selector for the UI control (eg, #button-id)
    :param callback: an optional callack function
    */
    attach_clipboard_support: function(inputid, linkid, content, callback) {
        if (FlashDetect && FlashDetect.installed) {
            var copy_button = $(linkid), input_field=$(inputid);
            copy_button.show();
            ZeroClipboard.setMoviePath('http://d1bseu12av3ou0.cloudfront.net/406ca1ec9595fd96424e6c8f3802bc898f080116.swf');
            var client = new ZeroClipboard.Client();
            client.setText(content);
            client.glue(copy_button.attr('id'));
            client.setHandCursor(true);
            $(window).bind('resize', function() {
                client.reposition();
            });
            client.show();
            client.addEventListener('onComplete', function() {
                input_field.highlight();
                if (callback && $.isFunction(callback)) {
                    callback(input_field, copy_button, content);
                }
                client.hide();
                client.show();
            });
        }
    },

    /* attach a loading indicator and text to the specified DOM element */
    attach_loading_indicator: function(dom_id, text) {
        text = text || "";
        return $('#' + dom_id).attach_local_overlay('<img class="loading-indicator" src="http://d1bseu12av3ou0.cloudfront.net/2eb3f9f430fb2a6267e0d252129ef6473d074f37.gif"><div class="loading-text">' + text + '</div>');
    },

    bind_to_channel_selector: function() {
        var channel_selector = $('#channel-selector');
        var channel_selector_dropdown = $('#channel-selector-dropdown');
        this.click(function() {
            if (!channel_selector.is('.enabled')) {
                $('body').click(close_channel_selector);
                channel_selector.addClass('enabled');
                channel_selector_dropdown[tippr.show_method]();
            } else {
                channel_selector.removeClass('enabled');
                $('body').unbind('click', close_channel_selector);
                channel_selector_dropdown[tippr.hide_method]();
            }
            return false;
        } );
    }, 

    /* checks for a flag on the current session */
    check: function( key, remove ) {
        var value = $.cookie( key );
        if ( remove ) {
            $.cookie( key, null );
        }
        return value;
    },

    /* presents a confirmation modal to the user */
    confirm: function(parameters) {
        var self = this;
        var modal = $('#confirm-modal');
        if (! $.mobile && ! modal.data('overlay')) {
          self.create_modal({modal: modal, prevent_close: true});
        }
        if (parameters.title) {
            modal.find('#confirm-modal-title').text(parameters.title);
            modal.find('.modal-header').show();
        } else {
            modal.find('.modal-header').hide();
            modal.find('#confirm-modal-title').text('&nbsp;');
        }
        var content = modal.find('#confirm-modal-content');
        if (parameters.content) {
            content.text(parameters.content);
            content.show();
        } else {
            content.text('');
            content.hide();
        }
        modal.find('#confirm-yes-button span').text(parameters.yes || "Yes");
        modal.find('#confirm-no-button span').text(parameters.no || "No");
        modal.find('#confirm-yes-button').unbind('click');
        modal.find('#confirm-yes-button').click(function(event) {
            if (! parameters.prevent_close_on_yes) {
                if ($.mobile) {
                    modal.hide();
                    $.mobile.changePage($('#mobile-page'));
                } else {
                    modal.overlay().close();
                }
            }
            if (parameters.onyes) {
                parameters.onyes();
            }
        });
        modal.find('#confirm-no-button').unbind('click');
        modal.find('#confirm-no-button').click(function(event) {
            if ($.mobile) {
                modal.hide();
                $.mobile.changePage($('#mobile-page'));
            } else {
                modal.overlay().close();
            }
            if (parameters.onno) {
                parameters.onno();
            }
        });
        if ($.mobile) {
            modal.show();
            $.mobile.changePage(modal);
        } else {
            modal.overlay().load();
        }
    },

    /* creates a modal */
    create_modal: function( parameters ) {
        var modal = parameters.modal;
        if (parameters.prevent_close) {
            modal.find('.modal-close-button').remove();
        }
        if ( !modal.parents().length ) {
            $( 'body' ).append( modal );
        }
        if ( !modal.data( 'overlay' ) ) {
            var options = {
                api: true,
                close: '.modal-close-button',
                top: parameters.top || '20%'
            }
            if ( !parameters.prevent_expose ) {
                options.expose = {
                    color: parameters.color || '#000',
                    loadSpeed: 200,
                    opacity: parameters.opacity || 0.3
                };
            }
            if ( parameters.prevent_close ) {
                options.closeOnClick = false;
                options.closeOnEsc = false;
            }
            if ( parameters.remove_on_close ) {
                options.onClose = function( event ) {
                    modal.remove();
                };
            }
            if (parameters.options) {
                options = $.extend(options, parameters.options);
            }
            modal.overlay( options );
        }
        return modal;
    },

    /* debugging support */
    debug: function() {
        if ( window.location.search.search( '_jsdebug' ) >= 0 ) {
            $.each( arguments, function( i, argument ) {
                console.debug( argument );
            } );
        }
    },

    /* facebook api */
    facebook: function( parameters ) {
        var self = this,
            fb_init_parameters = {
                appId: parameters.api_key,
                cookie: true,
                status: true,
                xfbml: true
            },
            auth_change_event;
        if (tippr.facebook_oauth) {
            fb_init_parameters.oauth = true;
            auth_change_event = 'auth.authResponseChange';
        }
        else {
            fb_init_parameters.oauth = false;
            auth_change_event = 'auth.sessionChange';
            // auth.sessionChange reference supports legacy non-oAuth FB auth
        }
        self.authenticating = false;
        self.callback = null;
        self.redirect = null;
        self.requested_properties = [];
        $.extend(self, parameters);
        if (self.debugging) {
            console.log("FB.init", fb_init_parameters);
        }
        FB.init(fb_init_parameters);
        FB.Event.subscribe(auth_change_event, function(response) {
            self.handle_session(response);
        });
        FB.provide('UIServer.Methods', {
            'permissions.request': {
                size: { width: 575, height: 240 },
                url: 'connect/uiserver.php',
                transform: function( call ) {
                    if ( call.params.display == 'dialog' ) {
                        call.params.display = 'iframe';
                        call.params.channel = FB.UIServer._xdChannelHandler( call.id, 'parent.parent' );
                        call.params.cancel = FB.UIServer._xdNextHandler( call.cb, call.id, 'parent.parent', true );
                        call.params.next = FB.UIServer._xdResult( call.cb, call.id, 'parent.parent', false );
                    }
                    return call;
                }
            }
        });
        self.originalPostTarget = FB.Content.postTarget;
        FB.Content.postTarget = function( opts ) {
            opts.params = FB.JSON.flatten( opts.params );
            self.originalPostTarget( opts );
        };
    },

    /* attaches the specified flag to the current session */
    flag: function( key, value ) {
        $.cookie( key, value || 'true' );
        return value;
    },

    /* displays a flash message */
    flash: function(parameters) {
        parameters = parameters || {};
        if (!parameters.text) {
            parameters.text = 'There was an error processing your request. Please try again.';
        }
        if (!parameters.tags) {
            parameters.tags = 'error';
        }
        if ( parameters.tags == 'error' || parameters.tags == 'warning' || parameters.tags == 'notice' ) {
            parameters.permanent = true;
        }
        var message = $( '<li class="' + parameters.tags + '"><a href="#" class="flash-message-close">close</a><div class="flash-message-text">' + parameters.text + '</div></li>' ).hide();
        var receiver = null;
        if ( parameters.source ) {
            source = $( parameters.source );
            var container = source.parents( '.flash-message-container' ).first();
            if ( container.length ) {
                receiver = container.find( '.flash-message-receiver' );
            }
        }
        if ( !( receiver && receiver.length ) ) {
            if ( tippr.mobile_context ) {
                receiver = $.mobile.activePage.find( '.flash-message-receiver' ).first();
            } else {
                receiver = $( '.flash-message-receiver' ).first();
            }
        }
        if ( !( receiver && receiver.length ) ) {
            return;
        }
        if ( parameters.clear_old ) {
            receiver.empty();
        }
        receiver.append( message );
        if ( receiver.is( '.nofading' ) ) {
            message.show();
            if ( !parameters.permanent ) {
                setTimeout( function() { message.remove(); }, 3000 );
            }
        } else {
            message.fadeIn( 500 );
            if ( !parameters.permanent ) {
                setTimeout( function() { message.fadeOut( 500, function() { message.remove(); } ) }, 3000 );
            }
        }
        if ($.mobile) {
            $(window).scroll();
        }
        return message;
    },

    /* validated form implementation */
    form: function(parameters) {
        var self = this;
        self.apply_params(parameters);
        self.marks = {};
        self.messages = [];
        self.tooltip = null;
        self.form.find('input:text,input:password,textarea').bind('blur', 
            function(event) {
                var element = $(event.target);
                if (element && element.length) {
                    self.revalidate(element);
                }
            }
        );
        self.form.find('select').bind('change', 
            function(event) {
                var element = $(event.target);
                if (element && element.length) {
                    self.revalidate(element);
                }
            }
        );
        self.form.submit(function(event) {
            return self.submit();
        });
        if (self.hook_submit_button) {
            self.button.click(function(event) {
                return self.submit();
            });
        }
    },

    formset: function(parameters) {
        /*
        Represents a formset (a set of forms that can be added to and removed.)
        Typically this class is used to represent a collection of "sub-objects"
        related to an object being edited in a form.
        (See, for example, the advertiser edit interface, in the Publisher Portal, 
        which has multiple locations that may be edited for a given advertiser.)
        Note: this class expects to work on a pre-existing HTML formset container with 
        an empty form template and very specific HTML attributes to identify moving parts. 
        Please see presentations/base/templates/support/formset.html for an example.

        :param prefix: An HTML name prefix associated with the formset elements (required)
        :param max_entries: Maximum number of form item entries this formset may contain (optional)
        :param formset_location: if specified, the DOM node after which formset will be inserted (optional)
        :type formset_location: jQuery object (eg, $('#id')) (has default)
        :param formset_full_message: Message to display when the max number of forms has been met (optional)
        :param form_title_template: Displayed title template for individual formset forms. 
            Defaults to the title template specified in the empty form
        :param index_token: This token will be replaced with the 1-indexed numeric index 
            of the form item in the form title template
        :param add_extra_form_label: Label for the add-extra-form-item control element (optional)
        */
        var self = this;
        
        // Initial Values
        self.formset_location = null;
        self.index_token = '__index__';

        // Overrides
        $.extend(self, parameters);

        self.container = $('#' + self.prefix + '-formset-container');

        // Optionally update the displays for the "add extra" and "formset full" controls
        if (self.add_extra_form_label) {
            $('#' + self.prefix + '-add-extra-form').text(this.add_extra_form_label);
        }
        if (self.formset_full_message) {
            $('#' + self.prefix + '-form-full').text(this.formset_full_message);
        }

        // Optionally set form title template
        if (self.form_title_template) {
            $('#empty-' + self.prefix + '-form .fieldset-title').text(self.form_title_template);
        }

        // Hook up add-extra-form control
        $('#' + self.prefix + '-add-extra-form').click(function(event) {
            event.preventDefault();
            self.add_extra_form();
        });

        // Hook up mark_deleted_form controls
        $('#' + self.prefix + '-formset-forms .delete-initial-form input').change(function(event) {
            self.mark_deleted_state($(event.target).parents('.formset-form').first().attr('data-index'), this.checked);
        });

        // Hook up remove-extra-form controls
        self.container.find('.remove-extra-form').live('click', function(event) {
            self.remove_extra_form($(event.target).parents('.formset-form').first().attr('data-index'));
        });

        // Add first (extra) form (if no initial forms)
        self.add_first_form();

        // Move template form to bottom of body (so it doesn't get submitted)
        $('#empty-' + self.prefix + '-form').appendTo(document.body);

        // Optionally move formset into place
        if (self.formset_location) {
            self.formset_location.after(self.container);
        }
    },

    /* ensures that external urls appear in new windows */
    externify: function() {
        var hostname = document.location.hostname, target = '', element = null;
        $.each(['http://', 'https://'], function(i, protocol) {
            $("a[href^='" + protocol + "']").each(function(i, element) {
                element = $(element);
                if (element.attr('data-nonexternal') != 'true') {
                    target = element.attr('href').substr(protocol.length).split('/')[0];
                    if (target != hostname) {
                        // URLs that are more than 2 subdomains long can still be the same, as long as the primary domains are the same.
                        // (i.e. ssl.bing.com & www.bing.com)
                        var targetSplits = target.split('.'),
                            hostSplits = hostname.split('.');
                        if (targetSplits.length < 3 || hostSplits.length < 3
                                || targetSplits[targetSplits.length - 1] != hostSplits[hostSplits.length - 1]
                                || targetSplits[targetSplits.length - 2] != hostSplits[hostSplits.length - 2])
                            element.attr('target', '_blank');
                    }
                }
            });
        });
    },

    get_sharing_url: function(query_url, offerid, callback) {
        $.ajax( {
            cache: false,
            type: 'POST',
            url: query_url,
            data: { 'offerid': offerid },
            success: function(response) {
                var share_url = response.messages[0].message;
                callback(share_url);
            }
        } );
    },

    hide_method: $.browser.msie ? 'hide' : 'slideUp',

    iframe_submit: function(params) {
        /* Creates and submits a form inside a hidden iframe.
        Note that this method shall submit a form; all validation and veto hooks 
            should be performed before this method is called.
        All params should be pass as key-value pairs inside a single struct.
        :param action: the destination URL of the form to be submitted, required
        :param iframe_id: the HTML ID attribute of an iframe container
        :param data: a mapping of key-value pairs to include in the POST
        :param on_response: an optional callback function
            The on_response callback is set using $.fn.data on the iframe DOM node.
            Any server responses should call the iframe's on_response callback out of courtesy.
        :param on_timeout: a function to be called if the iframe form response is not
            loaded within the timeout period
        :param timeout_period: a time limit to constrain the form submission to, in millisecs.
        */
        var action = params['action'], 
            iframe_id = params['iframe_id'] || ('iframe-' + (new Date().getTime())), 
            data = params['data'] || {}, 
            on_response = params['on_response'] || (function() {}),
            on_timeout = params['on_timeout'] || (function() {}),
            timeout_period = params['timeout_period'] || null;
        // remove any existing iframe relic to avoid namespace conflict
        $('#' + iframe_id).empty().remove();
        // create and populate iframe
        $('body').append($('<iframe id="' + iframe_id + '" style="display:none;" onload="clearInterval($(this).data(\'timeout_interval\'));"></iframe>'));
        var iframe = $('#' + iframe_id);
        iframe.data('on_response', on_response);
        iframe.data('on_timeout', on_timeout);
        var tr_doc = iframe.contents()[0];
        tr_doc.open();
        tr_doc.write('<form method="POST" action="' + action + '">');
        $.each(data, function(key, value) {
            tr_doc.write('<input type="text" name="' + key + '" value="' + value + '" />');
        });
        tr_doc.write('</form>');
        tr_doc.close();
        if (timeout_period) {
            // set timeout constraint
            iframe.data('timeout_interval', setInterval(
                '$("#' + iframe_id + '").data("on_timeout")();',
                timeout_period));
        }
        // submit form
        $(tr_doc).find('form').submit();
        return iframe;
    },


    /* ... */
    install_konami_listener: function( callback ) {
        var keys = [ 38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13 ], position = 0;
        $( window ).keyup( function( event ) {
            if ( event.keyCode == keys[ position ] ) {
                position++;
                if ( position == keys.length ) {
                    callback();
                    position = 0;
                }
            } else {
                position = 0;
            }
        } );
    },

    kissmetrics: function( kmq, context ) {
        var self = this;

        self.push = function( obj ) {
            _kmq.push(obj)
        };

        self.record = function( event, props ) {
            self.push( [
                'record',
                event,
                props ? jQuery.extend( {}, props ) : null
            ] );
        };

        self.identify = function( email ) {
            self.push( [ 'identify', email ] );
        };

        self.set = function( props ) {
            self.push( [ 'set', jQuery.extend( {}, props ) ] );
        };

        self.alias = function( id1, id2 ) {
            self.alias( [ 'alias', id1, id2 ] );
        };

        self.shared = function( medium ) {
            var data = {
                medium: medium
            };
            jQuery.extend( data, kissmetrics.context.offer );
            kissmetrics.client.record( 'shared', data );
        };

        _kmq.push(function() {
            if (KM.i() == 'ANONYMOUS') {
                KM.clearIdentity();
            }
        });
        if (context.user) {
            self.identify(context.user);
        }
        self.set( {
            channel: context.channel,
            site_domain: context.site_domain,
            user: context.user
        } );
    },

    /* loads a modal from the server and shows it */
    load_modal: function( parameters ) {
        var data = parameters.data, url = parameters.url;
        if ( parameters.delete_modal_selector && $( parameters.delete_modal_selector ) ) {
            $( parameters.delete_modal_selector ).remove();
        }
        // If an absolute URL is passed in, attempt to crop off the 'https?://server.name/',
        // leaving only the modal URL.
        if (url.match(/^https?:\/\//)) {
            url = url.replace(/^https?:\/\//, '');
            url = url.substring(url.indexOf('/'), url.length);
        }
        if ( ( ( this.mobile_context && this.mobile_dialog_cache[ url ] ) || ( !this.mobile_context && this.modal_cache[ url ] ) ) && !data && !parameters.no_cache && !parameters.post ) {
            if ( this.mobile_context ) {
                var modal = this.mobile_dialog_cache[ url ];
                if ( parameters.onload ) {
                    parameters.onload( modal );
                }
                if ( parameters.title ) {
                    modal.find( '.modal-header-title' ).text( parameters.title );
                }
            } else { 
                var modal = this.modal_cache[ url ];
                if ( parameters.onload ) {
                    parameters.onload( modal );
                }
                if ( parameters.title ) {
                    modal.find( '.modal-header-title span' ).text( parameters.title );
                }
            }
            if ( parameters.show ) {
                if ( this.mobile_context ) {
                    $.mobile.changePage( modal );
                } else {
                    modal.overlay().load();
                }
                if (parameters.flash) {
                    tippr.flash({
                        tags: parameters.flash.tags,
                        text: parameters.flash.text,
                        permanent: true,
                        show: true,
                        source: modal.find('.modal-content')
                    });
                }
            }
        } else {
            var self = this;
            $.ajax( {
                data: data,
                dataType: 'html',
                type: ( data || parameters.post ) ? 'POST' : 'GET',
                url: url,
                success: function( response ) {
                    var modal = $( response );
                    if ( !data && !parameters.no_cache && !parameters.post ) {
                        if ( self.mobile_context ) {
                            self.mobile_dialog_cache[ url ] = modal;
                        } else {
                            self.modal_cache[ url ] = modal;
                        }
                    }
                    if ( self.mobile_context ) {
                        $( 'body' ).append( modal );
                        if ( parameters.onload ) {
                            parameters.onload( modal );
                        }
                        if ( parameters.title ) {
                            modal.find( '.modal-header-title' ).text( parameters.title );
                        }
                        if ( parameters.show ) {
                            $.mobile.changePage( modal );
                        }
                    } else {
                        parameters.modal = modal;
                        self.create_modal(parameters);
                        if (parameters.onload) {
                            parameters.onload(modal);
                        }
                        if ( parameters.title ) {
                            modal.find( '.modal-header-title span' ).text( parameters.title );
                        }                            
                        if ( parameters.show ) {
                            modal.overlay().load();
                        }
                    }
                    if ( parameters.flash ) {
                        tippr.flash( {
                            tags: parameters.flash.tags,
                            text: parameters.flash.text,
                            permanent: true,
                            show: true,
                            source: modal.find( '.modal-content' )
                        } );
                    }
                    if ( !parameters.nofocus ) {
                        modal.find( 'input[type=text],input[type=password],textarea' ).first().focus();
                    }
                }
            } );
        }
        return modal;
    },

    /* Creates a listener for a hash tag change. Checks every 10th of a second for a change, and once
       a change is noted, the listener returns the callback and stops listening. */
    on_hash_change: function (callback) {
        var get_hash_value = function () {
            var hashValue = window.location.hash.split("#")[1];
            if (!hashValue) {
                return false;
            }
            var hashValue = hashValue.split("?")[0];
            return hashValue;
        };

        var lastHash = get_hash_value();

        (function watch_hash() {
            var hash = get_hash_value();
            if (hash !== lastHash) {
                return callback();
            } else {
                setTimeout(watch_hash, 100);
            }
        })();
    },

    /* displays the openid popup */
    openid_popup: function( dest, authtype ) {
        dest = dest || window.location;
        var url = '/openid/start/?return_to=' + escape( dest );
        if ( authtype ) {
            url += '&auth_type=' + authtype;
        }
        var popup = window.open( url + '&popup=1', 'OpenID Authentication', 'width=450, height=500' );
        if ( popup.closed ) {
            window.location = url + '&popup=0';
        }
    },

    /* submits a post via ajax */
    post: function( parameters ) {
        var data = '', self = this;
        if ( parameters.data ) {
            data = $.param( parameters.data );
        }
        var spinner = parameters.spinner;
        if ( spinner ) {
            self.spin_button( spinner, parameters.spin_text, parameters.unspin_text );
        }
        $.ajax( {
            cache: false,
            data: data,
            type: 'POST',
            url: parameters.url,
            success: function( response ) {
                if ( spinner ) {
                    self.unspin_button( spinner, true );
                }
                if ( response ) {
                    if ( response.messages ) {
                        $.each( response.messages, function( i, item ) {
                            self.flash( { text: item.message, tags: item.tags } );
                        } );
                    }
                    if ( response.response ) {
                        if ( parameters.reload_on_success ) {
                            window.location = window.location;
                        } else if ( parameters.onsubmit ) {
                            parameters.onsubmit( response.response );
                        }
                    } else {
                        if ( parameters.onfailure ) {
                            parameters.onfailure( response.error, response );
                        }
                    }
                } else {
                    self.flash();
                    if ( parameters.onerror ) {
                        parameters.onerror();
                    }
                }
            },
            error: function() {
                if ( spinner ) {
                    self.unspin_button( spinner, false );
                }
                self.flash();
                if ( parameters.onerror ) {
                    parameters.onerror();
                }
            }
        } );
    },

    /* calculate and apply subtotal, applied credits, and payment sums for purchase offer modal */
    purchase_modal_calculate: function() {
        var credit = parseFloat($('#credits-applied').attr('data-available'));
        var price = parseFloat($('#unit-cost').data('price'));
        if (!price) {
            price = parseFloat($('#unit-cost').attr('data-price'));
        }
        var total = "", youpay = "", quantity = 1;
        quantity = $( 'select[name=purchase-quantity]' ).val();
        var amount = quantity * price;
        if ( credit > amount ) {
            credit = amount;
        }
        total = amount.toFixed( 0 );
        youpay = ( amount - credit ).toFixed( 0 );
        credit = credit.toFixed( 0 );

        var sum_class = "two-digit-price";
        if ( total >= 10000 ) {
            sum_class = "five-digit-price";
        } else if ( total >= 1000 ) {
            sum_class = "four-digit-price";
        } else if ( total >= 100 ) {
            sum_class = "three-digit-price";
        }

        var total_element = $( '.subtotal strong' );
        total_element.parent().fadeOut( 'fast', function() {
            total_element.text( total );
            total_element.removeClass( 'two-digit-price three-digit-price four-digit-price' ).addClass( sum_class );
            total_element.parent().fadeIn( 'slow', function() {
                total_element.parent().show();
            } );
        } );
        var credits_element = $( '.credits strong' );
        credits_element.parent().fadeOut( 'fast', function() {
            credits_element.text( credit );
            credits_element.removeClass( 'two-digit-price three-digit-price four-digit-price' ).addClass( sum_class );
            credits_element.parent().fadeIn( 'slow', function() {
                credits_element.parent().show();
            } );
        } );
        var youpay_element = $( '.you-pay strong' );
        youpay_element.parent().fadeOut( 'fast', function() {
            youpay_element.text( youpay );
            youpay_element.removeClass( 'two-digit-price three-digit-price four-digit-price' ).addClass( sum_class );
            youpay_element.parent().fadeIn( 'slow', function() {
                youpay_element.parent().show();
            } );
        } );
    },

    /* manipulate the forms on the purchase modal when a user clicks to create an account */
    purchase_modal_choose_create_click_handler: function() {
        if ($.browser.msie) {
            $('#personal-info-signin').hide();
            $('#personal-info-create').show();
            $('#purchase-offer-shipping-fields').show();
            $('#purchase-offer-right-column').show();
            $('#billing-info').show();
        } else {
            $('#personal-info-signin')[tippr.hide_method]('fast', function() {
                $('#personal-info-create').show();
                $('#purchase-offer-right-column')[tippr.show_method]('fast');
                $('#purchase-offer-shipping-fields')[tippr.show_method]('slow');
                $('#billing-info')[tippr.show_method]('fast');
            });
        }
        tippr.purchase_modal_select_payment_method_handler();
    },

    /* manipulate the forms on the purchase modal when a user clicks to sign in */
    purchase_modal_choose_signin_click_handler: function() {
        if ($.browser.msie) {
            $('#purchase-offer-right-column').hide();
            $('#purchase-offer-shipping-fields').hide();
            $('#billing-info').hide();
            $('#personal-info-create').hide();
            $('#personal-info-signin').show();
        } else {
            $('#purchase-offer-right-column')[tippr.hide_method]('fast');
            $('#purchase-offer-shipping-fields').hide();
            $('#billing-info')[tippr.hide_method]('fast');
            $('#personal-info-create')[tippr.hide_method]('fast', function() {
                $('#personal-info-signin').show();
            });
        }
        $('#submit-purchase-offer span').text("Continue");
    },
    
    /* display the correct content and input controls for the selected payment method option */
    purchase_modal_select_payment_method_handler: function(event) {
        var create_payment_method_fieldset = $('#purchase-offer-update-payment-method');
        var payment_options = $('ul.credit-card-list');
        var new_card_option = payment_options.find('li.new-credit-card input');
        var amazon_option = payment_options.find('li.amazon input');
        var button_inner_text = $('#submit-purchase-offer span');

        if ((new_card_option.length && new_card_option[0].checked) || !payment_options.length) {
            create_payment_method_fieldset.show();
        }
        else {
            create_payment_method_fieldset.hide();
        }
        if (amazon_option.length && amazon_option[0].checked) {
            button_inner_text.text("Continue to Amazon Payments");
        }
        else {
            button_inner_text.text("Purchase");
        }
        return true;
    },

    /* activate the selected product and manipulate the purchase offer modal */
    purchase_modal_select_product_handler: function(button) {
        var button = $(button);

        if (tippr.offer.mechanic == 'external') {
            // For external offers, directly jump to the product Purchase URL, when supplied
            var product = tippr.offer.product_by_id(button.data('product-id'));
            return tippr.select_external_offer_product(product);
        }

        var unit_price, unit_limit, dropdown, dropdown_count, children;
        $('#multiple-product-offer-modal').hide();
        $('#id_purchase-product').val(button.data('product-id'));
        unit_price = button.data('product-unit-cost'); 
        $('#unit-cost').data('price', unit_price);
        $('#price').text(unit_price);
        unit_limit = Number(button.data('product-limit'));
        dropdown = $('#id_purchase-quantity');
        dropdown_count = dropdown.children().length;
        if (dropdown_count < unit_limit) {
            for(var i = dropdown_count + 1; i <= unit_limit; i++) {
                dropdown.append('<option value="' + i + '">' + i + '</option>');
            }
        } else if (dropdown_count > unit_limit) {
            for(var i = dropdown_count; i > unit_limit; i--) {
                dropdown.children().last().detach();
            }
        }
        tippr.purchase_modal_calculate();
        $('#final-purchase-offer-container').show();
        if ($('#personal-info-choose-create').length && $('#personal-info-choose-create')[0].checked) {
            tippr.purchase_modal_choose_create_click_handler();
        } 
        else if ($('#personal-info-choose-signin').length && $('#personal-info-choose-signin')[0].checked) {
            tippr.purchase_modal_choose_signin_click_handler();
        }

        var purchase_form = $('#purchase-offer-form');
        if (!purchase_form.data('original-action')) {
            purchase_form.data('original-action', purchase_form.attr('action'));
        }
        purchase_form.attr('action', purchase_form.data('original-action') + '?activity=purchase&product=' + button.data('product-id'));

        tippr.purchase_modal_select_payment_method_handler();

        return false;
    },

    /* remove a loading indicator from the specified DOM element */
    remove_loading_indicator: function(dom_id) {
        return $('#' + dom_id).remove_local_overlay();
    },

    /* redirects to ssl if necessary */
    require_ssl: function(secured_callback, unsecured_callback) {
        var self = this;
        if (! self.is_ssl_aware) {
            if (secured_callback) {
                secured_callback();
            }
        }
        else {
            if (window.location.protocol == self.secure_protocol) {
                if (secured_callback) {
                    secured_callback();
                }
            }
            else {
                if (unsecured_callback) {
                    unsecured_callback();
                }
                window.location = self.secure_url;
            }
        }
    },
    
    /* reposition an element's background image on page load / resize */
    reposition_background: function( jquery_element, image_width, image_height, x_offset, y_offset ) {
        try {
            var element_height = jquery_element.innerHeight(); 
            var element_width = jquery_element.innerWidth(); 
            var x_position = (element_width - image_width) / 2 + x_offset;
            
            var y_position = y_offset;
            
            jquery_element.style.backgroundPosition = x_position + 'px ' + y_position + 'px';
        } catch( e ) {}
    },

    select_external_offer_product: function (product) {
        if ($.mobile) {
            window.location = product.purchase_url;
        } else {
            $('#external-offer-container').append('<iframe id="external-offer-frame" src="' + product.purchase_url + '" noresize="noresize" frameborder="0"></iframe>');
            var calcHeight = function () {
                $('#external-offer-frame').height($(window).height() - $('#external-offer-banner').height());
            };
            $(window).resize(calcHeight);
            calcHeight();
            $('html').addClass('external-iframe');
            window.scroll(0, 0);
            window.location.hash = '/external-offer/';
            $('#publisher-referral-ribbon').show();
            $('#external-offer-container').show();
            tippr.on_hash_change(function () {
                if (window.location.hash === '') {
                    $('#external-offer-container').hide();
                    $('#external-offer-frame').hide().empty().remove();
                    $('html').removeClass('external-iframe');
                }
            });
        }
    },

    show_method: $.browser.msie ? 'show' : 'slideDown',

    /* ensures the purchase modal is properly displayed */
    show_purchase_modal: function() {
        if (tippr.offer.mechanic == 'external') {
            if (! tippr.offer.has_multiple_products) {
                tippr.select_external_offer_product(tippr.offer.products[0]);
                return;
            }
        }
        if ($('#multiple-product-offer-modal').length) {
            if ($('#final-purchase-offer-container').data('product-has-been-selected')) {
                $('#final-purchase-offer-container').show();
                $('#multiple-product-offer-modal').hide();
                $('#final-purchase-offer-container').data('product-has-been-selected', false)
            }
            else {
                $('#multiple-product-offer-modal').show();
                $('#final-purchase-offer-container').hide();
            }
        } else {
            $('#final-purchase-offer-container').show();
        }
        if ($('#offer-cost').length) {
            $('#offer-cost').hide();
        }
        tippr.purchase_modal = tippr.create_modal({
            modal: $('#purchase-offer-modal-container'),
            onLoad: function() {
                var modal = $(this);
                $('select[name=purchase-quantity]').change().focus();
                if ($('#personal-info-choose-create:checked').length) {
                    tippr.purchase_modal_choose_create_click_handler();
                }
                else if ($('#personal-info-choose-signin:checked').length) {
                    tippr.purchase_modal_choose_signin_click_handler();
                }
                tippr.purchase_modal_select_payment_method_handler();
            }
        });
        tippr.purchase_modal.overlay().load();
    },

    /* spins the specified button */
    spin_button: function(button, text, unspin_text) {
        var span = button.find('span');
        var inner_span = span.find('span');
        if (inner_span.length) {
            span = inner_span;
        }
        if (span.length) {
            button.queue(function(next) {
                if (text) {
                    if (unspin_text == null) {
                        unspin_text = span.text();
                    }
                    button.data('normal-text', span.text());
                    button.data('success-text', unspin_text);
                    span.text(text);
                }
                var spinner = span.find('img');
                if (! spinner.length) {
                    span.prepend($('<img src="http://d1bseu12av3ou0.cloudfront.net/a1952750e46f08d16f53de1123385bb4c6957c62.gif" class="spinner"/>'));
                    spinner = span.find('img');
                }
                spinner.show();
                button.attr('disabled', true);
                next();
            }).delay(300);
        }
    },

    /* shows a tooltip */
    tooltip: function( parameters ) {
        var div = $( '#default-tooltip' ), element = parameters.element;
        if ( element && element.length ) {
            div.find( 'span' ).text( parameters.text );
            var api = element.tooltip( {
                api: true,
                events: { input: 'nothing,nothing', widget: 'nothing,nothing' },
                offset: parameters.offset || [ 0, 0 ],
                opacity: parameters.opacity || 1.0,
                position: parameters.position || 'top left',
                tip: '#default-tooltip',
                onBeforeShow: function( event, position ) {
                    var tip = event.target.getTip();
                    event.target.getConf().offset = [ -6, tip.outerWidth() ];
                }
            } );
            if ( parameters.show ) {
                api.show();
            }
            return api;
        }
    },

    /* unspins the specified button */
    unspin_button: function(button, success) {
        var span = button.find('span');
        var inner_span = span.find('span');
        if (inner_span.length) {
            span = inner_span;
        }
        if (span.length) {
            button.queue(function(next) {
                span.find('img').hide();
                var text = null;
                if (success) {
                    text = button.data('success-text');
                }
                else {
                    text = button.data('normal-text');
                }
                if (text) {
                    span.text(text);
                }
                button.attr('disabled', false);
                next();
            });
        }
    },

    /* Serves as a callback for when the publisher-referral-ribbon is closed */
    update_external_iframe_height: function () {
        iframe = $('#external-offer-frame');
        if (iframe) {
            $('#external-offer-frame').height($(window).height() - $('#external-offer-banner').height());
        }
    },

    /* Interface to a single file uploader
     *
     * Uses the plupload (http://www.plupload.com) library internally.
     *   This library is pulled in automatically with the DOJO libraries,
     *   but it must be referenced explicitly if the DOJO libraries are not used.
     *
     * Depends on the DOM having a div with class "file-name-field" which will
     *   receive information on the state of the target file to upload
     *
     * Also, two paramters: browse_button and upload_button must contain ids of
     *   <buttons> that control the browsing (selecting) of a file to upload,
     *   and the actual uploading process respectively.
     *
     * See presentations/portal/templates/offers/edit-offer.html for an example.
     */
    uploader: function(options) {
        this.file_name_field = $('.file-name-field').first();
        this.browse_button = $('#' + options.browse_button);
        this.browse_text = this.browse_button.html();
        this.upload_button = $('#' + options.upload_button);
        this.onupload = options.onupload;

        options.runtimes = 'html5';

        this.init(options);
    },

    /* changes the current language in the current session & account */
    change_language: function(lang) {
        var redirect_to = window.location.protocol + '//' + window.location.host + window.location.pathname;
        if (window.location.search) {
            var lang_index = window.location.search.indexOf('lang');
            if (lang_index >= 0) {
                // strip out lang argument
                var trailing_querystring = '';
                var lang_end_index = window.location.search.indexOf('&', lang_index);
                if (lang_end_index !== -1) {
                    trailing_querystring = window.location.search.substring(lang_end_index + 1);
                }
                var stripped_querystring = window.location.search.substring(0, lang_index) + trailing_querystring;
                redirect_to += stripped_querystring;
                if (redirect_to[redirect_to.length - 1] !== '?' && redirect_to[redirect_to.length - 1] !== '&') {
                    redirect_to += '&';
                }
            }
            else {
                redirect_to += window.location.search + '&';
            }
        }
        else {
            redirect_to += '?';
        }
        redirect_to += 'lang=' + lang;
        if (window.location.hash) {
            redirect_to += window.location.hash;
        }
        window.location = redirect_to;
        return false;
    }
}

$.extend( tippr.facebook.prototype, {

    /* executes one of two callbacks depending on session status */
    check: function(success, failure) {
        var response,
            auth_data;
        FB.getLoginStatus(function(r){ response = r; });
        auth_data = response && (response.authResponse || response.session);
        // response.session reference supports legacy non-oAuth FB auth
        if (auth_data) {
            success();
        }
        else {
            failure();
        }
    },

    /* debugging support */
    debug: function() {
        if ( this.debugging ) {
            $.each( arguments, function( i, argument ) {
                console.debug( argument );
            } );
        }
    },

    /* handles session changes from facebook */
    handle_session: function(response) {
        var self = this,
            auth_data = response.authResponse || response.session;
            // response.session reference supports legacy non-oAuth FB auth
        if (self.debugging) {
            console.log("handle_session", response);
        }
        if (auth_data && ! self.authenticated && ! self.authenticating) {
            if (self.debugging) {
                console.log("FB authenticate to PBT via AJAX", self);
            }
            self.authenticating = true;
            $.ajax({
                cache: false,
                context: self,
                data: auth_data,
                dataType: 'json',
                type: 'POST',
                url: self.login_url,
                success: self.query_permissions,
                error: self.failed
            });
        }
    },

    /* attempts to login via facebook connect */
    login: function( next ) {
        var self = this;
        if (next) {
            self.redirect = next;
        }
        FB.getLoginStatus(function(response) {
            var auth_data = response.authResponse || response.session;
            // response.session reference supports legacy non-oAuth FB auth
            if (! auth_data && ! self.authenticated) {
                FB.login(function(response) {
                    self.handle_session(response);
                });
            } else {
                self.handle_session(response);
            }
        });
    },

    /* attempts to logout */
    logout: function() {
        var self = this;
        if ( login_method == 'facebook' ) {
            try {
                FB.getLoginStatus( function( response ) {
                    if ( response.session ) {
                        FB.logout( function( response ) {
                            window.location = self.logout_url;
                        } );
                    } else {
                        window.location = self.logout_url;
                    }
                } );
            } catch( exception ) {
                window.location = self.logout_url;
            }
        } else {
            window.location = self.logout_url;
        }
    },

    /* prompts the user for information we couldn't get from facebook */
    prompt_user: function( uid, fields, values ) {
        var self = this;
        self.debug( 'prompting user to connect account' );
        var connect_account_modal_url = $('link[rel=start]')[0].href + 'r/modal/connect-account/';
        var fields_string = fields.join(',');
        if ($.mobile) {
            var values_array = [];
            $.each(values, function(key, value) {
                values_array[values_array.length] = key + '=' + value;
            });
            var values_string = values_array.join(',');
            var connect_account_modal_form_id = 'connect-account-modal-form';
            var connect_account_modal_form = "<form id='" + connect_account_modal_form_id + "' method='post' " + 
                "action='" + connect_account_modal_url + "'>" +
                "<input type='hidden' name='fields' value='" + fields_string + "'/>" +
                "<input type='hidden' name='uid' value='" + uid + "'/>" +
                "<input type='hidden' name='values' value='" + values_string + "'/>" +
                "<input type='hidden' name='next' value='" + self.redirect + "'/>" +
                "</form>";
            $(connect_account_modal_form).submit();
        }
        else {
            tippr.load_modal({
                data: {fields: fields_string},
                prevent_close: true,
                show: true,
                url: connect_account_modal_url,
                onload: function(modal) {
                    var form = new tippr.form({
                        form: modal.find('#connect-account-form'),
                        reset_on_success: true,
                        spin_submit_button: true,
                        spin_text: 'Connecting',
                        submit_with_ajax: true,
                        use_inline_marks: true,
                        onsubmit: function(form, response) {
                            modal.overlay().close();
                            $.extend(values, response);
                            self.update_account(uid, values);
                        }
                    });
                }
            });
        }
    },

    /* publishes an attachment to this account's stream */
    publish: function( parameters ) {
        var actions = parameters.actions || [];
        FB.ui( {
            attachment: parameters.attachment,
            method: 'stream.publish',
            message: parameters.message || '',
            user_message_prompt: parameters.prompt || 'Share with friends!'
        } );
    },

    /* queries the permissions of the current facebook user */
    query_permissions: function(requests) {
        var self = this,
            fields = [],
            uid;
        if (tippr.facebook_oauth) {
            uid = FB.getAuthResponse().userID;
        }
        else {
            uid = FB.getSession().uid;
            // getSession() reference supports legacy non-oAuth FB auth
        }
        if ($.inArray('zipcode', requests) >= 0) {
            fields.push(requests.pop('zipcode'));
        }
        self.debug( 'querying permissions for uid = ' + uid, requests );
        FB.Data.query( 'select email,publish_stream from permissions where uid = ' + uid ).wait( function( rows ) {
            var permissions = [], row = rows[ 0 ];
            if ( row[ 'email' ] != '1' ) {
                permissions.push( 'email' );
            }
            if ( row[ 'publish_stream' ] != '1' ) {
                permissions.push( 'publish_stream' );
            }
            if ( permissions.length > 0 ) {
                FB.ui( { method: 'permissions.request', perms: permissions.join( ',' ) }, function( response ) {
                    self.debug( response );
                    if ( $.inArray( 'email', permissions ) >= 0 && response.perms.search( 'email' ) < 0 ) {
                        fields.push( 'email' );
                    }
                    self.query_profile( uid, requests, fields );
                } );
            } else {
                self.query_profile( uid, requests, fields );
            }
        } );
    },

    /* queries the profile of the current facebook user */
    query_profile: function( uid, requests, fields ) {
        var self = this, values = {};
        if ( requests.length > 0 ) {
            FB.Data.query( 'select ' + requests.join( ',' ) + ' from user where uid = ' + uid ).wait( function( rows ) {
                if ( rows.length > 0 ) {
                    $.each( rows[ 0 ], function( field, value ) {
                        if ( field != 'uid' ) {
                            if ( value ) {
                                values[ field ] = value;
                            } else if ( field != 'username' ) {
                                fields.push( field );
                            }
                        }
                    } );
                } else {
                    fields = fields.concat( requests );
                }
                if ( fields.length > 0 ) {
                    self.prompt_user( uid, fields, values );
                } else if ( !$.isEmptyObject( values ) ) {
                    self.update_account( uid, values );
                } else {
                    self.redirect_user();
                }
            } );
        } else if ( fields.length > 0 ) {
            self.prompt_user( uid, fields, values );
        } else if ( !$.isEmptyObject( values ) ) {
            self.update_account( uid, values );
        } else {
            self.redirect_user();
        }
    },

    /* redirects the user once authentication is completed */
    redirect_user: function() {
        if ( this.callback ) {
            this.callback();
        } else if ( this.redirect ) {
            window.location = this.redirect;
        } else {
            window.location = window.location;
        }
    },

    /* queries a particular permission */
    request_permissions: function( permissions, callback ) {
        FB.ui( { method: 'permissions.request', perms: permissions.join( ',' ) }, function( response ) {
            callback( response.perms );
        } );
    },

    /* shares a url via facebook */
    share: function( url ) {
        FB.ui( { method: 'stream.share', u: url } );
    },

    /* updates our account with the profile information from facebook */
    update_account: function( uid, values ) {
        var self = this;
        values[ 'uid' ] = uid;
        tippr.post( {
            data: values,
            url: self.update_url,
            onsubmit: function( response ) {
                if ( response.result == 'merge' ) {
                    tippr.require_ssl( function() { self.do_merge( response.mergeData ); }, null );
                } else {
                    self.redirect_user();
                }
            }
        } );
    },
    
    do_merge: function( context ) {
        tippr.load_modal( {
            data: { target: context.target },
            prevent_close: true,
            url: $('link[rel=start]')[0].href + 'r/modal/merge-account/',
            onload: function( modal ) {
                var form = new tippr.form( {
                    form: modal.find( '#merge-account-form' ),
                    extra_data: {
                        facebookid: context.facebookid,
                        facebook_username: context.username,
                        fullname: context.fullname,
                        zipcode: context.zipcode
                    },
                    reload_on_success: true,
                    spin_submit_button: true,
                    spin_text: 'Merging Accounts',
                    submit_with_ajax: true,
                    use_inline_marks: true
                } );
                modal.overlay().load();
                modal.find( 'input[name=password]' ).focus();
            }
        } );
    }

} );

$.extend( tippr.form.prototype, {
    
    apply_params: function(parameters) {
        /* Applies parameters to this form, using initialization logic that goes beyond
            simply copying data to this object.
        To be used for initializing the form, and for modifying the form's purpose dynamically.
        Does not modify already bound event handlers, but may manipulate DOM attrs.
        */
        var self = this;
        $.extend(self, parameters);
        if ($.isFunction(self.action)) {
            self.action = self.action();
        }
        if (self.action) {
            self.form.attr('action', self.action);
        }
        else {
            self.action = self.form.attr('action');
        }
        self.button = parameters.submit_button || self.form.find('button');
        self.tooltip_parameters = self.tooltip_parameters || {};
        self.extra_data = self.extra_data || {};
        if (self.spin_submit_button) {
            var spinner_preloader = new Image;
            spinner_preloader.src = "http://d1bseu12av3ou0.cloudfront.net/a1952750e46f08d16f53de1123385bb4c6957c62.gif";
        }
    },

    clear: function() {
        this.tooltip = null;
        this.form.find( 'input:text,input:password,select,textarea' ).removeClass( 'invalid' );
        $.each( this.marks, function( name, para ) {
            para.hide();
        } );
        $.each( this.messages, function( i, message ) {
            message.remove();
        } );
        this.messages = [];
    },

    field_html_name: function(name, prefix) {
        if (prefix) {
            return prefix + '-' + name;
        }
        else {
            return name;
        }
    },

    flash: function(parameters) {
        parameters['source'] = this.form;
        this.messages.push(tippr.flash(parameters));
    },
            
    focus: function( element ) {
        element.focus();
        if ( element.is( 'input[type=text],input[text=password],textarea' ) ) {
            element.select();
        }
    },

    mark: function(name, message) {
        var self = this;
        if (self.use_inline_marks || $.mobile) {
            var para = self.form.find('[for=' + name + '].form-error')
            if (para.length) {
                para.text(message);
                para.show();
            }
            else {
                self.form.find('[name=' + name + ']').parents('p').after(
                    '<p class="form-error" for="' + name + '">' + message + '</p>');
            }
        }
        else {
            var input = self.form.find('[name=' + name + ']');
            tooltip_parameters = self.tooltip_parameters;
            $.extend(tooltip_parameters, {element: input, show: true, text: message})
            self.tooltip = tippr.tooltip(tooltip_parameters);
        }
    },

    postsubmit: function(success) {
        var self = this;
        if (self.spin_submit_button) {
            tippr.unspin_button(self.button, success);
        }
        if (self.hide_on_submit) {
            $(self.hide_on_submit).show();
        }
        self.postsubmit_extension(success);
    },
    
    postsubmit_extension: function() {},

    presubmit: function() {
        var self = this;
        self.presubmit_extension();
        if (self.spin_submit_button) {
            tippr.spin_button(self.button, self.spin_text, self.unspin_text);
        }
        if (self.hide_on_submit) {
            $(self.hide_on_submit).hide();
        }
    },
    
    presubmit_extension: function() {},

    reset: function() {
        this.form.resetForm();
    },

    revalidate: function(element) {
        var self = this;
        var value = element.val();
        if (element.is('.invalid')) {
            var name = element.attr('name');
            if (value.length > 0 && value != element.data('previous-value')) {
                element.removeClass('invalid');
                self.unmark(name);
            }
        }
    },

    submit: function() {
        var self = this;
        try {
            self.clear();
            self.presubmit();
            if ( self.submit_with_ajax ) {
                self.form.ajaxSubmit( {
                    url: self.action,
                    cache: false,
                    data: self.extra_data,
                    dataType: 'json',
                    success: function( response ) {
                        if ( response ) {
                            // If there are new error messages, clear out the old
                            if (response.fields) {
                                self.clear();
                            }

                            // Create new flash messages from the response
                            if ( response.messages ) {
                                $.each( response.messages, function( i, item ) {
                                    self.flash( {
                                        text: item.message,
                                        tags: item.tags,
                                        source: self.form
                                    } );
                                } );
                            }

                            // Add the new form field error messages
                            if (response.fields) {
                                self.update(response.fields);
                            }

                            // If the request was successful
                            if ( response.response ) {
                                if ( !self.do_not_clear_on_success ) {
                                    self.clear();
                                }
                                if ( self.reset_on_success ) {
                                    self.form.resetForm();
                                }
                                if ( self.reload_on_success ) {
                                    window.location = window.location;
                                } else if ( self.onsubmit ) {
                                    self.onsubmit( self, response.response );
                                }
                            } else {
                                if ( self.onfailure ) {
                                    self.onfailure( self, response.error );
                                }
                            }

                            // Call postsubmit() with the server response, UNLESS
                            //   the request succeeded and unspin_on_success is false
                            if (!(response.response && !self.unspin_on_success)) {
                                self.postsubmit(response.response);
                            }

                        // Did not receive a response from the server
                        } else {
                            tippr.flash( { source: self.form } );
                            if ( self.onerror ) {
                                self.onerror( self );
                            }
                            self.postsubmit( false );
                        }
                    },
                    error: function() {
                        tippr.flash( { source: self.form, clear_old: true } );
                        if ( self.onerror ) {
                            self.onerror( self );
                        }
                        self.postsubmit( false );
                    }
                } );
            }
            else {
                $.each(self.extra_data, function(key, value) {
                    self.form.append('<input name="' + key + '" value="' + value + '" style="display: none;" />');
                });
                return true;
            }
        } catch( exception ) {
            tippr.flash( { source: self.form, clear_old: true } );
        }
        return false;
    },

    unmark: function(name) {
        var self = this;
        if (self.use_inline_marks || $.mobile) {
            self.form.find('[for=' + name + '].form-error').empty().remove();
        }
        else {
            self.tooltip.hide();
            self.tooltip = null;
        }
    },

    update: function(errors, prefix) {
        var self = this;
        if (errors && errors.length > 0) {
            $.each(errors, function(i, error) {
                var name = self.field_html_name(error[0], prefix),
                    message = error[1];
                var element = self.form.find('[name=' + name + ']');
                element.addClass('invalid');
                element.data('previous-value', element.val());
                if (self.use_inline_marks || $.mobile) {
                    self.mark(name, message);
                }
            });
            var first_name = self.field_html_name(errors[0][0], prefix),
                first_message = errors[0][1];
            if (! self.use_inline_marks && ! $.mobile) {
                self.mark(first_name, first_message);
            }
            if (self.focus_first_error) {
                self.focus(self.form.find('[name=' + first_name + ']'));
            }
        }
    }

});

$.extend(tippr.formset.prototype, {
    // Add an extra form to the formset
    add_extra_form: function() {
        var self = this, total = this._total(), form_index;
        form_index = total.value;
        total.input.val(form_index + 1);

        var extra_form = $('#empty-' + self.prefix + '-form .formset-form').clone().hide();
        var extra_form_title = extra_form.find('.fieldset-title').first();
        extra_form_title.text(extra_form_title.text().replace(self.index_token, form_index + 1));
        extra_form.find('.field').each(function(i, element) {
            $(this).attr('id', $(this).attr('id').replace(self.index_token, form_index));
        });
        extra_form.find('.field-widget-container').each(function(i, element) {
            // NOTE: __prefix__ is used by Django as a token to represent the form item index
            var child_input = $(this).find('p').children().first();
            child_input.attr('id', child_input.attr('id').replace('__prefix__', form_index));
            child_input.attr('name', child_input.attr('name').replace('__prefix__', form_index));
        });
        extra_form.attr('id', self.prefix + '-' + form_index + '-container');
        extra_form.attr('data-index', form_index);
        $('#' + self.prefix + '-formset-forms').append(extra_form);
        extra_form[tippr.show_method]();

        // Hide form controls and show form full message if maximum entries limit has been met
        if (self.max_entries && (form_index + 1) >= self.max_entries) {
            $('#' + self.prefix + '-add-extra-form').hide();
            $('#' + self.prefix + '-form-full')[tippr.show_method]();
        }
    },

    // Add first (extra) form to the formset if it is empty
    add_first_form: function() {
        var self = this;
        if (self._total().value == 0) {
            self.add_extra_form();
        }
    },

    form_container: function(form_index) {
        // Returns jQuery object containing the form item at the specified index
        var self = this;
        return $('#' + self.prefix + '-' + form_index + '-container');
    },

    field_container: function(form_index, field) {
        // Returns jQuery object containing the specified field in the specified form index
        var self = this;
        return $('#' + self.prefix + '-' + form_index + '-' + field + '-container');
    },

    // Hides the fields specified, optionally setting their value at the same time
    hide_fields: function(fields, value) {
        var self = this;
        $.each(fields, function(field_index, field) {
            for (var form_index = 0; form_index < self._total().value; ++form_index) {
                var field_object = self.field_container(form_index, field);
                // not all hide methods correctly hide field if parent container is hidden
                if (! field_object.is(':visible')) { 
                    field_object.hide();
                }
                else {
                    field_object[tippr.hide_method]();
                }
                if (value !== undefined) {
                    $('#' + self.prefix + '-' + form_index + '-' + field).attr('value', value);
                }
            }
        });
    },

    mark_deleted_state: function(form_index, deleted_state) {
        /*
        Mark the initial form item as pending deletion or not pending deletion.
        */
        var self = this;
        var fieldset_container = self.form_container(form_index).find('.fieldset-content');
        var input_elements = fieldset_container.find(':input:visible');
        if (deleted_state) {
            fieldset_container.addClass('disabled');
            input_elements.addClass('disabled');
        }
        else {
            fieldset_container.removeClass('disabled');
            input_elements.removeClass('disabled');
        }
    },

    // Removes the form at form_index
    //   NOTE: this function does not remove forms representing initial items (those rendered 
    //   by the server.)  Therefore, remove_extra_form() will do nothing if form_index is less than
    //   the index of the first extra form added to the formset.
    remove_extra_form: function(form_index) {
        // Traverse through all form elements after index, moving their input
        //   element values to the form elements preceding them.
        var self = this, initial = this._initial(), total = this._total();
        if (form_index >= initial.value) {
            for (var i = form_index; i < total.value; ++i) {
                var j = i + 1;
                if (j == total.value) {
                    break;
                }
                self.form_container(j).find(':input').each(function(index, element) {
                    // copy the contents of this input to the form immediately preceding it
                    var prev = $('#' + $(this).attr('id').replace('-' + j + '-', '-' + i + '-'));
                    prev.attr('value', $(this).attr('value'));
                });
            }

            self.form_container(total.value - 1)[tippr.hide_method]('fast', 'swing', function() {$(this).empty().remove();});

            total.input.val(total.value - 1);
            $('#' + self.prefix + '-add-extra-form').show();
            $('#' + self.prefix + '-form-full').hide();
        }
    },

    // Removes all extra forms in this formset after optional index (defaults to all items)
    //  NOTE: As above, this formset does not remove initial forms.  Therefore, if form_index
    //  is less than the index of the first added form, it will be set to the lowest
    //  possible index (i.e. the index of the first added form.)
    remove_all_extra_forms: function(form_index) {
        var self = this, total = this._total(), initial = this._initial();
        if (form_index === undefined || form_index < initial.value) {
            form_index = initial.value;
        }

        for (var i = form_index; i < total.value; ++i) {
            self.form_container(i).hide().empty().remove();
        }

        total.input.val(form_index);
        $('#' + self.prefix + '-add-extra-form').show();
        $('#' + self.prefix + '-form-full').hide();
    },

    // Displays the fields specified
    show_fields: function(fields) {
        var self = this;
        $.each(fields, function(field_index, field) {
            for (var form_index = 0; form_index < self._total().value; ++form_index) {
                self.field_container(form_index, field)[tippr.show_method]();
            }
        });
    },

    // Helper function to get the number of initial forms in this formset
    _initial: function() {
        var input = $('#id_' + this.prefix + '-INITIAL_FORMS');
        return { input: input, value: Number(input.val()) };
    },

    // External-facing utility to return the number of initial forms in the formset
    initial_form_count: function() {
        var self = this;
        return self._initial().value;
    },

    // Helper function to get the total number of forms currently in this formset
    _total: function() {
        var input = $('#id_' + this.prefix + '-TOTAL_FORMS');
        return { input: input, value: Number(input.val()) };
    },

    // External-facing utility to return the total number of all forms in the formset
    total_form_count: function() {
        var self = this;
        return self._total().value;
    }
});

$.extend(tippr.uploader.prototype, {
    init: function(parameters) {
        var self = this;

        // Class name to be applied to the file_name_field
        //  when no file is currently selected
        var empty_class = 'empty';

        // Create plupload uploader object
        self.uploader = new plupload.Uploader(parameters);

        self.uploader.bind('FilesAdded', function(up, files) {
            // Called when a file gets added to the upload list
            self.file_name_field.removeClass(empty_class).html(files[0].name);
            up.refresh();
        });

        self.uploader.bind('UploadFile', function(up, file) {
            // Called when the uploader begins uploading a file
            self.indicator = $('<img class="spinner" src="http://d1bseu12av3ou0.cloudfront.net/a1952750e46f08d16f53de1123385bb4c6957c62.gif">');
            self.browse_button.append(self.indicator);
        });

        self.uploader.bind('FileUploaded', function(up, file, response) {
            // Called after the uploader finishes uploading a file
            self.indicator.detach();
            self.indicator = null;
            self.browse_button.html(self.browse_text);
            self.file_name_field.addClass(empty_class);
            response = $.parseJSON(response.response);
            if (response.error) {
                self.displayError(response.error);
            } else {
                self.onupload(file, response);
            }
        });

        // Initialize the plupload object
        self.uploader.init();

        // Start the upload process when the user clicks the upload button
        $(self.upload_button).click(function(event) {
            self.uploader.start();
            event.preventDefault();
        });
    },

    /* Display an error in the DOM
     *
     * Depends on the DOM having a div with class "file-name-field" which is a
     *   child of a div with class "field". That div must have a child div of
     *   class ".field-error" which will be populated with the server's error
     *   response and show.
     *
     * Note that the portal support libraries in layout.html build this DOM
     *   structure for you.
     *
     * See presentations/portal/templates/offers/edit-offer.html for an example.
     */
    displayError: function(error) {
        var div = self.file_name_field.parents('.field').find('.field-error');
        if (error.length > 0) {
            div.html(error).show();
        } else {
            div.html('').hide();
        }
    }
});

// automatically append CSRF token from cookie into ajax request headers
// shamelessly copied from Django docs
$(document).ajaxSend(function(event, xhr, settings) {
    function sameOrigin(url) {
        // indicates if given url is the same origin as the current document
        // url could be relative or scheme relative or absolute
        var host = document.location.host; // host + port
        var protocol = document.location.protocol;
        var sr_origin = '//' + host;
        var origin = protocol + sr_origin;
        // Allow absolute or scheme relative URLs to same origin
        return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
            (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
            // or any other URL that isn't scheme relative or absolute i.e relative.
            !(/^(\/\/|http:|https:).*/.test(url));
    }
    function safeMethod(method) {
        // indicates if method may be run safely without side affects
        // (ie, no POST, PUT, DELETE, etc request types)
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
    }
    if (! safeMethod(settings.type) && sameOrigin(settings.url)) {
        xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
    }
});

$.each( [ 'create-account', 'signin', 'facebook-login', 'facebook-modal-login', 'already-member' ], function( i, target ) {
    $.initialize( '#' + target + '-link', function() {
        this.click( function( event ) {
            
            if ( typeof( request_is_http_only ) != 'undefined' && request_is_http_only ) {
                return true;
            }
            tippr.require_ssl(
                function() {
                    tippr.load_modal( { show: true, url: $('link[rel=start]')[0].href + 'r/modal/' + target + '/' } );
                },
                function() {
                    tippr.flag( 'request-modal', target );
                }
            );
            
            return false;
        } );
    } );
} );

function close_channel_selector( event ) {
    var target = $( event.target );
    if ( target.parents( '#channel-selector' ).length === 0 ) {
        $( '#channel-selector' ).removeClass( 'enabled' );
        $( 'body' ).unbind( 'click', close_channel_selector );
        $( '#channel-selector-dropdown' )[tippr.hide_method]("fast");
    }
};

$.attempt('#channel-selector h2 a, #change-channel', tippr.bind_to_channel_selector);

$.initialize( '.twitter-stream', function() {
    try {
        getTwitters('tweet', {
            id: 'tippr',
            count: 1,
            enableLinks: true,
            ignoreReplies: true,
            clearContents: true,
            template: '"%text%"'
        });
    } catch(error) {}
} );

$.initialize( '.login-required', function() {
    this.each( function( i, element ) {
        element = $( element );
        element.click( function( event ) {
            tippr.load_modal( { show: true, url: $('link[rel=start]')[0].href + 'r/modal/signin/', onload: function( modal ) {
                modal.find( 'input[name=redirect]' ).attr( 'value', element.attr( 'href' ) );
            } } );
            return false;
        } );
    } );
} );

$.initialize( '.toggle-block', function() {
    this.each( function( i, element ) {
        element = $( element );
        element.click( function( event ) {
            if ( element.is( '.active' ) ) {
                element.removeClass( 'active' );
                element.parents().first().find('.togglable-block').addClass('hidden');
            } else {
                element.addClass( 'active' );
                element.parents().first().find('.togglable-block').removeClass('hidden');
            }
        } );
    } );
} );

$.initialize( '.styled-select', function() {
    this.each( function( i, element ) {
        var select = $( element ).find( 'select' ), span = $( '<span/>' );
        select.focus( function() {
            $( this ).parent().addClass( 'focused' );
        } );
        select.blur( function() {
            $( this ).parent().removeClass( 'focused' );
        } );
        select.bind( 'change keyup', function() {
            span.text( $( this ).find( 'option:selected' ).text() );
        } );
        select.before( span ).change();
    } );
} );

$.initialize( '.custom-select-box', function() {
    this.each( function( i, element ) {
        var select = $( element ).find( 'select' ), span = $( '<span/>' );
        select.focus( function() {
            $( this ).parent().addClass( 'focused' );
        } );
        select.blur( function() {
            $( this ).parent().removeClass( 'focused' );
        } );
        select.bind( 'change keyup', function() {
            span.text( $( this ).find( 'option:selected' ).text() );
        } );
        select.before( span ).change();
    } );
} );

$.initialize( '.field-with-inline-label label', function() {
    this.each( function( i, element ) {
        $( element ).inFieldLabels();
    } );
} );

$( '.flash-message-close' ).live( 'click', function() {
    $( this ).parent().remove();
    return false;
} );

$( '.offer-snapshot' ).live( 'click', function() {
    window.location = $( this ).find( 'a' ).first().attr( 'href' );
} );

$( '.snapshot-content' ).live( 'click', function() {
    window.location = $( this ).find( 'a' ).first().attr( 'href' );
} );
$('.offer-exhibit').live('click', function() {
    window.location = $(this).find('a').first().attr('href');
});

$('a.change-language').live('click', function () {
    return tippr.change_language( $(this).attr('data-lang') );
});

$('select.change-language').live('change', function () {
    return tippr.change_language( $(this).value );
});




$( function() {
    if (! $.mobile) {
        var background_container = document.createElement("div");
        background_container.id = 'page-container';
        $(background_container).html( '<img src="http://d1bseu12av3ou0.cloudfront.net/0e15d84f49ac53508be0ffd92982c228e6c21124.jpeg" alt="Background">' );
        document.body.appendChild(background_container);
        $(background_container).ezBgResize();
    }
} );

