/* 
 Copyright 2008-2010 Xmarks Inc.

 review-cache.jsm: Implements the LRU cache of review data.

 */


EXPORTED_SYMBOLS = ["Review", "FB"]

var Review = {}

const MAX_CACHE_LEN = 100;
var cache = {};
var cacheLen = 0;
var Cc = Components.classes;
var Ci = Components.interfaces;

Review.AddFromResponse = function(response) {
    // Adds a reviews to the cache, converting from
    // the form returned by API to the object form we use here.

    // Also update our FB status.
    if (response.fb_session != undefined) {
        fb_session = response.fb_session;
    } else if (response.fb_status) {
        fb_session = false;
    }

    var urls = response.urls;
    if (!urls) return;

    for (var url in urls) {
        if (!urls.hasOwnProperty(url))
            continue;

        var ru = urls[url];
        var rr = ru && ru.review;
        if (!rr) {
            Review.Add(url, {
                full: true,
                url: url,
                url_id: -1,
                numratings: 0,
                average: 0,
                rating: 0,
                body: "" });
        } else if (ru.unwanted) {
            Review.Add(url, null);
        } else {
            Review.Add(url, {
            full: true,
            url: url,
            url_id: ru.url_id,
            locked: rr.locked,
            numratings: rr.numratings || 0,
            average: rr.avgrating || 0,
            rating: rr.userrating || 0,
            body: rr.userreview || ""});
        }
    }
}

Review.AddFromSERP = function(response) {
    if (response.results && response.results.data) {
        var urls = response.results.data;

        for (var url in urls) {
            if (!urls.hasOwnProperty(url))
                continue;
            var ru = urls[url];
            var rr = ru.rating;
            if (!rr) continue;

            var review = {
                url: url,
                url_id: ru.url_id,
                locked: rr.locked,
                numratings: rr.numreviews || 0,
                average: rr.rating || 0,
            };
            Review.Add(url, review);
        }
    }
}


Review.Add = function(key, value) {
    if (key in cache) {
        // Simple replace.
        Set(key, value);
    } else if (cacheLen < MAX_CACHE_LEN) {
        // Simple add.
        Set(key, value);
        cacheLen += 1;
    } else {
        // Cache is full. Dump the stalest.
        PurgeOldest();
        Set(key, value);
    }

    var os = Cc["@mozilla.org/observer-service;1"]
        .getService(Ci.nsIObserverService);

    os.notifyObservers(null, "xmarks-reviewadded", key);
    return value;
}

Review.Get = function(key) {
    if (key in cache) {
        cache[key].last = Date.now();
        return cache[key].value;
    } else {
        return undefined;
    }
}

Review.__defineGetter__("CacheLength", function() { return cacheLen; });


var gReviewPending;

Review.Update = function(win, widget) {
    var Xmarks = win.Xmarks;
    var review = Review.Get(widget.url);
    var reviewIsNew = !review || !review.rating;

    if (widget.fb_session && reviewIsNew) {
        Xmarks.gSettings.postToFB = widget.post_to_fb.checked;
    }

    if (widget.changed) {
        if (!widget.fb_session && widget.post_to_fb.checked) {
            gReviewPending = true;
            Xmarks.LogWrite("calling FBAuth");
            FB.Authenticate(win, function(success) {
                Xmarks.LogWrite("back from FBAuth, success=" + success);
                if (gReviewPending) {
                    gReviewPending = false;
                    Xmarks.UpdateReview(widget.url, widget.url_id,
                        widget.title, widget.description,
                        widget.stars.rating, widget.textbox.value,
                        success, UpdateCallback);
                }
            });
        } else {
            Xmarks.UpdateReview(widget.url, widget.url_id, 
                widget.title, widget.description, widget.stars.rating, 
                widget.textbox.value, 
                widget.post_to_fb.checked, UpdateCallback);
        }
    }
}

// Stolen from places/utils.js.
Review.GetDescriptionFromDocument = function(doc) {
    var metaElements = doc.getElementsByTagName("META");
    for (var i = 0; i < metaElements.length; ++i) {
        if (metaElements[i].name.toLowerCase() == "description" ||
            metaElements[i].httpEquiv.toLowerCase() == "description") {
            return metaElements[i].content;
        }
    }
    return "";
}

var FB = {};
var browser;
var fb_session = false;

FB.Authenticate = function(win, callback) {
    var Xmarks = win.Xmarks;
    var self = this;
    Xmarks.LogWrite("Opening browser window");
    browser = win.openDialog(
        "chrome://browser/content/browser.xul", 
        "_blank", 
        "chrome=yes,dialog=no,centerscreen=yes,width=880,height=640", 
        Xmarks.gSettings.acctMgrHost + "/facebook/headless?a=" +
        Xmarks.gSettings.auth);
    Xmarks.LogWrite("Adding event listener");
    browser.addEventListener("load", function() {
        Xmarks.LogWrite("adding progress listener");
        browser.gBrowser.addProgressListener(fbUrlListener, 
            Ci.nsIWebProgress.NOTIFY_LOCATION);
        Xmarks.LogWrite("progress listener added");
    }, false);
    browser.addEventListener("DOMWindowClose", function() {
        Xmarks.LogWrite("closing; calling callback");
        callback.apply(self, 
            [browser.gBrowser.currentURI.spec.indexOf("success") >= 0]);
    }, false);
    browser.addEventListener("close", function() {
        Xmarks.LogWrite("onclose detected");
        callback.apply(self, [false]);
    }, false);
    Xmarks.LogWrite("FBAuth hooks are in.");
    return;
}

var fbUrlListener = {
    QueryInterface: function(aIID) {
        if (aIID.equals(Ci.interfaces.nsIWebProgressListener) ||
            aIID.equals(Ci.nsISupportsWeakReference) ||
            aIID.equals(Ci.nsISupports))
        return this;
        throw Components.results.NS_NOINTERFACE;
    },

    onLocationChange: function(aProgress, aRequest, aURI) {
        var url = aURI.spec;
        if (url.indexOf("success") >= 0) {
            browser.close();
        } else if (url.indexOf("fail") >= 0) {
            browser.close();
        }
    },

    onStateChange: function(a, b, c, d) {},
    onProgressChange: function(a, b, c, d, e, f) {},
    onStatusChange: function(a, b, c, d) {},
    onSecurityChange: function(a, b, c) {}
};

FB.__defineGetter__("has_session", function() { 
        return fb_session ? true: false; });

// That's the whole externally visbile API. Below is implementation.

function Set(key, value) {
    cache[key] = { 'value': value, 'last': Date.now() };
}


function PurgeOldest() {
    var minLast = Infinity;
    var minKey = null;

    for (key in cache) {
        if (!cache.hasOwnProperty(key)) {
            continue;
        }

        if (cache[key].last < minLast) {
            minLast = cache[key].last;
            minKey = key;
        }
    }
    delete cache[minKey];
}

function UpdateCallback(response) {
    if (!response.status) {
        Review.AddFromResponse(response);
    }
}
