/* 
 Copyright 2008-2010 Xmarks Inc.

 foxmarks-turbotags.js: Implements Foxmarks Turbotags feature.

 */


(function(){
    const TAGS_PER_ROW = 5;
    const MAX_NEW_TAGS = 10;
    const MIN_TAG_PERCENT = 1;

    var browser = null;

    window.addEventListener("load", turboTagsOnLoad, false);

    var gTTEditBookmarkPopup = null;
    var gTTHandleEvent = null;
    var gCancelButtonOnCommand = null;
    var gRemoveBookmarkButtonCommand = null;
    var gTrackClick = true;
    var gCanceled = false;

    var gPanel = document.getElementById("editBookmarkPanel");
    var gTagRequestInFlight = false;
    gPanel.addEventListener(
        "popuphidden", 
        function(e){
            if (e.originalTarget == gPanel) {
                if (gTagRequestInFlight){
                    Xmarks.LogWrite("Cancelling TurboTag Request");
                    Xmarks.TurboTagsCancel();
                    gTagRequestInFlight = false;
                }
            }

            var widget = document.getElementById("XmarksReviewWidget");
            if (!widget) {
                return;
            }

            if (!gCanceled) {
                Xmarks.Review.Update(window, widget);
            }
        },
        false
    );
     
    function turboTagsOnLoad() {
        Components.utils.import("resource://xmarks/reviews.jsm", Xmarks)

        // Hook StarUI._doShowEditBookmarkPanel
        gTTEditBookmarkPopup = StarUI._doShowEditBookmarkPanel;
        StarUI._doShowEditBookmarkPanel= ttShowEditBookmarkPopup;

        // Also hook StarUI.handleEvent
        gTTHandleEvent = StarUI.handleEvent;
        StarUI.handleEvent = ttHandleEvent;

        // And the cancel button
        gCancelButtonOnCommand = StarUI.cancelButtonOnCommand;
        StarUI.cancelButtonOnCommand = function() {
            gCanceled = true;
            gCancelButtonOnCommand.apply(StarUI, arguments);
        }

        // And the remove bookmark button
        gRemoveBookmarkButtonCommand = StarUI.removeBookmarkButtonCommand;
        StarUI.removeBookmarkButtonCommand = function() {
            gCanceled = true;
            gRemoveBookmarkButtonCommand.apply(StarUI, arguments);
        }
    }
    function and(a1, a2) {
        return [x for each (x in a1) if (a2.indexOf(x) >= 0)];
    }

    function andNot(a1, a2) {
        return [x for each (x in a1) if (a2.indexOf(x) < 0)];
    }

    function ttDoNewUser(){
        //remove old one, regardless
        var warning = document.getElementById("ttNewUserWarning");
        if(warning){
            warning.parentNode.removeChild(warning);
        }

        // if we need to show it
        if (!Xmarks.gSettings.useOwnServer &&
            !Xmarks.gSettings.haveSynced && Xmarks.gSettings.wizardWarning) {
            var parent =
                document.getElementById("editBMPanel_tagsRow").parentNode;

            var row = document.createElement("row");
            row.setAttribute("id", "ttNewUserWarning");
            row.appendChild(document.createElement("spacer"));

            var hbox = document.createElement("hbox");
            hbox.setAttribute(
                "style",
                "padding: 0px;"
            );
            hbox.addEventListener(
                "click",
                function(e){
                    var callback = {
                        notify: function(){
                            Xmarks.OpenWizard(false, false);
                        }
                    };

                    //close the dialog
                    gPanel.hidePopup();

                // once the dust settles, tell the settings pane to popup
                    var timer = 
                        Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
                    timer.initWithCallback(callback, 10,
                        Ci.nsITimer.TYPE_ONE_SHOT);
                },
                false
            );
            
            var titlebox = document.createElement("hbox");
            titlebox.appendChild(document.createElement("spacer"));
            var logobox = document.createElement("vbox");
            logobox.setAttribute("width", "16");
            logobox.setAttribute("flex", "0");
            var logo = document.createElement("image");
            logo.setAttribute("width", "16");
            logo.setAttribute("height", "16");
            logo.setAttribute("flex", "0");
            logo.setAttribute("src", 
                "chrome://global/skin/icons/warning-16.png");
            logobox.appendChild(logo);
            logobox.appendChild(document.createElement("spacer"));
            hbox.appendChild(logobox);
            //titlebox.appendChild(logobox);
            //row.appendChild(titlebox);
            var text = document.createElement("description");
            logo.setAttribute("width", "100");
            text.setAttribute("flex", "0");
            text.setAttribute(
                "style",
                "padding: 1px 0px 0px 4px;"
            );
            text.appendChild(
                document.createTextNode(
                    Xmarks.Bundle().GetStringFromName("newuser.msg")
                )
            );
            hbox.appendChild(text);
            row.appendChild(hbox);

            var hidebox = document.createElement("label");
            hidebox.setAttribute("value", 
                    Xmarks.Bundle().GetStringFromName("newuserhide.msg")
            );
            hidebox.setAttribute(
                "style",
                "text-decoration: underline; cursor: pointer; padding: 0px;"
            );
            hidebox.addEventListener(
                "click",
                function(e){
                    e.stopPropagation();
                    Xmarks.gSettings.wizardWarning = false;
                    parent.removeChild(row);
                },
                true
            );
            row.appendChild(hidebox);
            parent.insertBefore(row, 
                document.getElementById("editBMPanel_tagsRow").nextSibling);
        }
    }
    function ttHandleEvent(event) {
        // Intercept presses of enter key if they're inside the review
        // textbox, otherwise StarUI swallows it to close the dialog.
        if (event.type == 'keypress' && 
                event.keyCode == KeyEvent.DOM_VK_RETURN &&
                event.target.nodeName == 'xmarks-review') {
            return;
        }
        gTTHandleEvent.apply(StarUI, arguments);
    }

    function ttShowEditBookmarkPopup() {
        gTTEditBookmarkPopup.apply(StarUI, arguments);

        ttHideRow("XmarksReviewRow");
        ttDoNewUser();
        if (!Xmarks.gSettings.tagSuggestionsEnabled || 
            Xmarks.gSettings.useOwnServer || 
            Xmarks.gSettings.privateBrowsing) {
            var row = document.getElementById("ttTagsButtonRow");
            if(row){
                row.parentNode.removeChild(row);
            }
            return;
        }
        gTrackClick = true;
        gCanceled = false;
        gReviewPending = false;

        var uri = gEditItemOverlay._uri;
        var url = uri.spec;
        var tagsField = document.getElementById("editBMPanel_tagsField");
        tagsField.addEventListener(
            "input",
            function(e){
                ttOnTagsFieldChanged(tagsField, e);
            },
            false
        );

        var progress= ttStartThrobber();
        var callback = function(response){
            gTagRequestInFlight = false;
            if (response.status != 0) {
                // Request failed; head for the hills!
                ttHideRow("XmarksReviewRow");
                ttHideRow("ttTagsButtonRow");
                return;
            }
            if (response.urls && response.urls[url] && 
                    response.urls[url]["topics"]) {
                // Get list of all tags
                var startTime = Xmarks.LogTimer("Time Turbo Calcs Start");
                var tsvc = Cc["@mozilla.org/browser/tagging-service;1"].
                    getService(Ci.nsITaggingService); 
                var allTags = tsvc.allTags.length > 
                    Xmarks.gSettings.maxUserTags ?  [] :tsvc.allTags;

                // Extract folders from response
                var folders = response.urls[url]["topics"];
                const MIN_TAG_COUNT = Math.round(
                    response.urls[url]["user_count"] * 
                    MIN_TAG_PERCENT / 100.0); 
                foldersAboveThreshold = [x[0] for each (x in folders) if 
                    (x[1] >= MIN_TAG_COUNT)];
                foldersBelowThreshold = [x[0] for each (x in folders) if
                    (x[1] < MIN_TAG_COUNT)];

                // Get tags already assigned to this bookmark
                var assignedTags = tsvc.getTagsForURI(uri, {});

                var tags = [];
                // in corpus (above threshold) and currently assigned 
                tags = tags.concat(and(foldersAboveThreshold, assignedTags));
                // in corpus (above threshold) and used by the user elsewhere 
                tags = tags.concat(andNot(and(foldersAboveThreshold, allTags), 
                        tags));
                // in corpus (above threshold) but not used anywhere 
                tags = tags.concat(andNot(foldersAboveThreshold, tags));  
                //  all assigned tags not already included
                tags = tags.concat(andNot(assignedTags, tags));
                // in corpus (below threshold) and currently assigned 
                tags = tags.concat(andNot(and(foldersBelowThreshold, 
                        assignedTags), tags));
                // in corpus (below threshold) and used by the user elsewhere
                tags = tags.concat(andNot(and(foldersBelowThreshold, allTags), 
                        tags));
                // in corpus (below threshold) but not used
                tags = tags.concat(andNot(foldersBelowThreshold, tags));
                Xmarks.LogTimer("Turbo Calcs, pre-dedupe", startTime);

                // de dup
                var a = [];
                var l = tags.length;
                var hash = {};
                for(var x = 0; x < l; x++){
                    var tag = tags[x];
                    if(!hash[tag]){
                        a.push(tag);
                        hash[tag] = true;
                    } else {
                        Xmarks.LogWrite("Duplicate Tag: " + tag);
                    }
                }
                Xmarks.LogTimer("Turbo Calcs, post-dedupe", startTime);
                tags = a;
                tags = tags.slice(0, MAX_NEW_TAGS);
                Xmarks.LogWrite("Tags are " + tags.toSource());
                ttBuildTagButtonRow(tags);
            } else {
                ttBuildTagButtonRow([]);
            }

            Xmarks.Review.AddFromResponse(response);
            var review = Xmarks.Review.Get(url);
            if (review && !review.locked) {
                try {
                    var post_to_fb = Xmarks.FB.has_session && 
                        Xmarks.gSettings.postToFB && !review.rating;
                    ttBuildReviewRow(review, post_to_fb);
                } catch(e) {
                    Xmarks.LogWrite("Failed creating review row: " +
                            e.toSource());
                }
            } else {
                Xmarks.LogWrite("Failed response is: " + response.toSource());
                ttHideRow("XmarksReviewRow");
            }
        };
        gTagRequestInFlight = true;
        if (url.indexOf("http") != 0 ||
                !Xmarks.FetchTurboTags(url, progress.text, progress.spinner, 
                callback)) {
            ttBuildTagButtonRow([]);
            gTagRequestInFlight = false;
        }
    }    

    function ttHideRow(id) {
        var el = document.getElementById(id);
        if (el) el.setAttribute("hidden", true);
    }

    function ttBuildReviewRow(review, post_to_fb) {
        var widget;
        var row = document.getElementById("XmarksReviewRow");
        if (!row) {
            row = document.createElement("row");
            var label = document.createElement("label");
            widget = document.createElement("xmarks-review");
            widget.setAttribute("flex", "1");
            var hbox = document.createElement("hbox");

            row.setAttribute("id", "XmarksReviewRow");
            row.setAttribute("style", "margin-top: 4px");
            label.setAttribute("value", 
                Xmarks.Bundle().GetStringFromName("reviews.rating"));
            label.setAttribute("style", "margin-top: 2px; text-align: right;");
            widget.setAttribute("id", "XmarksReviewWidget");
            row.appendChild(label);
            row.appendChild(hbox);
            hbox.appendChild(widget);

            var anchorRow = document.getElementById("editBMPanel_keywordRow");
            var rows = anchorRow.parentNode;
            rows.appendChild(row);
        } else {
            widget = document.getElementById("XmarksReviewWidget");
            row.setAttribute("hidden", false);
        }
        widget.initialize(review, gBrowser.contentDocument.title, 
            Xmarks.Review.GetDescriptionFromDocument(gBrowser.contentDocument), 
            Xmarks.FB.has_session, post_to_fb);
    }

    function ttTagsFromString(str) {
        if (!str.length) {
            return [];
        } else {
            var tags = str.split(",");
            return [t.replace(/^\s+|\s+$/g,"") for each (t in tags) 
                if (t.length > 0)];
        }
    }

    function ttEnumerateTagElements() {
        var box = document.getElementById("ttTagsButtonBox");
        var elements = [];
        if (box && box.hasChildNodes()) {
            for (var r = 0; r < box.childNodes.length; ++r) {
                var row = box.childNodes[r];
                for (var i = 0; i < row.childNodes.length; ++i) {
                    elements.push(row.childNodes[i]);
                }
            }
        }
        return elements;
    }

    function ttUpdateCheckedStates(tagString) {
        var tags = ttTagsFromString(tagString);

        ttEnumerateTagElements().forEach(function(element) {
            element.checked = (tags.indexOf(element.label) >= 0);
        });
    }

    function ttOnTagsFieldChanged(element) {
        ttUpdateCheckedStates(element.value);
    }

    function ttAddInfoIcon(row, iconId) {
        var vbox = document.createElement("vbox");
        vbox.setAttribute("id", iconId);
        var hbox = document.createElement("hbox");
        var img = document.createElement("image");
        img.setAttribute("src", 
            "chrome://foxmarks/skin/images/information.png");
        img.setAttribute("width", "16");
        img.setAttribute("height", "16");
        img.setAttribute("style", "margin-right: 8px; margin-top: 3px");
        img.setAttribute("tooltiptext", 
            Xmarks.Bundle().GetStringFromName("turbotags.whatsthis"));
        img.addEventListener(
            "click", 
            function() { 
                var callback = {
                    notify: function() {
                        var os = Cc["@mozilla.org/observer-service;1"].
                            getService(Ci.nsIObserverService);

                        os.notifyObservers(null, "foxmarks-showsettingpane", 
                            "foxmarks-discoverypane");
                    }
                };

                //close the dialog
                gPanel.hidePopup();

                // once the dust settles, tell the settings pane to popup
                var timer = Cc["@mozilla.org/timer;1"].
                    createInstance(Ci.nsITimer);
                timer.initWithCallback(callback, 10, Ci.nsITimer.TYPE_ONE_SHOT);
            },
            false
        );
        hbox.appendChild(vbox);
        vbox.appendChild(img);
        var spacer = document.createElement("spacer");
        spacer.setAttribute("flex", "1");
        row.appendChild(spacer);
        row.appendChild(hbox);
    }

    function ttBuildTagButtonRow(tags) {
        /* What we want is something like this:

        <row id="ttTagButtonRow">
        <label value="Turbotags" />
        <vbox>
            <hbox>
                <button>
                <button>
                ...
            </hbox>
            <hbox>
            <buttons>
            <button>
            <hbox>
        </vbox>
        </row>

        */

        var row = document.createElement("row");
        row.setAttribute("id", "ttTagsButtonRow");
        if (!tags.length) {
            row.setAttribute("hidden", true);
        }

        var label = document.createElement("label");
        label.setAttribute("value",  
            Xmarks.Bundle().GetStringFromName("turbotags.title"));
        label.setAttribute("style", "margin-top: 4px;");
        label.addEventListener("click", ttToggleAll, false);
        row.appendChild(label);

        var vbox = document.createElement("vbox");
        vbox.setAttribute("id", "ttTagsButtonBox");
        var hboxes = [];

        for (var r = 0; r < tags.length; r += TAGS_PER_ROW) {
            hbox = document.createElement("hbox");
            hboxes.push(hbox);
            tags.slice(r, r + TAGS_PER_ROW).forEach(function(t) {
                var button = document.createElement("checkbox");
                button.setAttribute("label", t);
                button.addEventListener(
                    "command", 
                    function(){
                        ttToggleTag(button);
                    }, 
                    false
                );
                button.setAttribute("type", "checkbox");
                hbox.appendChild(button);
            });
            vbox.appendChild(hbox);
        }
        if (hboxes.length) {
            ttAddInfoIcon(hboxes[0], "XmarksInfoIconTags");
        }
        row.appendChild(vbox);

        var anchorRow = document.getElementById("editBMPanel_tagsRow");
        var rows = anchorRow.parentNode;
        var existingRow = document.getElementById("ttTagsButtonRow");
        if (existingRow) {
            existingRow.parentNode.replaceChild(row, existingRow);
        } else {
            rows.insertBefore(row, anchorRow);
        }
        ttUpdateCheckedStates(document.getElementById(
                "editBMPanel_tagsField").value);
    }

    function ttToggleTag(element) {
        var tag = element.label;
        var tagsField = document.getElementById("editBMPanel_tagsField");
        var tags = ttTagsFromString(tagsField.value);
        var added = false;
        if (tags.indexOf(tag) < 0) {
            tags.push(tag);
            added = true;
            if(gTrackClick){
                Xmarks.gSettings.numTurboTags += 1;
                gTrackClick = false;
            }

        } else {
            tags.splice(tags.indexOf(tag), 1);
            added = false;
        }
        if(tags.length > 0) { 
            tagsField.value = tags.join(", ") + ",";
        } else {
            tagsField.value = "";
        }
        gEditItemOverlay._updateTags();
    }

    function ttToggleAll() {
        var all = ttEnumerateTagElements();
        var allChecked = all.every(function(e) { return e.checked; });
        var newState = !allChecked;
        all.forEach(function(e) { 
                if (e.checked != newState) {
                    e.checked = newState;
                    ttToggleTag(e)
                }; } );
    }


    function ttStartThrobber() {
        var row = document.createElement("row");
        row.setAttribute("id", "ttTagsButtonRow");
        row.setAttribute("align", "center");

        var label = document.createElement("label");
        label.setAttribute("value",  
            Xmarks.Bundle().GetStringFromName("turbotags.title"));
        label.setAttribute("onclick", "ttToggleAll()");
        row.appendChild(label);

        var hbox = document.createElement("hbox");
        var img = document.createElement("image");
        img.setAttribute("src", 
            "chrome://foxmarks/skin/images/Throbber-small.gif");
        img.setAttribute("height", 16);
        img.setAttribute("width", 16);
        hbox.appendChild(img);

        var label = document.createElement("label");
        label.setAttribute("value", "Loading...");
        hbox.appendChild(label);
        row.appendChild(hbox);

        var anchorRow = document.getElementById("editBMPanel_tagsRow");
        var rows = anchorRow.parentNode;
        var existingRow = document.getElementById("ttTagsButtonRow");
        if (existingRow) {
            existingRow.parentNode.replaceChild(row, existingRow);
        } else {
            rows.insertBefore(row, anchorRow);
        }

        return { spinner: img, text: label };
    }

})();
