var BIT = BIT || {};

BIT.api_base_url = "http://api.bandsintown.com";
BIT.base_url = "http://www.bandsintown.com";
BIT.widgets = BIT.widgets || [];
BIT.rsvp_dialog_enabled = true;

BIT.Utils = {
  
  RGB_REGEX : /\(\s*(\d+)\W+(\d+)\W+(\d+)\s*\)/, // 'rgb(10,20,30)'
  HEX_REGEX : /[A-F0-9]+/i,                      // '#ffffff', '#000', 'ffffff', '000'
  
  get_computed_style : function(element, attribute) {
    var computed_style;
    /* IE uses currentStyle, other browsers should use document.defaultView */
    if (typeof element.currentStyle != 'undefined' && typeof window.opera == 'undefined') {
      computed_style = element.currentStyle; 
    } else { 
      computed_style = document.defaultView.getComputedStyle(element, null); 
    }
    var value = computed_style[attribute];
    /* IE returns height 'auto' in currentStyle, but clientHeight works pretty well. */
    if (attribute == "height" && value == "auto") { value = element.offsetHeight; }
    return value;
  },
  
  rgb_from_hex_color : function( color_string ) {
    var hex = color_string.match(this.HEX_REGEX)[0];
    if (hex.length == 6) {
      return [
        (parseInt(hex.substr(0,2), 16) / 255),
        (parseInt(hex.substr(2,2), 16) / 255),
        (parseInt(hex.substr(4,2), 16) / 255),
      ];
    } else { // 3-char notation
      return [  
        (parseInt(hex[0] + hex[0], 16) / 255),
        (parseInt(hex[1] + hex[1], 16) / 255),
        (parseInt(hex[2] + hex[2], 16) / 255),
      ];
    }
  },
  
  rgb_from_rgb_color : function ( color_string ) {
    var colors = color_string.match(/\((\d+)\W+(\d+)\W+(\d+)\)/);
    return [
      colors[1] / 255,
      colors[2] / 255,
      colors[3] / 255    
    ];
  },
  
  parse_rgb : function( color_string ) {
    var rgb = [0, 0, 0];
    if ( color_string.match(this.RGB_REGEX) ) {
      rgb = this.rgb_from_rgb_color(color_string);
    } else if ( color_string.match(this.HEX_REGEX)) {
      rgb = this.rgb_from_hex_color(color_string);
    } 
    return {
      red   : rgb[0],
      green : rgb[1],
      blue  : rgb[2]
    };
  },
  
  image_color_for_background_color: function( color_string ) {
    var rgb = this.parse_rgb(color_string);
    var computed_value = ( 0.213 * rgb.red ) + ( 0.715 * rgb.green ) + ( 0.072 * rgb.blue );
    return computed_value < 0.5 ? 'white' : 'black';
  },

  // similar to Element.up from prototype
  get_parent : function(element, tag_name) {
    var found_element = null;
    var current_element = element;
    while (found_element == null && current_element.tagName.toLowerCase() != 'body') {
      current_element = current_element.parentNode;
      if (current_element && current_element.tagName.toLowerCase() == tag_name.toLowerCase()) { found_element = current_element; }
    }
    return found_element;
  },

  /* very simple element search.
   *   selector should be passed as "<class>", ".<class>", or "<tag>.<class>".  Single classname only.
   *   opt_element should be a DOM element if passed, otherwise search the whole document
   */
  get_elements_by_classname : function(selector, opt_element) {
    var selector_array = selector.split(".");
    var tag_name, class_name;

    // selector given as "classname"
    if (selector_array.length == 1) {
      tag_name = "*";
      class_name = new RegExp(selector_array[0]);
      
    // selector given as ".classname" or "div.classname"
    } else {
      class_name = new RegExp(selector_array[1])
      tag_name = selector_array[0] == "" ? "*" : selector_array[0];
    }
    
    // if opt_element was not passed, search the whole document
    var scope = opt_element ? opt_element : document;
    
    var i;
    var matches = [];
    
    var tags = scope.getElementsByTagName(tag_name);
    for (i=0; i<tags.length; i++) {
      var current_tag = tags[i];
      if (current_tag.className && class_name.exec(current_tag.className)) {
        matches.push(current_tag);
      }
    }
    
    return matches;
  },

  get_outer_height: function(element) {
    var outer_height = 0;
    var height_attributes = ["borderTop", "paddingTop", "height", "paddingBottom", "borderBottom"];
    for (var i=0; i<height_attributes.length; i++) {
      var attribute = height_attributes[i];
      var value = parseFloat(BIT.Utils.get_computed_style(element, attribute));
      if (isNaN(value)) { value = 0; }
      outer_height += value;
    }
    return outer_height;
  },

  get_scroll_top: function() {
    if (window.pageYOffset) {
      return window.pageYOffset;
    } else {
      return (document.documentElement || document.body).scrollTop;
    }
  },

  // taken from prototypes String.escapeHTML()
  escape_html: function(text) {
    return text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },

  // parseUri 1.2.2
  // (c) Steven Levithan <stevenlevithan.com>
  // MIT License
  parse_uri : function (str) {
    var  o   = this.parse_uri_options,
      m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
      uri = {},
      i   = 14;

    while (i--) uri[o.key[i]] = m[i] || "";

    uri[o.q.name] = {};
    uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
      if ($1) uri[o.q.name][$1] = $2;
    });

    return uri;
  },

  parse_uri_options : {
    strictMode: false,
    key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
    q:   {
      name:   "queryKey",
      parser: /(?:^|&)([^&=]*)=?([^&]*)/g
    },
    parser: {
      strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
      loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
    }
  },

  Text : {
    default_truncate_options : { 
      length : 100,
      omission : "...",
      max_lines : 3
    },
    
    // used to replace "test 123" => "test"
    last_word_with_preceding_whitespace_regexp : /\s*\S+$/,
    
    // used to replace "  test 123  " => "test 123"
    preceding_and_trailing_whitespace_regexp   : /^\s*|\s*$/g,
    
    truncate : function(text_to_truncate, options) {
      options       = options          || {};
      var length    = options.length   || BIT.Utils.Text.default_truncate_options.length;
      var omission  = typeof(options.omission) == "string" ? options.omission : BIT.Utils.Text.default_truncate_options.omission;
      var max_lines = options.max_lines || BIT.Utils.Text.default_truncate_options.max_lines;

      // used to replace "test\ntest\ntest" => "test\ntest", for max_lines number of linebreaks
      var max_lines_regexp = new RegExp("(?:.*\?\\n){" + max_lines + "}");

      // remove preceding + trailing whitespace
      var text = BIT.Utils.Text.strip(text_to_truncate);
      
      // if the text is already under the specified length and contains fewer line breaks than max_lines, return it
      if (text.length < length && !text.match(max_lines_regexp)) { return text; }
      
      // remove any text after max_lines number of line breaks, including trailing whitespace
      if (text.match(max_lines_regexp)) { text = BIT.Utils.Text.strip(text.match(max_lines_regexp)[0]); }
      
      // remove the last word and preceding whitespace from text until it is under the specified length
      while (text.length > length) {
        text = text.replace(BIT.Utils.Text.last_word_with_preceding_whitespace_regexp, "")
      }
      
      // return the truncated text with the omission
      return text + omission;
    },

    strip : function (text) {
      return text.replace(BIT.Utils.Text.preceding_and_trailing_whitespace_regexp, "").replace(/\r\n/g, "\n");
    },

    escape_quotes: function (text) {
      return text.replace(/'/g, "\\'").replace(/"/g, "\\\"");
    }
  },

  Log: function(message) {
    if (typeof(BIT.Utils.Log.enabled) == "undefined") { BIT.Utils.Log.enabled = false; }
    if (!BIT.Utils.Log.enabled) { return; }
    try { 
      var args = Array.prototype.slice.call(arguments);
      if (args.length > 1) {
        var extras = args.slice(1, args.length);
        console.log(message, extras);
      } else {
        console.log(message);
      }
    } catch(ex) {}
  },

  Support: {

    _support: {},
    
    // detect if position:fixed is supported. based on http://kangax.github.com/cft/
    fixed_position: function() {
      if (typeof(this._support.fixed_position) != 'undefined') { return this._support.fixed_position; }
      this._support.fixed_position = (function () {
        var container = document.body;
        if (document.createElement && container && container.appendChild && container.removeChild) {
          var el = document.createElement("div");
          if (!el.getBoundingClientRect) { return null; }
          el.innerHTML = "x";
          el.style.cssText = "position:fixed;top:100px;";
          container.appendChild(el);
          var originalHeight = container.style.height, originalScrollTop = container.scrollTop;
          container.style.height = "3000px";
          container.scrollTop = 500;
          var elementTop = el.getBoundingClientRect().top;
          container.style.height = originalHeight;
          var isSupported;
          var exactValue = 100;
          // IE7 supports fixed position but sometimes the computed elementTop is off by a few pixels, so check within a range.
          if (BIT.Utils.Browser.IE7) {
            var range = 5;
            isSupported = elementTop >= exactValue - range && elementTop <= exactValue + range
          } else {
            isSupported = elementTop === exactValue;
          }
          container.removeChild(el);
          container.scrollTop = originalScrollTop;
          return isSupported;
        }
        return null;
      })();
      return this._support.fixed_position;
    },

    // detect if HTML5 postMessage is available -- assume it isn't available for opera
    post_message: function() {
      return (typeof window.postMessage != 'undefined');
    }
  },

  JSONP: {
    /*
     * BIT.Utils.JSONP.getJSON provides basic JSONP support for the widget.
     *
     * params:
     *   url - a String which will be used for the script 'src' attribute
     *   callback - a function which accepts the JSON response as a single argument
     *   callback_obj - optional, used to bind 'this' when executing the callback function
     *
     */
    getJSON: function(params) {
      var callback_name = this.build_callback_name();
      var callback      = params.callback || function() {};
      var callback_obj  = params.callback_obj;
      
      // setup a self-deleting function that calls the real callback function with 'this' bound to params.callback_obj.
      window[callback_name] = function(data) {
        BIT.Utils.Log("Running JSONP Callback window." + callback_name);
        callback.call(callback_obj, data);
        try {
          delete(window[callback_name]); 
        } catch (e) { 
          BIT.Utils.Log("Error deleting JSONP callback function", e); 
        }
      }
      BIT.Utils.Log("JSONP callback function created: ", "window." + callback_name);
      
      // add the function created above to params.url as the 'callback' param.
      var url = params.url;
      var callback_separator = /\?/.test(url) ? "&" : "?";
      url += callback_separator + "callback=" + callback_name;
      BIT.Utils.Log("JSONP url: " + url);
      
      // setup a new script element with the url.
      var script = document.createElement("script");
          script.type = "text/javascript";
          script.src = url;
          script.async = true;

      // insert the script into the head element.
      // head.insertBefore(script, head.firstChild) is used instead of head.appendChild(script) to avoid a bug with IE6.
      // http://bugs.jquery.com/ticket/2709
      var head = document.getElementsByTagName("head")[0] || document.documentElement;
      head.insertBefore(script, head.firstChild);
    },

    build_callback_name: function() {
      var callback_name;
      do {
        callback_name = "bit_jsonp" + (new Date()).getTime();
      } while (typeof(window[callback_name]) != "undefined")
      return callback_name;
    }
  },

  XD: {
    /*
     * BIT.Utils.XD.send allows sending a message to an iframe on the current page that is hosted on http://www.bandsintown.com.
     * This uses postMessage when supported by the browser and falls back to using proxy iframe messaging.
     *
     * params:
     *   message - String or Object which will be converted to a query string (required)
     *   options -
     *     target_origin - The domain of the window we are sending a message to (used for postMessage)
     *     target_window_name - If not given, the message will be sent to window.parent.
     *                          If given, this should be the name attribute of the iframe in the document which will receive the message.
     *                          It will be looked up as window.frames["name"]
     */  
    send: function(message, options) {
      BIT.Utils.Support.post_message() ? BIT.Utils.XD.PostMessage.send(message, options)
                                       : BIT.Utils.XD.LegacyMessage.send(message, options);
    },

    /*
     * BIT.Utils.XD.listen allows listening for cross domain messages sent via postMessage or legacy/proxy iframe messaging.
     */
    listen: function(callback, options) {
      BIT.Utils.Support.post_message() ? BIT.Utils.XD.PostMessage.listen(callback, options) 
                                       : BIT.Utils.XD.LegacyMessage.listen(callback, options);
    },

    // HTML5 postMessage is supported by FF3+, IE8+, Safari, Chrome, Opera 9.5+
    PostMessage: {
      send: function(message, options) {
        options = options || {};
        
        var target_window = BIT.Utils.XD.PostMessage.get_target_window(options.target_window_name, window);
        var target_origin = options.target_origin || "*";
        
        message = BIT.Utils.XD.encode_message(message);
  
        BIT.Utils.Log("Sending postMessage... target_window / message / options", target_window, message, options);
        target_window.postMessage(message, target_origin);
      },

      get_target_window: function(target_window_name, win) {
        return target_window_name ? win.frames[target_window_name] : win.parent;
      },
      
      listen: function(callback, options) {
        options = options || {};
        BIT.Utils.Event.observe("message", window, function(event) {
          var valid_origin = typeof options.origin == "undefined" || event.origin == options.origin;
          if (valid_origin && BIT.Utils.XD.BIT_MESSAGE_REGEX.test(event.data)) {
            var message_data = BIT.Utils.XD.decode_message(event.data);
            BIT.Utils.Log("Received BIT postMessage", event);
            callback(message_data);
          }
        });
      }
    },
    
    // For IE7 and other older browsers that do not support postMessage
    LegacyMessage: {
      FRAGMENT_REGEX: /#.*/,
      LISTEN_INTERVAL: 200,
      
      send: function(message, options) {
        var proxy_window_name = options.target_window_name + "-proxy";

        message = BIT.Utils.XD.encode_message(message, { target_window_name: options.target_window_name });
        
        var existing_proxy_frame = BIT.Utils.XD.Proxy.get_iframe(proxy_window_name);
        var delay = existing_proxy_frame ? 0 : 500;
        var proxy_frame = existing_proxy_frame || BIT.Utils.XD.Proxy.create_iframe(proxy_window_name);
        
        BIT.Utils.Log("Sending legacy message... target / message / options", proxy_frame, message, options);
        proxy_frame.src = proxy_frame.src.replace(BIT.Utils.XD.LegacyMessage.FRAGMENT_REGEX, "") + "#" + message;
        setTimeout(function() { 
          proxy_frame.width = proxy_frame.width == 50 ? 100 : 50;
        }, delay);
      },
      
      listen: function(callback, options) {
        var old_fragment = window.location.hash;
        var current_fragment;
        setInterval(function() {
          current_fragment = window.location.hash;
          if (BIT.Utils.XD.LegacyMessage.message_received(current_fragment, old_fragment)) {
            old_fragment = current_fragment;
            var message_data = BIT.Utils.XD.decode_message(current_fragment);
            BIT.Utils.Log("Received BIT Legacy message", current_fragment, message_data);
            callback(message_data);
          }
        }, BIT.Utils.XD.LegacyMessage.LISTEN_INTERVAL);
      },

      message_received: function(current_fragment, old_fragment) {
        return current_fragment != old_fragment && BIT.Utils.XD.BIT_MESSAGE_REGEX.test(current_fragment);
      }
    },
    
    decode_message: function(message_string) {
      message_string = message_string.replace(/^#/, "");
      var data = {};
      var pairs = message_string.split("&");
      for (var i=0; i<pairs.length; i++) {
        var key_value = pairs[i].split("=");
        data[key_value[0]] = decodeURIComponent(key_value[1]);
      }
      return data;
    },

    encode_message: function(message, extras) {
      if (typeof message == "object") {
        var message_data = [];
        for (key in message) {
          var pair = key + "=" + encodeURIComponent(message[key]);
          message_data.push(pair);
        }
        message = message_data.join("&");
      }
      // ensure the message data string BIT=true so BIT.Utils.XD.listen will not ignore it
      if (!BIT.Utils.XD.BIT_MESSAGE_REGEX.test(message)) { message += "&BIT=true"; }

      if (extras) {
        for (key in extras) {
          message += "&" + key + "=" + encodeURIComponent(extras[key]);
        }
      }

      return message;
    },
    
    BIT_MESSAGE_REGEX: /BIT=true/,

    Proxy: {
      TARGET_WINDOW_NAME_REGEX: /target_window_name=([^&]+)/i,
      
      forward_message: function(event) {
        var target_frame_name = window.location.hash.match(BIT.Utils.XD.Proxy.TARGET_WINDOW_NAME_REGEX)[1];
        if (!target_frame_name) { return };
        var target_window = parent.frames[target_frame_name];
        if (target_window.location.hash != window.location.hash) {
          target_window.location.hash = window.location.hash;      
        }
      },
      
      create_iframe: function(iframe_name) {
        var iframe = document.createElement("iframe");
        iframe.setAttribute('id', iframe_name);
        iframe.setAttribute('name', iframe_name);
        iframe.setAttribute("src", BIT.base_url + "/xd_proxy.html");
        iframe.setAttribute('frameBorder', '1'); // IE needs this otherwise resize event is not fired
        iframe.setAttribute('scrolling', 'auto');
        iframe.setAttribute('width', 50);  // Need a certain size othwerise IE7 does not fire resize event
        iframe.setAttribute('height', 50);
        iframe.setAttribute('style', "position: absolute; left: -200px; top:0px;");
        // IE needs this because setting style attribute is broken. No really.
        if (iframe.style.setAttribute) {
          iframe.style.setAttribute('cssText', "position: absolute; left: -200px; top:0px;");
        }
        document.body.appendChild(iframe);
        return iframe;
      },

      get_iframe: function(iframe_name) {
        return document.getElementById(iframe_name);
      },
      
      start: function() {
        if (window.addEventListener) {
          window.addEventListener('resize', BIT.Utils.XD.Proxy.forward_message, false);
        } else if (document.body.attachEvent) {
          window.attachEvent('onresize', BIT.Utils.XD.Proxy.forward_message);
        }
      }
    }
  },
  
  Event: {
    /*
     * BIT.Utils.Event.observe provides a basic cross-browser event listener. Example:
     *
     *   BIT.Utils.Event.observe("click", document.getElementsByTagName("td"), function(event) {
     *     console.debug("this was clicked:", event.target);
     *   });
     *
     */
    observe: function(event_name, scope, callback, use_capture) {
      BIT.Utils.Log("adding event listener:", event_name, scope, callback, use_capture);

      // convert scope to an array if passed as a single DOM element or as window
      if (!scope.length || scope === window) { scope = [scope]; }

      // optional third argument to attachEvent/addEventListener
      if (typeof use_capture == "undefined") { use_capture = false; }

      // IE uses attachEvent and on<event_name>, other browsers use addEventListener and event_name
      var add_event_function = window.attachEvent ? "attachEvent" : "addEventListener";
      if (add_event_function == "attachEvent") { event_name = "on" + event_name; }

      for (var i=0; i<scope.length; i++) {
        scope[i][add_event_function](event_name, callback, use_capture);
      }
    },

    prevent_default: function(event) {
      if (event.preventDefault) {
        event.preventDefault();
      } else {
        event.returnValue = false;
      }
    }
  }
};

// Basic browser detection based on user agent
BIT.Utils.Browser = (function() {
  var Browser = {};
  // IE detection based on http://msdn.microsoft.com/en-us/library/ms537509%28v=vs.85%29.aspx
  var IE_REGEX = /MSIE ([0-9]{1,}[\.0-9]{0,})/i;
  if (IE_REGEX.test(navigator.userAgent)) {
    Browser.IE = true;
    var ie_version = parseFloat(navigator.userAgent.match(IE_REGEX)[1]);
    Browser.IE6 = ie_version < 7.0;
    Browser.IE7 = !Browser.IE6 && ie_version < 8.0;
  } else {
    Browser.IE = false;
    Browser.IE6 = false;
    Browser.IE7 = false;
  }
  // Opera detection
  Browser.Opera = !Browser.IE && typeof window.opera != "undefined";
  return Browser;
})();

BIT.FB = BIT.FB || {
  
  validate_access_token: function(access_token, callback) {
    FB.api("/me?access_token=" + access_token, function(response) {
      BIT.Utils.Log("validate access token response:", response);
      var valid_status = !response.error;
      callback(valid_status);
    });
  },

  check_permissions: function(permissions, callback) {
    if (FB.getAuthResponse() && FB.getAuthResponse().access_token) {
      var granted = true;
      var query = FB.Data.query("SELECT {0} FROM permissions WHERE uid=me()", permissions);
      query.wait(function(rows) {
        for (permission in rows[0]) {
          if (rows[0][permission] != "1") { granted = false; }
        }
        callback(granted);
      });
    } else {
      callback(false);
    }
  },
  
  rsvp_event: function(facebook_event_id, rsvp_status, callback) {
    BIT.Utils.Log("Submitting RSVP:", facebook_event_id, rsvp_status);
    FB.api("/" + facebook_event_id + "/" + rsvp_status, 'POST', callback);
  },
  
  rsvp_wall_post: function(params) {
    var wall_post = BIT.FB.rsvp_wall_post_data(params);
    FB.api(wall_post, params.callback);
  },

  rsvp_wall_post_data: function(params) {
    var artist_event = params.artist_event;
    var artist = artist_event.artists[0];
    var caption;
    if (params.rsvp_status == "attending") {
      caption = "{*actor*} is going to see " + artist.name + "!";
    } else { 
      caption = "{*actor*} might go see " + artist.name + ".";
    }
    var more_tour_dates_url = BIT.RSVPDialog.widget.share_url;
    var fb_event_url = artist_event.facebook_event_url.replace(/came_from=\d+/, "came_from=" + BIT.came_from_codes.iga_rsvp_wall_post);
    return {
      "message": params.message, 
      "attachment": {
        "href": fb_event_url,
        "name": (artist_event.title + " - " + BIT.Widget.formatted_date(artist_event)),
        "media": [{
          "href": fb_event_url,
          "src": decodeURIComponent(artist.image_url),
          "type": "image"
        }],
        "caption": caption,
        "description": "",
        "properties": {
          "More Tour Dates": { "href": BIT.RSVPDialog.widget.share_url, "text": BIT.RSVPDialog.widget.share_url }
        }
      },
      "action_links": [{
        "href": artist_event.facebook_rsvp_url, 
        "text": "RSVP" 
      }],
      "method": "facebook.stream.publish"
    };
  },

  event_attendees: function(facebook_event_id, callback) {
    var query = FB.Data.query("SELECT uid FROM event_member WHERE rsvp_status='attending' AND eid={0}", facebook_event_id);
    query.wait(callback);
  }
};

BIT.Janrain = BIT.Janrain || {
  
  janrain_int: null,
  session: null,
  UUID_REGEX: /uuid=([a-z\d-]+)/i,

  capture_base_url: null,
  capture_client_id: null,

  token_url: function() {
    var redirect_uri = BIT.base_url + "/janrain_close_window.html";
    var domain = window.location.protocol + "//" + window.location.host;
    return [
      BIT.Janrain.capture_base_url + "/oauth/token_url_1",
      "?response_type=code",
      "&domain=", encodeURIComponent(domain),
      "&bp_channel=", encodeURIComponent(Backplane.getChannelID()),
      "&client_id=", encodeURIComponent(BIT.Janrain.capture_client_id),
      "&redirect_uri=", encodeURIComponent(redirect_uri)
    ].join("");
  },
  
  facebook_permissions_url: function(permissions) {
    return [
      "https://signup.universalmusic.com/facebook/start",
      "?ext_perm=", encodeURIComponent(permissions),
      "&token_url=", encodeURIComponent(BIT.Janrain.token_url())
    ].join("");
  },

  open_permissions_window: function(permissions) {
    this.perms_window = window.open(BIT.Janrain.facebook_permissions_url(permissions), "permissions", "width=580,height=500");

    // hide the RSVP dialog when the permissions window is closed
    var permissions_timer; 
    permissions_timer = setInterval(function() {
      if (BIT.Janrain.perms_window && BIT.Janrain.perms_window.closed) {
        BIT.RSVPDialog.hide();
        // stop checking if the window was closed
        clearInterval(permissions_timer);
      }
    }, 1000);
  },

  request_facebook_permissions: function(permissions, permissions_cb) {
    // open a popup window to approve permissions for the Universal app
    this.open_permissions_window(permissions);

    // notify backplane to begin checking more frequently for login messages
    Backplane.expectMessagesWithin(60, "identity/login");
    
    BIT.Utils.Log("setting Backplane subscribe");
    // wait for a login message from Backplane if the user grants permissions
    BIT.Janrain.bp_login_subscription = Backplane.subscribe(function(message) {
      if (message.type == "identity/login") {
        BIT.Utils.Log("Backplane login message:", message);
        
        // stop listening for Backplane login since it happened
        Backplane.unsubscribe(BIT.Janrain.bp_login_subscription);
        
        // retrieve the janrain uuid from the login message
        var uuid = BIT.Janrain.get_uuid(message.payload.identities, true);
        // check if the user logged into janrain with FB
        BIT.Janrain.get_facebook_credentials(uuid, function(fb_response) {
          BIT.Utils.Log("FB credentials response:", fb_response);
          // logged in with FB, and we have an access token
          if (fb_response.type == "success" && fb_response.data.accessToken) {
            // check if the access token is valid (should be since they just granted permissions)
            FB._session = null;
            BIT.FB.validate_access_token(fb_response.data.accessToken, function(valid_access_token) {
              if (valid_access_token) {
                BIT.Utils.Log("valid access token");
                // initialize the FB javascript SDK with the valid access token
                FB.Auth.setAuthResponse({ accessToken: fb_response.data.accessToken });
                // check if perms were granted and handle the response
                BIT.FB.check_permissions(permissions, permissions_cb);
                
              // invalid access token 
              } else {
                BIT.Utils.Log("invalid access token");
                permissions_cb(false);
              }
            });

          // no access token
          } else {
            BIT.Utils.Log("no access token");
            permissions_cb(false);
          }
        });
        
      }
    });
  },

  get_facebook_credentials: function(janrain_uuid, callback) {
    jQuery.ajax({
      url: BIT.base_url + "/janrain/facebook_credentials/" + janrain_uuid,
      dataType: "jsonp",
      success: function(data) { callback({ "type" : "success", "data": data }); },
      error: function(data) { callback({ "type" : "error", "data": data }); }
    });
  },
  
  get_user_session: function(callback) {
    jQuery.ajax({
      url: "http://api.echoenabled.com/v1/users/whoami?appkey=" + this.echo_api_key + "&sessionID=" + encodeURIComponent(Backplane.getChannelID()),
      dataType: "jsonp",
      success: function(data) { 
        callback({ "type": "success", "data": data }); 
      },
      error: function(xhr, textStatus, errorThrown) { 
        callback({ "type": "error", "data": { "xhr":xhr, "textStatus":textStatus, "errorThrown":errorThrown } }); 
      }
    });
  },
  
  enable_capture: function(capture_links) {
    var self = this;
    this.janrain_int = setInterval(function() {
      if (jQuery && jQuery.fn.enableCapture) {
        try {
          jQuery(capture_links).attr("target", "");
          jQuery(capture_links).enableCapture("signinLink");
          // when the user clicks a link, expect a janrain login message and refresh the page when one is received
          jQuery(capture_links).click(function(event) {
            Backplane.expectMessagesWithin(60, "identity/login");
            Backplane.subscribe(function(message) {
              if (message.type == "identity/login") {
                window.location.reload();
              }
            });
          });
        } catch (ex) {
          BIT.Utils.Log("BIT.Janrain.enable_capture failed:", ex);
        }
        clearInterval(self.janrain_int);
      }
    }, 1000);
  },
  
  // returns the user's janrain uuid for the account they are currently logged into.
  // poco is return data from the 'whoami' echo API call, or the 
  // message.payload.identities object from a Backplane "identity/login" message
  get_uuid: function(poco, use_first_account) {
    if (typeof use_first_account == "undefined") { use_first_account = false; }
    var logged_in_account, uuid;
    var accounts = poco.entry.accounts;
    if (use_first_account) {
      logged_in_account = accounts[0]; 
    } else {
      for (var i=0; i<accounts.length; i++) {
        if (accounts[i].loggedIn.toString() == "true") {
          logged_in_account = accounts[i];
          break;
        }
      }
    }
    if (!logged_in_account) { return; }
    return this.UUID_REGEX.test(logged_in_account.identityUrl) ? this.UUID_REGEX.exec(logged_in_account.identityUrl)[1] : null;
  },
  
  process_rsvp_links: function(rsvp_links) {
    // user is logged into janrain
    if (BIT.Janrain.session) {
      // user is logged into janrain with FB account and valid access token
      if (FB.getAuthResponse()) { 
        BIT.RSVPDialog.enable(rsvp_links); 
      } 
      // else user is logged into janrain with non-FB account or invalid access token - use standalone RSVP
      
    // user is not logged into janrain - enable capture
    } else {
      BIT.Janrain.enable_capture(rsvp_links);
    }
  },

  // callback when a widget is written to HTML. 
  // rsvp_links is an array of DOM elements which contains all RSVP links for a widget.
  widget_rendered_callback: function(widget) {
    jQuery(document).ready(function() {
  
      var rsvp_links = BIT.Utils.get_elements_by_classname("a.bit-rsvp", widget.root_element());

      // wait until Backplane and FB are available
      if (typeof(Backplane)=="undefined" || typeof(FB)=="undefined" ) { 
        setTimeout(function() { BIT.Janrain.widget_rendered_callback(rsvp_links); }, 500);
        
      // FB and Backplane are loaded
      } else {
      
        // check if we already tried to load janrain session data (page with multiple widgets)
        if (BIT.Janrain.session != null) { 
          BIT.Janrain.process_rsvp_links(rsvp_links) 

        // load janrain session if this is the first try
        } else {
          BIT.Utils.Log("checking Janrain login status");
          BIT.Janrain.get_user_session(function(response) {
            if (response.type == "success") {
              // user is logged into janrain
              if (response.data.poco) {
                BIT.Utils.Log("logged into janrain");
              
                // prevent having to check Janrain login status again
                BIT.Janrain.session = true;
          
                // retrieve the janrain uuid
                var uuid = BIT.Janrain.get_uuid(response.data.poco);

                // check if the user logged into janrain with FB
                BIT.Janrain.get_facebook_credentials(uuid, function(fb_response) {
                  BIT.Utils.Log("FB credentials response:", fb_response);
                  // logged in with FB, and we have an access token
                  if (fb_response.type == "success" && fb_response.data.accessToken) {
                    // check if the access token is valid
                    FB._session = null;
                  
                    BIT.FB.validate_access_token(fb_response.data.accessToken, function(valid_access_token) {
                      if (valid_access_token) {
                        BIT.Utils.Log("valid access token");                      
                        // initialize the FB javascript SDK with the valid access token
                        FB.Auth.setAuthResponse({ accessToken: fb_response.data.accessToken });

                        // enable the RSVP dialog
                        BIT.RSVPDialog.enable(rsvp_links);

                      // logged in with FB, but access token is invalid and/or expired.  RSVP links goto standalone
                      } else {
                        BIT.Utils.Log("invalid access token");
                      }
                    });
              
                  // user is logged into janrain with a non-FB account, or no access token could be loaded
                  // rsvp links go to standalone page (default behavior)
                  } else {
                    BIT.Utils.Log("no access token");
                  }
                });
          
              // user is not logged into janrain
              } else {
                BIT.Utils.Log("not logged into Janrain");
                BIT.Janrain.session = false;
                BIT.Janrain.enable_capture(rsvp_links);
              }
      
            // error loading user data from janrain
            } else {
              BIT.Utils.Log("error loading Janrain session");
              BIT.Janrain.session = false;
              BIT.Janrain.enable_capture(rsvp_links);
            }
          });
        }
      }
    });
  },

  rsvp_form_html: "\
    <div class=\"popup-margin\">\
      <form action=\"\" class=\"rsvp-form\" id=\"rsvp-form\" method=\"post\" name=\"rsvp-form\">\
        <div class=\"error-message\" id=\"rsvp-error\" style=\"display: none;\">Something went wrong, please try again.<\/div>\
        <div class=\"message-container\">\
          <div class=\"say-something\">Say something about this event...<\/div>\
          <textarea id=\"rsvp_message\" name=\"rsvp_message\" class=\"message\"><\/textarea>\
          <input id=\"facebook_event_id\" name=\"facebook_event_id\" type=\"hidden\" value=\"\" />\
          <input id=\"rsvp_status\" name=\"rsvp_status\" type=\"hidden\" value=\"\" />\
        <\/div>\
        <div class=\"wall-post\">\
          <span class=\"artist-pic\" id=\"artist-pic\"><\/span>\
          <span class=\"attachment\" id=\"rsvp-preview-container\">\
            <div id=\"rsvp-preview\">\
              <div class=\"title\" id=\"event-title\"><\/div>\
              <div class=\"links\" id=\"message-links\"><\/div>\
            <\/div>\
            <div class=\"event-attendees\" id=\"rsvp-stats\">\
              <div class=\"loading\"></div>\
            </div>\
            <span class=\"buttons\" id=\"rsvp-buttons\">\
              <div class=\"blue-button-outer\"><div class=\"blue-button-inner\"><input type=\"submit\" name=\"rsvp_type\" value=\"I\'m Attending\" class=\"blue-button yes\" /><\/div><\/div>\
              <div class=\"blue-button-outer\"><div class=\"blue-button-inner\"><input type=\"submit\" name=\"rsvp_type\" value=\"No\" class=\"blue-button no\" /><\/div><\/div>\
              <div class=\"blue-button-outer\"><div class=\"blue-button-inner\"><input type=\"submit\" name=\"rsvp_type\" value=\"Maybe\" class=\"blue-button maybe\" /><\/div><\/div>\
              <div class=\"rsvp-loading\" id=\"rsvp-loading\" style=\"display:none;\"><\/div>\
              <div class=\"clear\"><\/div>\
              <div class=\"publish-to-wall\"><input checked=\"checked\" class=\"checkbox\" id=\"wall_post\" name=\"wall_post\" type=\"checkbox\" value=\"true\" /> Publish to wall<\/div>\
            <\/span>\
          <\/span>\
        <\/div>\
      <\/form>\
    <\/div>"
};

BIT.RSVPDialog = {         
  
  dialog_html: "\
    <div class=\"generic_dialog pop_dialog\" style=\"display:none\" id=\"bit-rsvp-dialog-container\">\
      <div class=\"generic_dialog_popup wide-popup\">\
        <div class=\"pop_container_advanced popup-border-outer\" id=\"bit-rsvp-dialog-y-offset\">\
          <div class=\"pop_container_advanced popup-border-inner\" id=\"bit-rsvp-dialog-border\"><\/div>\
          <div class=\"pop_content popup-content\">\
            <h2 class=\"dialog_title\"><span>RSVP<\/span><\/h2>\
            <div class=\"dialog_content\">\
              <div class=\"dialog_body\" id=\"bit-rsvp-dialog-content\">\
                <div id=\"bit-rsvp-dialog-loading\"></div>\
              <\/div>\
              <div class=\"dialog_buttons clearfix\">\
                <div class=\"rsvp-buttons\">\
                  <label class=\"uiButton uiButtonLarge uiButtonDefault\">\
                    <input type=\"button\" class=\"rsvp-cancel\" value=\"Close\" id=\"bit-rsvp-cancel\" />\
                  <\/label>\
                <\/div>\
              <\/div>\
            <\/div>\
          <\/div>\
        <\/div>\
      <\/div>\
    <\/div>",

  css: [
    "#bit-rsvp-dialog-container { position: fixed; width: 100%; height: 100%; margin: auto; }",
    "#bit-rsvp-dialog { color: #333333; direction: ltr; font-family: 'lucida grande',tahoma,verdana,arial,sans-serif; font-size: 11px; text-align: left; }",
    "#bit-rsvp-dialog a { color: #3B5998; cursor: pointer; text-decoration: none; }",
    "#bit-rsvp-dialog a:hover { text-decoration: underline; }",
    "#bit-rsvp-dialog textarea, #bit-rsvp-dialog .inputtext, #bit-rsvp-dialog .inputpassword { border: 1px solid #BDC7D8; font-family: 'lucida grande',tahoma,verdana,arial,sans-serif; font-size: 11px; padding: 3px; -webkit-appearance: none; -webkit-border-radius: 0; }",
    "#bit-rsvp-dialog ul { list-style-type: none; margin: 0; padding: 0; }",
    "#bit-rsvp-dialog label { color: #666666; cursor: pointer; font-weight: bold; vertical-align: middle; }",
    "#bit-rsvp-dialog img { border: 0 none; }",
    "body#bit-rsvp-dialog { margin: 0px; padding: 0px; }",
    "#bit-rsvp-dialog-loading { height: 32px; width: 100%; background: transparent url('http://static.bandsintown.com/images/facebook/ajax-loader-large.gif') no-repeat scroll center center; }",
    "body#bit-rsvp-dialog .wall-post .artist-pic { display: inline-block; text-align: center; width: 90px; overflow: hidden; }",
    "body#bit-rsvp-dialog .wall-post #artist-pic.hidden { visibility: hidden; }",
    "#bit-rsvp-dialog .wall-post .artist-pic img.shrink-tall-image { width: auto; max-height: 150px; }",
    "#bit-rsvp-dialog .wall-post .artist-pic img.shrink-wide-image { max-width: 90px; height: auto; }",
    "#bit-rsvp-dialog .pop_content{direction:ltr}",
    "#bit-rsvp-dialog {height:0;left:0;overflow:visible;outline:none;position:absolute;top:0;width:100%;z-index:250}",
    "#bit-rsvp-dialog .generic_dialog_popup{height:0;overflow:visible;position:relative;width:520px;margin:auto}",
    "#bit-rsvp-dialog .pop_content h2.dialog_title{background:#6d84b4;border:1px solid #3b5998;border-bottom:none;color:#fff;font-size: 14px;font-weight:bold;margin:0;}",
    "#bit-rsvp-dialog .pop_content h2 span{display:block;padding:5px 10px}",
    "#bit-rsvp-dialog .pop_content .dialog_content{background:#fff;border:1px solid #555;border-top-width:0}",
    "#bit-rsvp-dialog .pop_content .dialog_body{padding:10px;border-bottom:1px solid #ccc}",
    "#bit-rsvp-dialog .pop_content .dialog_buttons{background:#f2f2f2;padding:8px 10px 8px 10px;position:relative;text-align:right}",
    "#bit-rsvp-dialog .pop_container_advanced{-moz-border-radius:8px;-webkit-border-radius:8px;padding:10px}",
    "#bit-rsvp-dialog .uiButton,#bit-rsvp-dialog .uiButtonSuppressed:active,#bit-rsvp-dialog .uiButtonSuppressed:focus,#bit-rsvp-dialog .uiButtonSuppressed:hover{background:#eee url('http://static.bandsintown.com/images/facebook/silver-blue-bg.png') repeat 0 0;border:1px solid #999;border-bottom-color:#888;box-shadow:0 1px 0 rgba(0, 0, 0, .1);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, .1);cursor:pointer;display:-moz-inline-box;display:inline-block;font-size:11px;font-weight:bold;line-height:normal !important;padding:2px 6px;text-align:center;text-decoration:none;vertical-align:top;white-space:nowrap}",
    "#bit-rsvp-dialog .uiButtonConfirm{background-color:#5b74a8;background-position:0 -48px;border-color:#29447e #29447e #1a356e}",
    "#bit-rsvp-dialog .uiButton:active,#bit-rsvp-dialog .uiButtonDepressed{background:#ddd;border-bottom-color:#999;box-shadow:0 1px 0 rgba(0, 0, 0, .05);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, .05)}",
    "#bit-rsvp-dialog .uiButton .uiButtonText,#bit-rsvp-dialog .uiButton input{background:none;border:0;color:#333;cursor:pointer;display:-moz-inline-box;display:inline-block;font-family:'Lucida Grande', Tahoma, Verdana, Arial, sans-serif;font-size:11px;font-weight:bold;margin:0;outline:none;padding:1px 0 2px;white-space:nowrap}",
    "#bit-rsvp-dialog .uiButtonLarge,#bit-rsvp-dialog .uiButtonLarge .uiButtonText,#bit-rsvp-dialog .uiButtonLarge input{font-size:13px}",
    "#bit-rsvp-dialog .uiButtonSpecial .uiButtonText,#bit-rsvp-dialog .uiButtonSpecial input,#bit-rsvp-dialog .uiButtonSpecial.uiButtonDisabled .uiButtonText,#bit-rsvp-dialog .uiButtonSpecial.uiButtonDisabled input,#bit-rsvp-dialog .uiButtonConfirm .uiButtonText, #bit-rsvp-dialog .uiButtonConfirm input, #bit-rsvp-dialog .uiButtonConfirm.uiButtonDisabled .uiButtonText, #bit-rsvp-dialog .uiButtonConfirm.uiButtonDisabled input { color: #FFFFFF; }",
    "#bit-rsvp-dialog .uiButtonConfirm:active{background:#4f6aa3;border-bottom-color:#29447e}",
    "#bit-rsvp-dialog .popup-border-outer { z-index: 1; background: none; position: relative; }",
    "#bit-rsvp-dialog .popup-border-inner { background-color: #525252; -moz-opacity: 0.7; opacity: 0.7; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; padding: 0; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70); }",
    "#bit-rsvp-dialog .popup-content { position: relative; z-index: 2; }",
    "#bit-rsvp-dialog h2 { padding: 0px; }",
    "#bit-rsvp-dialog .blue-button-outer { display: block; border-bottom: 1px solid #d9d9d9; float: left; margin-right: 4px; }",
    "#bit-rsvp-dialog .blue-button-inner { border: 1px solid #29447E; border-bottom: 1px solid #1a3563; cursor: pointer; }",
    "#bit-rsvp-dialog .blue-button { background-color: #5c79ac; border-top: 1px solid #8a9cc2; border-right: 1px solid #5c79ac; border-left: 1px solid #5c79ac; border-bottom: 1px solid #5c79ac; color: #ffffff !important; cursor: pointer; font-size:13px; font-weight:bold; padding: 1px 5px 2px 5px; margin: 0px; }",
    "#bit-rsvp-dialog .yes { width: 107px; }",
    "#bit-rsvp-dialog .no { width: 34px; }",
    "#bit-rsvp-dialog .maybe { width: 59px; }",
    "#bit-rsvp-dialog .clear { clear: both; }",
    "#bit-rsvp-dialog .say-something { font-size: 11px; font-weight: bold; color: #808080; margin-bottom: 10px; padding: 0px 10px; }",
    "#bit-rsvp-dialog .message-container { margin-bottom: 15px; }",
    "#bit-rsvp-dialog .publish-to-wall { display: block; margin: 15px 0px 10px 0px; color: #808080; }",
    "#bit-rsvp-dialog .publish-to-wall input.checkbox { margin-left: 0px; }",
    "#bit-rsvp-dialog .error-message { font-size: 12px; font-weight: bold; margin: auto auto 10px; padding: 10px; width: 440px; border: 1px solid #DD3C10; background-color: #FFEBE8; }",
    "#bit-rsvp-dialog .rsvp-loading { background: transparent url('http://static.bandsintown.com/images/facebook/ajax-loader.gif') center left no-repeat scroll; width: 16px; height: 25px; display: inline-block; margin: 0 10px; padding: 0; vertical-align: middle; }",
    "#bit-rsvp-dialog .wall-post { width: 462px; margin: auto; }",
    "#bit-rsvp-dialog .attachment { display: inline-block; padding-left: 16px; vertical-align: top; width: 350px; font-size: 11px;}",
    "#bit-rsvp-dialog .attachment .links { min-height: 30px; }",
    "#bit-rsvp-dialog .attachment .links div { color: #999999; padding-bottom: 3px; }",
    "#bit-rsvp-dialog .attachment .links div a { text-decoration: none; color: #3B5998; }",
    "#bit-rsvp-dialog .attachment .links a.attendance { color: #3B5998; padding: 0; cursor: pointer; text-decoration: none; }",
    "#bit-rsvp-dialog .attachment .links a.attendance:hover { text-decoration: underline; }",
    "#bit-rsvp-dialog .attachment .title { padding-bottom: 3px; font-weight: bold; }",
    "#bit-rsvp-dialog .attachment .title a { text-decoration: none; }",
    "#bit-rsvp-dialog .attachment .text { color: #808080; padding-bottom: 3px; }",
    "#bit-rsvp-dialog textarea { width: 450px; height: 45px; border: 1px solid #BBBBBB; display: block; margin: auto; padding: 5px; resize: none; }",
    "#bit-rsvp-dialog .grey-button-outer { display: block; border-bottom: 1px solid #d9d9d9; float: left; margin-right: 4px; }",
    "#bit-rsvp-dialog .grey-button-inner { border-color: #999999 #999999 #888888; border-width: 1px; border-style: solid; cursor: pointer; background: transparent url('/images/facebook/buttons_bg.png') repeat scroll 0 0; }",
    "#bit-rsvp-dialog input.grey-button { background: transparent; border: 1px solid transparent; color: #333333 !important; cursor: pointer; font-size:13px; font-weight:bold; padding: 1px 5px 2px 5px; margin: 0px; }",
    ".event-attendees { height: 69px; }",
    ".event-attendees .faces { padding: 0px 0px 6px 0px; margin: 0px; height: 33px; }",
    ".event-attendees .hidden { display: block; visibility: hidden; }",
    ".event-attendees .faces a { padding: 0px; margin: 0px; float: left; }",
    ".event-attendees .faces img { padding: 1px 1px 0px 0px; margin: 0px; }",
    ".event-attendees .rsvp-count { padding-bottom: 16px; color: #999999; }",
    ".event-attendees .loading { background: transparent url('http://static.bandsintown.com/images/facebook/ajax-loader.gif') no-repeat scroll center left; height: 72px; padding: 0px; margin: 0px; }",
    "* html #bit-rsvp-dialog .popup-border-inner  { display: none; }",
    "* html #rsvp-dialog-container { position: absolute; }"
  ].join(""),

  iframe_height: "275px",
  iframe_width: "478px",
  
  show_event: function(artist_event) {
    if (!BIT.RSVPDialog.janrain_init_done) { BIT.RSVPDialog.janrain_init(); }
    BIT.RSVPDialog.artist_event = artist_event;
    
    var title = artist_event.title + " - " + BIT.Widget.formatted_date(artist_event);
    var tour_dates_url = BIT.RSVPDialog.widget.share_url;
    var image_url = artist_event.artists[0].image_url.replace("http://www.bandsintown.com", BIT.base_url);
    
    this.event_title.html("<a href=\"" + artist_event.facebook_event_url + "\" target=\"_blank\">" + BIT.Utils.Text.truncate(title, { length: 75 }) + "</a>");
    this.artist_pic.html("<img src=\"" + image_url + "\" width=\"90px\" />");
    this.message_links.html("<div class=\"link\">More Tour Dates: <a href=\"" + tour_dates_url + "\" target=\"blank\">" + BIT.Utils.parse_uri(tour_dates_url).host + "</a></div>");
      
    this.rsvp_status.val("");
    this.rsvp_message.val("");
    this.facebook_event_id.val(artist_event.facebook_event_id);
    
    this.load_rsvp_stats(artist_event.facebook_event_id);
    this.error_message.hide();
    this.show();
  },

  show: function() {
    this.container.style.display = 'block';
    this.center();
  },

  hide: function() {
    if (!this.janrain_init_done) {
      this.hide_iframes();
      this.container.style.display = 'none';
    } else {
      $(this.container).hide();
      this.loading_image.hide();
      this.error_message.hide();
      this.rsvp_message.val("");
    }
  },
  
  rsvp: function(params) {
    var facebook_event_id = params.facebook_event_id;
    var rsvp_status = params.status;
    var wall_post = params.wall_post;
    var message = params.message;
    
    BIT.FB.rsvp_event(facebook_event_id, rsvp_status, function(rsvp_success) {
      if (rsvp_success) {
        BIT.Utils.Log("RSVP succeeded:", facebook_event_id, rsvp_status);
        if (wall_post) {
          BIT.FB.rsvp_wall_post({ 
            message: message, 
            rsvp_status: rsvp_status, 
            artist_event: BIT.RSVPDialog.artist_event,
            callback: function(response) { BIT.RSVPDialog.hide(); }
          });
        } else {
          BIT.RSVPDialog.hide();
        }
        
      // error handling for failed RSVP
      } else {
        BIT.Utils.Log("RSVP failed:", facebook_event_id, rsvp_status);
        BIT.RSVPDialog.error_message.show();
        BIT.RSVPDialog.loading_image.hide();
      }
    });
  },

  enable: function(rsvp_links) {
    $(rsvp_links).click(function(event) {
      if (!BIT.RSVPDialog.janrain_init_done) { BIT.RSVPDialog.janrain_init(); }

      var widget;
      var rsvp_link = $(event.target);
      var timestamp = /-(\d+)/.exec(rsvp_link.closest("table").attr("name"))[1];
      var event_id  = /\/event\/(\d+)/.exec(rsvp_link.attr("href"))[1];

      for (var i=0; i<BIT.widgets.length; i++) {
        widget = BIT.widgets[i];
        if (BIT.widgets[i].timestamp == timestamp) { break; }
      }

      for (var i=0; i<widget.artist_events.length; i++) {
        var artist_event = widget.artist_events[i];
        if (artist_event.id == event_id) {
          // show RSVP dialog if FB event exists
          if (artist_event.facebook_event_id) {
            event.preventDefault();
            event.stopPropagation();

            BIT.RSVPDialog.widget = widget;
            BIT.RSVPDialog.show_event(widget.artist_events[i]);
            break;
          }
          // else fall back to standalone RSVP if no FB event id
        }
      }
    });
  },

  insert_content: function() {
    if (BIT.RSVPDialog.content_added) { return; }
    var rsvp_dialog = document.createElement("div");
        rsvp_dialog.setAttribute("id", "bit-rsvp-dialog");
        rsvp_dialog.innerHTML = this.dialog_html;      
    document.getElementsByTagName("body")[0].appendChild(rsvp_dialog);
    BIT.RSVPDialog.content_added = true;
  },

  hide_iframes: function() {
    var iframes = this.container.getElementsByTagName("iframe");
    for (var i=0; i<iframes.length; i++) { 
      var iframe = iframes[i];
      iframe.onload = iframe.onreadystatechange = function() { 
        BIT.RSVPDialog.loaded_iframes[iframe.getAttribute("id")] = true; 
      }
      iframe.style.display = 'none';
    }
    BIT.RSVPDialog.dialog_loading.style.display = '';
  },

  show_iframe_and_event: function(iframe, bit_event_id) {
    if (this.loaded_iframes[iframe.getAttribute("id")]) {
      iframe.style.display = 'block';
      BIT.RSVPDialog.dialog_loading.style.display = 'none';
      this.show();
      var message = { "event_id": bit_event_id, "action": "show_event" };
      BIT.Utils.XD.send(message, { target_window_name: iframe.name });
    } else {
      this.show();
      this.show_iframe_and_event_on_iframe_load(iframe, bit_event_id);
    }
  },

  show_iframe_and_event_on_iframe_load: function(iframe, bit_event_id) {
    iframe.onload = iframe.onreadystatechange = function() {
      BIT.RSVPDialog.loaded_iframes[iframe.getAttribute("id")] = true;
      BIT.RSVPDialog.show_iframe_and_event(iframe, bit_event_id);
    }
  },

  find_iframe_for_widget: function(widget_timestamp) {
    var iframes = BIT.RSVPDialog.container.getElementsByTagName("iframe");
    for (var i=0; i<iframes.length; i++) {
      if (iframes[i].getAttribute("id").match(widget_timestamp)) {
        return iframes[i];
      }
    }
  },

  load_rsvp_stats: function(facebook_event_id) {
    var rsvp_stats_container = BIT.RSVPDialog.rsvp_stats_container;
    if (BIT.RSVPDialog.rsvp_stats[facebook_event_id] !== undefined) {
      rsvp_stats_container.html(BIT.RSVPDialog.rsvp_stats[facebook_event_id]);
      rsvp_stats_container.css("visibility", "visible");
      FB.XFBML.parse(document.getElementById(rsvp_stats_container.attr("id")), function() { BIT.RSVPDialog.center(); });
    } else {
      rsvp_stats_container.html("<div class=\"loading\"></div>");
      rsvp_stats_container.css("visibility", "visible");
      BIT.FB.event_attendees(facebook_event_id, function(fql_rows) {
        var rsvp_count = fql_rows.length;
        if (rsvp_count > 0) {
          var rsvp_count_text = "";
          if (rsvp_count == 1) {
            rsvp_count_text = "<div class=\"rsvp-count\">1 person going</div>";
          } else if (rsvp_count <= 998) {
            rsvp_count_text = "<div class=\"rsvp-count\">" + rsvp_count + " people going</div>";
          } else {
            rsvp_count_text = "<div class=\"rsvp-count\">1000+ people going</div>";
          }
          var fb_profile_pics = "";
          var max_pics = Math.min(10, rsvp_count);
          for (i=0; i<max_pics; i++) {
            fb_profile_pics += "<a target=\"_blank\" href=\"http://www.facebook.com/profile.php?id=" + fql_rows[i].uid + "\"><img src=\"http://graph.facebook.com/" + fql_rows[i].uid + "/picture?type=square\" height=\"32\" width=\"32\" /></a>";
          }
          rsvp_content = "<div class=\"faces\">" + fb_profile_pics + "</div>" + rsvp_count_text;
          BIT.RSVPDialog.rsvp_stats[facebook_event_id] = rsvp_content;
          rsvp_stats_container.html(rsvp_content);
          FB.XFBML.parse(document.getElementById(rsvp_stats_container.attr("id")), function() { BIT.RSVPDialog.center(); });
        } else {
          rsvp_stats_container.css("visibility", "hidden");
        }
      });
    }
  },

  // based on http://david-tang.net/plugins/fixedCenter/fixedCenter.html
  center: function() {
    if (BIT.RSVPDialog.container.style.display == 'none') { return; }

    // safari positioning fix (dialog appears at bottom of screen)
    // this.outer_container.style.position = "relative";
    // setTimeout(function() { BIT.RSVPDialog.outer_container.style.position = "absolute"; }, 13);

    BIT.RSVPDialog.outer_container.style.position = "absolute";

    var element = BIT.RSVPDialog.y_offset;

    var elementHeight, windowHeight, Y2;
    elementHeight = BIT.Utils.get_outer_height(element);
    
    windowHeight = document.documentElement ? document.documentElement.clientHeight : window.innerHeight;
    
    Y2 = (windowHeight/2 - elementHeight/2);
    if (!BIT.Utils.Support.fixed_position()) { Y2 += BIT.Utils.get_scroll_top(); }
    
    element.style.top = Y2 + "px"; 
  },

  janrain_init: function() {
    if (this.janrain_init_done) { return; }

    // call base .init() if not already done
    if (!this.init_done) { this.init(); }
  
    // add RSVP form html
    this.content.innerHTML = BIT.Janrain.rsvp_form_html;
  
    // setup scope for dialog elements
    this.root = jQuery("#bit-rsvp-dialog");
    
    // error message, loading image
    this.loading_image = jQuery("#rsvp-loading", this.root);
    this.error_message = jQuery("#rsvp-error", this.root);

    // form inputs
    this.form              = jQuery("#rsvp-form", this.root);
    this.rsvp_message      = jQuery("#rsvp_message", this.form);
    this.rsvp_status       = jQuery("#rsvp_status", this.form);
    this.facebook_event_id = jQuery("#facebook_event_id", this.form);
    this.wall_post         = jQuery("#wall_post", this.form);

    // rsvp wall post preview
    this.artist_pic    = jQuery("#artist-pic", this.root);
    this.event_title   = jQuery("#event-title", this.root);
    this.message_links = jQuery("#message-links", this.root);

    // rsvp faces
    this.rsvp_stats = {};
    this.rsvp_stats_container = jQuery("#rsvp-stats", this.root);
    
    // prevent form submission
    this.form.submit(function(event) {
      event.preventDefault();
      event.stopPropagation();
    });
    
    // click handler for RSVP buttons
    jQuery("input[name=rsvp_type]", this.form).click(function(event) {
      BIT.RSVPDialog.loading_image.show();
      BIT.RSVPDialog.error_message.hide();

      BIT.Utils.Log("clearing Backplane subscribe");
      Backplane.unsubscribe(BIT.Janrain.bp_login_subscription);

      var status;
      var button = jQuery(event.target).val();
      if (button == "I'm Attending") { status = "attending"; }
      if (button == "No")            { status = "declined";  }
      if (button == "Maybe")         { status = "maybe";     }

      var wall_post = BIT.RSVPDialog.wall_post.attr("checked");
      var permissions = wall_post ? "rsvp_event,offline_access,publish_stream" : "rsvp_event,offline_access";
      
      var rsvp_params = {
        "facebook_event_id": BIT.RSVPDialog.facebook_event_id.val(),
        "status": status,
        "wall_post": wall_post,
        "message": BIT.RSVPDialog.rsvp_message.val()
      };
      
      BIT.FB.check_permissions(permissions, function(granted) {
        // all required permissions are granted, do the RSVP
        if (granted) {
          BIT.RSVPDialog.rsvp(rsvp_params);

        // RSVP links open the perms request window for Universal if not all permissions are granted
        } else {
          BIT.Janrain.request_facebook_permissions(permissions, function(granted) {
            if (granted) { BIT.RSVPDialog.rsvp(rsvp_params); }
          });
        }
      });
    });

    this.janrain_init_done = true;
  },

  init: function() {
    if (this.init_done) { return; }
    this.insert_content();
    this.loaded_iframes = {};
    
    // visibility and positioning elements
    this.outer_container = document.getElementById("bit-rsvp-dialog");
    this.container = document.getElementById("bit-rsvp-dialog-container");
    this.border    = document.getElementById("bit-rsvp-dialog-border");
    this.y_offset  = document.getElementById("bit-rsvp-dialog-y-offset");
    this.content   = document.getElementById("bit-rsvp-dialog-content");
    this.dialog_loading = document.getElementById("bit-rsvp-dialog-loading");

    // center the dialog on window resize
    var resize_target = document.onresize ? document : window;
    BIT.Utils.Event.observe("resize", resize_target, function(event) { BIT.RSVPDialog.center() });  

    // cancel button (closes the dialog)
    this.cancel = document.getElementById("bit-rsvp-cancel");
    BIT.Utils.Event.observe("click", this.cancel, function(event) {
      BIT.Utils.Event.prevent_default(event);
      BIT.RSVPDialog.hide();
    });

    // adjust iframe height and recenter after event data loads
    BIT.Utils.XD.listen(function(data) {
      if (data.action != "update_iframe_height") { return; }
      document.getElementById(data.iframe_id).style.height = data.height + "px";
      BIT.RSVPDialog.center();
    });

    // close RSVP dialog after successful RSVP
    BIT.Utils.XD.listen(function(data) {
      if (data.action != "close_rsvp_dialog") { return; }
      BIT.RSVPDialog.hide();
    });
    
    // fix popup border issue in IE7
    this.border.style.display = 'block';

    this.init_done = true;
  }
};

BIT.Widget = function(options) {
  var self = this;

  this.unique_id = function(id_base) {
    return [id_base, this.timestamp].join("-");
  }

  this.artist            = options.artist;
  this.text_color        = options.text_color;
  this.link_color        = options.link_color;
  this.bg_color          = options.bg_color;
  this.separator_color   = options.separator_color   || '#E9E9E9';
  this.width             = options.width             || '100%';
  this.display_limit     = options.display_limit     || Number.MAX_VALUE;
  this.rsvp_links        = options.rsvp_links        != false;
  this.notify_me         = options.notify_me         != false;
  this.share_links       = options.share_links       != false;
  this.share_url         = options.share_url         || window.location.href;
  this.facebook_comments = options.facebook_comments != false;
  this.myspace_layout    = options.myspace_layout    || false;
  this.div_id            = options.div_id;
  this.rsvp_dialog       = options.rsvp_dialog       || false; 
  this.facebook_page_id  = String(options.facebook_page_id || '');

  this.source = options.source;
  if (typeof(this.source) == 'string' && this.source.toLowerCase() == 'iga' && options.janrain) {
    this.use_janrain_rendered_callback = true; 
    BIT.Janrain.echo_api_key           = options.janrain.echo_api_key;
    BIT.Janrain.capture_client_id      = options.janrain.capture_client_id;
    BIT.Janrain.capture_base_url       = options.janrain.capture_base_url;
  }
  
  this.prefix            = options.prefix                          || 'js';
  this.affil_code        = this.prefix + "_" + (options.affil_code || window.location.host);
  this.app_id            = this.prefix + "_" + (options.app_id     || window.location.host);
  
  this.timestamp         = new Date().getTime();
  this.unique_classname  = this.unique_id('bit');
  this.loader_div_id     = this.unique_id('bit-widget-loader');

  this.bandsintown_footer_link = options.bandsintown_footer_link != false;
  this.force_narrow_layout     = options.force_narrow_layout || false;
  
  this.local_events_loaded        = false;
  this.artist_events_loaded       = false;
  this.event_descriptions_hash    = {};
  this.rsvp_links_hash            = {};
  this.facebook_comments_xid_hash = {};
  this.ticket_types_hash          = {};
  this.pending_writes = [];    
  this.write_pending  = false;

  /*
   * The insert_events() function renders the widget:
   *
   *   1. A loading image is inserted.
   *   2. Bandsintown API requests are made with JSONP callbacks (1 request for local upcoming events, 1 for all upcoming events)
   *   3. When both requests have finished, the loading image is replaced with the event data.
   */
  this.insert_events = function() {
    if (this.artist) {
      this.write(this.loading_image());
      
      BIT.Utils.Log("Setting up local events JSONP");
      // load local events
      BIT.Utils.JSONP.getJSON({ 
        url: this.local_events_url(), 
        callback: this.local_events_callback, 
        callback_obj: this
      });

      BIT.Utils.Log("Setting up artist events JSONP");
      // load artist events
      BIT.Utils.JSONP.getJSON({ 
        url: this.artist_events_url(), 
        callback: this.artist_events_callback, 
        callback_obj: this
      });
    } else {
      this.write(this.no_artist_given());
    }
  }

  this.write = function(html, retry) {
    if (typeof(retry) == "undefined") { retry = false; }
    // If the widget was created without specifying a div_id, assume 
    // it should be written to the page in place using document.write.
    // Assign this.div_id to a unique id and create a div which will contain the widget.
    if (!this.div_id) {
      this.div_id = this.unique_id("widget-container");
      document.write("<div id=\"" + this.div_id + "\"></div>");
    }
  
    // If the div where the widget should be rendered exists and no content is pending
    if (document.getElementById(this.div_id) && !this.write_pending) {
      document.getElementById(this.div_id).innerHTML = html;

    // the widget div does not exist in the document yet, or content already has been queued
    } else {

      // if this is not a retry, html is new content and should be added to the pending_writes queue
      if (!retry) { this.pending_writes.push(html); }

      // if this is the first call to this.write, setup retries
      if (!this.write_pending) {
        this.write_pending = true;
        setTimeout(function() { self.retry_write(); }, 13);
      }
    }
  }

  // used when widget content loads before the div containing the widget is ready
  this.retry_write = function() {
    
    // div is ready, write the most recently added pending content
    if (document.getElementById(this.div_id)) {
      this.write_pending = false;
      this.write(this.pending_writes[this.pending_writes.length - 1], true);

    // div is still not ready, retry
    } else { 
      setTimeout(function() { self.retry_write(); }, 13);
    }
  }

  // called when widget content is ready to be written to the page (after Bandsintown API calls have finished)
  this.render = function() {
    this.write(this.to_html(this.artist_events, this.local_events));
    this.invert_description_link_colors();
    if (this.use_janrain_rendered_callback) { 
      BIT.Janrain.widget_rendered_callback(this);
    } else {
      this.rendered_callback();
    }
  }

  this.loading_image = function() {
    return [
      "<div id=\"" + this.loader_div_id + "\" style=\"text-align:center;\">",
        "<img src=\"http://static.bandsintown.com/images/widget-ajax-loader.gif\" />",
      "</div>"
    ].join("");
  }

  // returns the memoized root element of this widget 
  this.root_element = function() {
    if (this._root_element) { return this._root_element; }
    var classname_regex = new RegExp( this.unique_classname );
    var divs = document.getElementsByTagName('div');
    for ( var i=0; i<divs.length; i++ ) {
      var div = divs[i];
      if ( classname_regex.test(div.className) ) { this._root_element = div; }
    }
    return this._root_element;
  }

  /*
    Used for the links to expand/collapse event descriptions.
    For each link with a matching name that is a child element of this.root_element,
    the background color is set to the current text color, and the text color is set to
    either black or white based on how light or dark the new background color is.
  */
  this.invert_description_link_colors = function() {
    var links = BIT.Utils.get_elements_by_classname("a.bit-event-description-link", this.root_element());
    for (i=0; i<links.length; i++) {
      var link = links[i];
      if (BIT.Utils.get_parent(link, 'div') == this.root_element()) {
        var current_color = BIT.Utils.get_computed_style(link, 'color');
        link.style.backgroundColor = current_color;
        var white_or_black = BIT.Utils.image_color_for_background_color( current_color );
        var background_image = 'url("http://static.bandsintown.com/images/widget/plus_' + white_or_black + '.gif")';
        link.style.backgroundImage = background_image;
        link.style.backgroundPosition = 'center center';
        link.style.backgroundRepeat = 'no-repeat';
      }
    }
  }

  this.table_header = function(type) {
  
    var table_id    = "bit-" + type + "-events";
    var table_name  = this.unique_id("bit-" + type + "-events");
    var table_style = type == "local" ? "display:none;" : "";
  
    if (type == 'upcoming') {
      var event_links = "<span class='bit-header-links'>Upcoming | <a href='javascript:void(0);' onclick='return BIT.Widget.toggle_events(this);'>Local Dates</a></span>";
    } else {
      var event_links = "<span class='bit-header-links'><a href='javascript:void(0);' onclick='return BIT.Widget.toggle_events(this);'>Upcoming</a> | Local Dates</span>";
    }

    var bit_header_content = ["<div class='bit-header-overflow-fix'>", event_links, this.artist_share_links(), "</div>" ].join("");

    if (this.use_narrow_layout()) {
      return [
        "<table cellpadding='0' cellspacing='0' class='bit-events-narrow' style='" + table_style + "' id='" + table_id + "' name='" + table_name + "'><tbody>",
          "<tr class='bit-header-narrow'>",
            "<th colspan='" + this.adjusted_colspan(3) + "'>" + bit_header_content + "</th>",
          "</tr>"
      ].join("");
    } else {
      return [
        "<table cellpadding='0' cellspacing='0' class='bit-events' style='" + table_style + "' id='" + table_id + "' name='" + table_name + "'><tbody>",
          "<tr class='bit-header'>",
            "<th class='bit-description-links'>&nbsp;</th>",
            "<th colspan='" + this.adjusted_colspan(4) + "'>" + bit_header_content + "</th>",
          "</tr>",
          "<tr>",
            "<th class='bit-description-links'>&nbsp;</th>",
            "<th class='bit-date'>Date</th>",
            "<th class='bit-venue'>Venue</th>",
            "<th class='bit-location'>Location</th>",
            "<th class='bit-tickets' colspan='" + this.adjusted_colspan(1) + "'>Infos...</th>",
          "</tr>"
      ].join("");
    } 
  }

  this.table_footer = function(events, type) {
    var html = [];

    if (this.use_narrow_layout()) {
      var colspan = this.adjusted_colspan(4);
    } else {
      var colspan = this.adjusted_colspan(5);
    }
  
    if (this.display_limit < events.length) {
      html.push("<tr class='bit-bottom'><td colspan='" + colspan + "'><a href='javascript:void(0);' onClick='BIT.Widget.show_all_events(this)'>Show All Dates</a></td></tr>");
    }

    if (this.bandsintown_footer_link) {
      html.push("<tr class='bit-bottom'><td colspan='" + colspan + "'><a href='http://www.bandsintown.com/facebookapp?came_from=" + BIT.came_from_codes.footer + "' target='_blank' class='bit-logo' title='Bandsintown'></a><a href='http://www.bandsintown.com/facebookapp?came_from=" + BIT.came_from_codes.footer + "' target='_blank'>Bandsintown</a></td></tr>");
    }
  
    html.push("</tbody></table>");
    return html.join('\n');
  }

  this.to_html = function(upcoming_events, local_events) {
    // IE fix so the widget CSS works correctly:
    //   http://allofetechnical.wordpress.com/2010/05/21/ies-innerhtml-method-with-script-and-style-tags/
    var html = ["<div style=\"display:none\">&nbsp;</div>"];
    html.push(this.css());
    html.push("<div id='bit-events' name='bit-events' class='" + this.unique_classname + "'>");
  
    html.push(this.table_header('upcoming'));
    if (upcoming_events.length > 0) {
      for (var index = 0; index < upcoming_events.length; ++index) {
        var event = upcoming_events[index];
        var event_type = index < this.display_limit ? '' : 'bit-hidden';
        html.push(this.event_html(event, event_type)); 
      }
    } else {
      html.push(this.no_dates_message('upcoming'));
    }
    html.push(this.table_footer(upcoming_events, 'upcoming'));

    html.push(this.table_header('local'));
    if (local_events.length > 0) {
      for (var index = 0; index < local_events.length; ++index) {
        var event = local_events[index];
        var event_type = index < this.display_limit ? '' : 'bit-hidden';
        html.push(this.event_html(event, event_type)); 
      }
    } else {
      html.push(this.no_dates_message('local'));
    }
    html.push(this.table_footer(local_events, 'local'));

    html.push('</div>');
    return html.join('');
  }

  this.details_link_or_nbsp = function(event) {
    if (this.event_descriptions_hash[event.id]) {
      var layout_allows_comments = this.use_narrow_layout() ? this.myspace_layout : true;
      var onclick = this.show_facebook_comments() && layout_allows_comments ? this.toggle_event_details_js(event) : this.toggle_event_description_js(event);
      var id = "bit-event-description-link-" + event.id;
      return "<a href=\"javascript:void(0);\" class=\"bit-event-description-link\" onclick=\"" + onclick + "\" id=\"" + id + "\">&nbsp;</a>";
    } else {
      return "&nbsp;";
    }
  }

  this.adjusted_colspan = function(base_colspan) {
    var colspan = base_colspan;
    if (this.show_rsvp_links()) { colspan += 1; }
    if (this.show_facebook_comments()) { colspan += 1; }
    return colspan;
  }

  this.wide_event_html = function(event, event_type) {
    var html = [
      "<tr class='" + event_type + "'>",
        "<td class='bit-description-links'>", this.details_link_or_nbsp(event), "</td>",
        "<td class='bit-date'>", BIT.Widget.formatted_date(event), "</td>",
        "<td class='bit-venue'>", event.venue.name, "</td>",
        "<td class='bit-location'>", this.formatted_location(event), "</td>",
        "<td class='bit-tickets'>", this.ticket_link(event), "</td>",
        this.facebook_comments_td(event),
        this.wide_layout_rsvp_td(event),
      "</tr>"
    ];
  
    if (this.show_facebook_comments()) {
      html.push(this.event_details_row(event, event_type));
    } else if (this.event_descriptions_hash[event.id]) {
      html.push(this.event_description_row(event));
    }
    return html.join("");
  }

  this.event_details_row = function(event) {
    var colspan = this.use_narrow_layout() ? 5 : 7;
    var name    = this.unique_id("bit-event-details");
    var row_id  = "bit-event-details-" + event.id;
  
    if (this.event_descriptions_hash[event.id]) {
      var description_content = [
        "<div class='bit-details-title'>Event Details:</div>",
        "<div class='bit-details-text'>",
          "<div class='bit-details-description'>", this.truncate_and_process_description(this.event_descriptions_hash[event.id]), "</div>",
        "</div>"
      ].join("");
    } else {
      var description_content = [
        "<div class='bit-details-title' style='display:none;'></div>",
        "<div class='bit-details-text' style='display:none;'></div>"
      ].join("");
    }
  
    return [
      "<tr class='bit-event-details' name='" + name + "' " + "id='" + row_id + "' style='display:none;'>",
        "<td colspan='" + colspan + "' class='bit-details'>",
          description_content,
          "<div class='bit-details-comments'></div>",
        "</td>",
      "</tr>"
    ].join("");
  }

  this.event_description_row = function(event) {
    return [
      "<tr class='bit-event-description bit-dashed-border' style='display:none;'>",
        "<td class='bit-description-links'>&nbsp;</td>",
        "<td class='bit-date'>&nbsp;</td>",
        "<td colspan='" + this.adjusted_colspan(3) + "' class='bit-description'>" + this.process_description(this.event_descriptions_hash[event.id]) + "</td>",
      "</tr>"
    ].join("");
  }

  this.narrow_event_html = function(event, event_type) {
    var html = [
      "<tr class='" + event_type + "'>",
        "<td class='bit-description-links'>", this.details_link_or_nbsp(event), "</td>",
        "<td class='bit-date'>", BIT.Widget.formatted_date(event), "</td>",
        "<td class='bit-concert'>", this.narrow_layout_middle_column_html(event), "</td>",
        this.facebook_comments_td(event),
        this.narrow_layout_right_column_html(event),
      "</tr>"
    ];
  
    if (this.show_facebook_comments() && this.myspace_layout) {
      html.push(this.event_details_row(event, event_type));
    } else if (this.event_descriptions_hash[event.id]) {
      html.push(this.event_description_row(event));
    }
    return html.join("");
  }

  this.narrow_layout_right_column_html = function(event) {
    if (this.show_rsvp_links()) {
      return ["<td class='bit-rsvp'>", this.rsvp_link(event), "</td>"].join("");
    } else {
      return ["<td class='bit-tickets'>", this.ticket_link(event), "</td>"].join("");
    }
  }

  // the middle column in narrow layout contains venue name, location, and ticket link if RSVP is enabled and tix available, separated by line breaks.
  this.narrow_layout_middle_column_html = function(event) {
    var html = [event.venue.name, "<br/><strong>", this.formatted_location(event), "</strong>"].join("");
    if (this.show_rsvp_links()) {
      var ticket_link = this.ticket_link(event);
      if (ticket_link != '&nbsp;') { html += '<br/>' + ticket_link; }
    }
    return html;
  }

  this.rsvp_link = function(event) {
    return "<a target='_blank' class='bit-rsvp' href='" + this.rsvp_links_hash[event.id] + "&came_from=" + BIT.came_from_codes.rsvp + "'>RSVP</a>";
  }

  this.event_html = function(event, event_type) {
    return this.use_narrow_layout() ? this.narrow_event_html(event, event_type) : this.wide_event_html(event, event_type);    
  }

  this.formatted_location = function(event) {
    if (event.venue.country.toLowerCase() == 'united states') {
      return event.venue.city + ', ' + event.venue.region;
    } else {
      return event.venue.city + ', ' + event.venue.country;
    }
  }
  
  this.artist_param = function() {
    var param = this.artist;
    param = param.replace(/\?/g, encodeURIComponent("?"));
    param = param.replace(/\//g, encodeURIComponent("/"));
    param = encodeURIComponent(param);
    param = param.replace(/'/g, escape("'"));
    param = param.replace(/"/g, escape("\""));
    return param;
  }

  this.facebook_page_id_param = function() {
    var all_digits = /^\d+$/;
    if (all_digits.test(this.facebook_page_id)) { 
      return 'fbid_' + this.facebook_page_id;
    } else {
      return null;
    }
  }

  this.ticket_link = function(event) {
    if (event.ticket_status == 'available') {
      // v2 responses have the artist param already appended to the ticket_url, v1 responses do not
      var artist_param = event.ticket_url.match(/artist=/) ? '' : ('?artist=' + this.artist_param());
      var href = event.ticket_url + artist_param + '&affil_code=' + encodeURIComponent(this.affil_code);
      return '<a target="_blank" class="bit-buy-tix" href="' + href + '">more...</a>';
    } else {
      return '&nbsp;';
    }
  }

  this.wide_layout_rsvp_td = function(event) {
    if (this.show_rsvp_links()) {
      return '<td class="bit-rsvp">' + this.rsvp_link(event) + '</td>';
    } else {
      return '';
    }
  }

  this.facebook_comments_td = function(event) {
    var layout_allows_comments = this.use_narrow_layout() ? this.myspace_layout : true;  
    if (this.show_facebook_comments() && layout_allows_comments) {
      var name    = this.unique_id("bit-comments-button");
      var onclick = this.toggle_event_details_js(event);
      return [
        "<td class=\"bit-comment\">",
          "<a href=\"#\" class=\"bit-comment\" name=\"", name, "\" onclick=\"", onclick, "\"></a>",
        "</td>"
      ].join("");
    } else {
      return "";
    }
  }

  this.css = function() {
    var css = [
      ".bit-events, .bit-events-narrow {overflow: hidden;display: table;width:", this.width, ";font-family:arial,helvetica,sans-serif;font-size:13px;}",
      ".bit-events th, .bit-events td {width: auto;text-align: left; padding: 4px;vertical-align:middle;}",
      ".bit-events td {height: 36px; background:none;border-top: 1px solid " + this.separator_color + ";}",
      ".bit-events-narrow td {width:auto; height:57px; background:none;padding:4px; border-top: 1px solid " + this.separator_color + ";vertical-align:middle;}",
      (this.text_color ? "#bit-events td, #bit-events th { color: " + this.text_color + ";}" : ""),
      "#bit-events td.bit-tickets, #bit-events th.bit-tickets {width:55px; padding-right: 8px;}",
      "#bit-events td.bit-actions a, #bit-events td.bit-rsvp a { float: right; }",
      "#bit-events td.bit-rsvp { width: 42px; padding-right: 8px; }",
      "#bit-events td.bit-comment { width: 21px; padding-left: 8px; padding-right: 8px; }",
      "#bit-events td a.bit-rsvp { background:#EEEEEE url('http://static.bandsintown.com/images/widget/rsvp_bg.png') repeat 0 0; border:1px solid #999999; border-top-color:#888888; box-shadow:0 1px 0 rgba(0, 0, 0, .1); -moz-box-shadow:0 1px 0 rgba(0, 0, 0, .1);cursor:pointer; display:-moz-inline-box; display:inline-block; font-size:11px; font-weight:bold; line-height:normal !important; text-align:center; text-decoration:none; vertical-align:top; white-space:nowrap; font-family: 'Lucida Grande',Tahoma,Verdana,Arial,sans-serif; color: #333333; padding: 2px 6px 1px; height: 15px;}",
      "#bit-events td.bit-comment a.bit-comment { background: transparent url('http://static.bandsintown.com/images/facebook/comments_icon.gif') 0px 0px no-repeat; width: 15px; height: 16px; display: inline-block; margin-top: 2px; float: right; }",
      "#bit-events td.bit-comment a:hover, #bit-events td.bit-comment a.bit-comment-open { background-position: 0px -16px; transparent url('http://static.bandsintown.com/images/facebook/comments_icon.gif') -16px 0px no-repeat; }",
      "#bit-events td.bit-location {font-weight:bold;}",
      "#bit-events td.bit-description, #bit-events th.bit-description {font-size: 85%; left: 8px 4px; }",
      "#bit-events td.bit-description-links, #bit-events th.bit-description-links {padding-left: 8px; width: 6px;}",
      "#bit-events .bit-hidden {display:none;}",
      "#bit-events .bit-bottom td {padding-left:8px;height:36px;}",
      "#bit-events .bit-bottom td.concerts-by-bandsintown {text-align:right;}",
      "#bit-events .bit-bottom a { vertical-align: middle; border: none; display: inline-block; }",
      "#bit-events .bit-bottom a.bit-logo { background: transparent url('http://static.bandsintown.com/images/favicon.gif') no-repeat top left; height: 16px; width:16px; margin-right:4px; }",
      "#bit-events a { text-align: left; float: left; width:auto; }",
      "#bit-events a:hover { -webkit-transition: none; -moz-transition: none; -o-transition: none; transition: none; }",
      "#bit-events td.bit-description a { float: none; }",
      "#bit-events a.bit-event-description-link { text-decoration: none; margin: 0; padding: 0; display: inline-block; height: 9px; width: 9px; line-height: 9px; font-size: 9px; text-align: center; vertical-align: middle; border: none;}",
      (this.link_color ? "#bit-events a { color: " + this.link_color + ";}" : ""),
      (this.bg_color ? "#bit-events td, #bit-events th { background-color: " + this.bg_color + ";}" : ""),
      ".bit-events tr.bit-dashed-border td, .bit-events-narrow tr.bit-dashed-border td.bit-description { border-top: 1px dashed " + this.separator_color + ";}",
      ".bit-events tr.bit-dashed-border td.bit-description-links, .bit-events tr.bit-dashed-border td.bit-date, .bit-events-narrow tr.bit-dashed-border td { border-top: 1px solid transparent;}",
      "td.bit-concert { }",
      "td.bit-concert span { float: left; padding: 0 6px; color: " + this.link_color + "; }",
      "td.bit-date { width: 45px; }",
      "tr.bit-header th, tr.bit-header-narrow { line-height: 26px; }",
      "#bit-events tr.bit-header a, #bit-events tr.bit-header-narrow a { float: none; font-weight: normal; }",
      "#bit-events tr.bit-header-narrow th { text-align: left; padding: 4px;}",
      "#bit-events .bit-events-narrow tr.no-dates td a { display: block; }",
      (this.myspace_layout ? "" : "#bit-events .bit-events-narrow tr.no-dates td span { display: block; }"),
      "#bit-events .bit-events td.no-dates td { padding: 20px 0px; }",
      "#bit-events .bit-header-links { margin-right: 15px; }",
      "#bit-events .bit-share-text { float:right; }",
      "#bit-events .bit-share-links { float: right; }",
      "#bit-events .bit-share-links a { display: inline-block; width: 26px; height: 26px; vertical-align: middle; }",
      "#bit-events .bit-fb-share { background: transparent url('http://static.bandsintown.com/images/facebook/icons/fb_share.gif') top left no-repeat; margin-left: 4px; display: inline-block; width: 26px; height:26px; vertical-align: middle;}",
      "#bit-events .bit-twitter-share { background: transparent url('http://static.bandsintown.com/images/facebook/icons/twitter_share.gif') top left no-repeat; display: inline-block; width: 26px; height:26px; vertical-align: middle;}",
      "#bit-events .bit-events-narrow tr.no-dates td { padding-bottom: 25px; padding-top: 20px; }",
      "#bit-events tr.no-dates td { padding-top: 10px; padding-bottom: 10px; }",
      "#bit-events tr.no-dates td a { float: none; margin-top: 15px; }",
      "#bit-events table { border-bottom: 1px solid " + this.separator_color + ";}",
      ".bit-header-overflow-fix { height: 26px; overflow: hidden; }",
      "#bit-events iframe { border: none; }",
      "#bit-events .comments-title, #bit-events .description-title { color: #323232; font-size: 11px; font-weight: bold; margin: 0px 0px 4px 0px;}",
      "#bit-events .bit-event-details { color: #000000; }",
      "#bit-events .bit-details-title { background-color: #ffffff; font-weight: bold; padding: 4px 8px 0px 20px; color: #0e0e0e; font-size: 11px; }",
      "#bit-events .bit-details-title a { float: right; color: #8296cc; text-decoration: none; }",
      "#bit-events .bit-details-title a:hover { color: #ffffff; text-decoration: none; }",
      "#bit-events .bit-details-text { background-color: #ffffff; margin-bottom: 1px; padding: 5px 8px 5px 20px; color: #1A1A1A; }",
      "#bit-events .bit-details-comments { background: transparent; padding: 0px; margin: 0px; }",
      "#bit-events .bit-details-description { }",
      "#bit-events .bit-details-text a { color: #3857a0; float: none; }",
      "#bit-events a.bit-fb-event-link { font-weight: bold; display: block; text-decoration: none; margin: 4px 0px; }",
      "#bit-events tr td.bit-details { padding: 0px; }"
    ].join("");
    
    if (!BIT.RSVPDialog.css_added) {
      css += BIT.RSVPDialog.css;
      BIT.RSVPDialog.css_added = true;
    }
    return "<style type=\"text/css\">" + css + "</style>";
  }

  this.no_artist_given = function() {
    return [this.css(), "<div class='bit-no-upcoming-events'>No artist given.</div>"].join("\n");
  }

  this.process_description = function( description ) {
    var processed_description = BIT.Utils.escape_html(description || '');
    if (processed_description != '') {
      processed_description = processed_description.replace(/(https?:\/\/[^\s]+)/g, "<a href=\"$1\">$1</a>"); // add auto links
      processed_description = processed_description.replace(/\n/g, "<br/>"); // add line breaks
    }
    return processed_description;
  }

  this.truncate_and_process_description = function ( description ) {
    var truncate_length = 100;
    var max_lines = 3;
    var max_lines_regexp = new RegExp("(?:.*\?\\n){" + max_lines + "}");
    var stripped = BIT.Utils.Text.strip(description || '');

    if (stripped.length < truncate_length && !stripped.match(max_lines_regexp)) {
      return [
        "<span>", 
          this.process_description(stripped),
        "</span>"
      ].join("");
    } else {
      var truncated = BIT.Utils.Text.truncate(stripped, { length : truncate_length, omission : '', max_lines : max_lines });
      var view_more_link = "<a href=\"#\" onclick=\"BIT.Widget.view_more(this); return false;\" class=\"bit-toggle-description\">view more</a>";
      var view_less_link = "<a href=\"#\" onclick=\"BIT.Widget.view_less(this); return false;\" class=\"bit-toggle-description\">view less</a>";
      return [
        "<span>",
          this.process_description(truncated), "...",
          view_more_link,
        "</span>",
        "<span style=\"display:none;\">",
          this.process_description(stripped), 
          "<br/>",
          view_less_link,
        "</span>"
      ].join(""); 
    }
  }

  this.no_dates_message = function(type) {
    if (this.notify_me) {
      var text = "No " + type + " dates. <a href='" + this.notify_me_link() + "'>Notify me when <span>" + this.artist + "</span> comes to my area.</a>";
    } else {
      var text = "No " + type + " dates."
    }
  
    if (this.use_narrow_layout()) {
      return "<tr class='no-dates'><td colspan='4'>" + text + "</td></tr>";
    } else {
      return "<tr class='no-dates'><td class='bit-description-links'>&nbsp;</td><td colspan='" + this.adjusted_colspan(4) + "'>" + text + "</td></tr>";
    }
  }

  this.notify_me_link = function() {
    return ["http://www.bandsintown.com/track/", this.artist_param(), "?came_from=", BIT.came_from_codes.notify_me].join("");
  }

  this.toggle_event_details_js = function(event) {
    return [
      "return BIT.Widget.toggle_event_details({",
        "\'link\':this,",
        "\'artist\':\'", this.artist_param(), "\',",
        "\'event_id\':\'", event.id, "\',",
        "\'widget_timestamp\':\'", this.timestamp, "\'",
      "});"
    ].join("");
  }

  this.toggle_event_description_js = function(event) {
    return [
      "return BIT.Widget.toggle_event_description({",
        "'link':this,",
        "'artist':'", this.artist_param(), "',",
        "'event_id':'", event.id, "'",
      "});"
    ].join("");
  }


  // share link methods
  this.artist_share_links = function() {
    if (this.share_links) {
      return [
        "<span class='bit-share-links'>",
          "<a target='_blank' title='Share this on Facebook' class='bit-fb-share' href='" + this.fb_share_link() + "'></a>",
          "<a target='_blank' title='Share this on Twitter' class='bit-twitter-share' href='" + this.twitter_share_link() + "'></a>",
        "</span>",
        "<span class='bit-share-text'>Share:</span>"
      ].join("");
    } else {
      return "";
    }
  }

  this.bit_share_link = function(came_from) {
    return [BIT.base_url, "/", this.artist_param(), "/share?u=", encodeURIComponent(this.share_url), "&came_from=", came_from].join("");
  }

  this.fb_share_link = function() {
    var link         = this.bit_share_link(BIT.came_from_codes.fb_share);
    var app_id       = '123966167614127';
    var picture      = BIT.base_url + "/" + this.artist_param() + '/photo/medium.jpg';
    var name         = this.artist + ' - Tour Dates';
    var caption      = BIT.Utils.parse_uri(this.share_url).host;
    var properties   = "";
    var redirect_uri = BIT.base_url + '/redirect?u=' + encodeURIComponent(this.share_url);

    if (/^http:\/\/myspace\.com\/\d+/.test(this.share_url)) { name += ' on MySpace'; }

    if (this.artist_events.length == 0) {
      var description = '';
    } else if (this.artist_events.length == 1) {
      var description = '1 upcoming tour date';
    } else {
      var description = this.artist_events.length + ' upcoming tour dates';
    }

    return [
      "http://www.facebook.com/dialog/feed?", 
      "app_id=", app_id,
      "&link=", encodeURIComponent(link),
      "&picture=", picture,
      "&name=", encodeURIComponent(name).replace(/'/g, escape("'")),
      "&caption=", encodeURIComponent(caption),
      "&description=", encodeURIComponent(description),
      "&properties=", encodeURIComponent(properties),
      "&redirect_uri=", encodeURIComponent(redirect_uri)
    ].join("");
  }

  this.twitter_share_link = function () {
    return ["http://www.bandsintown.com/", this.artist_param(), "/twitter_share?u=", encodeURIComponent(this.share_url)].join("");
  }

  // boolean methods
  this.show_rsvp_links = function() {
    if (this._show_rsvp_links != null) { return this._show_rsvp_links; }
    this._show_rsvp_links = this.rsvp_links && this.artist_events != null && this.artist_events[0] != null && this.artist_events[0].facebook_rsvp_url != null;
    return this._show_rsvp_links;
  }

  this.show_facebook_comments = function() {
    if (this._show_facebook_comments != null) { return this._show_facebook_comments; }  
    if (this.facebook_comments && this.artist_events) {
      for (i=0; i<this.artist_events.length; i++) {
        if (this.artist_events[i].facebook_comments_xid) { this._show_facebook_comments = true; }
      }
    } 
    this._show_facebook_comments = this._show_facebook_comments || false;
    return this._show_facebook_comments;
  }

  this.use_narrow_layout = function() {
    if (this.force_narrow_layout == true) { return true; }
    if (this.width.match(/\d+px$/)) {
      return (parseInt(this.width) < 275);
    } else { 
      return false;
    }
  }

  // bandsintown API methods
  // url for all of the widget artist's events
  this.artist_events_url = function() {
    var artist_events_url = [BIT.api_base_url, '/artists/', this.artist_param(), '/events.json?api_version=2.0&extended=true&app_id=', this.app_id].join('');
    if (this.facebook_page_id_param()) { artist_events_url += "&artist_id=" + this.facebook_page_id_param(); }
    return artist_events_url;
  }

  // url for all events near the user's current location featuring the widget artist
  this.local_events_url = function() {
    var artists = "&artists[]=" + encodeURIComponent(this.artist);
    if (this.facebook_page_id_param()) { artists += "&artists[]=" + this.facebook_page_id_param(); }
    return [BIT.api_base_url, '/events/search.json?app_id=', this.app_id, artists, '&location=use_geoip&per_page=100'].join('');
  }

  // jsonp callback for Artist - Events response
  this.artist_events_callback = function(events) {
    this.artist_events_loaded = true;
    this.artist_events = events || [];
    for (i=0; i<this.artist_events.length; i++) {
      var event = this.artist_events[i];
      this.event_descriptions_hash[event.id] = event.description;
      this.rsvp_links_hash[event.id] = event.facebook_rsvp_url;
      this.facebook_comments_xid_hash[event.id] = event.facebook_comments_xid;
      this.ticket_types_hash[event.id] = event.ticket_type || 'Tickets';
    }

    if (this.local_events_loaded) { this.render(); }
  }

  // jsonp callback for Events - Search response 
  this.local_events_callback = function(events) {
    this.local_events_loaded = true;
    this.local_events = events;

    if (this.artist_events_loaded) { this.render(); }
  },

  this.insert_rsvp_iframe = function(target) {
    var iframe_id = this.unique_id("rsvp-iframe");
    var iframe = document.createElement("iframe");
    iframe.setAttribute("id", iframe_id);
    iframe.setAttribute("name", iframe_id);
    iframe.setAttribute("src", BIT.base_url + "/" + this.artist_param() + "/rsvp_iframe?iframe_id=" + iframe_id + "&parent_window_location=" + encodeURIComponent(window.location.href) + "#");
    iframe.setAttribute("width", BIT.RSVPDialog.iframe_width);
    iframe.setAttribute("height", BIT.RSVPDialog.iframe_height);
    iframe.setAttribute("frameBorder", "0"); // ie wants frameBorder not frameborder
    iframe.setAttribute("scrolling", "no");
    iframe.onload = iframe.onreadystatechange = function() { 
      BIT.RSVPDialog.loaded_iframes[iframe_id] = true; 
    }
    iframe.style.display = "none";
    target.appendChild(iframe);
    this.rsvp_iframe = document.getElementById(iframe_id);
  },

  this.rendered_callback = function() {
    if (this.artist_events.length == 0) { return; }
    if (BIT.rsvp_dialog_enabled && this.rsvp_dialog && !BIT.Utils.Browser.IE6) {
      BIT.RSVPDialog.init();
      this.insert_rsvp_iframe(BIT.RSVPDialog.content);    
      var rsvp_links = BIT.Utils.get_elements_by_classname("a.bit-rsvp", this.root_element());
      BIT.Utils.Event.observe("click", rsvp_links, this.rsvp_link_clicked_callback);
    }
  },

  this.rsvp_link_clicked_callback = function(event) {
    BIT.Utils.Event.prevent_default(event);
    var clicked = typeof event.target == "undefined" ? event.srcElement : event.target;
    var bit_event_id = clicked.getAttribute("href").match(/\/event\/(\d+)\//)[1];
    BIT.RSVPDialog.hide_iframes();
    var iframe = BIT.RSVPDialog.find_iframe_for_widget(self.timestamp);
    BIT.RSVPDialog.show_iframe_and_event(iframe, bit_event_id);
  }
  BIT.widgets.push(this);
}

// onclick handler for showing all upcoming or local events
BIT.Widget.show_all_events = function( link ) {
  var events_tbody = BIT.Utils.get_parent(link, 'tbody');
  var skipped_tr_classnames = /bit-(local|dashed-border|event-description|bottom|header)/;

  for (var i=0; i<events_tbody.childNodes.length; i++) {
    var tr = events_tbody.childNodes[i];
    if (tr.className && !tr.className.match(skipped_tr_classnames)) { tr.className = ''; }
  }

  BIT.Utils.get_parent(link, 'tr').style.display = 'none';
}

// onclick handler for switching between viewing upcoming or local events
BIT.Widget.toggle_events = function ( events_link ) {
  var events_table = BIT.Utils.get_parent(events_link, 'table');
  events_table.style.display = 'none';
  var other_events_table = events_table.nextSibling || events_table.previousSibling;
  other_events_table.style.display = '';
  return false;
}

// onclick handler for expanding/collapsing description when FB comments are not enabled
BIT.Widget.toggle_event_description = function ( params ) {
  var event_row        = BIT.Utils.get_parent(params.link, 'tr');
  var description_row  = event_row.nextSibling;
  var description_link = event_row.childNodes[0].childNodes[0];
  
  if (description_row.style.display == 'none') {
    description_row.style.display = '';
    description_link.style.backgroundImage = description_link.style.backgroundImage.replace("plus", "minus");
    
  } else {
    description_row.style.display = 'none';
    description_link.style.backgroundImage = description_link.style.backgroundImage.replace("minus", "plus");
  }
}

// onclick handler for expanding/collapsing description + FB comments
BIT.Widget.toggle_event_details = function ( params ) {
  var event_row    = BIT.Utils.get_parent(params.link, 'tr');
  var events_table = BIT.Utils.get_parent(event_row, 'table');
  
  var comments_added_id = params.widget_timestamp + "-" + events_table.id;
  BIT.Widget.initialize_comments_added(comments_added_id);
  
  var shown_event_key     = params.widget_timestamp + "-" + events_table.id;
  BIT.Widget.shown_events = BIT.Widget.shown_events || {};
  var shown_event_id      = BIT.Widget.shown_events[shown_event_key];
  var opening_details     = shown_event_id != params.event_id;

  var widget;
  for (var i=0; i<BIT.widgets.length; i++) {
    if (BIT.widgets[i].timestamp == params.widget_timestamp) {
      widget = BIT.widgets[i];
    }
  }

  var detail_rows     = BIT.Utils.get_elements_by_classname("tr.bit-event-details", widget.root_element());
  var comment_buttons = BIT.Utils.get_elements_by_classname("a.bit-comment", widget.root_element());
  
  for (i=0; i<detail_rows.length; i++) {
    // there will always be 1 comment button per event row so we can use the same index on these element arrays
    var details_row    = detail_rows[i];
    var comment_button = comment_buttons[i];
    
    // if we are opening details for this event, show the details row and add open state to comments button
    if (opening_details && details_row.id.match(params.event_id)) {
      details_row.style.display = '';
      details_row.previousSibling.className = details_row.previousSibling.className + ' bit-details-open';
      comment_button.className = comment_button.className + " bit-comment-open";
      // add comments iframe if not already added
      if (!BIT.Widget.comments_added[comments_added_id][params.event_id]) {
        BIT.Widget.comments_added[comments_added_id][params.event_id] = true;
        var comments_div = details_row.childNodes[0].childNodes[2];
        BIT.Widget.add_comments_iframe(comments_div, params);
      }
      
    // if we are not opening details for this event, hide the details and remove open state from comments button
    } else {
      details_row.style.display = 'none';
      details_row.previousSibling.className = details_row.previousSibling.className.replace(' details-open', '');
      comment_button.className = comment_button.className.replace(" bit-comment-open", "");
    }
  }

  var description_links = BIT.Utils.get_elements_by_classname("a.bit-event-description-link", widget.root_element());

  for (i=0; i<description_links.length; i++) {
    var link = description_links[i];
    // if we are opening details for this event, set the description button to open state
    if (opening_details && link.id.match(params.event_id)) {
      link.style.backgroundImage = link.style.backgroundImage.replace("plus", "minus");

    // if we are not opening details for this event, remove the open state from description button
    } else {
      link.style.backgroundImage = link.style.backgroundImage.replace("minus", "plus");
    }    
  }

  BIT.Widget.shown_events[shown_event_key] = BIT.Widget.shown_events[shown_event_key] == params.event_id ? null : params.event_id;
  return false;
}

BIT.Widget.initialize_comments_added = function(comments_added_id) {
  BIT.Widget.comments_added = BIT.Widget.comments_added || {};
  BIT.Widget.comments_added[comments_added_id] = BIT.Widget.comments_added[comments_added_id] || {};
}

BIT.Widget.add_comments_iframe = function (element, params) {  
  var iframe_width = parseInt(BIT.Utils.get_computed_style(element, "width"));
  // use clientWidth in case "auto" was returned (IE issue)
  if (isNaN(iframe_width)) { iframe_width = element.clientWidth; }
  
  var iframe_url    = BIT.base_url + "/event/" + params.event_id + "/fb_comments_iframe?artist=" + params.artist + "&width=" + iframe_width;
  element.innerHTML = "<iframe src='" + iframe_url + "' width='" + iframe_width + "' frameborder='0'></iframe>";
  element.childNodes[0].style.height = "350px";
}

BIT.Widget.view_more = function( element ) {
  element.parentNode.style.display = 'none';
  element.parentNode.nextSibling.style.display = '';
}

BIT.Widget.view_less = function( element ) {
  element.parentNode.style.display = 'none';
  element.parentNode.previousSibling.style.display = '';
}

BIT.Widget.formatted_date = function(event) {
  var months = {
   '01' : 'Jan',
   '02' : 'Feb',
   '03' : 'Mar',
   '04' : 'Apr',
   '05' : 'May',
   '06' : 'Jun',
   '07' : 'Jul',
   '08' : 'Aug',
   '09' : 'Sep',
   '10' : 'Oct',
   '11' : 'Nov',
   '12' : 'Dec'
  };
  var y_m_d = event.datetime.split('T')[0].split('-');
  return months[y_m_d[1]] + ' ' + y_m_d[2];
}

BIT.came_from_codes = {
  footer : 10,
  fb_share : 36,
  notify_me : 38,
  rsvp : 39,
  event_details : 46,
  iga_rsvp_wall_post : 72
};

