/** Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// ==UserScript==
// @name           Retro Links
// @namespace      http://www.google.com/
// @description    Show links to other search engines at the bottom of Google's result page.
// @include        http://google.tld/search*
// @include        http://*.google.tld/search*
// ==/UserScript==

// This array selects which search engine links are shown to users.
// You can select different search engine names from the list below. You can
// also add or remove elements from this list to show more or fewer links.
var RL_DEFAULT_LINKS = ['Yahoo',
                        'Live',
                        'Ask',
                        'Amazon',
                        'eBay',
                        'Wikipedia',
                        'Digg',
                        'Flickr',
                        'Imdb'];

// Add additional search engines to this list following the examples below.
var RL_LINK_OPTIONS = {
  'About.com': 'http://search.about.com/fullsearch.htm?terms=',
  'AllTheWeb': 'http://www.alltheweb.com/search?cat=web&q=',
  'AltaVista': 'http://www.altavista.com/web/results?q=',
  'Amazon': 'http://www.amazon.com/s/?url=search-alias%3Daps&field-keywords=',
  'AOL': 'http://search.aol.com/aol/search?query=',
  'Ask': 'http://www.ask.com/web?q=',
  'Baidu': 'http://www.baidu.com/s?wd=',
  'Blog Search': 'http://blogsearch.google.com/?q=',
  'Bloglines': 'http://www.bloglines.com/search?q=',
  'Cuil': 'http://www.cuil.com/search?q=',
  'Delicious': 'http://delicious.com/search?p=',
  'Digg': 'http://digg.com/search?s=',
  'Dogpile': 'http://www.dogpile.com/dogpile/web/',
  'eBay': 'http://search.ebay.com/',
  'Facebook': 'http://www.facebook.com/s.php?init=q&q=',
  'Flickr': 'http://flickr.com/search/?q=',
  'FriendFeed': 'http://friendfeed.com/search?q=',
  'Gmail': 'http://mail.google.com/mail/#search/',
  'Google Reader': 'http://www.google.com/reader/view/#search/',
  'Google Scholar': 'http://scholar.google.com/scholar?q=',
  'ICanHasCheezburger': 'http://icanhascheezburger.com/?s=',
  'Imdb': 'http://www.imdb.com/find?q=',
  'Lifehacker': 'http://lifehacker.com/search/',
  'Live': 'http://search.live.com/results.aspx?q=',
  'Lycos': 'http://search.lycos.com/?query=',
  'MySpace': 'http://searchservice.myspace.com/index.cfm?fuseaction=sitesearch.results&type=AllMySpace&qry=',
  'PicasaWeb': 'http://picasaweb.google.com/lh/view?psc=G&q=',
  'Popurls': 'http://search.popurls.com/?q=',
  'Reddit': 'http://www.reddit.com/search?q=',
  'Rollyo': 'http://rollyo.com/search.html?sid=web&q=',
  'Slashdot': 'http://slashdot.org/search.pl?query=',
  'StumbleUpon': 'http://www.stumbleupon.com/search?q=',
  'Techmeme': 'http://www.techmeme.com/search/query?q=',
  'Technorati': 'http://technorati.com/search/',
  'Twitter': 'http://search.twitter.com/search?q=',
  'Web History': 'http://www.google.com/history/find?q=',
  'Wikia': 'http://search.wikia.com/search.html#',
  'Wikipedia': 'http://en.wikipedia.org/wiki/Special:Search?search=',
  'Yahoo': 'http://search.yahoo.com/search?p=',
  'Yahoo Answers': 'http://answers.yahoo.com/search/search_result?p=',
  'Yelp': 'http://www.yelp.com/search?find_desc=',
  'YouTube': 'http://www.youtube.com/results?search_query='
};

// This version number is compared to that listed in the latestversion file to
// determine whether an update is available.
// This should be incremented when updates are made.
var RL_VERSION = 1;

var RL_HOME_DIR =
    'http://www.mattcutts.com/retrolinks/';
var RL_LATEST_VERSION_URL = RL_HOME_DIR + 'latestversion';
var RL_DOWNLOAD_LINK = RL_HOME_DIR + 'retrolinks.user.js';
var RL_SCRIPT_NAME = 'Retro Links';


/**
 * Class for handling updates to this script.
 * @param {string} latestVersionUrl This url has the latest version number.
 * @param {string} downloadLink Url of the latest version.
 * @param {number} currentVersion The current version.
 * @param {string} scriptName Name of this script.
 * @constructor
 */
RL_Updater = function(latestVersionUrl,
                      downloadLink,
                      currentVersion,
                      scriptName) {
  this.UPDATE_MSG = 'An update is available!';
  this.DAY_IN_MS = 86400000;  // One day in milliseconds.
  this.now_ = new Date();
  var lastCheck = parseInt(GM_getValue('lastcheck', '0'));
  if (lastCheck > 0) {
    this.lastCheck_ = new Date(lastCheck);
  } else {
    this.lastCheck_ = new Date(0);
  }
  this.latestVersionUrl_ = latestVersionUrl;
  this.downloadLink_ = downloadLink;
  this.currentVersion_ = currentVersion;
  this.scriptName_ = scriptName;

  // Adds a menu option that allows user to turn off/on the update notification.
  var menuText = this.scriptName_ + ' -> Check for updates daily';
  if (GM_getValue('checkForUpdates', true)) {
    menuText = this.scriptName_ + ' -> Never check for updates';
  }
  GM_registerMenuCommand(menuText, function() {
    GM_setValue('checkForUpdates', !GM_getValue('checkForUpdates', true));
    window.location.reload();
  });
};

/**
 * If it's been a day since we last checked, checks for a new version.
 */
RL_Updater.prototype.checkForUpdates = function() {
  if (!GM_getValue('checkForUpdates', true)) {
    // Don't check if the user has notifications turned off.
    return;
  }
  if (this.now_ - this.lastCheck_ < this.DAY_IN_MS) {
    // Check has happened within the last day.
    return;
  }
  var updaterObject = this;
  // Checks if a newer version is available.
  GM_xmlhttpRequest({
    method: 'GET',
    url: updaterObject.latestVersionUrl_,
    onload: function(details) {
      GM_setValue('lastcheck', updaterObject.now_.getTime().toString());
      if (parseFloat(details.responseText) > updaterObject.currentVersion_) {
        updaterObject.displayNewVersion();
      }
    }
  });
};

/**
 * Displays notification that a new version is available and provides a link.
 */
RL_Updater.prototype.displayNewVersion = function() {
  var div = document.createElement('div');
  div.style.position = 'fixed';
  div.style.bottom = '0';
  div.style.right = '0';
  div.style.color = '#ffffff';  // white
  div.style.background = '#cc0000';  // red
  div.style.padding = '4px';
  div.appendChild(document.createTextNode(this.scriptName_));
  div.appendChild(document.createElement('br'));
  var a = document.createElement('a');
  a.href = this.downloadLink_;
  a.appendChild(document.createTextNode(this.UPDATE_MSG));
  div.appendChild(a);
  document.body.appendChild(div);
};


/**
 * Class for the row of links and their options.
 * @param {string} query The search that was done on Google.
 * @param {string} element Insert the links below this element.
 * @param {Array.<string>} defaultLinks Sites that will be the default links.
 * @param {Object} linkOptions Maps the name of a site to its url.
 * @param {string} prefix Used as a namespace for this class instance.
 * @constructor
 */
RL_Links = function(query, element, defaultLinks, linkOptions, prefix) {
  this.query_ = query;
  this.element_ = element;
  this.defaultLinks_ = defaultLinks;
  this.linkOptions_ = linkOptions;
  this.prefix_ = prefix;
};

/**
 * Saves which links the user has selected and hides the options interface.
 */
RL_Links.prototype.saveChanges = function() {
  for (var i = 0; i < this.defaultLinks_.length; i++) {
    var selectElement = document.getElementById(this.prefix_ + '_select_' + i);
    if (selectElement) {
      GM_setValue(this.prefix_ + '_position_' + i, selectElement.value);
      var linkElement = document.getElementById(this.prefix_ + '_link_' + i);
      if (linkElement) {
        linkElement.innerHTML = selectElement.value;
        linkElement.href = this.linkOptions_[selectElement.value] + this.query_;
      }
    }
  }
  var optionsInterface =
      document.getElementById(this.prefix_ + '_options_interface');
  if (optionsInterface) {
    optionsInterface.style.display = 'none';
  }
  var optionsLink = document.getElementById(this.prefix_ + '_options_link');
  if (optionsLink) {
    optionsLink.innerHTML = '[+]';
  }
};

/**
 * Toggles the visibility of the options interface.
 */
RL_Links.prototype.showHideOptions = function() {
  var optionsInterface =
      document.getElementById(this.prefix_ + '_options_interface');
  var optionsLink = document.getElementById(this.prefix_ + '_options_link');
  if (optionsInterface.style.display == 'none') {
    optionsInterface.style.display = 'block';
    optionsLink.innerHTML = '[-]';
  } else {
    optionsInterface.style.display = 'none';
    optionsLink.innerHTML = '[+]';
  }
};

/**
 * Add the links and options interface to the page.
 */
RL_Links.prototype.insertOnPage = function() {
  var divElement = document.createElement('div');
  divElement.align = 'center';
  var hrElement = document.createElement('hr');
  hrElement.size = 1;
  divElement.appendChild(hrElement);
  divElement.appendChild(document.createTextNode('Try your query on: '));

  // Create the links.
  for (var i = 0; i < this.defaultLinks_.length; i++) {
    var name = this.defaultLinks_[i];
    name = GM_getValue(this.prefix_ + '_position_' + i, this.defaultLinks_[i]);
    var aElement = document.createElement('a');
    aElement.href = this.linkOptions_[name] + this.query_;
    aElement.id = this.prefix_ + '_link_' + i;
    aElement.appendChild(document.createTextNode(name));
    divElement.appendChild(aElement);
    divElement.appendChild(document.createTextNode(' '));
  }

  var optionsElement = document.createElement('small');
  optionsElement.style.color = '#006633';
  optionsElement.style.cursor = 'pointer';
  optionsElement.id = this.prefix_ + '_options_link';
  var optionsText = document.createTextNode('[+]');
  optionsElement.appendChild(optionsText);
  divElement.appendChild(optionsElement);
  divElement.appendChild(document.createElement('br'));
  divElement.appendChild(document.createElement('br'));


  // Construct the options interface.
  var interfaceElement = document.createElement('p');
  interfaceElement.style.display = 'none';
  interfaceElement.id = this.prefix_ + '_options_interface';
  interfaceElement.align = 'center';
  var formElement = document.createElement('form');

  // Create a select box for each link.
  for (var i = 0; i < this.defaultLinks_.length; i++) {
    var selectElement = document.createElement('select');
    selectElement.id = this.prefix_ + '_select_' + i;
    // Add options for all available link options.
    for (var j in this.linkOptions_) {
      var optionElement = document.createElement('option');
      optionElement.value = j;
      if (j == GM_getValue(this.prefix_ + '_position_' + i,
                           this.defaultLinks_[i])) {
        optionElement.selected = true;
      }
      optionElement.appendChild(document.createTextNode(j));
      selectElement.appendChild(optionElement);
    }
    formElement.appendChild(selectElement);
    formElement.appendChild(document.createTextNode(' '));
  }
  var buttonElement = document.createElement('input');
  buttonElement.type = 'button';
  buttonElement.id = this.prefix_ + '_save';
  buttonElement.value = 'Save';
  formElement.appendChild(buttonElement);
  interfaceElement.appendChild(formElement);

  // Insert our elements into the page.
  this.element_.parentNode.insertBefore(interfaceElement,
                                        this.element_.nextSibling);
  this.element_.parentNode.insertBefore(divElement, this.element_.nextSibling);
  var thisObject = this;
  // Set up event listeners.
  var saveButton = document.getElementById(this.prefix_ + '_save');
  saveButton.addEventListener('click',
                              function() { thisObject.saveChanges(); },
                              true);
  var customizeButton = document.getElementById(this.prefix_ + '_options_link');
  customizeButton.addEventListener('click',
                                   function() { thisObject.showHideOptions(); },
                                   true);
};


/**
 * Finds the query from the url parameters.
 * @return {string} query The search query from Google.
 */
function RL_getQuery() {
  var paramString = window.location.search;
  // Get rid of the question mark.
  paramString = paramString.substr(1, paramString.length - 1);
  var params = paramString.split('&');  // Find the value of the 'q' parameter.
  for (var i = 0; i < params.length; i++) {
    if (params[i].substr(0, 2) == 'q=') {
      query = params[i].replace(/^q=/, '');
      // These have no effect in Firefox, we are just being extra cautious.
      query = query.replace(/>/g, escape('>'));
      query = query.replace(/</g, escape('<'));
      query = query.replace(/"/g, escape('"'));
      query = query.replace(/'/g, escape("'"));
      query = query.replace(/\s/g, escape(' '));
      return query;
    }
  }
}


// GM_getValue and GM_setValue were the latest api functions added.
// This script will not work without them.
if (!GM_getValue) {
  alert ('Retro Links requires Greasemonkey version 0.3 or higher.');
} else {
  // Handles update notification.
  var RL_updateController = new RL_Updater(RL_LATEST_VERSION_URL,
                                           RL_DOWNLOAD_LINK,
                                           RL_VERSION,
                                           RL_SCRIPT_NAME);
  RL_updateController.checkForUpdates();

  // Finds the place on the page to add the links.
  var RL_parentElement = document.getElementById('nav');
  if (!RL_parentElement) {
    RL_parentElement = document.getElementById('res');
  }
  if (RL_parentElement) {
    // Create the links and add them below the parent element.
    var RL_links = new RL_Links(RL_getQuery(),
                                RL_parentElement,
                                RL_DEFAULT_LINKS,
                                RL_LINK_OPTIONS,
                                'RL_bottom');
    RL_links.insertOnPage();
  }
}
