// ==UserScript==
// @name           Reader Rater
// @namespace      http://people.virginia.edu/~nah7n/
// @description    Rate items in Google Reader to identify (non-)favorite feeds
// @include        http://www.google.com/reader*
// @include        https://www.google.com/reader*
// ==/UserScript==

// Written by Nicholas Hamblet
// Version 0.1 released June 26, 2008
//
// With a nod to:
// http://userscripts.org/scripts/review/25522
// http://userscripts.org/scripts/review/22507

/////////////////////////////////////////////////////////////
// This script sets up the following keyboard shortcuts:
// 'z', 'x', and 'c' rate an item as 0, 1, or 2 (poor, ok, nice)
// capitalized also stars the item (why would you star a 0-rated item?)
// 'R' shows the ratings (should be 'g' then 'r', but that's more work)
//
// You should also find a 'Ratings' link,
//   beneath 'Trends' in the navigation bar (left side)
/////////////////////////////////////////////////////////////


// colorful ratings
var colors = new Array(3);
colors[0] = "#A68576";
colors[1] = "#F9DF8E";
colors[2] = "#F9AE8E";

// simulate a key click
function simulate(node, ascii, hex){
  if(!node){
	return;
  }
  var e = node.ownerDocument.createEvent("KeyboardEvent");
  e.initKeyEvent('keypress',true,true,window,false,false,false,false,
				 ascii, hex);
  node.dispatchEvent(e);
}

// update GM values with new rating
// also star the item according to 'star'
// and move to the next item
function rateAs(rating, star){
  var curItem = document.getElementById("current-entry");
  if(!curItem || rating < 0 || rating > 2){
	return;
  }
  // worried about how fragile this is
  // please don't change things, Google
  var feedTitle = curItem.firstChild.childNodes[2].childNodes[1].innerHTML;

  // update the value
  var curRating = GM_getValue("RR" + rating + ":" + feedTitle, 0);
  GM_setValue("RR" + rating + ":" + feedTitle, curRating + 1);
  curItem.firstChild.style.backgroundColor = colors[rating];

  if(star){
	simulate(curItem, 83, 0x53); // 'S' (acts as 's', why?)
  }
  simulate(curItem, 74, 0x4A); // 'J' (acts as 'j', why?)
}

// put lines into the main ('entries') div of the Reader interface
// 'score' is the average rating
// 'num' is the number of ratings
// 'title' is the title of the feed
// 'node' is the finalChild of the 'entries' div
//    we stick the entry before that
//
// returns the newly made entry
function addEntry(score, num, title, node){
  var e = document.createElement('div');
  e.className = "entry";

  // get the right color (index) set up
  var c = 0;
  if(score >= .5 && score < 1.5){
	c = 1;
  } else if(score >= 1.5){
	c = 2;
  }
  e.setAttribute("style", "background-color:" + colors[c]);

  // eww (seems to work though...)
  e.innerHTML = "<div class=\"entry-main\"><div class=\"collapsed\" style=\"background-color:" + colors[c] + ";border-color:" + colors[c] + "\"><span class=\"entry-source-title link\">" + score + " from " + num + " rankings" + "</span><div class=\"entry-secondary\"><h2 class=\"entry-title\">" + title + "</h2></div></div></div>";

  node.parentNode.insertBefore(e, node);
  return e;
}

function showRatings(){
  // change some of the headers
  var label = document.getElementById("chrome-stream-title");
  label.innerHTML = "Ratings";
  label = document.getElementById("viewer-top-links");
  label.firstChild.innerHTML = "Showing Ratings";

  // grab the main ('entries') div
  var entriesNode = document.getElementById("entries");

  // remove the existing entries...
  while(entriesNode.firstChild.className != "hidden"){
	entriesNode.removeChild(entriesNode.firstChild);
  }
  var entriesLastChild = entriesNode.firstChild;
  // ... including 'You have no...' message
  var noEntriesMsg = document.getElementById("no-entries-msg");
  if(noEntriesMsg){
	entriesNode.removeChild(noEntriesMsg);
  }

  // storage for the greasemonkey values (seems redundant...)
  var rated = new Object();
  var outOf = new Object();

  // the 'Search' box (handily) has a list of the feed titles...
  var subsUL = document.getElementById("search-restrict-button");
  var subsNames = subsUL.getElementsByClassName("goog-menuitem");

  // ... and they start after the item called 'Subscriptions'
  // (folders and other things occur before that)
  var startIdx = 0;
  while(subsNames[startIdx].innerHTML.indexOf("Subscriptions") < 0){
	startIdx++;
  }

  // compute and store average ratings and number of ratings
  for(var i = startIdx + 1; i < subsNames.length; i++){
	var title = subsNames[i].innerHTML;
	var ratingsTotal = 0;
	var ratings = new Array(3);
	for(var j = 0; j < ratings.length; j++){
	  ratings[j] = GM_getValue("RR" + j + ":" + title, 0);
	  ratingsTotal += ratings[j];
	}
	if(ratingsTotal){
	  var score = 0;
	  for(j = 1; j < ratings.length; j++){
		score += (j * ratings[j]);
	  }
	  score /= ratingsTotal;
	  rated[title] = Math.round(score*1000)/1000;
	  outOf[title] = ratingsTotal;
	}
  }

  // display the rated entries
  for(var t in rated){
	var e = addEntry(rated[t], outOf[t], t, entriesLastChild);
	entriesLastChild = e.nextSibling;
  }
}

// grab our new keyboard shortcuts
function keyPressEvent(event){
  var kcode = (event.keyCode)?event.keyCode:event.which;
  var k = String.fromCharCode(kcode);
  if(k == 'z' || k == 'Z'){ // rate as 0 (poor)
	rateAs(0, k == 'Z');
	event.preventDefault();
  } else if (k == 'x' || k == 'X') { // rate as 1 (ok)
	rateAs(1, k == 'X');
	event.preventDefault();
  } else if (k == 'c' || k == 'C') { // rate as 2 (nice)
	rateAs(2, k == 'C');
	event.preventDefault();
  } else if (k == 'R') { // show ratings (should be 'g then r'...)
	showRatings();
	event.preventDefault();
  }
}

document.addEventListener("keypress", keyPressEvent, true);

// put a 'Ratings' link beneath 'Trends'
var trendsNode = document.getElementById("trends-selector");
if(trendsNode){
  var ratingsNode = document.createElement('li');
  ratingsNode.setAttribute("id", "ratings-selector");
  ratingsNode.className = "selector";
  var ratingsLinkNode = document.createElement('a');
  ratingsLinkNode.className = "link";
  ratingsLinkNode.setAttribute("href", "#");
  ratingsLinkNode.setAttribute("id", "ratings-link");
  var ratingsLinkText = document.createElement('span');
  ratingsLinkText.setAttribute("id", "ratings-link-text");
  ratingsLinkText.className = "text";
  ratingsLinkText.innerHTML = "Ratings";
  ratingsLinkNode.appendChild(ratingsLinkText);
  ratingsNode.appendChild(ratingsLinkNode);
  trendsNode.parentNode.insertBefore(ratingsNode, trendsNode.nextSibling);
}

// Clicking on 'Ratings' should show them
document.addEventListener('click', function(event){
							var targetId = event.target.getAttribute("id");
							if(targetId == "ratings-selector"
							   || targetId == "ratings-link"
							   || targetId == "ratings-link-text"){
							  event.stopPropagation();
							  event.preventDefault();
							  showRatings();
							}
						  }, true);
