/*
    Digg Sidebar - Shows Digg stories in the Firefox sidebar in real time.
    Copyright (C) 2008  Abhinav Sarkar <abhinav dot sarkar at gmail dot com>

    This file is a part of Digg Sidebar.

    Digg Sidebar 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.

    Digg Sidebar 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 Digg Sidebar.  If not, see <http://www.gnu.org/licenses/>.

*/

var DiggSidebar = {
    homepageURL: "http://code.google.com/p/digg-sidebar/",
    prefs: Application.extensions.get("diggsidebar@abhinavsarkar.net").prefs,
    observerService: Components.classes["@mozilla.org/observer-service;1"]
        .getService(Components.interfaces.nsIObserverService),
    storyListRefreshEventTopic: "extension-diggsidebar-abhinavsarkar-net-storylist-refresh",
    updateInterval: 1000,
    updateIntervalDecay: 0,
    shownStoriesCount: 30,//make configurable
    cachedStoriesCount: 1000,//make configurable
    categories: ["All", "Popular", "Upcoming", "Hot", "Top"],

    endpoint: {
        container: null,
        topic: null,
        category: "all"
    },
    lastUpdateAt: new Date().getTime(),
    timerId: null,
    storyIds: [],
    refreshing: false,
    stories: [],
    expandedStory: null,

    $ce: function (tagName) {
        return document.createElement(tagName);
    },

    fetchData: function (url, loadHandler, errorHandler) {
        var request = new XMLHttpRequest();
        request.onerror = DiggSidebar.errorHandler;
        request.onload = loadHandler;
        request.onerror = errorHandler;
        request.open("GET", url, true);
        request.send(null);
    },

    errorHandler: function (e) {
        /*if (DiggSidebar.UI.notificationBox.allNotifications.length > 0)
            DiggSidebar.UI.notificationBox.removeAllNotifications(true);*/
        DiggSidebar.UI.notificationBox.appendNotification(
            "Unable to access Digg",
            "dsAccessFailNotification",
            null,
            "PRIORITY_CRITICAL_HIGH",
            [{callback: DiggSidebar.getStories, label: "Retry", accessKey: "R"}]
        );
        DiggSidebar.refreshing = false;
        DiggSidebar.indicateInactivity();
    },

    populateMenu: function (e) {
        var XHR = e.target;
        var data = DiggSidebar.Utils.decodeJson(XHR.responseText);
        var containers = data.containers;

        containers.forEach(function (container) {
            var cmenu = DiggSidebar.$ce("menu");
            with (cmenu) {
                setAttribute("id", container.short_name + "Menu");
                setAttribute("label", container.name);
                setAttribute("accesskey", container.name.charAt(0));
            }

            var cmenupopup = DiggSidebar.$ce("menupopup");
            cmenupopup.setAttribute("id", container.short_name + "Popup");

            DiggSidebar.categories.forEach(function (category) {
                var cmenuitem = DiggSidebar.$ce("menuitem");
                with (cmenuitem) {
                    setAttribute("label", category);
                    setAttribute("value",
                        "/container/" + container.short_name +
                        "/" + category.toLowerCase());
                    setAttribute("accesskey", category.charAt(0));
                    setAttribute("oncommand",
                        "DiggSidebar.setEndPoint(this.value)");
                }
                cmenupopup.appendChild(cmenuitem);
            });
            cmenupopup.appendChild(DiggSidebar.$ce("menuseparator"));
            var topics = container.topics;
            topics.forEach(function (topic) {
                var tmenu = DiggSidebar.$ce("menu");
                with (tmenu) {
                    setAttribute("id", topic.short_name + "Menu");
                    setAttribute("label", topic.name);
                    setAttribute("accesskey", topic.name.charAt(0));
                }

                var tmenupopup = DiggSidebar.$ce("menupopup");
                tmenupopup.setAttribute("id", topic.short_name + "Popup");

                DiggSidebar.categories.forEach(function (category) {
                    var tmenuitem = DiggSidebar.$ce("menuitem");
                    with (tmenuitem) {
                        setAttribute("label", category);
                        setAttribute("value", "/topic/" + topic.short_name +
                            "/" + category.toLowerCase());
                        setAttribute("accesskey", category.charAt(0));
                        setAttribute("oncommand",
                            "DiggSidebar.setEndPoint(this.value)");
                    }
                    tmenupopup.appendChild(tmenuitem);
                });
                tmenu.appendChild(tmenupopup);
                cmenupopup.appendChild(tmenu);
            });
            cmenu.appendChild(cmenupopup);
            DiggSidebar.UI.storiesPopup.appendChild(cmenu);
        });
    },

    populateStoryList: function () {
        var ep = DiggSidebar.prefs.get("endpoint").value || "";
        DiggSidebar.UI.endPointDesc.value = "digg" + ep;

        var newStoryIds = [];

        var jp = new JPath(DiggSidebar.stories);
        var filteredStories = jp.$(function (story) {
            return (story.$("category").json == DiggSidebar.endpoint.category);
        }).json;

        jp = new JPath(filteredStories);
        if (DiggSidebar.endpoint.topic) {
            filteredStories = jp.$(function (story) {
                return story.$("topic/short_name").json == DiggSidebar.endpoint.topic;
            }).json;
        }
        if (DiggSidebar.endpoint.container) {
            filteredStories = jp.$(function (story) {
                return story.$("container/short_name").json == DiggSidebar.endpoint.container;
            }).json;
        }

        if (filteredStories.length > 0) {
            DiggSidebar.Utils.removeAllChildren(DiggSidebar.UI.storyListBox);
        }

        var now = new Date();

        filteredStories.forEach(function (story, index) {
            if (index < DiggSidebar.shownStoriesCount) {
                var then = (story.promote_date != null) ?
                    new Date(story.promote_date * 1000) :
                    new Date(story.submit_date * 1000);

                var diff = Math.max(now - then, 0);

                var hr = Math.floor(diff / (1000 * 3600));
                var min = Math.floor(diff / (1000 * 60)) - 60 * hr;

                var relativeTime = ((hr > 0) && (min > 0)) ?
                    (hr + " hr " + min + " mins ago") :
                    ((hr == 0) && (min > 0)) ?
                    (min + " mins ago") :
                    ((hr == 0) && (min == 0)) ?
                    "just now" : "";

                var li = DiggSidebar.UI.storyListBox.appendChild(
                    DiggSidebar.$ce("richlistitem"));
                li.id = "story_" + story.id;
                var attributes = {
                    title: story.title,
                    date: relativeTime,
                    status: story.status,
                    container: story.container.name,
                    topic: story.topic.name,
                    username: story.user.name,
                    userlink: "http://digg.com/users/" + story.user.name,
                    diggs: story.diggs,
                    comments: story.comments,
                    desc: story.description,
                    link: story.link,
                    href:  story.href
                };
                for (var attr in attributes) {
                    li.setAttribute(attr, attributes[attr]);
                }

                li.read = story.read;
                if (story.id == DiggSidebar.expandedStory) {
                    li.showDescription();
                }
            }
            if (DiggSidebar.storyIds.indexOf(story.id) == -1) {
                newStoryIds.push(story.id);
            }
        });
        DiggSidebar.storyIds = newStoryIds.concat(DiggSidebar.storyIds);
        if (DiggSidebar.storyIds.length > DiggSidebar.cachedStoriesCount) {
            DiggSidebar.storyIds.length = DiggSidebar.cachedStoriesCount;
        }
    },

    showDescription: function (storyId) {
        var jp = new JPath(DiggSidebar.stories);
        var story = jp.query("//[id == " + storyId + "]")[0];
        story.read = true;
        DiggSidebar.expandedStory = parseInt(storyId);

        var listitems = DiggSidebar.UI.storyListBox.children;
        for (var i = 0; i < listitems.length; i++) {
            listitems[i].hideDescription();
        }

        document.getElementById("story_" + story.id).showDescription();
    },

    getStories: function () {
        if (!DiggSidebar.refreshing) {
            DiggSidebar.refreshing = true;
            var ep = DiggSidebar.prefs.get("endpoint").value || "";

            DiggSidebar.indicateActivity();
            DiggSidebar.fetchData(
                "http://services.digg.com/stories" + ep.replace(/\/all/g, "") +
                "?count=30" + "&type=json" +
                "&appkey=" + encodeURIComponent(DiggSidebar.homepageURL),

                function (e) {
                    var stories = DiggSidebar.Utils.decodeJson(e.target.responseText).stories;
                    stories.forEach(function (story) {
                        story.id = parseInt(story.id);
                    });

                    var newStories = [
                        story for each (story in stories)
                        if (DiggSidebar.storyIds.indexOf(story.id) == -1)];

                    newStories.forEach(function (story) {
                        story.category = DiggSidebar.endpoint.category;
                        story.read = false;
                    });
                    DiggSidebar.stories = newStories.concat(DiggSidebar.stories);
                    if (DiggSidebar.stories.length > DiggSidebar.cachedStoriesCount) {
                        DiggSidebar.stories.length = DiggSidebar.cachedStoriesCount;
                    }

                    DiggSidebar.observerService.notifyObservers(
                        null, DiggSidebar.storyListRefreshEventTopic, null);

                    DiggSidebar.refreshing = false;
                    DiggSidebar.setUpdateInterval(stories);

                    window.clearTimeout(DiggSidebar.timerId);
                    var timeout = DiggSidebar.calculateTimeout();
                    DiggSidebar.timerId = window.setTimeout(DiggSidebar.getStories, timeout);

                    DiggSidebar.indicateInactivity();
                },
                DiggSidebar.errorHandler);
        } else {
            window.setTimeout(DiggSidebar.getStories, 1000);
        }
    },

    createMenu: function () {
        DiggSidebar.indicateActivity();
        DiggSidebar.fetchData(
            "http://services.digg.com/containers" + "?type=json" +
            "&appkey=" + encodeURIComponent(DiggSidebar.homepageURL),
            DiggSidebar.populateMenu, DiggSidebar.errorHandler);
    },

    setUpdateInterval: function (stories) {
        //Adaptive update interval code START
        if (stories.length == 0) {
            return;
        }
        var sum = 0;
        var weights = [4, 3, 2, 1];
        var date = (stories[0].promote_date != null) ? "promote_date" : "submit_date";
        stories.sort(function (a, b) {
            if (a[date] > b[date]) {
                return -1;
            } else if (a[date] < b[date]) {
                return 1;
            } else {
                return 0;
            }
        });
        for (var i = 0; i < Math.min(4, stories.length - 1); i++) {
            var diff = stories[i][date] - stories[i + 1][date];
            sum += weights[i] * diff;
        }
        var newUpdateInterval = Math.round(
            sum * 1000 /
            weights.slice(0, Math.min(4, stories.length - 1))
                .reduce(function (a, b) a + b));

        if (newUpdateInterval > 0) {
            var previousUpdateInterval = DiggSidebar.updateInterval;
            DiggSidebar.updateInterval = newUpdateInterval;

            if (previousUpdateInterval == newUpdateInterval) {
                DiggSidebar.updateIntervalDecay += 1;
            } else {
                DiggSidebar.updateIntervalDecay = 0;
            }
        }
        //Adaptive update interval code END
    },

    calculateTimeout: function () {
        return Math.round(DiggSidebar.updateInterval *
            Math.pow(1.5, DiggSidebar.updateIntervalDecay));
    },

    setEndPoint: function (ep) {
        DiggSidebar.prefs.get("endpoint").value = ep;
        DiggSidebar.endpoint = DiggSidebar.getEndpointParts(ep);
        window.clearTimeout(DiggSidebar.timerId);
        DiggSidebar.getStories();
    },

    getEndpointParts: function (ep) {
        var tmp = {
            topic: null,
            container: null,
            category: "all"
        };
        var parts = ep.split("/");
        if (parts.length == 2) {
            tmp.category = parts[1];
        } else if (parts.length == 4) {
            tmp[parts[1]] = parts[2];
            tmp.category = parts[3];
        }
        return tmp;
    },

    indicateActivity: function () {
        DiggSidebar.UI.indicator.src = "chrome://global/skin/icons/loading_16.png";
        with (DiggSidebar.UI.indicator.parentNode) {
            disabled = true;
            setAttribute("tooltiptext", "Refreshing");
            onmouseover = null;
        }
    },

    indicateInactivity: function () {
        DiggSidebar.UI.indicator.src = "chrome://global/skin/icons/notloading_16.png";
        with (DiggSidebar.UI.indicator.parentNode) {
            disabled = false;
            onmouseover = function () {
                var autoRefreshTime = Math.round(
                    (DiggSidebar.lastUpdateAt +
                     DiggSidebar.calculateTimeout() - new Date().getTime()) / 1000);

                autoRefreshTime = (autoRefreshTime > 3600) ?
                    Math.round(autoRefreshTime / 3600) + " hours":
                    (autoRefreshTime > 60) ?
                        Math.round(autoRefreshTime / 60) + " minutes":
                        autoRefreshTime + " seconds";
                setAttribute("tooltiptext", "Click to refresh now.\n" +
                    "Autorefreshing in " + autoRefreshTime);
            };
        }
        DiggSidebar.lastUpdateAt = new Date().getTime();
    },

    initialize: function () {
        DiggSidebar.endpoint = DiggSidebar.getEndpointParts(
            DiggSidebar.prefs.get("endpoint").value);
        var $ = function (id) {
            return document.getElementById(id);
        };

        DiggSidebar.UI = {};
        DiggSidebar.UI.notificationBox = $("dsNotificationBox");
        DiggSidebar.UI.indicator = $("dsBusyIndicator");
        DiggSidebar.UI.storiesPopup = $("dsStoriesPopup");
        DiggSidebar.UI.storyListBox = $("dsStoryListBox");
        DiggSidebar.UI.endPointDesc = $("dsEndPointDesc");

        DiggSidebar.storyListObserver = new DiggSidebar.StoryListObserver();
        DiggSidebar.storyListObserver.register();
        DiggSidebar.createMenu();
        DiggSidebar.getStories();
    },

    destroy: function () {
        DiggSidebar.storyListObserver.unregister();
        DiggSidebar.stories = [];
    }
};

DiggSidebar.Utils = {
    url: function (spec) {
        var ios = Components.classes["@mozilla.org/network/io-service;1"]
                    .getService(Components.interfaces.nsIIOService);
        return ios.newURI(spec, null, null);
    },

    openUrlInTab: function (url) {
        Application.activeWindow.open(DiggSidebar.Utils.url(url));
    },

    decodeJson: function (string) {
        var json = Components.classes["@mozilla.org/dom/json;1"]
            .createInstance(Components.interfaces.nsIJSON);
        return json.decode(string);
    },

    removeAllChildren: function (node) {
        while (node.firstChild) {
            node.removeChild(node.firstChild);
        }
    }
};

DiggSidebar.StoryListObserver = function () {
    this.registered = false;
};

DiggSidebar.StoryListObserver.prototype = {
    observe: function (subject, topic, data) {
        if (topic == DiggSidebar.storyListRefreshEventTopic) {
            DiggSidebar.populateStoryList();
        }
    },

    register: function () {
        if (!this.registered) {
            DiggSidebar.observerService.addObserver(
                this, DiggSidebar.storyListRefreshEventTopic, false);
            this.registered = true;
        }
    },

    unregister: function () {
        if (this.registered) {
            DiggSidebar.observerService.removeObserver(
                this, DiggSidebar.storyListRefreshEventTopic);
            this.registered = false;
        }
    }
}

/*DONE
 v0.2
 added kdb navigation
 Added progressmeter
 added error notification
 added accesskeys
 added new story notification

 v0.2.1
 adaptive polling interval
 promote_date
 pause/play

 v0.5
 new ui

 v0.7
 moved to JSON data format
 moved HTML template to XBL
 refactored JavaScript/Using FUEL
 implemented caching and notifier-observer
 new UI
 added toolbar button
 put license
*/
