// ==UserScript==
// @name            Not a Fan
// @namespace       com.tuggy.nathan
// @description     (De)highlights or removes fan page suggestions on Facebook's People You May Know page
// @include         http://www.facebook.com/*find-friends/*
// @include         http://www.facebook.com/*home.php*
// @include         http://www.facebook.com/*reqs.php*
// @require         http://usocheckup.dune.net/60045.js
// @require         http://userscripts.org/scripts/source/49700.user.js
// @require         http://userscripts.org/scripts/source/50018.user.js
// @resource        configCSS http://www.tuggycomputer.com/nathan/software/userscripts/not_a_fan_config.css
//
// @copyright       2009 © Nathan Tuggy (<http://nathan.tuggycomputer.com/>; <http://userscripts.org/users/root>)
// @license         GPL version 3 or any later version; <http://www.gnu.org/copyleft/gpl.html>
// @timestamp       1258698712773
// @version         2.0.10.01
// ==/UserScript==

/*  This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* ===== Configuration Settings ===== */
GM_config.init("Options for Not a Fan",
  {
    selectedAction: {
      label: "Action",
      title: "An action to take on each matching suggestion",
      type:  "select",
      _def:  "autoHideWithDelay",
      options: {
        fade:              "Fade entire suggestion",
        fadePic:           "Fade suggestion picture",
        autoHide:          "Hide suggestion completely",
        autoHideWithDelay: "Hide suggestion after delay"
      }
    },
    autohide_delay: {
      label: "Auto-Hide Delay",
      title: "Only applicable if action is set to hide after a delay; number of seconds to delay before hiding suggestions",
      type:  "int",
      _def:  120
    }
  },
  
  GM_getResourceText("configCSS"),
  
  {
    save: function () {
      document.location.reload();
    },
    open: function () {
      GM_config.resizeFrame("50%");
      GM_config.fadeOut();
      
      if (configOpeningInError) {
        alert("Hey, fix your config!");
      }
    },
    close: function () {
      GM_config.fadeIn();
    }
  }
);

var selectedAction = GM_config.get("selectedAction");

/* ===== XPath ===== */
function $xi(xpath, root, type) {
  return document.evaluate(xpath, root || document, null, type, null);
}
function $x(xpath, root) {
  return $xi(xpath, root, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
}
function $x1(xpath, root) {
  return $xi(xpath, root, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
}

/* ===== Click on an element (borrowed from Facebook Fixer, @namespace http://userscripts.org/people/14536) ===== */
function click(elm) {
	var evt = document.createEvent('MouseEvents');
	evt.initMouseEvent( 'click', true, true, window, 0, 1, 1, 1, 1, false, false, false, false, 0, null );
	elm.dispatchEvent(evt);
}


/* ===== Internal Utilities ===== */
function log(message, level) {
  level = level.toString().toUpperCase();
  switch (level) {
    case "ERROR":
    case "WARNING":
    case "INFO":
    case "DEBUG": {
      GM_log("Not a Fan " + level + ": " + message.toString());
      break;
    }
    default: {
      throw new Error(level + " is not a valid message level.");
    }
  }
}

function getSuggestionGrid() {
  return document.getElementById("pymk_ajax_grid") || document.getElementById("pymk_grid_/reqs.php");
}

function getContainingSuggestionBlock(el) {
  if (el.id && el.className && el.className.indexOf("friend_grid_col") >= 0) {
    return el;
  }
  else {
    return getContainingSuggestionBlock(el.parentNode);
  }
}

function findHideLink(block) {
  return $x1("descendant::*[contains(@class, 'fg_action_hide')]", block);
}

var _utilityImageUrl = null;
function getUtilityImageUrl() {
  if (!_utilityImageUrl) {
    var firstXButton = findHideLink(getSuggestionGrid());
    if (firstXButton) {
      _utilityImageUrl = window.getComputedStyle(firstXButton, null).backgroundImage
          .replace(/^url\((.*)\)$/, "$1");
    }
    else {
      log("Unable to find any suggestion hide links to use for background picture information.", "WARNING");
    }
  }
  return _utilityImageUrl;
}

function fadeInHideLink(block) {
  var hideLink = findHideLink(block);
  if (hideLink) {
    // Insanely hacky, but ehy!
    hideLink.style.backgroundPosition = "-857px -65px";
    hideLink.style.backgroundColor = "black";
    hideLink.style.textDecoration = "none";
    // TODO: Make hover work as expected (i.e. turn blue)
    
    log("Faded in hide link for block with ID '" + getContainingSuggestionBlock(hideLink).id + "'.", "INFO");
    return true;
  }
  return false;
}
function strikeOut(block) {
  var textContainer = $x1("descendant::div[contains(@class, 'UIImageBlock_Content')]", block);
  if (textContainer) {
    textContainer.style.textDecoration = "line-through";
    return true;
  }
  return false;
}

function getSuggestionLink(linkTextPossibilities, suggestionGrid) {
  var linkClass = "as_link", criteria = [], i, 
      xpath = "//button[contains(@class, '" + linkClass + "')]/span";
  if (linkTextPossibilities && linkTextPossibilities.length > 0) {
    for (i = 0; i < linkTextPossibilities.length; i++) {
      criteria.push("contains(., '" + linkTextPossibilities[i] + "')");
    }
    xpath += "[" + criteria.join(" or ") + "]";
  }
  log(xpath, "DEBUG");
  return $x(xpath, suggestionGrid);
}

/* ===== Top-Level Functionality ===== */
var actions = {
  fade: function (block) {
    block.style.opacity = "0.35";
    log("Faded out suggestion block with ID '" + block.id + "'.", "INFO");
    fadeInHideLink(block);
    return true;
  },
  fadePic: function (block) {
    var pic = $x1("descendant::img", block);       // Hacky, but there's (currently) only the one <img> tag
    if (pic) {
      pic.style.opacity = "0.25";
    }
    log("Faded out picture '" + pic.src + "' for block with ID '" + block.id + "'.", "INFO");
    fadeInHideLink(block);
    return true;
  },
  autoHide: function (block) {
    var hideLink = findHideLink(block);
    if (hideLink) {
      click(hideLink);
      log("Hid suggestion block with ID '" + block.id + "'.", "INFO");
      return true;
    }
    else {
      log("Failed to hide suggestion block with innerHTML '" + block.innerHTML + "'; unable to find a hide button.", "WARNING");
      return true;      // ?
    }
  },
  autoHideWithDelay: function (block) {
    var delaySeconds = GM_config.get("autohide_delay"), delay = delaySeconds * 1000, targetTime = new Date();
    targetTime.setTime(targetTime.getTime() + delay);
    window.setTimeout(this.autoHide, delay, block);
    log("Preparing to automatically hide suggestion block with ID '" + block.id + "' at " + targetTime.toLocaleTimeString() + ".", "INFO");
    strikeOut(block);
    return true;
  }
}

var configOpeningInError = false;       // Hacky signal to indicate that we're opening the config screen because of a config error
var suggestionGrid;

function addConfigLink() {
  // TODOING: Open config with unobtrusive link
  // TODO: Stick inside <small> element, like FB's See All (to avoid colliding with other suggestions that are not PYMK)\
  var s;
  var elemOptions = suggestionGrid.parentNode.appendChild(document.createElement("small"));
  var elemOptionsLink = elemOptions.appendChild(document.createElement("a"));
  elemOptionsLink.addEventListener("click", function (e) {
        configOpeningInError = false;
        GM_config.open();
        e.preventDefault();
      }, false);
  elemOptionsLink.className += "UIImageBlock_Ext";
  elemOptionsLink.textContent = "Naf Options";
  elemOptionsLink.href = "#";
  elemOptionsLink.title = "Open configuration for Not a Fan";
  s = elemOptionsLink.style;
  s.position = "relative";
  s.top = "-0.5em";
  var elemIcon = elemOptionsLink.appendChild(document.createElement("div"));
  s = elemIcon.style;
  s.backgroundImage = "url(" + getUtilityImageUrl() + ")";
  // HACK: Conveniently imitate the options icon in FB Chat -- could break with a resounding clatter.
  s.backgroundPosition = "-623px -101px";
  s.margin = "3px 4px 0 0";
  s.width = (s.height = "8px");
  s.overflow = "hidden";
  s.cssFloat = "left";
}

function actOnSuggestions() {
  var links = getSuggestionLink(["Become a Fan", "Become a Supporter"], suggestionGrid), e, i;
  if (!links || links.snapshotLength === 0) {
    log("No fan page suggestions found.", "DEBUG");
  }
  else {
    for (i = 0; i < links.snapshotLength; i++) {
      if (typeof actions[selectedAction] === "function") {
        var link = links.snapshotItem(i);
        var fContinue = actions[selectedAction](getContainingSuggestionBlock(link));
        // TODO: Make debug summary more useful again or remove entirely
        log("Performed action " + selectedAction /* + 
            " on target with className of " + link.className */, "DEBUG");
        if (!fContinue) {
          log("Previous action requested action-chain stop. No more actions will be processed.", "DEBUG");
          break;
        }
      }
      else {
        log("Invalid selectedAction " + selectedAction + "! Ignoring.", "ERROR");
        // TODOING: Take actions to resolve this problem
        configOpeningInError = true;
        GM_config.open();
      }
    }
  }
}

window.addEventListener("load", function () {
      suggestionGrid = getSuggestionGrid();
      if (suggestionGrid) {
        addConfigLink();
        actOnSuggestions();
      }
      else {
        log("Couldn't find suggestion grid!", "ERROR");
      }
    }, false);
