/**
 * Dishola Javascripts
 * Copyright 2008, Dishola.
 * @author Lindsey Simon <lsimon@dishola.com>
 *
 */

/**
 * Overall code container for generic functionality.
 * @type {Object}
 */
var Dishola = {};


/**
 * window unload handler
 */
Dishola.onUnload = function() {
  Dishola.resetSubmitButtons();
};

/**
 * Ajax generic error handling
 */
Ajax.Responders.register({
  onException: function(requestObj, exception) {
    // fix for a FF bug that throws an exception if the listener is gone away && exception.name.match( /80040111/ )
    if (exception &&
        exception.message && exception.message.match(/80040111/)) {
      return;
    }
  }
});
Dishola.Ajax = {};

Dishola.Ajax._phpErrorMatch = new RegExp(
    '<b>(Warning|Fatal Error|Parse Error)</b>:', 'i');

Dishola.Ajax._onComplete = function(transport) {

  // look for weird PHP errors in the text
  if (transport.responseText.match(Dishola.Ajax._phpErrorMatch)) {
    throw {
      name: 'PHP error, either Notice, Warning, or Fatal',
      stack: response.transport.responseText
    };
  }
  // did we get a json object?
  else if (transport.responseJSON) {
    //console.debug('onComplete transport.responseJSON', transport.responseJSON);

    // we are trying an ajax operation but we oughta be logged in
    if (transport.responseJSON.message == 'needlogin') {
      //console.debug('captured that we need login');
      var returnTo = transport.request.url;
      Dishola.forceLogin(returnTo);
      return;

      //window.location = loginUrl;
      //return;
    }
    // do we want to show a specific message?
    else if (!transport.responseJSON.success &&
             transport.responseJSON.message) {

      // show the message in the open popup.
      if (this._popupWindow) {
        var popupContent = $('popupWindow_content');
        popupContent.innerHTML = '<div id="d-popup-status">' +
            transport.responseJSON.message + '</div>' +
            '<div id="d-popup-status-close"></div>';
        popupContent.removeClassName('loadingSpinner32');
        popupContent.style.backgroundImage = 'none';
        // highlight our fancy message.
        var eff = new Effect.Highlight('d-popup-status');
        // close the window on a timer.
        Dishola.popupWindow.timer = 2;
        Dishola.countdownPopupWindow();

      // Maybe a bad form submit?
      } else {
        alert(transport.responseJSON.message);
        Dishola.resetSubmitButtons();
      }
      return;
    }
    // We got a redirect message instead of a message.
    else if (transport.responseJSON.location) {
      if (this._popupWindow) {
        Dishola.popupWindow.close();
      }
      window.location = transport.responseJSON.location;
      return;
    }
    // something's borked, sorry
    else if (!transport.responseJSON.success) {
      //console.debug(json);
      alert("Oops, there's something wrong on the server. We're sorry, please try again in a bit.");
      return;
    }
  }

  // pass to original oncomplete if we get past the error check
  if (this._onComplete) {
    this._onComplete(transport);
  }
  this._onComplete = null;
};

Dishola.Ajax.Request = function(url, options) {
  //console.debug('Dishola.Ajax.Request', url);
  options = options || {};
  this._onComplete = options.onComplete;
  this._popupWindow = options.popupWindow;
  options.onComplete = Dishola.Ajax._onComplete.bind(this);
  new Ajax.Request(url, options);
};

Dishola.Ajax.Updater = function(el, url, options) {
  //console.debug('Dishola.Ajax.Updater', url);
  options = options || {};
  this._onComplete = options.onComplete;
  this._popupWindow = options.popupWindow;
  options.onComplete = Dishola.Ajax._onComplete.bind(this);
  var xhr = new Ajax.Updater(el, url, options);
};

/**
 * Forces a lightbox login.
 * @param {string|event} returnTo
 * @param {boolean} opt_showCloseButton Can they close this login?
 */
Dishola.forceLogin = function(returnTo, opt_showCloseButton) {
  var event = null;
  // In case this was set as an event listener and returnTo is the e object.
  if (typeof returnTo == 'object') {
    event = returnTo;
    returnTo = Event.element(returnTo).href;
  }

  var showCloseButton = typeof opt_showCloseButton != 'undefined' ?
      opt_showCloseButton : true;

  var loginUrl = '/users/login?return=' + encodeURIComponent(returnTo);
  //alert('login:'+ loginUrl);
  var title = 'Please Sign In or Join Dishola';

  Dishola.hijackForm(event, {href: loginUrl, title: title}, 450, 200,
      false, showCloseButton);
};

/**
 * Pseudo get-satisfaction
 */
Dishola.enableSiteFeedback = function() {
  var feedbackLink = document.createElement('a');
  feedbackLink.href = '/features/contact?page=' +
      encodeURIComponent(window.location.href);
  feedbackLink.id = 'd-feedback';
  feedbackLink.innerHTML = 'FEEDBACK';
  document.body.appendChild(feedbackLink);
};

/**
 * enhanceForm
 * @param {null|string} optional id
 */
Dishola.enhanceForm = function(id) {
  Dishola.watchSubmitButtons(id);
  Dishola.watchFormFields(id);
};

/**
 * Holds a list of submit buttons on the page
 * @type {Array}
 */
Dishola._submitButtons = [];

/**
 * watchSubmitButtons
 * @param {null|string} optional id
 */
Dishola.watchSubmitButtons = function(id) {
  var submitButtons;
  if (id) {
    Dishola._submitButtons = $(id).select('.submitButton');
  }
  // this means we can style button without getting the
  // value change
  else {
    Dishola._submitButtons = $$('.submitButton');
  }
  Dishola._submitButtons.each(function(button) {
    button._originalValue = button.value;
    button.observe(
      'click',
      function(e) {
        //button.disabled = true; // this causes ie6 not to work
        button.value = 'cool, hang on ...';
        button.addClassName('submitButtonClicked');
      }
    );
  });
};

/**
 * Resets the submit buttons in case the user comes Back.
 */
Dishola.resetSubmitButtons = function() {
  Dishola._submitButtons.each(function(button) {
    button.disabled = false;
    button.value = button._originalValue;
    button.removeClassName('submitButtonClicked');
  });
};

/**
 * watchFormFields
 * @param {null|string} optional id
 */
Dishola.watchFormFields = function(id) {
  var fieldTypes = [
    'input[type=text]',
    'input[type=password]',
    'input[type=Ubox]',
    'textarea',
    'select'
  ];
  if (id) {
    for (var i = 0, fieldType; fieldType = fieldTypes[i]; i++) {
      fieldTypes[i] = '#' + id + ' ' + fieldType;
    }
  }

  $$(fieldTypes.join(',')).each(function(el) {
    el.observe(
      'focus',
      function(e) { this.addClassName('d-field-focus'); }
    );
    el.observe(
      'blur',
      function(e) { this.removeClassName('d-field-focus'); }
    );
  });
};

/**
 * Hijack any login links and turn them into lightboxes.
 */
Dishola.hijackLoginLinks = function() {
  //var links = $$('a[href="/users/login"]');
  var realLinks = [];
  var links = document.getElementsByTagName('a');
  for (var i = 0, link; link = links[i]; i++) {
    if (link.href.match('login')) {
      realLinks.push(link);
    }
  }
  $A(realLinks).each(function(link) {
    Dishola.hijackLink(link, null, null, false);
  });
};

/**
 * Confirm or deny - a generic window.confirm handler.
 * @param {Event} e
 * @param {string} opt_message Optional confirmation message to show.
 */
Dishola.confirm = function(e, opt_message) {
  var message = opt_message || 'Are you sure?';
  var confirm = window.confirm(message);
  if (!confirm) {
    Event.stop(e);
  }
};

/**
 * Initialize user profile edit page.
 */
Dishola.initProfileEdit = function() {

  Dishola.profileUserId_ = $F('ProfileUserId');

  // watch for change on the user image select.
  $('ProfileImage').observe(
    'change', Dishola.changeUserIcon);

  // special code for the tip/motto/quote
  Dishola.initProfileTipMottoQuote();

  // watch for change on the user header image select.
  $('ProfileHeaderImage').observe(
    'change', Dishola.changeUserIcon);

  Dishola.toggleProfileIndustry();
  $('ProfileProfessionalEver0').observe(
    'click', Dishola.toggleProfileIndustry);

  $('ProfileProfessionalEver1').observe(
    'click', Dishola.toggleProfileIndustry);

  Dishola.toggleProfileWriting();
  $('ProfileWritingEver0').observe(
    'click', Dishola.toggleProfileWriting);

  $('ProfileWritingEver1').observe(
    'click', Dishola.toggleProfileWriting);
};

/**
 * initProfileTipMottoQuote
 */
Dishola.initProfileTipMottoQuote = function() {

  // Tip/Motto/Quote text input should fill the h2 value
  Dishola.profileTipMottoQuoteContainer_ = $('d-profile-motto');
  Dishola.profileTipMottoQuoteContainer_.dimensions =
      Dishola.profileTipMottoQuoteContainer_.getDimensions();
  //console.debug(Dishola.profileTipMottoQuoteContainer_.dimensions);

  Dishola.profileTipMottoQuoteInput_ = $('ProfileTipMottoQuote');
  Dishola.profileTipMottoQuoteText_ = $('d-profile-motto-text');
  Dishola.profileTipMottoQuoteText_.dimensions = {};
  Dishola.updateProfileTipMottoQuoteText();

  Dishola.profileTipMottoQuoteInput_.observe(
    'keydown', Dishola.updateProfileTipMottoQuoteText);

  Dishola.profileTipMottoQuoteInput_.observe(
    'keyup', Dishola.updateProfileTipMottoQuoteText);

  Dishola.profileTipMottoQuoteTopInput_ = $('ProfileTipMottoQuoteTop');
  Dishola.profileTipMottoQuoteLeftInput_ = $('ProfileTipMottoQuoteLeft');


  // Tip/Motto/Quote is draggable
  Dishola.profileTipMottoQuoteText_.addClassName('d-draggable');
  var d = new Draggable(Dishola.profileTipMottoQuoteText_, {
    snap: function(x, y) {
      var maxX = Dishola.profileTipMottoQuoteContainer_.dimensions.width -
          Dishola.profileTipMottoQuoteText_.dimensions.width;
      var maxY = Dishola.profileTipMottoQuoteContainer_.dimensions.height -
          Dishola.profileTipMottoQuoteText_.dimensions.height;

      //console.debug(x, y, ' - ', maxX, maxY);
      var newX = (x > maxX) ? maxX : ((x < 0) ? 0 : x);
      var newY = (y > maxY) ? maxY : ((y < 0) ? 0 : y);
      return [ newX, newY ];
    },

    onEnd: function(e) {
      var top = parseInt(Dishola.profileTipMottoQuoteText_.style.top, 10);
      var left = parseInt(Dishola.profileTipMottoQuoteText_.style.left, 10);
      Dishola.profileTipMottoQuoteTopInput_.value = top;
      Dishola.profileTipMottoQuoteLeftInput_.value = left;
    }
  });
};

/**
 * updateProfileTipMottoQuoteText
 */
Dishola.updateProfileTipMottoQuoteText = function(e) {
  var tipText = $F(Dishola.profileTipMottoQuoteInput_);
  Dishola.profileTipMottoQuoteText_.innerHTML = tipText;

  // hide the blue box if empty
  if (tipText === '') {
    Dishola.profileTipMottoQuoteText_.hide();
  } else {
    Dishola.profileTipMottoQuoteText_.show();
  }

  // update the dimensions with the new text
  Dishola.profileTipMottoQuoteText_.dimensions =
      Dishola.profileTipMottoQuoteText_.getDimensions();


  //console.debug(Dishola.profileTipMottoQuoteText_.dimensions);
};

/**
 * changeUserIcon
 *
 * Swap out the visual of the user icon
 * @param {Event} e
 */
Dishola.changeUserIcon = function(e) {
  var select = Event.element(e);
  var selected = $F(select);
  //console.debug('Dishola.changeUserIcon selected: ' + selected);
  // set src
  $(select.id + '-image').src = selected;
};

/**
 * toggleProfileIndustry
 * @param {Event} e
 */
Dishola.toggleProfileIndustry = function(e) {
  //console.debug('Dishola.toggleProfileIndustry f:' + $F('ProfessionalEver1'));
  if ($F('ProfileProfessionalEver1') == 1) {
    $('profile-industry-container').show();
  }
  else {
    $('profile-industry-container').hide();
  }
};

/**
 * toggleProfileWriting
 * @param {Event} e
 */
Dishola.toggleProfileWriting = function(e) {
  if ($F('ProfileWritingEver1') == 1) {
    $('profile-writing-container').show();
  }
  else {
    $('profile-writing-container').hide();
  }
};

/**
 * Resizes the popup window to fit the contents.
 */
Dishola.resizePopup = function() {
  var popupWindow = $('popupWindow');
  var popupContent = $('popupWindow_content');
  popupContent.removeClassName('loadingSpinner32');
  popupContent.style.backgroundImage = 'none';

  var pageContent = popupContent.firstDescendant();
  var newContentHeight = pageContent.offsetHeight + 10; // 10 for bottom.

  var currentContentHeight = parseInt(popupContent.style.height, 10);
  var currentWindowHeight = parseInt(popupWindow.style.height, 10);

  var delta = currentContentHeight - newContentHeight;
  var newPopupHeight = (currentWindowHeight - delta) + 10; // 10 for bottom.

  popupContent.style.height = newContentHeight + 'px';
  popupWindow.style.height = newPopupHeight + 'px';

  // change it on the object in case it hasn't already rendered.
  Dishola.popupWindow.height = newPopupHeight;

  // Re-center
  Dishola.popupWindow.showCenter();
};

/**
 * countdownPopupWindow
 */
Dishola.countdownPopupWindow = function() {

  //console.debug('Dishola.countdownPopupWindow timer: ' +
      //Dishola.popupWindow.timer);

  var closeEl = $('d-popup-status-close');

  // return if they already closed it
  if (!closeEl)  {
    return;
  }

  if (Dishola.popupWindow.timer === 0) {
    Dishola.popupWindow.close();
  }
  else {
    /*
    closeEl.innerHTML = 'This window will automatically close in ' +
        Dishola.popupWindow.timer + ' seconds.';
    */
    Dishola.popupWindow.timer--;
    window.setTimeout(Dishola.countdownPopupWindow, 1000);
  }
};
/**
 * toggleAdvancedSearch
 * @param {Event} e
 */
Dishola.toggleAdvancedSearch = function(e) {
  var form = $('search-advanced-form');
  var text = $('search-advanced-text');
  var toggle = $('search-advanced-toggle');
  if (form.visible()) {
    form.hide();
    text.show();
    toggle.firstChild.nodeValue = 'Refine search';
  } else {
    form.show();
    $('quicksearch').focus();
    text.hide();
    toggle.firstChild.nodeValue = 'Hide search';
  }
  // to unfocus the link
  toggle.blur();

  // stop href
  Event.stop(e);
};

/**
 * Hijack a link and turn the href into a popup.
 * @param {Element|string} anchorEl The anchor.
 * @param {number} opt_width The width of the popup, optionally.
 * @param {number} opt_height The height of the popup, optionally.
 * @param {boolean} opt_hijackSubmit If true, hijack the form submit as well.
 */
Dishola.hijackLink = function(anchorEl, opt_width, opt_height,
    opt_hijackSubmit) {
  var width = opt_width || 450;
  var height = opt_height || 200;
  var hijackSubmit = typeof opt_hijackSubmit != 'undefined' ?
      opt_hijackSubmit : true;
  $(anchorEl).observe('click', function(e) {
    Dishola.hijackForm(e, this, width, height, hijackSubmit);
  });
};

/**
 * hijackForm and send the data as XHR post.
 * @param {Event} e
 * @param {Element} anchorEl The anchor.
 * @param {number} width The width of the popup.
 * @param {number} height The height of the popup.
 * @param {boolean} hijackSubmit If true, hijack the form submit as well.
 * @param {boolean} opt_showCloseButton If true, show X in the popup.
 */
Dishola.hijackForm = function(e, anchorEl, width, height,
    hijackSubmit, opt_showCloseButton) {

  var href = anchorEl.href;
  var showCloseButton = typeof opt_showCloseButton != 'undefined' ?
      opt_showCloseButton : true;

  if (Dishola.popupWindow) {
    Dishola.popupWindow.close();
  }

  // open a popup
  Dishola.popupWindow = new Window('popupWindow', {
    title: anchorEl.title || anchorEl.innerHTML,
    className: 'dialog',
    width: width,
    height: height,
    showEffect: Element.show,
    hideEffect: Element.hide,
    minimizable: false,
    maximizable: false,
    recenterAuto: false,
    destroyOnClose: true
  });

  Dishola.popupWindow.setDestroyOnClose();

  if (showCloseButton) {
    $('popupWindow_close').update('<button type="submitButton">X</button>');
  } else {
    $('popupWindow_close').remove();
  }
  $('popupWindow_content').addClassName('loadingSpinner32');

  // window properties and show
  Dishola.popupWindow.setAjaxContent(
    href,
    {
      method: 'get',
      popupWindow: true,
      onComplete: function(transport) {

        Dishola.resizePopup();

        // add coolness to form submits
        Dishola.enhanceForm('popupWindow_content');

        var popupContent = $('popupWindow_content');
        var form = $(popupContent.getElementsByTagName('form')[0]);
        form.focusFirstElement();

        if (hijackSubmit) {
          form.observe('submit', Dishola.hijackFormSubmit);
        }
      }
    },
    true, // showCentered
    true  // showModal
  );
  Dishola.popupWindow.showCenter();

  // Prevent following an href
  if (e) {
    Event.stop(e);
  }
};

/**
 * hijackFormSubmit
 * @param {Event} e
 */
Dishola.hijackFormSubmit = function(e) {
  var form = $(e.target);
  var xhr = new Dishola.Ajax.Request(form.action, {
    method: 'post',
    parameters: Form.serialize(form),
    onComplete: function(transport) {
      var json = transport.responseJSON;

      // form error of some kind
      if (!json.success) {
        Dishola.resetSubmitButtons();

      // need to reload a page
      } else if (json.location) {

        window.location = json.location;
        return;

      // closes the popup
      } else if (json.message) {
        $(form).insert({
          after: '<div id="d-popup-status">' + json.message + '</div>' +
          '<div id="d-popup-status-close"></div>'
        });

        // the status message has gone in after the form, so hide it.
        form.hide();

        // Resize
        Dishola.resizePopup();

        // highlight our fancy message.
        var eff = new Effect.Highlight('d-popup-status');

        // close the window on a timer.
        Dishola.popupWindow.timer = 2;
        Dishola.countdownPopupWindow();

        // Last but not least, eval anything back from the server.
        if (json.evalString) {
          eval(json.evalString);
        }
      }
    }
  });

  // prevent the form
  Event.stop(e);
};

/**
 * geolocateLocation
 * from the Edit Location page
 * @param {Event}
 */
Dishola.geolocateLocation = function(e) {

  /**
   * Open a popup.
   * @type {Window}
   */
  Dishola.popupWindow = new Window('popupWindow', {
    title: 'Location Lookup',
    className: 'dialog',
    width: 300,
    height: 500,
    minimizable: false,
    maximizable: false,
    destroyOnClose: true,
    recenterAuto: false,
    hideEffect: Element.hide
  });
  Dishola.popupWindow.setDestroyOnClose();

  $('popupWindow_close').update('<button type="submitButton">X</button>');
  $('popupWindow_content').addClassName('loadingSpinner32');


  var url = '/locations/geolocate';
  url += '/thoroughfare:' + $F('LocationThoroughfare');
  url += '/locality_name:' + $F('LocationLocalityName');
  url += '/administrative_area:' + $F('LocationAdministrativeArea');
  url += '/postal_code:' + $F('LocationPostalCode');
  url += '/country_name_code:' + $F('LocationCountryNameCode');

  // load in Ajax
  Dishola.popupWindow.setAjaxContent(
    url,
    {
      popupWindow: true,
      onComplete: function(transport) {
        $('popupWindow_content').removeClassName('loadingSpinner32');
        $('popupWindow_content').style.backgroundImage = 'none';

        var copyButton = $('d-locations-autogeo-copy');
        copyButton.observe('click', Dishola.geolocateCopyData);
      }
    }
   );

  Dishola.popupWindow.setLocation(200, 600);

  // window properties
  Dishola.popupWindow.show();

  // prevent href
  Event.stop(e);
};

/**
 * Copies geolocation info into the admin edit form
 * @param {Event} e
 */
Dishola.geolocateCopyData = function(e) {
  var map = {
    'd-autogeo-thoroughfare': 'LocationThoroughfare',
    'd-autogeo-locality_name': 'LocationLocalityName',
    'd-autogeo-administrative_area': 'LocationAdministrativeArea',
    'd-autogeo-sub_administrative_area': 'LocationSubAdministrativeArea',
    'd-autogeo-country_name_code': 'LocationCountryNameCode',
    'd-autogeo-postal_code': 'LocationPostalCode',
    'd-autogeo-latitude': 'LocationLatitude',
    'd-autogeo-longitude': 'LocationLongitude'
  };

  for (var key in map) {
    if ($(map[key]) && $(key)) {
      $(map[key]).value = $(key).value;
    }
  }
};

/**
 * checkUsernameAvailable
 * Just a static method for the user register page
 */
Dishola.checkUsernameAvailable = function() {
  var loadingEl = $('checkUserNameLoading');
  var messageEl = $('checkUserNameMessage');

  var username = document.forms.UserRegister.UserName.value;
  //console.debug('Dishola.checkUsernameAvailable username: ' + username);

  // simple validation
  if (!username) {
    alert('Please enter a Username first.');
    return;
  }

  // gotta be at least 5 chars
  if (username.length < 3 || username.length > 15) {
    alert('Your Username must be between 3 and 15 characters long.');
    return;
  }

  if (username.match(' ')) {
    alert('Your Username cannot contain spaces.');
    return;
  }

  // show loading
  messageEl.hide();
  messageEl.className = '';
  messageEl.innerHTML = 'Checking...';
  messageEl.show();
  loadingEl.show();

  // check
  var xhr = new Dishola.Ajax.Request('/users/checkfor/' + username, {
    onComplete: function(transport) {
      var json = transport.responseJSON;

      // hide loading
      loadingEl.hide();

      // set message
      messageEl.hide();
      messageEl.innerHTML = json.message;
      messageEl.className = json.className;
      messageEl.show();
    }
  });
};

/** Opens the video in a lightbox on the homepage.
 * @param {Event} e
 */
Dishola.openCelebrityVideo = function(e) {

  var href = Event.element(e).href;
  var windowname = 'd-movie';

  var width = 440;
  var height = 365;
  var x = (640 - width)/2, y = (480 - height)/2;

  if (screen) {
    x = (screen.availWidth - width)/2;
    y = (screen.availHeight - height)/2;
  }
  var options = 'status=0,toolbar=0,width=' + width + ',height=' + height + ',scrollbars=0,screenX=' + x + ',screenY=' + y + ',top=' + y+',left=' + x;

  var newWindow = window.open(href, windowname, options);

  // preventDefault
  Event.stop(e);
};

/**
 * Opens the video in a lightbox on the homepage.
 * @param {Event} e
Dishola.openCelebrityVideo = function(e) {

  var modal = false;
  var evalScripts = true;

  // open a popup
  Dishola.popupWindow = new Window('popupWindow', {
    title: 'Dishola Celebrity Disher Video',
    className: 'dialog',
    width: 435,
    height: 360,
    minimizable: false,
    maximizable: false,
    hideEffect: Element.hide
  });
  $('popupWindow_close').update('<button type="submitButton">X</button>');
  $('popupWindow_content').addClassName('loadingSpinner32');


  // ajax
  Dishola.popupWindow.setAjaxContent(
    Event.element(e).href,
    {
      popupWindow: true,
      evalScripts: true,
      onComplete: function() {
        $('popupWindow_content').removeClassName('loadingSpinner32');
      }
    }
    true,
    modal
  );

  // window properties
  Dishola.popupWindow.setDestroyOnClose();
  Dishola.popupWindow.showCenter(modal);

  // preventDefault
  Event.stop(e);
};
 */


/**
 * Dishola.QuickSearch
 * @param {String} defaultSearchQ
 * @constructor
 */
Dishola.QuickSearch = function(defaultSearchQ) {
  var qId = 'SearchQ';
  this._q = $(qId);
  this._qDefault = defaultSearchQ;
  this._location = $('SearchLocation');
  this._locationGhostText = $F(this._location);
  this._defaultSearchText = 'Search for...';

  // Don't insert default search text if they're already focused there.
  if ($F(this._q) == '') {
    if (document.activeElement && document.activeElement.id == qId) {
    } else {
      this._q.value = this._defaultSearchText;
    }
  }

  this._docClickListener = this.docClick.bindAsEventListener(this);

  this._q.observe(
    'focus',
    this.focus.bind(this)
  );
  this._q.observe(
    'blur',
    this.blur.bind(this)
  );
  this._location.observe(
    'focus',
    this.locationFocus.bind(this)
  );
  this._location.observe(
    'blur',
    this.locationBlur.bind(this)
  );

  // Installs a tag completer for locations.
  this._completer = new Dishola.TagCompleter(this._location, '/locations/json',
      false);
  this._location.observe(
    'keyup',
    this.hideLocationPopup.bind(this)
  );


  // init the location popup
  this._locationPopup = new Element('div', {
    className: 'd-q-popup'
  });
  this._locationPopup.style.width = this._location.offsetWidth - 23 + 'px';
  this._locationPopup.style.display = 'none';
  document.body.appendChild(this._locationPopup);

  // get the contents and observe clicks
  var xhr = new Dishola.Ajax.Updater(
      this._locationPopup, '/locations/topcities');

  this._locationPopup.observe(
    'click',
    this.locationPopupClick.bind(this)
  );

  // make sure return doesn't submit for no reason
  $('d-q-submit').observe(
    'click',
    this.submit.bind(this)
  );
};
Dishola.QuickSearch.prototype = {
  /**
  * focus
  * @param {Event} e
  */
  focus: function(e) {
    if ($F(this._q).match(this._defaultSearchText)) {
      this._q.value = '';
    }
  },

  /**
  * blur
  * @param {Event} e
  */
  blur: function(e) {
    if ($F(this._q) === '') {
      this._q.value = this._defaultSearchText;
    }
  },

  hideLocationPopup: function(e) {
    this._locationPopup.hide();
  },

  /**
  * @param {Event} e
  */
  locationFocus: function(e) {
    document.observe('click', this._docClickListener);
    this._location.select();
    this._locationPopup.clonePosition(
      this._location,
      {
        setWidth: false,
        setHeight: false,
        offsetLeft: 1,
        offsetTop: this._location.offsetHeight + 1
      }
    );
    this._locationPopup.show();
    Event.stop(e);
  },

  /**
  * locationBlur
  * @param {Event} e
  */
  locationBlur: function(e) {
    if ($F(this._location) === '') {
      this._location.value = this._locationGhostText;
    }
  },

  /**
  * locationPopupClick
  * @param {Event} e
  */
  locationPopupClick: function(e) {
    var el = Event.element(e);
    if (el.tagName == 'A' && !el.href.match('/locations/cities')
        && !el.href.match('/dishes/search')) {
      this._location.value = el.innerHTML;
      this._locationPopup.hide();
      Event.stopObserving(document, 'click', this._docClickListener);
      Event.stop(e);
    }
  },

  /**
  * docClick
  * @param {Event} e
  */
  docClick: function(e) {
    var el = Event.element(e);
    // if we click outside of the popup or the text field the popup goes away
    if (!Position.within(this._locationPopup, Event.pointerX(e),
                         Event.pointerY(e)) &&
        !Position.within(this._location, Event.pointerX(e),
                         Event.pointerY(e))
        ) {
      Event.stopObserving(document, 'click', this._docClickListener);
      this._locationPopup.hide();
    }
  },

  /**
  * Remove the string 'Search for ...'
  * @param {Event} e
  */
  submit: function(e) {
    var curSearchText = $F(this._q);

    // reset search value to nothing for submit
    if (curSearchText == this._qDefault) {
      this._q.value = '';
    }
  }
};

/**
 * Map
 * @type {Object}
 */
Dishola.Map = {};

/**
 * Google Maps API Key
 * @type {string}
 */
Dishola.Map.KEY = '';

/**
 * getDisholaIcon
 * @return {Object} GIcon
 */
Dishola.Map.getDisholaIcon = function() {
  if (!Dishola.Map.disholaIcon) {
    Dishola.Map.disholaIcon = new GIcon(G_DEFAULT_ICON);
    Dishola.Map.disholaIcon.image = '/img/dishola_map_marker.png';
    Dishola.Map.disholaIcon.iconSize = new GSize(13, 13);
    Dishola.Map.disholaIcon.shadow = '';
    Dishola.Map.disholaIcon.shadowSize = new GSize(0,0);
    Dishola.Map.disholaIcon.iconAnchor = new GPoint(5, 11);
  }
  return Dishola.Map.disholaIcon;
};

/**
 * createGoogleMapMarker
 * Creates a marker at the given point and using the html for our info window.
 * @param {Object} point
 * @param {string} html for inside the popup
 * @param {number} item_id
 * @return {Object} marker
 */
Dishola.Map.createGoogleMapMarker = function(point, html, item_id, icon) {

  icon = icon || Dishola.Map.getDisholaIcon();
  var marker = new GMarker(point, icon);
  marker.infoWindowHtml = html;
  Dishola.Map.markers[item_id] = marker;
  GEvent.addListener(marker, 'click', function() {
    marker.openInfoWindowHtml(marker.infoWindowHtml);
  });
  return marker;
};

/**
 * We use a small timeout to call the loadScript to allow
 * our application code to execute before bothering with maps.
 */
Dishola.Map.loadScript = function() {
  window.setTimeout(
    Dishola.Map.loadScriptTimeout,
    100
  );
};

/**
 * Dynamically load the Google Maps Javascript API code
 */
Dishola.Map.loadScriptTimeout = function() {

  // callback is hardcoded for now
  var src = 'http://maps.google.com/maps?file=api&v=2.x&key=';
  src += Dishola.Map.KEY + '&c&async=2&callback=Dishola.Map.loadMap';

  var script = new Element('script', {
    src: src,
    type: 'text/javascript'
  });
  // inject script tag with callback
  document.documentElement.firstChild.appendChild(script);
};

/**
 * Performs some checks for browser/setup
 * @returns {Boolean} compatibile
 */
Dishola.Map.checkCompatibility = function() {
  if (typeof GMap2 != 'function') {
    return false;
  }
  var compat = GBrowserIsCompatible();
  if (!compat) {
    alert("Your browser is not supported by the Google Maps API. " +
        "We're really sorry!");
    return false;
  }

  // prevent memory leakage
  Event.observe(window, 'unload', GUnload);
  return true;
};

/**
 * Sidebar
 * @constructor
 */
Dishola.Sidebar = function() {
  var dishWantedEl = $('d-sidebar-wanted-c');
  if (dishWantedEl) {
    // periodically reload the dish wanted
    window.setTimeout(
      function() {
        // update the divine dish
        new Ajax.PeriodicalUpdater(
          dishWantedEl,
          '/dish_wanteds/random',
          {
            asynchronous: true,
            frequency: 40
          }
        );
      }, 20000
    );
  }
};

/**
 * Loops through animated sprite backgrounds.
 * @param {Number} w width
 * @param {Number} frameCount
 * @constructor
 */
Dishola.Roulette = function(w, frameCount) {

  /**
  * @type {Element}
  */
  this._el = $('d-sidebar-roulette-wheel');

  /**
  * @type {Element} container
  */
  this._link = $('d-sidebar-roulette-spin');

  /**
  * @type {Boolean}
  */
  this._spinning = false;

  /**
  * @type {number}
  */
  this._w = w;

  /**
  * @type {number}
  */
  this._frameCount = frameCount;

  /**
  * @type {number}
  */
  this._startSpeed = 1;

  /**
  * @type {number}
  */
  this._endSpeed = 3000;

  /**
  * @type {number}
  */
  this._speedDelta = this._endSpeed - this._startSpeed;

  /**
  * @type {number}
  */
  this._counter = 0;

  /**
  * The number of 'frames' in the animation as it counts through
  * @type {number}
  */
  this._counterCycles = 1000;

  /**
  * @type {number}
  */
  this._speed = 0;

  /**
  * @type {number}
  */
  this._frame = 1;

  /**
  * @type {window.timeout}
  */
  this._timeout = null;

  this._el.observe(
    'mouseover',
    this.mouseOverHandler.bind(this)
  );
  this._el.observe(
    'mouseout',
    this.mouseOutHandler.bind(this)
  );

  this._link.observe(
    'mouseover',
    this.mouseOverHandler.bind(this)
  );
  this._link.observe(
    'mouseout',
    this.mouseOutHandler.bind(this)
  );
};
Dishola.Roulette.prototype = {

  /**
  * mouseOverHandler
  * @param {Event} e
  */
  mouseOverHandler: function(e) {
    //console.debug('over toEl', toEl, within);
    if (!this._spinning) {
      this._counter = 1;
      this._speed = this._startSpeed;
      this._spinning = true;
      this.spin();
    }
  },

  /**
  * mouseOutHandler
  * @param {Event} e
  */
  mouseOutHandler: function(e) {
    //console.debug('out toEl', toEl);
    if (this._spinning) {
      window.clearInterval(this._timeout);
      this._spinning = false;
    }
  },

  /**
  * spin goes forward one frame or back to the beginning.
  */
  spin: function() {
    if (this._counter >= this._counterCycles/10) {
      return;
    }

    // reset if we're at the end
    if (this._frame > this._frameCount) {
      this._frame = 1;
    }
    this._el.style.backgroundPosition = '-' + ((this._frame - 1) * this._w) + 'px 0';
    this._counter++;
    this._frame++;

    // easeOut calculation
    this._speed = Math.ceil(this._startSpeed + (Math.pow(((1 / this._counterCycles) * this._counter), .98) * this._speedDelta));

    // here we go again
    this._timeout = window.setTimeout(
      this.spin.bind(this),
      this._speed
    );

  }
};


/**
 * LocationChange
 * Sets the links to populate the location on /locations/change
 */
Dishola.LocationChange = function() {
  this._locationChangeCities = $('d-locations-change-topcities');
  this._locationChangeProximity = $('LocationProximity');
  this._locationChangeString = $('LocationString');

  this._locationChangeCities.observe(
    'click',
    this.locationChangeCitiesClick.bind(this)
  );
};

/**
 * toggleMustTry
 * @param {Event} e
 * @param {String} 'add' or 'delete'
 * @private
 */
Dishola._toggleMustTry = function(e, toggle) {
  var el = Event.element(e);

  // inner child span for icon
  if (el.tagName == 'SPAN') {
    el = el.parentNode;
  }

  el.update('cool, hang on ...');

  // we don't want the return part on the url
  var href = el.href.replace(/\?.*/, '');
  var xhr = new Dishola.Ajax.Request(href, {
    method: 'get',
    onComplete: function(transport) {
      var json = transport.responseJSON;

      // switch event function
      if (toggle == 'add') {
        el.update(json.message);
        Event.stopObserving(el, 'click', Dishola.addMustTryListener);
        el.observe( 'click', Dishola.deleteMustTryListener);
        el.setAttribute('href', el.href.replace('add', 'delete'));
      }
      else {
        el.update(json.message);
        Event.stopObserving(el, 'click', Dishola.deleteMustTryListener);
        el.observe('click', Dishola.addMustTryListener);
        el.setAttribute('href', el.href.replace('delete', 'add'));
      }
    }
  });

  // stop href
  Event.stop(e);
};

/**
 * addMustTryListener
 */
Dishola.addMustTryListener =
    Dishola._toggleMustTry.bindAsEventListener(Dishola, 'add');

/**
 * deleteMustTryListener
 */
Dishola.deleteMustTryListener =
    Dishola._toggleMustTry.bindAsEventListener(Dishola, 'delete');


/**
 * toggleTablemate
 * @param {Event} e
 * @param {String} 'add' or 'delete'
 * @private
 */
Dishola._toggleTablemate = function(e, toggle) {

  var el = Event.element(e);
  var friend_id = el.id.replace('tablemates-toggle-', '');

  //console.debug('Dishola._toggleTablemate toggle: ' + toggle + ', friend_id:' + friend_id);

  $('tablemates-goto-' + friend_id).hide();
  el.update('cool, hang on ...');

  var xhr = new Dishola.Ajax.Request('/friends/' + toggle + '/' + friend_id, {
    method: 'get',
    onComplete: function(transport) {
      var json = transport.responseJSON;
      el.update(json.message);
      $('tablemates-goto-' + friend_id).show();

      // switch event function
      if (toggle == 'add') {
        Event.stopObserving(el, 'click', Dishola.addTablemateListener);
        el.observe( 'click', Dishola.deleteTablemateListener);
        el.setAttribute('href', el.href.replace('add', 'delete'));
      }
      else {
        Event.stopObserving(el, 'click', Dishola.deleteTablemateListener);
        el.observe( 'click', Dishola.addTablemateListener);
        el.setAttribute('href', el.href.replace('delete', 'add'));
      }
    }
  });

  // stop href
  Event.stop(e);
};

/**
 * addTablemateListener
 */
Dishola.addTablemateListener =
    Dishola._toggleTablemate.bindAsEventListener(Dishola, 'add');

/**
 * deleteTablemateListener
 */
Dishola.deleteTablemateListener =
    Dishola._toggleTablemate.bindAsEventListener(Dishola, 'delete');

/**
 * getNumberOfWords
 * @param {String} str The string that's to be determined
 * @return {number} numberOfWords
 */
Dishola.getNumberOfWords = function(str) {
  if (str === '') {
    return 0;
  }
  str = str.strip();
  var tmp = str.replace(/[\n\t\r ]+/g, ' ');
  return tmp.split(' ').length;
};


/**
 * Reviews
 * @type {Object}
 */
Dishola.Reviews = {};

/**
 * cancelEditReview
 * @param {Event} e
 */
Dishola.Reviews.cancelEditReview = function(e) {
  var review_id = Event.element(e).id.replace('d-review-edit-cancel-', '');
  var eff = new Effect.BlindUp('review-' + review_id + '-edit', {
    afterFinish: function() {
      $('review-' + review_id).show();
    }
  });
};

/**
 * add
 * @param {Event} e
 */
Dishola.Reviews.add = function(e) {
  //console.debug('Dishola.addReview');
  var eff = new Effect.Appear('review-add-container', {
    afterFinish: function() {
      Form.Element.focus('ReviewReview');
    }
  });

  // add the anchor
  if (!document.location.href.match('#add')) {
    window.location.hash = 'add';
  }

  // prevent hrefs
  Event.stop(e);
};

/**
 * addSubmit
 * If they left the review textarea blank, complain
 * @return bool
 */
Dishola.Reviews.addSubmit = function(e) {
  var review = $('ReviewReview');

  if (Dishola.getNumberOfWords(review.value) < 5) {
    alert('Come on, give us at least 5 words!');

    $('addReviewButton').value = 'Add My Review';
    $('addReviewButton').style.backgroundColor = '#54a0a8';

    // prevent submit
    Event.stop(e);
  }
};

/**
 * edit
 * @param {Event} e
 */
Dishola.Reviews.edit = function(e) {
  var review_id = Event.element(e).id.replace('d-review-edit-', '');
  var xhr = new Dishola.Ajax.Updater(
    'review-' + review_id + '-edit',
    '/reviews/edit/' + review_id, {
      evalScripts: true,
      onComplete: function(transport) {
        $('review-' + review_id).hide();
        var eff = new Effect.BlindDown('review-' + review_id + '-edit');
      }
    }
  );
  Event.stop(e);
};

/**
 * Photos
 * @type {Object}
 */
Dishola.Photos = {};

/**
 * Add a photo
 * @param {Event} e
 */
Dishola.Photos.add = function(e) {
  var eff = new Effect.Appear('photo-add-container', {
    afterFinish: function() {
      Form.Element.focus('ImageFile');
    }
  });

  // add the anchor
  if (!document.location.href.match('#add')) {
    window.location.hash = 'add';
  }

  // prevent hrefs
  Event.stop(e);
};

/**
 * Dishola.Promos
 * On the homepage, the promotionals which cycle.
 * @param {string} promosJson
 */
Dishola.Promos = function(promosJson) {
  /**
  * @type {Object}
  * @private
  */
  // not too evil since promosJson is coming from our handcrafter template.
  this._promoInfo = eval(promosJson);
  this._promoContainer = $('d-home-promo');
  this._promoLinks = $$('#d-home-promo-nav a');
  this._promoLinkCurrentIndex = 0;

  /**
  * @type {Element}
  * @private
  */
  this._promoLinkCurrent = this._promoLinks[this._promoLinkCurrentIndex];

  /**
   * @type {Array}
   * @private
   */
  this._promoImgLinks = $$('#d-home-promo a.d-home-promo-imglink');


  // set up listeners on the 1,2,3 links
  for (var i = 0, el; el = $(this._promoLinks[i]); i++) {

    el.observe(
      'click',
      this.promoClick.bind(this)
    );

    // creates any images that aren't already there
    // insert them into the DOM, but hide them
    if (i !== 0) {
      var link = new Element('a', {
          className: 'd-home-promo-imglink',
          href: this._promoInfo[i].href
      });
      link.style.display = 'none';
      var img = new Element('img', {
          src: this._promoInfo[i].img,
          alt: 'Dishola promo'
      });
      link.insert(img);
      this._promoContainer.insert(link);
      this._promoImgLinks.push(link);
    }
  }


  // timeout makes it scroll through automatically
  this._promoTimeout = false;
  this._promotTimeoutTime = 15000;
  this.setPromoTimeout(this._promotTimeoutTime);

  // IE6 hourglass flicker fix
  try {
    document.execCommand('BackgroundImageCache', false, true);
  } catch(err) {}

};
Dishola.Promos.prototype = {

  /**
  * promoClick
  * @param {Event} e
  */
  promoClick: function(e) {
    var el = Event.element(e);
    if (el != this._promoLinkCurrent) {
      this.setPromoLinkCurrent(el);
      this.setPromoTimeout(this._promotTimeoutTime);
    }
    Event.stop(e);
  },

  /**
  * Fades out the current image and fades in the new one.
  * @param {Element} el
  */
  setPromoLinkCurrent: function(el) {
    var eff = new Effect.Fade(this._promoImgLinks[this._promoLinkCurrentIndex]);
    this._promoLinkCurrent.className = '';
    this._promoLinkCurrent = el;
    this._promoLinkCurrent.className = 'd-selected';
    this._promoLinkCurrentIndex =
        parseInt(this._promoLinkCurrent.innerHTML, 10) - 1;
    var imgLink = this._promoImgLinks[this._promoLinkCurrentIndex];
    var eff2 = new Effect.Appear(imgLink);
  },

  /**
  * setPromoTimeout
  * @param {Number} time in milliseconds
  */
  setPromoTimeout: function(time) {
    window.clearTimeout(this._promoTimeout);
    this._promoTimeout = window.setTimeout(
      this.promoNext.bind(this), time
    );
  },

  /**
  * promoNext
  */
  promoNext: function() {
    var nextIndex = this._promoLinkCurrentIndex + 1;
    // reset at the turnaround
    if (nextIndex == this._promoLinks.length) {
      nextIndex = 0;
    }
    this.setPromoLinkCurrent(this._promoLinks[nextIndex]);
    this.setPromoTimeout(this._promotTimeoutTime);
  }

};

/**
 * Safaris
 */
Dishola.Safaris = function() {};

/**
 * safariLocationRowClick
 * @param {Event} e
 * @static
 */
Dishola.Safaris.rowClick = function(e) {
  // the row
  var el = Event.findElement(e, 'TR');

  // get location id
  var location_id = el.id.replace('d-safari-location-', '');

  // make sure not hover classed
  el.removeClassName('d-hover');

  // only one selected row
  $$('#d-safari-locations tr.d-selected').each(function(row) {
    row.removeClassName('d-selected');
    var previous_location_id = row.id.replace('d-safari-location-', '');
    $('d-safari-reviews-' + previous_location_id).hide();
    $('d-safari-photos-' + previous_location_id).hide();
    $('d-safari-title-' + previous_location_id).hide();
  });
  el.addClassName('d-selected');


  // map if loaded
  if (Dishola.Map.markers) {
    var marker = Dishola.Map.markers[location_id];
    marker.openInfoWindowHtml(marker.infoWindowHtml);
    Dishola.Map.markerSelected = marker;
  }

  // show reviews for this location
  $('d-safari-photos-' + location_id).show();
  $('d-safari-reviews-' + location_id).show();
  $('d-safari-title-' + location_id).show();
};

Dishola.Safaris.prototype = {

  /**
  * tableRowHover
  * @param {Event} e
  */
  tableRowHover: function(e) {
    var el = Event.findElement(e, 'TR');
    if (!el.hasClassName('selected')) {
      el.addClassName('hover');
    }
  },

  /**
  * tableRowOut
  * @param {Event} e
  */
  tableRowOut: function(e) {
    var el = Event.findElement(e, 'TR');
    el.removeClassName('hover');
  }
};


/**
 * Dishola.AddDish Class
 * @param {Boolean} afterSubmit whether a submit has occurred
 * @param {string} minwords
 * @param {string} maxwords
 * @constructor
 */
Dishola.AddDish = function(afterSubmit, minwords, maxwords) {
  this.dishFieldGroup_ = $('d-add-dish-group');
  this.photoFieldGroup_ = $('d-add-photo-group');
  this.restaurantFieldGroup_ = $('d-add-restaurant-group');
  this.submitEl_ = $('d-add-dish-submit');
  this.photoAddYes_ = $('d-add-photo-yes');
  this.photoAddNo_ = $('d-add-photo-no');
  this.dishWantedEl_ = $('d-wanted-index');
  $('DishName').focus();

  // store a reference to the add dish fields
  this._addDishLocationFields = [
    $('RestaurantName'),
    $('LocationLocalityName'),
    $('LocationThoroughfare'),
    $('LocationAdministrativeArea'),
    $('LocationCountryNameCode'),
    $('LocationPhone1'),
    $('RestaurantUrl')
  ];

  // store a reference to the hidden fields
  this._addDishLocationHiddenFields = [
    $('LocationThoroughfare'),
    $('LocationLocalityName'),
    $('LocationAdministrativeArea'),
    $('LocationCountryNameCode'),
    $('LocationPhone1'),
    $('RestaurantUrl')
  ];


  // event on lookup button click
  $('d-add-location-lookup').observe(
    'click',
    this.lookupRestaurant.bind(this)
  );


  // make sure the form is really clean onload
  if (!afterSubmit) {

    this.dishFieldGroup_.hide();
    this.restaurantFieldGroup_.hide();
    this.submitEl_.hide();

    if (this.dishWantedEl_) {
      this.dishWantedEl_.hide();
    }

    // Prep for adding a photo
    if (this.photoAddYes_) {
      this.photoAddYes_.observe('click',
          this.gotPhotoYesHandler.bind(this));
      this.photoAddNo_.observe('click',
          this.gotPhotoNoHandler.bind(this));

    // They did a photo, now show the dish part.
    } else {
      this.dishFieldGroup_.show();
      this.restaurantFieldGroup_.show();
      this.submitEl_.show();
      $('DishName').focus();
    }

    // hides various location fields
    $A(this._addDishLocationHiddenFields).each(function(el) {
      el.value = '';
      el.parentNode.style.display = 'none';
    });

    // Adds a done with Dish button
    /*
    this.doneWithDishBtn_ = new Element('button', {
        type: 'button',
        value: '0',
        className: 'submit'
      }).update('Next - the Restaurant');
    this.doneWithDishBtn_.observe('click',
        this.doneWithDishClickHandler.bind(this));
    this.dishFieldGroup_.appendChild(this.doneWithDishBtn_);
    */

  } else {
    // Just hide it in this error condition..
    // this means they said no and then submitted w/ an error
    if (this.photoAddYes_) {
      this.photoFieldGroup_.hide();
    }
  }


  // If we do this too soon IE freaks out.
  document.observe('dom:loaded', function() {
    // word counter
    var wc = new Dishola.WordCounter('ReviewReview', minwords, maxwords);

    // tag completer
    var tc = new Dishola.TagCompleter('TagTag');

    // pre-cache validated image
    var img = new Image(12, 12);
    img.src = '/img/blue_check.gif';
  });

};
Dishola.AddDish.prototype = {
  /**
  * lookupRestaurant
  * from the Add a Dish page
  * See if we already have this restaurant.
  * @param {Event} e
  */
  lookupRestaurant: function(e) {
    //console.debug('Dishola.lookupRestaurant');

    // validate
    if (!($F('RestaurantName') && $F('LocationLocation'))) {
      alert("Please fill in 'Name of the Restaurant' and 'Location' first.");

      // re-focus on Name or City
      if (!$F('RestaurantName')) {
        $('RestaurantName').focus();
      } else {
        $('LocationLocation').focus();
      }
      return;
    }

    // make sure the form is really clean onload
    $A(this._addDishLocationHiddenFields).each(function(el) {
      el.value = '';
      el.parentNode.style.display = 'none';
    });

    // open a popup
    Dishola.popupWindow = new Window('popupWindow', {
      title: 'Address Lookup',
      className: 'dialog',
      width: 350,
      height: 350,
      minimizable: false,
      maximizable: false,
      destroyOnClose: true,
      recenterAuto: false,
      hideEffect: Element.hide
    });
    Dishola.popupWindow.setDestroyOnClose();

    $('popupWindow_close').update('<button type="submitButton">X</button>');
    $('popupWindow_content').addClassName('loadingSpinner32');

    var restaurantName = escape($F('RestaurantName').replace('&', ''));
    var location = escape($F('LocationLocation').replace('&', ''));
    Dishola.popupWindow.setAjaxContent(
      '/locations/search/' + restaurantName + '/' + location,
      {
        popupWindow: true,
        onComplete: this.locationSearchComplete.bind(this)
      },
      true,
      true
    );

    // window properties
    Dishola.popupWindow.showCenter();
  },

  doneWithDishClickHandler: function() {
    this.doneWithDishBtn_.hide();
    this.restaurantFieldGroup_.show();
    this.submitEl_.show();
    //window.location.hash = 'd-add-restaurant-group';
    $('RestaurantName').focus();
  },

  gotPhotoYesHandler: function(e) {
    Event.stop(e);
    $('d-add-photo-form').show();
  },

  gotPhotoNoHandler: function(e) {
    if (this.dishWantedEl_) {
      this.dishWantedEl_.show();
    }
    this.photoFieldGroup_.hide();
    this.dishFieldGroup_.show();
    this.restaurantFieldGroup_.show();
    this.submitEl_.show();
    $('DishName').focus();
  },

  /**
   * locationSearchComplete
   */
  locationSearchComplete: function() {
    $('popupWindow_content').removeClassName('loadingSpinner32');
    $('popupWindow_content').style.backgroundImage = 'none';

    Dishola.popupWindow.setTitle(
        'Address Lookup - ' + $F('d-add-location-search-count') + ' matches');

    // localize this
    var thisInstance = this;

    // listen to clicks on the choose links
    // #TODO(elsigh): when these get removed from the DOM
    // we will have memory leaks
    $$('.d-choose-me').each(function(el) {
       el.observe(
         'click',
         thisInstance.locationSearchResultHandler.bind(thisInstance)
       );
    });

    // cancel links
    $('d-add-location-search-cancel').observe(
      'click',
      thisInstance.restaurantNotInSearchResults.bind(thisInstance)
    );
  },

  /**
   * locationSearchResultHandler
   * fill in values on the form from GLocalResult
   * @param {Event} e
   */
  locationSearchResultHandler: function(e) {
    var el = Event.element(e);
    var iterator = el.id.replace('d-add-location-', '');

    // DisholaRestaurantLocations is eval'd in the popup window
    var result = DisholaRestaurantLocations[iterator];

    // set hidden input
    $('LocationValidated').value = 1;

    // show them that we know the location is ok
    $('d-add-location-validated').show();
    var eff = new Effect.Highlight($('d-add-location-validated'));

    // piecemeal add things to the form
    if (result.Location.id) {
      $('LocationId').value = result.Location.id;
    }
    if (result.Location.source) {
      $('LocationSource').value = result.Location.source;
    }
    if (result.Location.yahoo_id) {
      $('LocationYahooId').value = result.Location.yahoo_id;
    }
    if (result.Location.yelp_id) {
      $('LocationYelpId').value = result.Location.yahoo_id;
    }
    if (result.Restaurant.name) {
      $('RestaurantName').value = result.Restaurant.name;
    }
    if (result.Restaurant.url) {
      $('RestaurantUrl').value = result.Restaurant.url;
    }
    if (result.Location.thoroughfare) {
      $('LocationThoroughfare').value = result.Location.thoroughfare;
    }
    if (result.Location.locality_name) {
      $('LocationLocalityName').value = result.Location.locality_name;
    }
    if (result.Location.administrative_area) {
      $('LocationAdministrativeArea').value = result.Location.administrative_area;
    }
    if (result.Location.phone1) {
      $('LocationPhone1').value = result.Location.phone1;
    }
    if (result.Location.country_name_code) {
      $('LocationCountryNameCode').value = result.Location.country_name_code;
    }

    if (result.Location.latitude) {
      $('LocationLatitude').value = result.Location.latitude;
    }
    if (result.Location.longitude) {
      $('LocationLongitude').value = result.Location.longitude;
    }

    // close the popup last once everything's ready
    Windows.close('popupWindow');


  },

  /**
  * restaurantNotInSearchResults
  */
  restaurantNotInSearchResults: function() {
    //console.debug('Dishola.restaurantNotInSearchResults');

    // turn on the hidden fields
    $A(this._addDishLocationHiddenFields).each(function(el) {
      el.parentNode.style.display = '';
    });

    // set hidden input to flag us
    $('LocationValidated').value = 0;

    // we do not know the location is ok
    $('d-add-location-validated').hide();

    // go to street
    $('LocationThoroughfare').focus();

    // close the popup
    Windows.close('popupWindow');
  }
};


/**
 * DisholaWordCount
 * @param {String} el_id
 * @param {Number} minwords
 * @param {Number} maxwords
 * @constructor
 */
Dishola.WordCounter = function(el, minwords, maxwords) {
  this._wordcountTextarea = $(el);
  this._wordcountStatus = $('d-wordcount-remaining');
  this._wordcountMin = minwords;
  this._wordcountMax = maxwords;

  // initialize the tetarea word counter
  this.updateRemainingWords_();
  this._wordcountTextarea.observe(
    'keyup',
    this.updateRemainingWords_.bind(this)
  );
  this._wordcountTextarea.observe(
    'keydown',
    this.updateRemainingWords_.bind(this)
  );
};
Dishola.WordCounter.prototype = {

  /**
  * updateRemainingCharacters
  * @private
  */
  updateRemainingWords_: function() {
    var numberOfWords = Dishola.getNumberOfWords(this._wordcountTextarea.value);
    // show how many words they've typed
    this._wordcountStatus.innerHTML = numberOfWords;

    // change the classname
    var newClassName = (numberOfWords >= this._wordcountMin && numberOfWords <= this._wordcountMax) ? 'd-good' : 'd-bad';
    this._wordcountStatus.className = newClassName;
  }
};


/**
 * Dishola TagCompleter
 * @param {String} el id
 * @constructor
 */
Dishola.TagCompleter = function(el, opt_tagsUrl, opt_useLastWord) {

  this.tagCompleterInput = $(el);
  this.tagCompleterInput.setAttribute('autocomplete', 'off');

  this._tagsUrl = opt_tagsUrl || '/tags/json/';

  this._tags = [];
  this.getTags();

  this._useLastWord = opt_useLastWord === undefined ? true : opt_useLastWord;

  this.tagCompleter = new Element('ul', {
    className: 'd-tags'
  });
  this.tagCompleter.hide();
  document.body.appendChild(this.tagCompleter);

  this._tagKeyListener = this.onTagKey.bindAsEventListener(this);

  this.tagCompleterInput.observe(
    'blur',
    this.onTagBlur.bind(this)
  );
  this.tagCompleterInput.observe(
    'keyup',
    this._tagKeyListener
  );
  this.tagCompleterInput.observe(
    'keydown',
    this._tagKeyListener
  );
};
Dishola.TagCompleter.prototype = {

  /**
   * Can be overwritten by subclasses.
   */
  getTags: function() {
    var instanceOfCompleter = this;
    var xhr = new Dishola.Ajax.Request(this._tagsUrl, {
      onComplete: function(transport) {
        instanceOfCompleter._tags = transport.responseJSON.tags;
      }
    });
  },

  /**
  * onTagBlur
  * @param {Event} e
  */
  onTagBlur: function(e) {
    window.setTimeout(this.tagCompleterHide.bind(this), 250);
  },

  /**
  * tagCompleterHide
  * @param {Event} e
  */
  tagCompleterHide: function(e) {
    //console.debug("Dishola.tagCompleterHide");
    this.tagCompleter.hide();
    this.tagCompleter.innerHTML = "";
  },

  getRegexpForMatch: function(lastTag, tagsString) {
    return new RegExp('^' + lastTag, 'i');
  },

  /**
  * onTagKey
  * @param {Event} e
  */
  onTagKey: function(e) {

    var keyCode = e.keyCode;
    //console.debug('Dishola.onTagKey keyCode:' + e +', ' + keyCode + ', ' + e.type);

    // if we don't have tags in memory
    if (!this._tags) {
      //console.log('no tags', this._tags);
      return;
    }

    // Return on space or right or left arrow keys
    if (keyCode == Event.KEY_LEFT || keyCode == Event.KEY_RIGHT) {
      return;
    }
    // if they delete everything close
    else if (keyCode == Event.KEY_BACKSPACE) {
      if (this.tagCompleterInput.value === '') {
        window.setTimeout(this.tagCompleterHide.bind(this), 10);
        return;
      }
    }
    // if we've keyboard nav'd down
    else if (keyCode == Event.KEY_TAB) {
      // see if there's a currently active tag completion
      var activeTag = this.tagCompleter.getElementsBySelector('li.active');
      if (activeTag.length) {
        this.updateTagCompleterInput(activeTag[0].innerHTML.strip());
        this.tagCompleterHide();
        this.tagCompleterInput.focus();
        Event.stop(e);
      }
      return;
    }
    // kill any other keydown cases, it's just for getting the TAB
    else if (e.type == 'keydown') {
      // if the popup is still showing, then Return here should
      // have an event stop, otherwise, submitting the form is ok
      if (this.tagCompleter.style.display != 'none' &&
          keyCode == Event.KEY_RETURN) {
        Event.stop(e);
      }
      return;
    }
    // keyboard navigation
    else if (keyCode == Event.KEY_UP || keyCode == Event.KEY_DOWN ||
             keyCode == Event.KEY_RETURN || keyCode == Event.KEY_TAB) {
      this.onTagKeySpecial(keyCode);
      Event.stop(e);
      return;
    }


    // clean up
    this.tagCompleter.update('');

    // get last tag fragment
    var tagsString = $F(this.tagCompleterInput);

    if (this._useLastWord) {
      var lastTag = tagsString.substring(tagsString.lastIndexOf(' ')).strip();

      // remove quotes
      lastTag = lastTag.replace('"', '');
    } else {
      lastTag = tagsString;
    }

    if (lastTag === '') {
      this.tagCompleterHide();
      return;
    }

    // look for up to 3 matches
    var tagCompletions = [];
    var matchRegexp = this.getRegexpForMatch(lastTag, tagsString);
    for (var i=0, tag; tag=this._tags[i]; i++) {
      if (tag.match(matchRegexp)) {
        tagCompletions.push(tag);
        if (tagCompletions.length == 3) {
          break;
        }
      }
    }
    if (tagCompletions.length === 0) {
      this.tagCompleterHide();
      return;
    }

    // add li's
    for (i=0, tag; tag=tagCompletions[i]; i++) {
      var li = new Element('li');
      // for multi-word tags if really "Tags"
      if (this._tagsUrl == '/tags/json' && tag.match(' ')) {
        li.insert('"' + tag + '"');
      }
      else {
        li.insert(tag);
      }
      $(li).observe(
        'click',
        this.onTagCompletionClick.bind(this)
      );
      this.tagCompleter.insert(li);
    }

    // make sure we're showing at the x/y of the last tag
    this.tagCompleter.clonePosition(
      this.tagCompleterInput,
      {
        setWidth: false,
        setHeight: false,
        offsetTop: this.tagCompleterInput.offsetHeight + 1
      }
    );

    this.tagCompleter.style.display = 'block'; //.show() don't work in IE here

  },


  /**
  * onTagKeySpecial
  * @param {Number} keyCode
  */
  onTagKeySpecial: function(keyCode) {
    //console.debug("Dishola.onTagKeySpecial keyCode:" + keyCode);

    // get the ol and currentlyActive
    var currentlyActive = $(this.tagCompleter).getElementsBySelector('li.active');

    // arrow down
    if (keyCode == Event.KEY_DOWN && this.tagCompleter.firstChild) {
      if (currentlyActive.length === 0) {
        this.tagCompleter.firstChild.addClassName('active');
      }
      else if ($(currentlyActive[0]).nextSibling) {
        $(currentlyActive[0]).removeClassName('active');
        $(currentlyActive[0]).nextSibling.addClassName('active');
      }
    }

    // arrow up
    else if (keyCode == Event.KEY_UP && this.tagCompleter.firstChild) {
      if (currentlyActive.length === 0) {
        this.tagCompleter.firstChild.addClassName('active');
      }
      else if ($(currentlyActive[0]).previousSibling) {
        $(currentlyActive[0]).removeClassName('active');
        $(currentlyActive[0]).previousSibling.addClassName('active');
      }
    }

    // return
    else if (keyCode == Event.KEY_RETURN || keyCode == Event.KEY_TAB) {
      if (currentlyActive.length > 0) {
        var newTag = currentlyActive[0].innerHTML;
        this.updateTagCompleterInput(newTag);

        // clean up
        this.tagCompleterHide();
      }
    }
  },

  /**
  * onTagCompletionClick
  * @param {Event} e
  */
  onTagCompletionClick: function(e) {
    //console.debug('Dishola.onTagCompletionClick');
    var newTag = Event.element(e).innerHTML;
    this.updateTagCompleterInput(newTag);
  },

  /**
  * updateTagCompleterInput
  * @param {String} newTag the active completion
  */
  updateTagCompleterInput: function(newTag) {
    var tagsString = $F(this.tagCompleterInput);
    if (this._useLastWord) {
      var lastTag = new RegExp(tagsString.substring(
          tagsString.lastIndexOf(' ')).strip() + '$');
      this.tagCompleterInput.value =
          this.tagCompleterInput.value.replace(lastTag, newTag);
    } else {
      this.tagCompleterInput.value = newTag;
    }

  }
};


/**
 * @param {string|Element} el
 * @constructor
 */
Dishola.DishCompleter = function(el) {

  this.selectEl = $(el);

  // For showing the new options.
  this.selectedEls = document.createElement('ul');
  this.selectedEls.className = 'd-dc-opts compact d-sortable';
  this.selectEl.parentNode.appendChild(this.selectedEls);
  // So we can clone this thing to visually represent chosen opts.
  this.dishOption = document.createElement('li');
  this.dishOption.className = 'd-dc-opt';
  this.dishOption.appendChild(document.createElement('span'));
  var rmLink = document.createElement('a');
  rmLink.className = 'd-link';
  rmLink.appendChild(document.createTextNode('remove'));
  this.dishOption.appendChild(rmLink);

  this.tagCompleterInput = document.createElement('input');
  this.tagCompleterInput.type = 'text';
  this.tagCompleterInput.name = 'dishes-text'
  this.tagCompleterInput.setAttribute('autocomplete', 'off');
  //this.tagCompleterInput.insert({'after': this.selectEl});
  this.selectEl.parentNode.appendChild(this.tagCompleterInput);
  this.tagCompleterInput.style.marginBottom = '50px';

  this.selectEl.style.display = 'none';

  this._tags = [];
  this._dishOptions = {};
  var options = this.selectEl.getElementsByTagName('option');
  for (var i = 0, option; option = options[i]; i++) {
    var dishText = option.innerHTML;
    this._tags.push(dishText);
    this._dishOptions[dishText] = option;
    if (option.selected) {
      this.addDishOption(option);
    }
  }

  this._useLastWord = true

  this.tagCompleter = new Element('ul', {
    className: 'd-tags'
  });
  this.tagCompleter.hide();
  document.body.appendChild(this.tagCompleter);

  this._tagKeyListener = this.onTagKey.bindAsEventListener(this);

  this.tagCompleterInput.observe(
    'blur',
    this.onTagBlur.bind(this)
  );
  this.tagCompleterInput.observe(
    'keyup',
    this._tagKeyListener
  );
  this.tagCompleterInput.observe(
    'keydown',
    this._tagKeyListener
  );
};
Dishola.DishCompleter.prototype = Object.clone(Dishola.TagCompleter.prototype);
Dishola.DishCompleter.prototype.updateTagCompleterInput = function(newTag) {
  var option = this._dishOptions[newTag];
  this.tagCompleterInput.value = '';
  this.addDishOption(option);
};
Dishola.DishCompleter.prototype.getRegexpForMatch = function(lastTag,
    tagsString) {
  return new RegExp(tagsString, 'i');
};
Dishola.DishCompleter.prototype.addDishOption = function(option) {
  option.selected = true;
  var clone = this.dishOption.cloneNode(true);
  this.selectedEls.appendChild(clone);
  var textSpan = clone.getElementsByTagName('span')[0];
  var rmLink = clone.getElementsByTagName('a')[0];
  $(rmLink).observe('click', this.removeDishOption.bind(this));
  rmLink.option = option;
  textSpan.innerHTML = option.innerHTML;
};

Dishola.DishCompleter.prototype.removeDishOption = function(e) {
  var rmLink = e.target;
  $(rmLink).stopObserving('click', this.removeDishOption.bind(this));
  var option = rmLink.option;
  option.selected = false;
  var clone = $(rmLink.parentNode);
  clone.remove();
};


/**
 * Initialize any thumbnails on the page
 */
Dishola.initThumbnails = function() {

  // initialize the thumbnailviewer
  thumbnailviewer.init();
  $('thumbBox').observe(
    'click',
    thumbnailviewer.closeit.bind(thumbnailviewer)
  );

  // go find them
  $$('#contents .d-thumbnail a').each(function(el) {
    var dt = new Dishola.Thumbnail(el);
  });
};

/**
 * Activate thumbnails with popup viewer
 */
Dishola.Thumbnail = function(el) {
  this._el = $(el);
  this._el.observe('click', this._clickHandler.bind(this));
};
/**
 * When you click on a thumbnail, bring up the big photo.
 * @param {Event} e
 */
Dishola.Thumbnail.prototype._clickHandler = function(e) {
  thumbnailviewer.stopanimation();
  thumbnailviewer.loadimage(this._el);
  e.stop();
};


/**
 * Initializes the Dish Set Edit sortable for dish order.
 */
Dishola.setsEditOrderInit = function() {
  var dishesOrderEl = $('d-sets-edit-order');
  dishesOrderEl.addClassName('d-sortable');
  Sortable.create(dishesOrderEl, {
    name: 'dishsetdish-order',
    onChange: function() {
      var sequences = Sortable.sequence('d-sets-edit-order');
      for (var i = 0, sequence; sequence = sequences[i]; i++) {
        var inputEl = $('d-sets-dish-' + sequence);
        inputEl.value = i+1;
      }
    }
  });
};

/**
 * Show a special message for removing a set with 0 dishes.
 * @param {Event} e
 */
Dishola.confirmRemoveDishSet = function(e) {
  return Dishola.confirm(
    e,
    'Removing a set with 0 dishes is basically the same as deleting it. There will be no way to get it back. Are you sure you want to do this?'
  );
};

/**
 * @param {id} el The anchor element id.
 * @param {number} dishId
 * @param {Object} options
 * @constructor
 */
Dishola.SetSelect = function(id, options) {
  this.el_ = $(id);
  this.select_ = this.createSelect_(options);
  this.el_.insert({'after': this.select_});
  this.el_.observe('click', this.clickHandler_.bind(this));

  /**
  * Listener for document clicks
  * @type {Object}
  */
  this.docClickListener_ = this.docClick_.bindAsEventListener(this);
};
/**
 * @param {Object} options
 * @private
 */
Dishola.SetSelect.prototype.createSelect_ = function(options) {
  var select = document.createElement('div');
  $(select).addClassName('d-sets-select');
  select.style.display = 'none'; // hidden initially
  for (key in options) {
    var option = $(document.createElement('div'));
    option.observe('click', this.optionClickHandler_.bind(this));
    option.value_ = key; // store the dishSetId for retrieval
    option.appendChild(document.createTextNode(options[key]));
    select.appendChild(option);
  }

  // Tack on the option to add a new set
  var option = $(document.createElement('div'));
  option.addClassName('d-act');
  var text = $(document.createElement('span'));
  text.addClassName('d-plus');
  text.appendChild(document.createTextNode('create a dish set'));
  option.appendChild(text);
  option.observe('click', function(e) {
    window.location.href = '/dish_sets/add';
  });
  select.appendChild(option);

  return select;
};
/**
 * @param {Event} e
 * @private
 */
Dishola.SetSelect.prototype.clickHandler_ = function(e) {
  if (this.select_.visible()) {
    this.select_.hide();
    Event.stopObserving(document, 'click', this.docClickListener_);
  } else {
    this.select_.show();
    document.observe('click', this.docClickListener_);
  }
  Event.stop(e);
};
/**
 * @param {Event} e
 * @private
 */
Dishola.SetSelect.prototype.docClick_ = function(e) {
  var el = Event.element(e);
  // if we click outside of the popup or the text field the popup goes away
  if (!Position.within(this.select_, Event.pointerX(e),
                       Event.pointerY(e))) {
    this.select_.hide();
    Event.stopObserving(document, 'click', this.docClickListener_);
  }
};
/**
 * POSTs a form that should add the dish to the set.
 * @param {Event} e
 * @private
 */
Dishola.SetSelect.prototype.optionClickHandler_ = function(e) {
  var el = Event.element(e);
  var dishSetId = el.value_;
  var action = this.el_.href.replace(/\?.*/, '');

  var form = document.createElement('form');
  $(form).hide();
  form.action = action;
  form.method = 'post';

  var input = document.createElement('input');
  input.name = 'data[return]';
  input.type = 'hidden';
  input.value = window.location.href;
  form.appendChild(input);

  var input = document.createElement('input');
  input.name = 'data[DishSet][DishSet][]';
  input.type = 'hidden';
  input.value = dishSetId;
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();

  el.parentNode.hide();
};


/**
 * @constructor
 */
Dishola.DishView = function(tabSelected) {
  this.tabSelected_ = tabSelected;
  this.initTabs_();
};

/**
 *
 */
Dishola.DishView.prototype.initTabs_ = function() {
  var navEls = $$('#sub-nav li');
  var viewEl = $('dish-view-' + this.tabSelected_);
  var tabs = [];
  for (var i = 0, el; el = navEls[i]; i++) {
    $(el).firstDescendant().addClassName('d-act');
    var tab = el.id.replace('dish-tab-', '');

    // Hides the not-selected tabs
    if (tab != this.tabSelected_) {
      var containerEl = $(document.createElement('div'));
      containerEl.id = 'dish-view-' + tab;
      containerEl.hide();
      viewEl.parentNode.appendChild(containerEl);

      var newUrl = window.location.href.replace(/\/?page:[^&]+/, '');
      if (newUrl.match('tab:')) {
        newUrl = newUrl.replace(/tab:[^&]+/, 'tab:' + tab);
      } else {
        newUrl += '/tab:' + tab;
      }
      var xhr = new Dishola.Ajax.Updater(
        containerEl,
        newUrl, {
          evalScripts: true
        }
      );
    }
    el.observe('click', this.tabClickHandler_.bind(this, tab));
  }
};

/**
 *
 */
Dishola.DishView.prototype.tabClickHandler_ = function(tab, e) {
  // Makes sure our new tab is in the DOM.
  if (!$('dish-tab-' + tab) || tab == this.tabSelected_) {
    return;
  }

  $('dish-tab-' + this.tabSelected_).removeClassName('active');
  $('dish-view-' + this.tabSelected_).hide();

  this.tabSelected_ = tab;

  $('dish-tab-' + this.tabSelected_).addClassName('active');
  $('dish-view-' + this.tabSelected_).show();
  Event.stop(e);
};


/**
 * @constructor
 */
Dishola.Comment = function(el, modelName) {
  this.el = el;
  this.modelName = modelName;
  this.addForm = null;
  this.submitButton = null;
  this.textarea = null;

  this.defaultText = 'Agree, Disagree? Comment on this review ...';

  this.addLink = new Selector('.d-comment-add a').findElements(this.el)[0];
  // If they can't comment, then die.
  if (!this.addLink) {
    return;
  }
  Event.observe(
    this.addLink,
    'click',
    this.show.bind(this)
  );

  this.comments = new Selector('.d-comment').
      findElements(this.el);

  this.commentCountHeaderEl = new Selector('.d-comment-count').
      findElements(this.el)[0];
  this.commentCountEl = new Selector('span').
      findElements(this.commentCountHeaderEl)[0]
  this.commentCount = parseInt(this.commentCountEl.innerHTML, 10);
  if (this.commentCount == 0) {
    this.commentCountHeaderEl.hide();
  }
};

Dishola.Comment.prototype.createAddForm = function() {
  this.addForm = $(document.createElement('form'));
  this.addForm.action = this.addLink.href.replace(/\??\&?return=[^\&]+/, '');
  this.addForm.method = 'post';
  Event.observe(
    this.addForm,
    'submit',
    this.submit.bind(this)
  );

  this.textarea = document.createElement('textarea');
  this.textarea.name = 'data[' + this.modelName + '][message]';
  this.textarea.innerHTML = this.defaultText;
  this.addForm.appendChild(this.textarea);
  Event.observe(
    this.textarea,
    'focus',
    this.focus.bind(this)
  );

  this.submitButton = document.createElement('input');
  this.submitButton.type = 'submit';
  this.submitButton.className = 'submitButton';
  this.submitButton.value = '> add comment';
  this.addForm.appendChild(this.submitButton);

  //console.log(this.addForm, this.addLink);

  this.addLink.insert({after: this.addForm});
  Dishola.enhanceForm(this.addForm);
};

Dishola.Comment.prototype.focus = function(e) {
  if ($F(this.textarea) == this.defaultText) {
    Form.Element.setValue(this.textarea, '');
    this.textarea.addClassName('d-editing');
  }
};
Dishola.Comment.prototype.show = function(e) {
  if (!this.addForm) {
    this.createAddForm();
  }
  this.addLink.hide();
  this.focus();
  var eff2 = new Effect.Appear(this.addForm);
  this.textarea.focus();
  e.preventDefault();
};
Dishola.Comment.prototype.submit = function(e) {

  if (this.textarea.readOnly) {
    //do nothing cause they're double clicking some shit.
  } else if ($F(this.textarea) == '' ||
             $F(this.textarea) == this.defaultText) {
    alert('You gots to type something in the comment box!');
    Dishola.resetSubmitButtons();
  } else {
    this.textarea.readOnly = true;

    var xhr = new Dishola.Ajax.Request(this.addForm.action, {
      method: this.addForm.method,
      parameters: Form.serialize(this.addForm),
      onComplete: this.submitComplete.bind(this)
    });
  }
  e.preventDefault();
};

Dishola.Comment.prototype.submitComplete = function(transport) {
  this.textarea.readOnly = false;
  Dishola.resetSubmitButtons();

  // form error of some kind
  if (!transport.responseJSON.success) {
    alert(transport.responseJSON.message);

  } else {
    // Show their new comment.
    var el = document.createElement('div');
    el.className = 'd-comment';

    var reviewer = document.createElement('div');
    reviewer.className = 'd-reviewer-info';
    reviewer.innerHTML = $('d-user').innerHTML + ' moments ago';
    el.appendChild(reviewer);

    var comment = $(document.createElement('p'));
    comment.className = 'description';
    comment.innerHTML = $F(this.textarea);
    el.appendChild(comment);

    this.commentCountHeaderEl.show();
    this.commentCountEl.innerHTML = this.commentCount + 1;

    this.addForm.parentNode.insert({'before': el});
    var eff = new Effect.Highlight(el);

    this.addForm.hide();
    this.addLink.show();
    Form.Element.setValue(this.textarea, this.defaultText);
  }
};

/**
 * Stars
 * @constructor
 */
Dishola.Stars = function(el) {
  this.el_ = el;
  this.currentEl_ = new Selector('.s-current').findElements(this.el_)[0];
  this.scoreEl_ = new Selector('.d-stars-score').findElements(this.el_)[0];
  this.votesEl_ = new Selector('.d-stars-votes').findElements(this.el_)[0];
  this.mouseOverListener_ = this.mouseOver_.bind(this);
  this.mouseOutListener_ = this.mouseOut_.bind(this);
  this.el_.observe('mouseover', this.mouseOverListener_);
  this.el_.observe('mouseout', this.mouseOutListener_);
  this.starEls_ = new Selector('a').findElements(this.el_);
  this.starsClickListener = this.click_.bind(this);
  var that = this;
  this.starEls_.each(function(starEl) {
    starEl.observe('click', that.starsClickListener);
  });
};
Dishola.Stars.prototype.mouseOver_ = function(e) {
  this.currentEl_.hide();
};
Dishola.Stars.prototype.mouseOut_ = function(e) {
  this.currentEl_.show();
};
Dishola.Stars.prototype.click_ = function(e) {
  var link = $(e.target);
  var xhr = new Dishola.Ajax.Request(link.href, {
    onComplete: this.clickComplete_.bind(this)
  });
  Event.stop(e);
};
Dishola.Stars.prototype.clickComplete_ = function(transport) {
  // error of some kind
  if (!transport.responseJSON.success) {
    alert(transport.responseJSON.message);
  } else {
    this.scoreEl_.innerHTML = transport.responseJSON['score'];
    this.votesEl_.innerHTML = transport.responseJSON['votes'];
    this.currentEl_.style.width = (transport.responseJSON['score'] * 20) + '%';
    this.currentEl_.show();
    var eff = new Effect.Highlight(this.el_);
    // unset the click function to do nothing now.
    var that = this;
    this.starEls_.each(function(starEl) {
      that.el_.stopObserving('mouseover', that.mouseOverListener_);
      that.el_.stopObserving('mouseout', that.mouseOutListener_);
      starEl.stopObserving('click', that.starsClickListener);
      starEl.observe('click', function(e){Event.stop(e);});
    });
  }
};


/*** FB Connect **/
Dishola.Facebook = {};

Dishola.Facebook.API_KEY;
Dishola.Facebook.SECRET;
Dishola.Facebook.SECRET;
Dishola.Facebook.REVIEW_TEMPLATE_BUNDLE_ID;
Dishola.Facebook.SCRIPT_SRC;
Dishola.Facebook.loadScriptRequested = false;
Dishola.Facebook.isUserConnected = false;

Dishola.Facebook.Text = {
  'MANAGE': 'Manage facebook permissions',
  'LOGIN': 'Share your reviews on Facebook!'
};

Dishola.Facebook.init = function() {
  var debugLogLevel = 0; // 0-6
  FB.init(Dishola.Facebook.API_KEY, '/facebook/xd', {
    'ifUserConnected': Dishola.Facebook.setUserConnected,
    'ifUserNotConnected': Dishola.Facebook.setUserNotConnected,
    'debugLogLevel': debugLogLevel
  });
};


Dishola.Facebook.setUserConnected = function(uid) {
  // ping us to link and update session_key
  var sessionKey = null;
  if (FB.Facebook.apiClient.get_session()) {
    sessionKey = encodeURIComponent(
        FB.Facebook.apiClient.get_session().session_key);
  }
  if (!(uid && sessionKey)) {
    return;
  }
  Dishola.Facebook.isUserConnected = true;
  var xhr = new Dishola.Ajax.Request('/facebook/dishola_connect/' + uid +
      '/' + sessionKey);

  var loginLink = $('d-fb-share');
  if (loginLink) {
    loginLink.stopObserving('click', Dishola.Facebook.onLogin);
    loginLink.innerHTML = Dishola.Facebook.Text.MANAGE;
    loginLink.href = 'http://www.facebook.com/editapps.php?v=allowed';
    loginLink.target = '_blank';
  }
};

Dishola.Facebook.setUserNotConnected = function() {
  var xhr = new Dishola.Ajax.Request('/facebook/dishola_disconnect');
  var loginLink = $('d-fb-share');
  if (loginLink) {
    loginLink.stopObserving('click', Dishola.Facebook.onLogin);
    loginLink.observe('click', Dishola.Facebook.login);
    loginLink.innerHTML = Dishola.Facebook.Text.LOGIN;
  }
};

/**
 * Share an URL.
 * @param {string} url
 */
Dishola.Facebook.shareUrl = function(url) {
  if (!Dishola.Facebook.isUserConnected) {
    Dishola.Facebook.init();
  }
  FB.ensureInit(function() {
    FB.Connect.showShareDialog(url);
  });
};

/**
 * Post the feed.
 * @param {number} templateId
 * @param {string} templateData
 */
Dishola.Facebook.postUserAction = function(templateId, templateData) {
  if (!Dishola.Facebook.isUserConnected) {
    Dishola.Facebook.init();
  }
  FB.ensureInit(function() {
    //http://wiki.developers.facebook.com/index.php/JS_API_M_FB.Connect.showFeedDialog
    FB.Connect.showFeedDialog(templateId,
        templateData, null, null, FB.RequireConnect.promptConnect, null);
  });
};

Dishola.Facebook.publishReview = function(review_id) {
  var url = '/reviews/facebook_template_data/' + review_id;
  var xhr = new Dishola.Ajax.Request(url, {
    onComplete: function(transport) {
      var json = transport.responseJSON;
      var templateData = json['template_data'];
      Dishola.Facebook.postUserAction(
          Dishola.Facebook.REVIEW_TEMPLATE_BUNDLE_ID,
          templateData);
    }
  });
};

Dishola.Facebook.publishReviewClickHandler = function(e) {
  Event.stop(e);
  var el = Event.element(e);
  var review_id = el.id.replace('d-review-fb-share-', '');
  Dishola.Facebook.publishReview(review_id);
};


Dishola.Facebook.shareUrlClickHandler = function(e) {
  var href = e.target.href;
  if (href.match('editapps.php')) {
    href = window.location.href;
  } else if (href.match('invite')) {
    href = 'http://www.dishola.com/';
  }

  if (href) {
    Dishola.Facebook.shareUrl(href);
  }
  Event.stop(e);
};

Dishola.Facebook.inviteFriends = function() {
  if (!Dishola.Facebook.isUserConnected) {
    Dishola.Facebook.init();
  }
  FB.ensureInit(function() {
      FB.Connect.inviteConnectUsers();
  });
};

/**
 * Google Friend Connect
 */
Dishola.GFC = {};

Dishola.GFC.init = function() {
  //window.console.log('Dishola.GFC.init');
  google.friendconnect.container.setParentUrl('/');
  google.friendconnect.container.initOpenSocialApi({
    site: Dishola.GFC.SITE,
    onload: function(securityToken) {
      Dishola.GFC.initData();
    }
  });
};

Dishola.GFC.initData = function() {
  //window.console.log('Dishola.GFC.initData');
  google.friendconnect.renderSignInButton({
    'id': 'gfc-button',
    'style': 'long'});
  var params = {};
  params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [
    opensocial.Person.Field.ABOUT_ME,
    opensocial.Person.Field.STATUS,
    opensocial.Person.Field.THUMBNAIL_URL,
    opensocial.Person.Field.PROFILE_URL
  ];

  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonRequest('OWNER'), 'owner_data');
  //req.add(req.newFetchPeopleRequest(opensocial.IdSpec.PersonId.VIEWER,
  //    params), 'viewer_data');
  req.send(Dishola.GFC.onData);
  window.console.debug('initData done.');
};

Dishola.GFC.onData = function(data) {
  //window.console.log('Dishola.GFC.onData', data);
  var viewerInfo = document.getElementById('gfc-user');
  var gfcButton = document.getElementById('gfc-button');
  if (data.get('viewer_data').hadError()) {
    gfcButton.style.display = '';
    viewerInfo.style.display = 'none';
    google.friendconnect.renderSignInButton({
      'id': 'gfc-button',
      'style': 'long'});
  } else {
    gfcButton.style.display = 'none';
    viewerInfo.style.display = '';
    var viewer = data.get('viewer_data').getData();
    //window.console.log('viewer', viewer);
  }
};


