// ==UserScript==
// @name           IMDb Decoder Ring
// @namespace      http://rephrase.net/box/user-js/
// @description    Show what an IMDb rating means in terms of its percentile
// @include        http://*.imdb.com/title/tt*/
// @include        http://imdb.com/title/tt*/
// ==/UserScript==

/*
Using data from Tom Moertel's IMDb Decoder Ring
<http://community.moertel.com/ss/space/IMDB+Movie-Rating+Decoder+Ring>
*/

(function(){
/* UTILITY FUNCTIONS &C */

/* Remove leading and trailing whitespace. */
String.prototype.strip = function() {
    return this.replace(/^\s+|\s+$/gm, "");
}

/* Split by regular expression. */
String.prototype.regexsplit = function(separator, limit, separators_too) {
    var o = [], match, self=this.toString();
    var i = 0;
    if (typeof limit == "undefined")
        limit = 1000; // sanity check
    while ((match = self.match(separator)) && self.length > 0 && o.length < limit) {
        if (RegExp.leftContext.length > 0)
            o.push(RegExp.leftContext);
        if (!!separators_too)
            o.push(match[0]);
        self = self.substr(match[0].length + RegExp.leftContext.length);
    }
    if (self.length)
        o.push(self);
    return o;
}

/* Get elements by CSS class. */
function getElementsByClassName(elm, classname) {
    var a = [];
    var re = new RegExp('(^| )'+classname+'( |$)');
    var els = elm.getElementsByTagName("*");
    for(var i=0,j=els.length; i<j; i++) {
        if(re.test(els[i].className)) {
            a.push(els[i]);
        }
    }
    return a;
}

/* Extract the film's genres from the page. */
function extract_genres() {
    var exp = '//a[contains(@href, "/Genres/")]'; 
    var as = document.evaluate(exp, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    var genres = [null];
    for(var elm = null, i = 0; (elm = as.snapshotItem(i)); i++) {
        /*
        Sometimes pages have "related links" to genre pages, e.g.
        <http://imdb.com/rg/title-related/maindetails-genre/Sections/Genres/Drama/>
        Ignore those.
        */
        if (elm.href.match(/\.com\/Sections/))
            genres.push(elm.firstChild.nodeValue);
    }
    return genres;
}

/* Extract the film's rating from the page. */
function extract_rating() {
    var rating_div = getElementsByClassName(document, "general rating")[0];
    try {
        var rating = rating_div.getElementsByTagName("b")[1].firstChild.nodeValue;
    } catch(e) {
        return false;
    }
    return rating.split("/")[0];
}

/* END UTILITY FUNCTIONS &C. */

var allname = "All movies";

/*
This data courtesy Thomas G. Moertel
<http://community.moertel.com/ss/space/IMDB+Movie-Rating+Decoder+Ring>
March 2007
*/
var data = "\
All movies   10  23 27 33 39  46 51 60 66  74 79 86 89  93 95 97 98  99\
Action       22  40 44 51 56  61 66 73 77  82 85 89 91  93 95 96 97  98\
Adventure    14  30 33 41 46  52 56 65 69  75 78 83 86  91 93 95 96  97\
Animation     4  15 18 24 28  33 35 44 48  57 62 69 76  84 89 94 96  98\
Biography     3   5  7 12 15  23 29 40 50  62 70 83 87  94 97 99 ++  ++\
Comedy        9  23 27 35 40  48 55 64 71  78 84 90 93  95 97 98 99  99\
Crime         7  20 24 31 37  45 52 63 69  76 81 89 92  95 97 98 99  99\
Documentary   5  11 13 16 19  22 25 32 37  46 53 66 73  83 88 93 96  98\
Drama         4  13 16 22 27  35 42 53 60  71 78 87 91  96 97 99 99  ++\
Family       14  34 39 48 55  62 66 76 80  86 88 92 94  97 98 99 99  99\
Fantasy      13  26 30 38 45  52 56 65 69  76 79 84 87  91 93 95 96  97\
History       2   4  4  8 10  14 18 26 33  45 51 64 74  84 89 94 96  98\
Horror       35  56 61 68 72  78 81 86 89  92 93 96 96  97 98 98 98  99\
Music         7  14 16 20 24  30 34 40 47  53 60 68 74  82 87 91 95  97\
Musical       8  18 19 27 31  37 44 52 60  71 77 85 90  95 97 98 99  ++\
Mystery       7  21 26 33 38  46 51 62 69  78 81 86 89  93 95 96 98  98\
Reality TV   28  40 40 52 56  60 64 64 68  68 72 72 80  84 88 92 92  96\
Romance       4  14 17 24 30  39 46 59 66  76 82 89 93  97 98 99 99  ++\
Sci-Fi       26  43 47 52 57  62 65 71 74  79 81 86 89  93 94 96 97  98\
Short         4   8  9 12 15  19 23 30 37  49 57 72 77  87 93 97 98  99\
Sport         8  20 24 30 35  45 52 64 68  76 81 88 91  95 97 98 99  ++\
Talk Show    25  25 25 25 25  25 25 25 25  25 25 50 50  50 50 75 75  75\
Thriller     15  35 40 48 54  62 67 75 80  86 88 93 95  97 98 98 99  99\
War           5   9 11 15 18  23 28 34 43  50 59 71 77  86 92 95 97  99\
Western      14  23 26 34 42  47 52 68 76  81 83 90 95  96 97 98 99  99\
";

// process the data into something useful
var lines = data.regexsplit(/[A-Z][A-Z\- ]+/i, 100, true);
var genre, genre_data = {};
while(lines.length) {
    genre = lines.shift().strip();
    genre_data[genre] = lines.shift().regexsplit(/\s+/);
}

/* Find the (approximate) percentile of the film. */
function get_percentile(rating, genre) {
    if (!genre)
        genre = allname;
    
    rating *= 10;
    var data = genre_data[genre];
    var o, cmp;
    for (var i=data.length-1, j=0; i>=0; i--, j++) {
        if (data[i] == "++")
            data[i] = "100";
        cmp = !!i ? (90-(2.5*j)) : 40; // we only have data down to 4.0/10
        if (rating >= cmp) {
            return genre + ": " + data[i];
        }
    }
    return genre + ": <" + data[0];
}


var rating = extract_rating();
// movie is unrated
if (rating === false)
    return;

// collect data
var genre_details = [get_percentile(rating, g) for each (g in extract_genres())];

// htmlify it
var div = document.createElement("div");
var h = document.createElement("h5");
h.appendChild(document.createTextNode("Rating Percentile:"));
div.appendChild(h);
for each (text in genre_details) {
    var li = document.createElement("div");
    li.appendChild(document.createTextNode(text));
    div.appendChild(li); 
}

div.className = "info";
document.getElementById("tn15rating").appendChild(div);
document.getElementById("tn15rating").style.height = 6 + (genre_details.length * 1.5) + "em";
// if we don't change the height, our content is clipped

}());