/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0. */

var Dom = YAHOO.util.Dom;

YAHOO.bugzilla.commentTagging = {
    ctag_div  : false,
    ctag_add  : false,
    counter   : 0,
    min_len   : 3,
    max_len   : 24,
    tags_by_no: {},
    nos_by_tag: {},
    current_id: 0,
    current_no: -1,
    can_edit  : false,
    pending   : {},

    init : function(can_edit) {
        this.can_edit = can_edit;
        this.ctag_div = Dom.get('bz_ctag_div');
        this.ctag_add = Dom.get('bz_ctag_add');
        YAHOO.util.Event.on(this.ctag_add, 'keypress', this.onKeyPress);
        YAHOO.util.Event.onDOMReady(function() {
            YAHOO.bugzilla.commentTagging.updateCollapseControls();
        });
        if (!can_edit) return;

        var ds = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
        ds.connTimeout = 30000;
        ds.connMethodPost = true;
        ds.connXhrMode = "cancelStaleRequests";
        ds.maxCacheEntries = 5;
        ds.responseSchema = {
            metaFields : { error: "error", jsonRpcId: "id"},
            resultsList : "result"
        };

        var ac = new YAHOO.widget.AutoComplete('bz_ctag_add', 'bz_ctag_autocomp', ds);
        ac.maxResultsDisplayed = 7;
        ac.generateRequest = function(query) {
            query = YAHOO.lang.trim(query);
            YAHOO.bugzilla.commentTagging.last_query = query;
            YAHOO.bugzilla.commentTagging.counter = YAHOO.bugzilla.commentTagging.counter + 1;
            YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
            return YAHOO.lang.JSON.stringify({
                method : "Bug.search_comment_tags",
                id : YAHOO.bugzilla.commentTagging.counter,
                params : [ { query : query, limit : 10 } ]
            });
        };
        ac.minQueryLength = this.min_len;
        ac.autoHighlight = false;
        ac.typeAhead = true;
        ac.queryDelay = 0.5;
        ac.dataReturnEvent.subscribe(function(type, args) {
            args[0].autoHighlight = args[2].length == 1;
        });
    },

    toggle : function(comment_id, comment_no) {
        if (!this.ctag_div) return;
        var tags_container = Dom.get('ct_' + comment_no);

        if (this.current_id == comment_id) {
            // hide
            this.current_id = 0;
            this.current_no = -1;
            Dom.addClass(this.ctag_div, 'bz_default_hidden');
            this.hideError();
            window.focus();

        } else {
            // show or move
            this.rpcRefresh(comment_id, comment_no);
            this.current_id = comment_id;
            this.current_no = comment_no;
            this.ctag_add.value = '';
            tags_container.parentNode.insertBefore(this.ctag_div, tags_container);
            Dom.removeClass(this.ctag_div, 'bz_default_hidden');
            Dom.removeClass(tags_container, 'bz_default_hidden');
            var comment = Dom.get('comment_text_' + comment_no);
            if (Dom.hasClass(comment, 'collapsed')) {
                var link = Dom.get('comment_link_' + comment_no);
                expand_comment(link, comment, comment_no);
            }
            window.setTimeout(function() {
                YAHOO.bugzilla.commentTagging.ctag_add.focus();
            }, 50);
        }
    },

    hideInput : function() {
        if (this.current_id != 0) {
            this.toggle(this.current_id, this.current_no);
        }
        this.hideError();
    },

    showError : function(comment_id, comment_no, error) {
        var bz_ctag_error = Dom.get('bz_ctag_error');
        var tags_container = Dom.get('ct_' + comment_no);
        tags_container.parentNode.appendChild(bz_ctag_error, tags_container);
        Dom.get('bz_ctag_error_msg').innerHTML = YAHOO.lang.escapeHTML(error);
        Dom.removeClass(bz_ctag_error, 'bz_default_hidden');
    },

    hideError : function() {
        Dom.addClass('bz_ctag_error', 'bz_default_hidden');
    },

    onKeyPress : function(evt) {
        evt = evt || window.event;
        var charCode = evt.charCode || evt.keyCode;
        if (evt.keyCode == 27) {
            // escape
            YAHOO.bugzilla.commentTagging.hideInput();
            YAHOO.util.Event.stopEvent(evt);

        } else if (evt.keyCode == 13) {
            // return
            YAHOO.util.Event.stopEvent(evt);
            var tags = YAHOO.bugzilla.commentTagging.ctag_add.value.split(/[ ,]/);
            var comment_id = YAHOO.bugzilla.commentTagging.current_id;
            var comment_no = YAHOO.bugzilla.commentTagging.current_no;
            YAHOO.bugzilla.commentTagging.hideInput();
            try {
                YAHOO.bugzilla.commentTagging.add(comment_id, comment_no, tags);
            } catch(e) {
                YAHOO.bugzilla.commentTagging.showError(comment_id, comment_no, e.message);
            }
        }
    },

    showTags : function(comment_id, comment_no, tags) {
        // remove existing tags
        var tags_container = Dom.get('ct_' + comment_no);
        while (tags_container.hasChildNodes()) {
            tags_container.removeChild(tags_container.lastChild);
        }
        // add tags
        if (tags != '') {
            if (typeof(tags) == 'string') {
                tags = tags.split(',');
            }
            for (var i = 0, l = tags.length; i < l; i++) {
                tags_container.appendChild(this.buildTagHtml(comment_id, comment_no, tags[i]));
            }
        }
        // update tracking array
        this.tags_by_no['c' + comment_no] = tags;
        this.updateCollapseControls();
    },

    updateCollapseControls : function() {
        var container = Dom.get('comment_tags_collapse_expand_container');
        if (!container) return;
        // build list of tags
        this.nos_by_tag = {};
        for (var id in this.tags_by_no) {
            if (this.tags_by_no.hasOwnProperty(id)) {
                for (var i = 0, l = this.tags_by_no[id].length; i < l; i++) {
                    var tag = this.tags_by_no[id][i].toLowerCase();
                    if (!this.nos_by_tag.hasOwnProperty(tag)) {
                        this.nos_by_tag[tag] = [];
                    }
                    this.nos_by_tag[tag].push(id);
                }
            }
        }
        var tags = [];
        for (var tag in this.nos_by_tag) {
            if (this.nos_by_tag.hasOwnProperty(tag)) {
                tags.push(tag);
            }
        }
        tags.sort();
        if (tags.length) {
            var div = document.createElement('div');
            div.appendChild(document.createTextNode('Comment Tags:'));
            var ul = document.createElement('ul');
            ul.id = 'comment_tags_collapse_expand';
            div.appendChild(ul);
            for (var i = 0, l = tags.length; i < l; i++) {
                var tag = tags[i];
                var li = document.createElement('li');
                ul.appendChild(li);
                var a = document.createElement('a');
                li.appendChild(a);
                Dom.setAttribute(a, 'href', '#');
                YAHOO.util.Event.addListener(a, 'click', function(evt, tag) {
                    YAHOO.bugzilla.commentTagging.toggleCollapse(tag);
                    YAHOO.util.Event.stopEvent(evt);
                }, tag);
                li.appendChild(document.createTextNode(' (' + this.nos_by_tag[tag].length + ')'));
                a.innerHTML = YAHOO.lang.escapeHTML(tag);
            }
            while (container.hasChildNodes()) {
                container.removeChild(container.lastChild);
            }
            container.appendChild(div);
        } else {
            while (container.hasChildNodes()) {
                container.removeChild(container.lastChild);
            }
        }
    },

    toggleCollapse : function(tag) {
        var nos = this.nos_by_tag[tag];
        if (!nos) return;
        toggle_all_comments('collapse');
        for (var i = 0, l = nos.length; i < l; i++) {
            var comment_no = nos[i].match(/\d+$/)[0];
            var comment = Dom.get('comment_text_' + comment_no);
            var link = Dom.get('comment_link_' + comment_no);
            expand_comment(link, comment, comment_no);
        }
    },

    buildTagHtml : function(comment_id, comment_no, tag) {
        var el = document.createElement('span');
        Dom.setAttribute(el, 'id', 'ct_' + comment_no + '_' + tag);
        Dom.addClass(el, 'bz_comment_tag');
        if (this.can_edit) {
            var a = document.createElement('a');
            Dom.setAttribute(a, 'href', '#');
            YAHOO.util.Event.addListener(a, 'click', function(evt, args) {
                YAHOO.bugzilla.commentTagging.remove(args[0], args[1], args[2])
                YAHOO.util.Event.stopEvent(evt);
            }, [comment_id, comment_no, tag]);
            a.appendChild(document.createTextNode('x'));
            el.appendChild(a);
            el.appendChild(document.createTextNode("\u00a0"));
        }
        el.appendChild(document.createTextNode(tag));
        return el;
    },

    add : function(comment_id, comment_no, add_tags) {
        // build list of current tags from html
        var tags = new Array();
        var spans = Dom.getElementsByClassName('bz_comment_tag', undefined, 'ct_' + comment_no);
        for (var i = 0, l = spans.length; i < l; i++) {
            tags.push(spans[i].textContent.substr(2));
        }
        // add new tags
        var new_tags = new Array();
        for (var i = 0, l = add_tags.length; i < l; i++) {
            var tag = YAHOO.lang.trim(add_tags[i]);
            // validation
            if (tag == '')
                continue;
            if (tag.length < YAHOO.bugzilla.commentTagging.min_len)
                throw new Error("Comment tags must be at least " + this.min_len + " characters.");
            if (tag.length > YAHOO.bugzilla.commentTagging.max_len)
                throw new Error("Comment tags cannot be longer than " + this.min_len + " characters.");
            // append new tag
            if (bz_isValueInArrayIgnoreCase(tags, tag))
                continue;
            new_tags.push(tag);
            tags.push(tag);
        }
        tags.sort();
        // update
        this.showTags(comment_id, comment_no, tags);
        this.rpcUpdate(comment_id, comment_no, new_tags, undefined);
    },

    remove : function(comment_id, comment_no, tag) {
        var el = Dom.get('ct_' + comment_no + '_' + tag);
        if (el) {
            el.parentNode.removeChild(el);
            this.rpcUpdate(comment_id, comment_no, undefined, [ tag ]);
        }
    },

    // If multiple updates are triggered quickly, overlapping refresh events
    // are generated. We ignore all events except the last one.
    incPending : function(comment_id) {
        if (this.pending['c' + comment_id] == undefined) {
            this.pending['c' + comment_id] = 1;
        } else {
            this.pending['c' + comment_id]++;
        }
    },

    decPending : function(comment_id) {
        if (this.pending['c' + comment_id] != undefined)
            this.pending['c' + comment_id]--;
    },

    hasPending : function(comment_id) {
        return this.pending['c' + comment_id] != undefined
               && this.pending['c' + comment_id] > 0;
    },

    rpcRefresh : function(comment_id, comment_no, noRefreshOnError) {
        this.incPending(comment_id);
        YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
        YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi',
        {
            success: function(res) {
                YAHOO.bugzilla.commentTagging.decPending(comment_id);
                data = YAHOO.lang.JSON.parse(res.responseText);
                if (data.error) {
                    YAHOO.bugzilla.commentTagging.handleRpcError(
                        comment_id, comment_no, data.error.message, noRefreshOnError);
                    return;
                }

                if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id))
                    YAHOO.bugzilla.commentTagging.showTags(
                        comment_id, comment_no, data.result.comments[comment_id].tags);
            },
            failure: function(res) {
                YAHOO.bugzilla.commentTagging.decPending(comment_id);
                YAHOO.bugzilla.commentTagging.handleRpcError(
                    comment_id, comment_no, res.responseText, noRefreshOnError);
            }
        },
        YAHOO.lang.JSON.stringify({
            version: "1.1",
            method: 'Bug.comments',
            params: {
                comment_ids: [ comment_id ],
                include_fields: [ 'tags' ]
            }
        })
        );
    },

    rpcUpdate : function(comment_id, comment_no, add, remove) {
        this.incPending(comment_id);
        YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
        YAHOO.util.Connect.asyncRequest('POST', 'jsonrpc.cgi',
        {
            success: function(res) {
                YAHOO.bugzilla.commentTagging.decPending(comment_id);
                data = YAHOO.lang.JSON.parse(res.responseText);
                if (data.error) {
                    YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, data.error.message);
                    return;
                }

                if (!YAHOO.bugzilla.commentTagging.hasPending(comment_id))
                    YAHOO.bugzilla.commentTagging.showTags(comment_id, comment_no, data.result);
            },
            failure: function(res) {
                YAHOO.bugzilla.commentTagging.decPending(comment_id);
                YAHOO.bugzilla.commentTagging.handleRpcError(comment_id, comment_no, res.responseText);
            }
        },
        YAHOO.lang.JSON.stringify({
            version: "1.1",
            method: 'Bug.update_comment_tags',
            params: {
                comment_id: comment_id,
                add: add,
                remove: remove
            }
        })
        );
    },

    handleRpcError : function(comment_id, comment_no, message, noRefreshOnError) {
        YAHOO.bugzilla.commentTagging.showError(comment_id, comment_no, message);
        if (!noRefreshOnError) {
            YAHOO.bugzilla.commentTagging.rpcRefresh(comment_id, comment_no, true);
        }
    }
}
