// require prototype effects

var route = {
  join: function() { return '/' + $A(arguments).compact().join('/') },
  
  category: function() { return Try.these(function(){ return $F('category_id') }, function() { return '' }) },
  
  categoryQueryString: function() { var cat = this.category(); return cat ? '?category=' + cat : '' }, 
  
  bio: function(id) { return this.join(id) + this.categoryQueryString() }, 
  
  url: function(controller, id, action) { return this.join(controller, id, action) }
}


var Broadcaster = {
  listeners: {},
  
  listen: function() {
    var events = $A(arguments), callback = events.pop();
    events.each(function(event){ (this.listeners[event] = this.listeners[event] || []).push(callback) }.bind(this));
  },
  
  dispatch: function() {
    var args = $A(arguments), event = args.shift(), callbacks = this.listeners[event] || [];
    if (typeof args.last() == 'function') {
      var callback = args.pop();
      this.dispatch.apply(this, [event + ':before'].concat(args));
      callback.call(this);
      this.dispatch.apply(this, [event + ':after'].concat(args));
    } else {
      callbacks.invoke('apply', this, args);
    }
  }
}


Object.aliasMethodChain = function(object, method, ext) {
  object[method + 'Without' + ext] = object[method];
  object[method] = object[method + 'With' + ext];
};


var SampleText = Class.create();
Object.extend(SampleText.prototype, {
  initialize: function(element, classname) {
    this.className = classname || 'sample';
    this.element = $(element);
    this.text = this.element.value;
    this.element.setSampleText = function(text) { this.text = text; this.onBlur() }.bind(this);
    this.element.getValue = function(){ return this.element.hasClassName(this.className) ? '' : this.element.value }.bind(this);
    this.element.observe('focus', this.onFocus.bindAsEventListener(this));
    this.element.observe('blur', this.onBlur.bindAsEventListener(this));
  },
  
  onFocus: function() {
    if (this.element.hasClassName(this.className)) {
      this.element.value = ''; this.element.removeClassName(this.className);
    }
  },
  
  onBlur: function() {
    if (!this.element.value) {
      this.element.addClassName(this.className); this.element.value = this.text;
    }
  }
});


var MoreLess = Class.create();
Object.extend(MoreLess.prototype, {
  initialize: function(name) {
    if (!$('more-' + name + '-link')) return;
    
    this.more = $(name + '-more');
    this.less = $(name + '-less');
    this.moreLink = $('more-' + name + '-link');
    this.lessLink = $('less-' + name + '-link');
    this.moreLink.observe('click', this.onMore.bindAsEventListener(this));
    this.lessLink.observe('click', this.onLess.bindAsEventListener(this));
  },
  
  onMore: function(event) {
    this.less.hide();
    this.moreLink.hide()
    this.more.show();
    this.lessLink.show();
    Event.stop(event);
  },
  
  onLess: function(event) {
    this.more.hide();
    this.lessLink.hide();
    this.less.show();
    this.moreLink.show()
    Event.stop(event);
  }
});


var Pager = {
  page: 1,
  
  hookPager: function(scope) {
    scope = scope || this.controls;
    var left = scope.down('.left'), right = scope.down('.right');
    (this.leftButtons = this.leftButtons || []).push(left);
    (this.rightButtons = this.rightButtons || []).push(right);
    (this.pagerDisplays = this.pagerDisplays || []).push(scope.down('.page'));
    left.observe('click', this.onLeft.bindAsEventListener(this));
    right.observe('click', this.onRight.bindAsEventListener(this));
  },
  
  isFirstPage: function() { return this.page == 1 },
  
  isLastPage: function() { return this.page + 1 > this.pageCount },
  
  onLeft: function(event) {
    this.left();
    Event.preventDefault(event);
  },
  
  onRight: function(event) {
    this.right();
    Event.preventDefault(event);
  },
  
  left: function(e) {
    if (this.isFirstPage()) {
      Event.stop(e);
    } else {
      this.paginate(this.page -= 1);
    }
  },
  
  right: function(e) {
    if (this.isLastPage()) {
      Event.stop(e);
    } else {
      this.paginate(this.page += 1);
    }
  },
  
  updatePager: function() {
    var display = 'page ' + this.page;
    this.pagerDisplays.invoke('update', display);
    this.leftButtons.invoke(this.isFirstPage() ? 'addClassName' : 'removeClassName', 'disabled');
    this.rightButtons.invoke(this.isLastPage() ? 'addClassName' : 'removeClassName', 'disabled');
  },
  
  paginate: function(page) {
    var params = window.location.search.toQueryParams();
    params.page = page;
    window.location.search = $H(params).toQueryString();
  },
  
  addPagination: function(object) {
    var paginate = object.paginate;
    Object.extend(object, Pager);
    if (paginate) object.paginate = paginate;
  }
};


var Notice = {
  id: function(name) {
    if (/^alert/.test(name)) return name;
    else return 'alert' + (name || '');
  },
  
  show: function(id, message) {
    id = this.id(id);
    var alertContent = $$('#' + id + ' .alert-content')[0];
    var alertContainer = alertContent.ancestorWithClass('alert-container');
    alertContent.innerHTML = /<\w+(\s+\w+=["'](.*)["'])*\s*\/?>/.test(message) ? message : message.escapeHTML();
    alertContainer.style.visibility = '';
  },
  
  hide: function(id) {
    id = this.id(id);
    var alertContent = $$('#' + id + ' .alert-content')[0];
    var alertContainer = alertContent.ancestorWithClass('alert-container');
    alertContainer.style.visibility = 'hidden';
  }
};

var Alert = {
  add: function(alert, callback) {
    this.hide();
    this.current = $(alert).show();
    if (this.current.down('a').$$alertCallback) this.current.down('a').stopObserving('click', $$alertCallback);
    this.current.down('a').observe('click', this.current.$$alertCallback = callback);
  },
  
  hide: function() {
    if (this.current) this.current.hide();
  }
};

/******* User lifecycle stuff *******/

var Signup = {
  divId: 'hidden_content_signup_user', 
  
  begin: function(options) {
    options = Object.extend({context: ''}, options || {});

    new Ajax.Updater(Signup.divId, '/signup', {
      asynchronous: true, 
      evalScripts: true, 
      method: 'get', 
      parameters: '_context=' + options.context,
      
      onComplete: function(request) {
        RedBox.addHiddenContent(Signup.divId);
        setTimeout(function(){$('reg_login').focus()}, 401);
      },
      
      onLoading: function(request) {
        RedBox.loading();
      }
    });
  }
}

var Login = {
  divId: 'hidden_content_login_user', 
  
  begin: function(options) {
    options = Object.extend({context: ''}, options || {});
    
    new Ajax.Updater(Login.divId, '/login', {
      asynchronous: true, 
      evalScripts: true, 
      method: 'get', 
      parameters: '_context=' + options.context, 
      
      onComplete: function(request) {
        RedBox.addHiddenContent(Login.divId);
        setTimeout(function(){$('username').focus()}, 801);
      }, 
      
      onLoading: function(request) {
        RedBox.loading();
      }
    });
  }, 
  
  done: function() {
    if (Login.targetUrl) {
      window.location.href = Login.targetUrl;
    } else {
      window.location.reload();
    }
  }
}

var CategoryChooser = {
  containerId: 'categories', 
  
  container: function() { return $(this.containerId) }, 
  
  show: function() {
    if (this.container())
      this.container().show();
  }, 
  
  hide: function() {
    if (this.container())
      this.container().hide();
  }
}

var Spinner = {
  spinner: function(id) {
    return $(/_spinner$/.test(id) ? id : (id+'_spinner'));
  }, 
  
  show: function(id) {
    this.spinner(id).show();
  }, 
  
  hide: function(id) {
    this.spinner(id).hide();
  }
}

var GeoChanger = {
  zipChangerId: 'hidden_content_change_geo', 
  zipChangerSubmitButtonId: 'location_submit', 
  metroChangerId: 'change_geo', 
  metroInputId: 'active_metro', 
  zipInputId: 'location_search_values', 
  radiusInputId: 'search_radius', 
  
  // getters
  
  metroChanger: function() { return $(this.metroChangerId) }, 
  zipChanger: function() { return $(this.zipChangerId) }, 
  zipChangerSubmitButton: function() { return $(this.zipChangerSubmitButtonId) }, 
  metroInput: function() { return $(this.metroInputId) }, 
  metro: function() { return $F(this.metroInput()) }, 
  zipInput: function() { return $(this.zipInputId) }, 
  zip: function() { return $F(this.zipInput()) }, 
  radiusInput: function() { return $(this.radiusInputId) }, 
  radius: function() { return $F(this.radiusInput) }, 
  
  // logical groups, useful for serialization
  metroInputs: function() { return [this.metroInput()] }, 
  zipInputs: function() { return [this.radiusInput(), this.zipInput()] },
  
  // gui
  
  byZip: function() {
    this.clear();
    CategoryChooser.hide();
    this.metroChanger().hide();
    this.zipChanger().show();
  }, 
  
  byMetro: function() {
    this.clear();
    this.restoreMetro();
    this.zipChanger().hide();
    this.metroChanger().show();
    CategoryChooser.show();
  }, 
  
  recordMetro: function() {
    this.oldMetro = this.metro();
  }, 
  
  restoreMetro: function() {
    if (typeof this.oldMetro != 'undefined') {
      this.metroInput().value = this.oldMetro;
    }
  }, 
  
  clear: function() {
    this.zipInput().clear();
    this.metroInput().clear();
    this.zipChangerSubmitButton().disabled = false;
  }, 
  
  // backend
  
  submitMetro: function(refreshPage) {
    this.submit(Form.serializeElements(this.metroInputs()), refreshPage);
  }, 
  
  submitZip: function(refreshPage) {
    this.zipChangerSubmitButton().disabled = true;
    this.submit(Form.serializeElements(this.zipInputs()), refreshPage);
  }, 
  
  submit: function(params, refreshPage) {
    if (typeof refreshPage != 'undefined')
      params += "&refresh_page=" + refreshPage;
    
    new Ajax.Request('/my_account/update_geo', {
      asynchronous: false, 
      evalScripts: true, 
      method: 'post', 
      parameters: params
    });
  }, 
  
  // events
  
  onMetroChange: function(refreshPage) {
    if (this.metro() == 'other') {
      this.byZip();
    } else if (this.metro()) {
      this.submitMetro(refreshPage);
    }
  }, 
  
  onCancelZip: function() {
    this.byMetro();
  }
}


var CalendarSubscription = {
  containerId: 'hidden_content_setup_calendar', 
  
  ensureContainerExists: function() {
    if (!$(this.containerId)) {
      var container = document.createElement('div');
      container.id = this.containerId;
      document.body.appendChild(container);
    }
  }, 
  
  setup: function(options) {
    this.ensureContainerExists();
    
    new Ajax.Updater(CalendarSubscription.containerId, '/subscription/setup', {
      asynchronous: true, 
      evalScripts: true, 
      method: 'get', 
      parameters: $H(options).toQueryString(), 
      
      onComplete: function(request) {
        RedBox.addHiddenContent(CalendarSubscription.containerId);
      }, 
      
      onLoading: function(request) {
        RedBox.loading();
      }
    });
  }, 
  
  updateCalendar: function(type) {
    new Ajax.Request('/my_events/update_calendar', {method: 'get', parameters: 'id=' + type});
  }, 
  
  setSubscribed: function() {
    if (!Me.hasEverSubscribed) {
      Me.hasEverSubscribed = true;
      if (EventCard.pendingEventCard) {
        EventCard.pendingEventCard.setSavedToCalendar(true);
        delete EventCard.pendingEventCard;
      }
    }
  }
}

/******* Event extensions *******/

Object.extend(Event, {
  preventDefault: function(event) {
    if (event.preventDefault)
      event.preventDefault();
    else
      event.returnValue = false;
  }
});

/******* Element extensions *******/

Element.addMethods({
  anchor: function(element, expression) {
    var children = Selector.matchElements($A(element.childNodes), expression || '*');
    var first, last;
    while (children.length) {
      if (!first && children.first().visible())
        (first = children.shift()).addClassName('first');
      else if (!last && children.last().visible())
        (last = children.pop()).addClassName('last');
      else
        children.shift().removeClassName('first').removeClassName('last');
    }
    return null;
  },
    
  ancestorWithClass: function(element, klass) {
    while (element && !$(element).hasClassName(klass)) element = element.parentNode;
    return element;
  }, 
  
  objectId: function(element) {
    return element.id.replace(/^.*_(\d+)$/, '$1');
  }
});

/******* Function extensions *******/

Function.prototype.delay = function(ms) {
  var binding = $A(arguments);
  binding.shift();
  return setTimeout(binding.length ? this.bind.apply(this, binding) : this, ms);
}

/******* Hash extensions *******/

Object.extend(Hash.prototype, {
  indexOf: function(value) {
    var pair = this.find(function(p){ return p.value == value });
    return pair ? pair.key : null;
  },
  
  toOrderedQueryString: function() {
    return this.toQueryString.call(this.flatten().toArray().sortBy(function(pair){ return pair.key }).inject({}, function(memo,pair){ memo[pair.key] = pair.value; return memo }));
  },
  
  flatten: function() {
    return this.flattenHelper($H(), '');
  },
  
  flattenHelper: function(target, key) {
    var simple_and_complex = this.toArray().partition(function(pair) { return typeof pair.value != 'object' });
    var simple = simple_and_complex[0], complex = simple_and_complex[1];

    simple.each(function(pair) {
      target[key ? (key + '[' + pair.key + ']') : pair.key] = pair.value;
    });
    
    complex.each(function(pair) {
      $H(pair.value).flattenHelper(target, key ? (key + '[' + pair.key + ']') : pair.key);
    });
    
    return target;
  }
});

/******* String extensions *******/
Object.extend(String.prototype, {
  pluralize: function(n, plural) {
    plural = plural || this + 's';
    return n.toString() + ' ' + (n == 1 ? this : plural)
  }
})


/******* Make anchor stop any other pending events *******/
function gotoLink(currentObject, event) {
  Event.stop(event)
  document.location.href = currentObject.href;
}

/******* Add event card common javascript *******/
var EventCard = Class.create();
// instance methods
Object.extend(EventCard.prototype, {
  
  // instance variables
  
  _values: {}, 
  
  // initializers
  
  initialize: function(element) {
    this.element = element;
    this.eventId = this.element.objectId();
    this.initElements();
    this.initValues();
  }, 
  
  initElements: function() {
    this.hiddenContent        = 'redbox_hidden_content'
    this.saveToCalendarButton = this.element.getElementsByClassName('cal_sync')[0]
    this.calSyncButton        = this.saveToCalendarButton;  // alias
    this.autoSaveDisplay      = this.element.getElementsByClassName('auto_saved')[0];
    this.notInterestedButton  = this.element.getElementsByClassName('not_interested')[0];
    this.pickedButton         = this.element.getElementsByClassName('picked')[0];
    this.attendingButton      = this.element.getElementsByClassName('attending')[0];
  }, 
  
  initValues: function() {
    this._values = {
      cal_sync: this.getValue('cal_sync'), 
      not_interested: this.getValue('not_interested'), 
      picked: this.getValue('picked'), 
      attending: this.getValue('attending')
    };
  }, 
  
  release: function() {
    this.element.__event_card__ = null;
    delete this.element;
    delete this.saveToCalendarButton;
    delete this.autoSaveDisplay;
    delete this.notInterestedButton;
    delete this.pickedButton;
    delete this.attendingButton;
  }, 
  
  // talkin' to the server
  
  update: function(parameters) {
    var fbToken = window.fb_update_token_id == null ? '' : ('fb_token_id=' + window.fb_update_token_id + '&')
    parameters = fbToken + parameters + '&id=' + this.eventId;
    new Ajax.Request('/' + EventCard.controller + '/update', {parameters: parameters});
  }, 
  
  downloadIcs: function() {
    if (Me.calendar.ics) {
      window.location.href = window.ics_url + '?event_id=' + this.eventId;
    }
  }, 
  
  // auto-saved functions
  
  toggleAutoSavedToCalendar: function() {
    this.setAutoSavedToCalendar(!this.isAutoSavedToCalendar());
  }, 
  
  setAutoSavedToCalendar: function(value) {
    if (value) {
      this.autoSaveDisplay.show();
    } else {
      this.autoSaveDisplay.hide();
    }
  }, 
  
  isAutoSavedToCalendar: function() {
    return this.autoSaveDisplay.visible();
  }, 
  
  // saved to calendar stuff
  
  onClickSaveToCalendar: function() {
    this.toggleSavedToCalendar();
  }, 
  
  toggleSavedToCalendar: function() {
    this.setSavedToCalendar(!this.isSavedToCalendar());
  }, 
  
  setSavedToCalendar: function(value) {
    if (!Me.hasEverSubscribed) {
      // show the dialog instead of setting everything
      EventCard.pendingEventCard = this;
      CalendarSubscription.setup({event_id: this.eventId});
    } else {
      this.update('cal_sync=' + (value ? '1' : '0'));
      this._setSavedToCalendar(value);
      this.setAutoSavedToCalendar(false);
      if (value) this.showInterest();
      this.downloadIcs();
      this._syncDisplay();
    }
  }, 
  
  isSavedToCalendar: function() {
    return this.getValue('cal_sync');
  }, 
  
  _setSavedToCalendar: function(bool) {
    if (this.saveToCalendarButton) {
      this.setValue('cal_sync', bool);
    }
  }, 
  
  // not interested stuff
  
  onClickNotInterested: function() {
    this.toggleNotInterested();
  }, 
  
  toggleNotInterested: function() {
    this.setNotInterested(true);
  }, 
  
  setNotInterested: function(value) {
    this.update('not_interested=' + (value ? '1' : '0'));
    this._setNotInterested(value);
    if (value) {
      this._setAttending(!value);
      this._setPicked(!value);
      this._setSavedToCalendar(!value);
    }
    this._syncDisplay();
  }, 
  
  isNotInterested: function() {
    return this.getValue('not_interested');
  }, 
  
  _setNotInterested: function(bool) {
    this.setValue('not_interested', bool);
  }, 
  
  // picked stuff
  
  onClickPicked: function() {
    // if the user has the ability to explicitly declare non-interest
    // in an event, don't make this toggle - just turn it on
    if (this.notInterestedButton) {
      this.setPicked(true);
    } else {
      this.togglePicked();
    }
  }, 
  
  togglePicked: function() {
    this.setPicked(!this.isPicked());
  }, 
  
  setPicked: function(value) {
    this.update('picked=' + (value ? '1' : '0'));
    this._setPicked(value);
    if (value) {
      this._setAttending(!value);
      this._setNotInterested(!value);
    }
    this._syncDisplay();
  }, 
  
  isPicked: function() {
    return this.getValue('picked');
  }, 
  
  _setPicked: function(bool) {
    this.setValue('picked', bool);
  }, 
  
  // attending stuff
  
  onClickAttending: function() {
    // if the user has the ability to explicitly declare non-interest
    // in an event, don't make this toggle - just turn it on
    if (this.notInterestedButton) {
      this.setAttending(true);
    } else {
      this.toggleAttending();
    }
  }, 
  
  toggleAttending: function() {
    this.setAttending(!this.isAttending());
  }, 
  
  setAttending: function(value) {
    this.update('attending=' + (value ? '1' : '0'));
    this._setAttending(value);
    if (value) {
      this._setPicked(!value);
      this._setNotInterested(!value);
    }
    this._syncDisplay();
  }, 
  
  isAttending: function() {
    return this.getValue('attending');
  }, 
  
  _setAttending: function(bool) {
    this.setValue('attending', bool);
  }, 
  
  // general interest
  
  getValue: function(type) {
    if (!this.hasValue(type) && this.buttonForType(type))
      this.setValue(type, this.buttonForType(type).getAttribute('on') == 'true');
    return this._getValue(type);
  }, 
  
  hasValue: function(type) {
    return typeof this._getValue(type) != 'undefined';
  }, 
  
  _getValue: function(type) {
    return this._values[type];
  }, 
  
  setValue: function(type, value) {
    var button = this.buttonForType(type);
    if (button) {
      button.setAttribute('on', value ? 'true' : 'false');
    }
    this._values[type] = value;
  }, 
  
  buttonForType: function(type) {
    return this[type.dasherize().camelize() + 'Button'];
  }, 
  
  showInterest: function() {
    if (!this.isPicked() && !this.isAttending()) this.setPicked(true);
  }, 
  
  hideCard: function() {
    this.element.hide();
  },

  requestNextCard: function() {
    new Effect.Fade(this.element);
    var fbToken = window.fb_next_token_id == null ? '' : ('fb_token_id=' + window.fb_next_token_id + '&')
    new Ajax.Request('/' + EventCard.controller + '/show_next_four_events' , {parameters: fbToken + 'page=' + $F('popular_page') + '&event_id=' + this.eventId});
  },
  
  // tell friends stuff
  
  onClickTellFriends: function() {
    this.tellFriends();
  }, 
  
  tellFriends: function() {
    window.open('/publish/' + this.eventId + '/tell_friends?type=event', 
      'tellFriendsWindow',
      'width=476,height=576,toolbar=no,location=no,directories=no,status=no,' +
      'menubar=no,scrollbars=no,copyhistory=yes,resizable=no');
    this.showInterest();
  }, 
  
  // mobile reminder stuff
  
  onClickSetMobileReminder: function() {
    this.setMobileReminder();
  }, 
  
  setMobileReminder: function() {
    new Ajax.Updater(this.hiddenContent, '/events/' + this.eventId + '/notifications/new', {
      asynchronous: true, 
      evalScripts: true, 
      method: 'get', 
      onComplete: function(request) { RedBox.addHiddenContent(this.hiddenContent) }.bind(this), 
      onLoading: function(request) { RedBox.loading() }
    });
    this.showInterest();
  }, 
  
  // UI stuff
  
  toggle: function() {
    this.element.toggleClassName('expanded').toggleClassName('collapsed');
  }, 
  
  _checkButton: function(button) {
    button.className = button.className.replace(/-off\b/, '-on');
  }, 
  
  _uncheckButton: function(button) {
    button.className = button.className.replace(/-on\b/, '-off');
  }, 
  
  _syncDisplay: function() {
    if (this.attendingButton != null && this.isAttending()) {
      this._checkButton(this.attendingButton);
    } else {
      this._uncheckButton(this.attendingButton);
    }
    if (this.pickedButton != null && this.isPicked()) {
      this._checkButton(this.pickedButton);
    } else {
      this._uncheckButton(this.pickedButton);
    }
    if (this.saveToCalendarButton != null && this.isSavedToCalendar()) {
      this._checkButton(this.saveToCalendarButton);
    } else if(this.saveToCalendarButton != null) {
      this._uncheckButton(this.saveToCalendarButton);
    }
    if (this.notInterestedButton != null && this.isNotInterested()) {
      this._checkButton(this.notInterestedButton);
    } else if(this.notInterestedButton != null) {
      this._uncheckButton(this.notInterestedButton);
    }
  }
});

// class methods
Object.extend(EventCard, {
  __cards__: [],
  
  forElement: function(element) {
    var el = element, card;
    
    card = EventCard._forElement(el);
    if (card) {
      return card;
    }
    // look for the container
    while (el && !$(el).hasClassName('vevent')) el = el.up();
    if (!el) {
      // we're probably on the event detail page
      el = element;
    }
    
    return EventCard._createForElement(el);
  }, 
  
  _forElement: function(el) {
    return el.__event_card__;
  }, 
  
  _createForElement: function(el) {
    if (!el.__event_card__) {
      EventCard.__cards__.push(el.__event_card__ = new EventCard(el));
    }
    return EventCard._forElement(el);
  }, 
  
  unloadCache: function() {
    EventCard.__cards__.invoke('release');
    EventCard.__cards__ = [];
  }, 
  
  hideCard: function(element) {
    EventCard.forElement(element).hideCard();
  },
  
  toggle: function(element) {
    EventCard.forElement(element).toggle();
  }, 
  
  onClickSaveToCalendar: function(element) {
    EventCard.forElement(element).onClickSaveToCalendar();
  }, 
  
  toggleSavedToCalendar: function(element) {
    EventCard.forElement(element).toggleSavedToCalendar();
  }, 
  
  onClickNotInterested: function(element) {
    EventCard.forElement(element).onClickNotInterested();
  },
  
  toggleNotInterested: function(element) {
    EventCard.forElement(element).toggleNotInterested();
  },
  
  onClickPicked: function(element) {
    EventCard.forElement(element).onClickPicked();
  }, 
  
  togglePicked: function(element) {
    EventCard.forElement(element).togglePicked();
  }, 
  
  onClickAttending: function(element) {
    EventCard.forElement(element).onClickAttending();
  }, 
  
  toggleAttending: function(element) {
    EventCard.forElement(element).toggleAttending();
  }, 
  
  onClickTellFriends: function(element) {
    EventCard.forElement(element).onClickTellFriends();
  }, 
  
  tellFriends: function(element) {
    EventCard.forElement(element).tellFriends();
  }, 
  
  onClickSetMobileReminder: function(element) {
    EventCard.forElement(element).onClickSetMobileReminder();
  },
  
  setMobileReminder: function(element) {
    EventCard.forElement(element).setMobileReminder();
  },
  
  requestNextCard: function(element) {
    EventCard.forElement(element).requestNextCard();
  },
  
  setControllerName: function(controller) {
    EventCard.controller = controller;
  }, 
  
  startHighlight: function(element) {
    element.className = element.className.replace(/-off\b/, '-on');
  }, 
  
  stopHighlight: function(element) {
    element.className = element.className.replace(/-on\b/, '-off');
  }
});

EventCard.controller = 'my_events';

/* prevent memory leaks in IE */
if (Prototype.Browser.IE)
  Event.observe(window, 'unload', EventCard.unloadCache, false);


var Suggestion = Class.create();
Object.extend(Suggestion.prototype, {
  initialize: function(element, callback) {
    this.setElement(element);
    this.callback = callback;
    this.initFloater();
  },
  
  setElement: function(element) {
    this.element = $(element);
    this.element.__suggestion__ = this;
    new Form.Element.Observer(this.element, 0.1, this.updateSuggestion.bind(this));
    this.element.observe('focus', this.onFocus.bind(this));
    this.element.observe('blur', this.onBlur.bind(this));
    this.element.observe('keypress', this.onKeypress.bind(this));
  }, 
  
  initFloater: function() {
    var f = this.floater = $(document.createElement('div'));
    f.id = this.element.id + '_suggestion';
    f.className = 'suggestion';
    document.body.appendChild(f);
    
    this.resetFloater();
    this.updateSuggestion();
  }, 
  
  resetFloater: function() {
    var f = this.floater;
    
    f.setStyle({
      'padding-left': null, 
      'padding-right': null, 
      'padding-top': null, 
      'padding-bottom': null, 
      'color': null, 
      'width': null, 
      'height': null,  
      'border-left-color': null, 
      'border-right-color': null, 
      'border-top-color': null, 
      'border-bottom-color': null
    });
    
    Position.absolutize(f);
    Position.clone(this.element, f);
    
    var offsetX = parseInt(f.getStyle('left')), offsetY = parseInt(f.getStyle('top'));
    offsetX += parseInt(this.element.getStyle('padding-left')) - parseInt(f.getStyle('padding-left'));
    offsetY += parseInt(this.element.getStyle('padding-top')) + parseInt(f.getStyle('padding-top'));
    offsetY += parseInt(this.element.getStyle('font-size')) * 1.2 /* try to get line-height */;
    
    f.style.left = offsetX + 'px';
    f.style.top = offsetY + 'px';
    f.style.width = '';
    f.style.height = '';
    
    f.hide();
  }, 
  
  updateSuggestion: function() {
    var suggestion = this.callback(this.getValue());
    if (suggestion) {
      this.floater.innerHTML = suggestion;
      if (this.hasFocus)
        this.showFloater();
    } else {
      this.hideFloater();
    }
  }, 
  
  useSuggestion: function() {
    this.setValue(this.floater.innerHTML);
  }, 
  
  animateSuggestion: function() {
    if (!this.floater.visible()) return false;
    
    var pos = Position.cumulativeOffset(this.element);
    // makes it appear that floater has morphed to become element, 
    // though really it just looks exactly likes it and then disappears
    this.floater.morph({
      left: pos[0] + 'px', 
      top: pos[1] + 'px', 
      'color': this.element.getStyle('color'), 
      'border-left-color': this.element.getStyle('border-left-color'), 
      'border-right-color': this.element.getStyle('border-right-color'), 
      'border-top-color': this.element.getStyle('border-top-color'), 
      'border-bottom-color': this.element.getStyle('border-bottom-color'), 
      'width': this.element.getStyle('width'), 
      'height': this.element.getStyle('height'), 
      'padding-left': this.element.getStyle('padding-left'), 
      'padding-right': this.element.getStyle('padding-right'), 
      'padding-top': this.element.getStyle('padding-top'), 
      'padding-bottom': this.element.getStyle('padding-bottom')
    }, {duration: 0.25, afterFinish: function(){ this.useSuggestion(); this.resetFloater() }.bind(this)});
    
    return true;
  }, 
  
  showFloater: function() {
    this.resetFloater();
    this.floater.show();
  }, 
  
  hideFloater: function() {
    this.floater.hide();
  }, 
  
  onFocus: function() {
    this.hasFocus = true;
  }, 
  
  onBlur: function() {
    this.hasFocus = false;
    this.animateSuggestion();
  }, 
  
  onKeypress: function(e) {
    if (e.keyCode == Event.KEY_RETURN) {
      if (this.animateSuggestion()) {
        var next = this.element.next('.text');
        if (next) next.activate();
        Event.stop(e);
      }
    }
  }, 
  
  getValue: function() {
    return this.element.getValue();
  }, 
  
  setValue: function(value) {
    this.element.value = value;
  }
});

Object.extend(Suggestion, {
  forElement: function(element) {
    return element.__suggestion__;
  }
});

function showAlertMessage(divName, textString, options) {
  if(textString != null) {
    $(divName + '_text').innerHTML = textString;
  } 
  new TransparentMenu(divName,options);
}

function showSignupMessage(divName, textString, options) {
  if(textString != null) {
    $('alert_text').innerHTML = textString;
  } 
  new TransparentMenu(divName,options);
}

RegExp.escape = function(text) { return text.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') }

function removeSavedSearch(searchId) {
  new Ajax.Request(route.url('search', 'destroy'), {parameters: 'search_id=' + searchId});
}

function addSearch(searchTerm, userId) {
  new Ajax.Request(route.url('search', 'create'), {parameters: 'search[search]=' + searchTerm + '&user_id=' + userId});
}
