/*
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) Open-Xchange Inc., 2006-2011
 * Mail: info@open-xchange.com
 */

// if module.mail isn't available we shouldn't continue!!!
if (!configGetKey("modules.mail.module")) return;

// PulldownMenu without the Menu
function CalendarPopup(node, trigger, parent) {
    node.oxPopupMenu = true;
    MousePopup.call(this, node);
    this.trigger = trigger;
    this.parent = parent || trigger;
    trigger.oxPulldownTrigger = true;
    var Self = this;
    /**
     * A closure which is either bound to a click event on the trigger or
     * called manually.
     * @param {DOM Event} e The DOM Event object of the click event.
     */
    this.toggle = function(e) {
        if (PopupMenu.last == Self) PopupMenu.last = null; else Self.show();
        if (e) cancelBubbling(e);
    }
}

CalendarPopup.prototype = extend(MousePopup, {
    show: function() {
        this.node.style.minWidth = this.parent.offsetWidth + "px";
        MousePopup.prototype.show.call(this);
        PopupMenu.last = this;
        var pos = getAbsolutePosition(this.parent);
        this.position(pos.x, pos.y + this.parent.offsetHeight,
                      this.parent.offsetWidth, -this.parent.offsetHeight);
    },
    close: function() {
        PopupMenu.last = null;
        MousePopup.prototype.hide.call(this);
    },
    captureEvents: PulldownMenu.prototype.captureEvents
});

// Date widget with a minicalendar popup
function DatePicker(label) {
    ox.UI.Widget.apply(this);
    this.label = label;
}

DatePicker.prototype = extend(ox.UI.Widget, {
    default_value: null,
    addContent: function(node_id) {
        var Self = this;
        var calendarJ = jQuery([
            '<div class="miniCalendar popup-window popupBorder popupBackground" style="margin-top:0">',
                '<div class="miniCalendarTop popupHeaderBackground font-style-headline" style="position: relative;">',
                    '<table cellpadding="0" cellspacing="0" border="0" style="text-align: center;">',
                        '<tr>',
                            '<td style="text-align: left; width: 5%;">',
                                '<img src="" alt="" style="margin-right:5px" />',
                            '</td>',
                            '<td style="cursor:pointer; text-align: left; width: 50%;">',
                                '<div>',
                                    '<table cellpadding="0" cellspacing="0" border="0">',
                                        '<tr>',
                                            '<td style="width:8em"><div></div></td>',
                                            '<td style="width:4em"><div></div></td>',
                                        '</tr>',
                                    '</table>',
                                '</div>',
                            '</td>',
                            '<td style="text-align: right; width: 5%;">',
                                '<img src="" alt="" style="margin-left:5px"/>',
                            '</td>',
                        '</tr>',
                    '</table>',
                '</div>',
                '<div class="miniCalendarContainer" style="overflow:hidden"></div>',
                '<div class="miniCalendarBottom">',
                    '<button style="float:right"></button>',
                '</div>',
            '</div>'].join(""));
        var calendarNode = calendarJ[0];
        calendarJ.find("button").click(function() { popup.close(); })
            .append(addTranslated(_("Cancel")));
        var img = calendarJ.find("img");
        img[0].src = getFullImgSrc("img/arrows/arrow_darkgrey_left.gif");
        img[1].src = getFullImgSrc("img/arrows/arrow_darkgrey_right.gif");
        jQuery(img[0]).click(function() { calendar.back(); });
        jQuery(img[1]).click(function() { calendar.next(); });

        var divs = calendarJ.find("td > div");
        var calendar = new cMiniCalendar(null,
            calendarJ.find("div.miniCalendarContainer")[0],
            function() {
                var d = calendar.getDateByID(this.id).getTime();
                Self.set(Math.floor(d / 864e5) * 864e5); // ms/day
                popup.close();
            },
            false, "", divs[1], divs[2]);

        this.input = newnode("input", 0, { type: "text" });

        var button = newnode("img", { marginLeft: "5px" },
                { src: getFullImgSrc("img/calendar/datepicker.gif") });
        jQuery(button).click(function(e) {
            if (!popup.isVisible) {
                var d = Self.get();
                if (d != null) {
                    d = new Date(d);
                    calendar.setSelectedByDate(d.getUTCFullYear(),
                        d.getUTCMonth(), d.getUTCDate());
                } else {
                    calendar.setUnselected();
                }
            }
            popup.toggle(e);
        });

        var span = newnode("span", { whiteSpace: "nowrap",
            verticalAlign: "middle" }, 0, [this.input, button]);

        var popup = new CalendarPopup(calendarNode, button, span);
   
        this.node = this.parent.addCells(this.label, span);
        ox.UI.Widget.prototype.addContent.apply(this, arguments);
    },
    get: function() {
        var str = this.input.value;
        return str ? parseDateString(str, "date").getTime() : null;
    },
    set: function(value) {
        this.input.value = value != null ? formatDate(new Date(value), "date")
                                         : "";
    }
});

  /////////////////////
 //   Mail Filter   //
/////////////////////

var node = new ox.Configuration.LeafNode("configuration/mail/filter",
                                         _("Filter"));
var split = new ox.Configuration.VSplit(node, _("Mail filter"), 0.28, true);
split.init = function() {
    var enabled_color = "font-color-default";
    var disabled_color = "font-color-disabled";
    var selection = new Selection();
    var grid = new LiveGrid([
        { 
            text: _("Rule Name"),
            clear: LiveGrid.makeClear(""),
            set: function(div, rule) {
                if (div.firstChild)
                    div.firstChild.data = rule.rulename || "";
                else
                    div.appendChild(document.createTextNode(rule.rulename || ""));
                var cname = div.className;
                if (   cname.indexOf(disabled_color) < 0
                    && cname.indexOf(enabled_color) < 0)
                {
                    div.className = cname + " " +
                        (rule.flags && rule.flags.length ? disabled_color : enabled_color);
                } else {
                    div.className = cname.replace(
                        rule.flags && rule.flags.length ? enabled_color : disabled_color,
                        rule.flags && rule.flags.length ? disabled_color : enabled_color);
                }
            }
        },
        {
            text: _("Active"),
            width: "4em",
            clear: function(div) {
                if (!div.firstChild) div.appendChild(addCheckBox());
                div.firstChild.style.display = "none";
            },
            set: function(div, rule) {
                if (!div.firstChild) div.appendChild(addCheckBox());
                div.firstChild.style.display = "";
                div.firstChild.checked = rule.active;
            }
        }
    ], selection);
    grid.emptylivegridtext = _("No rules available");

    function addCheckBox() {
        var checkbox = newnode("input", 0, { type: "checkbox" });
        addDOMEvent(checkbox, "click", function() {
            storage.newIterate(selection.getSelected(),
                emptyFunction, function(i, rule) {
                    rule.active = checkbox.checked;
                    ox.JSON.put(AjaxRoot +
                        "/mailfilter?action=update&session=" +
                        session, rule, emptyFunction);
                });
        });
        return checkbox;
    }

    split.list = grid;
    var storage = new Storage(0, [], 0, 0, 0, 0,
        function(rule) { return rule.id; });

    var vacationIndex = -1;
    
    split.enableList = function() {
        ox.JSON.get(AjaxRoot + "/mailfilter?action=list&session=" + session,
            function(reply) {
                vacationIndex = -1;
                for (var i = 0; i < reply.data.length; i++) {
                    if (arrayContains(reply.data[i].flags, "vacation")) {
                        vacationIndex = i;
                        break;
                    }
                }
                function arrayContains(array, value) {
                    for (var i = 0; i < array.length; i++)
                        if (array[i] == value) return true;
                    return false;
                }
                storage.remove(0, storage.ids.length);
                storage.append(reply.data);
                grid.enable(storage);
            });
    };
    
    var headerTests = {
        from: ["From"],
        tocc: ["To", "Cc"],
        subject: ["Subject"],
        mailinglist: ["X-BeenThere", "X-Mailinglist"],
        to: ["To"],
        cc: ["Cc"]
    };
    
    function convertSaveTest(test) {
        switch (test.id) {
            case "envelope":
                test.headers = ["To"];
                break;
            case "body":
                test.extensionskey = "text";
                test.extensionsvalue = null;
                break;
            case "currentdate":
                test.datepart = "date";
                test.datevalue = [test.datevalue];
                break;
            default:
                var tst = headerTests[test.id];
                if (tst) {
                    test.headers = tst;
                    test.id = "header";
                }
        }        
    }
    
    split.save = function(data, cont) {
        for (var i = 0; i < data.actioncmds.length; i++) {
            var act = data.actioncmds[i];
            switch (act.id) {
                case "mark":
                    act.id = "addflags";
                    act.flags = [act.marks];
                    delete act.marks;
                    break;
                case "tag":
                    act.id = "addflags";
                    var match = /[^!#$&'+-[^-z|-~]/.exec(act.tags);
                    if (match) {
                        ox.Configuration.error(format(
                            //#. %s is the invalid character.
                            //#, c-format
                            _("Invalid tag name. Tags may not contain the character '%s'."),
                            match[0]));
                        return;
                    }
                    act.flags = ["$" + act.tags];
                    delete act.tags;
                    break;
                case "flag":
                    act.id = "addflags";
                    act.flags = [act.flags];
                    break;
                case "redirect":
                    if (!trimStr(act.to)) {
                        ox.Configuration.error(
                            _("You must specify an E-Mail address for redirection."));
                        return;
                    }
                    if (!validateEmail(act.to)) {
                        ox.Configuration.error(format(
                            //#. %s is the invalid address
                            //#, c-format
                            _("The E-Mail address \"%s\" is not valid."),
                            act.to));
                        return;
                    }
                    break;
            }
        }
        if (!data.dontstop)
            data.actioncmds.push({ id: "stop" });
        delete data.dontstop;
        switch (data.test.id) {
            case "anyof":
            case "allof":
                var len = data.test.tests.length;
                switch (len) {
                    case 0:
                        data.test = { id: "true" };
                        break;
                    case 1:
                        data.test = data.test.tests[0];
                        convertSaveTest(data.test);
                        break;
                    default:
                        for (var i = 0; i < len; i++)
                            convertSaveTest(data.test.tests[i]);
                }
                break;
            default:
                convertSaveTest(data.test);
        }
        data.flags = [];
        if (!("active" in data)) data.active = true;
        if ("id" in data) {
            ox.JSON.put(AjaxRoot + "/mailfilter/?action=update&session=" +
                        session, data, function(reply) {
                            storage.localUpdate([data.id], function(old) {
                                if (!equals(data, old)) return data;
                            });
                            ox.Configuration.info(
                                _("Your settings have been saved."));
                            cont(reply.data);
                        });
        } else {
            if (vacationIndex >= 0) data.position = vacationIndex;
            ox.JSON.put(AjaxRoot + "/mailfilter/?action=new&session=" +
                        session, data, handleNew);
        }

        function handleNew(reply) {
            data.id = reply.data;
            if (vacationIndex >= 0) {
                var ids = new Array(storage.ids.length + 1);
                for (var i = 0; i < vacationIndex; i++)
                    ids[i] = { id: storage.ids[i] };
                ids[vacationIndex] = { id: reply.data };
                for (var i = vacationIndex + 1; i < ids.length; i++)
                    ids[i] = { id: storage.ids[i - 1] };
                storage.update(0, ids, [data]);
                vacationIndex++;
            } else {
                storage.append([data]);
            }
            ox.Configuration.info(_("Your settings have been saved."));
            cont(data);
        }
    };
    
    function convertLoadTest(test) {
        switch (test.id) {
            case "envelope":
                delete test.headers;
                break;
            case "header":
                var headerSet = {};
                var setSize = 0;
                for (var i in test.headers) {
                    var header = test.headers[i];
                    if (!(header in headerSet)) setSize++;
                    headerSet[header] = true;
                }
                HeaderSearch: for (var i in headerTests) {
                    var headerTest = headerTests[i];
                    if (headerTest.length != setSize) continue HeaderSearch;
                    for (var j = 0; j < headerTest.length; j++) {
                        if (!(headerTest[j] in headerSet))
                            continue HeaderSearch;
                    }
                    test.id = i;
                    delete test.headers;
                    break HeaderSearch;
                }
                break;
            case "currentdate":
                delete test.datepart;
                test.datevalue = test.datevalue[0];
                break;
            case "true":
            case "size":
            case "body":
                break;
            default:
                return true;
        }
    }

    split.load = function(cont) {
        storage.newIterate(selection.getSelected(), emptyFunction,
            function(i, data) {
                if (data.errormsg || data.flags.length) return cont();
                var d = clone(data);
                d.dontstop = true;
                var stopindex = -1;
                for (var i = 0; i < d.actioncmds.length; i++) {
                    var act = d.actioncmds[i];
                    switch (act.id) {
                        case "addflags":
                            var flag = act.flags[0];
                            switch (flag.charAt(0)) {
                                case "\\":
                                    act.id = "mark";
                                    act.marks = flag;
                                    delete act.flags;
                                    break;
                                case "$":
                                    var match = /^\$cl_([1-9]|10)$/.exec(flag);
                                    if (match) {
                                        act.id = "flag";
                                        act.flags = flag;
                                    } else {
                                        act.id = "tag";
                                        act.tags = flag.substring(1);
                                        delete act.flags;
                                    }
                            }
                            break;
                        case "stop":
                            d.dontstop = false;
                            stopindex = i;
                        case "keep":
                        case "discard":
                        case "redirect":
                        case "move":
                        case "reject":
                            break;
                        default:
                            return cont();
                    }
                }
                if (stopindex >= 0) d.actioncmds.splice(stopindex, 1);
                switch (d.test.id) {
                    case "allof":
                    case "anyof":
                        var len = d.test.tests.length;
                        for (var i = 0; i < len; i++)
                            if (convertLoadTest(d.test.tests[i])) return cont();
                        break;
                    default:
                        if (convertLoadTest(d.test)) return cont();
                        d.test = { id: "anyof", tests: [d.test] };
                }
                cont(d);
            });
    };
    
    split.onNew = function() {
        this.addNew({ rulename: _("New rule"), active: true });
    };

    menuarrows[node.id] = {};
    
    var rulesmenu = MenuNodes.createSmallButtonContext("mailfilter.rules",
        _("Rules"));
    MenuNodes.createSmallButton(rulesmenu, "mailfilter.rules.delete",
        _("Delete"), getFullImgSrc("img/menu/delete.gif"),
        getFullImgSrc("img/menu/delete_d.gif"), confirmDelete);
    function confirmDelete() {
        if (!selection.count) return;
        newConfirm(ngettext("Delete Rule", "Delete Rules", selection.count),
            ngettext("Are you sure you want to delete the selected rule?",
                     "Are you sure you want to delete the selected rules?",
                     selection.count),
            AlertPopup.YES | AlertPopup.NO, null, null, handleYes);
    }
    function handleYes() {
        storage.newIterate(selection.getSelected(), emptyFunction,
                           handleDelete);
    }
    function handleDelete(idx, data) {
        ox.JSON.put(AjaxRoot + "/mailfilter?action=delete&session=" + session,
            { id: data.id }, handleReply);
        function handleReply(reply) {
            grid.deleteIDs([data.id]);
            if (vacationIndex == idx) vacationIndex = -1;
            else if (vacationIndex > idx) vacationIndex--;
        }
    }
    addMenuNode(rulesmenu.node, MenuNodes.FIXED, 31);
    changeDisplay(node.id, "mailfilter.rules");

    var movemenu = MenuNodes.createSmallButtonContext("mailfilter.move",
        _("Move"));
    MenuNodes.createSmallButton(movemenu, "mailfilter.move.up", _("Up"),
        getFullImgSrc("img/mail/email_priolhigh.gif"), getFullImgSrc("img/mail/email_priolhigh.gif"),
        function() {
            if (selection.count != 1) return;
            storage.newIterate(selection.getSelected(), emptyFunction,
                function(idx, rule) {
                    if (idx < 1) return;
                    var ids = new Array(storage.ids.length);
                    for (var i = 0; i < ids.length; i++)
                        ids[i] = storage.ids[i];
                    var id = ids[idx];
                    ids[idx] = ids[idx - 1];
                    ids[idx - 1] = id;
                    var newVacationIndex = vacationIndex;
                    if (vacationIndex == idx) newVacationIndex--;
                    else if (vacationIndex == idx - 1) newVacationIndex++;
                    ox.JSON.put(AjaxRoot +
                        "/mailfilter?action=reorder&session=" + session,
                        ids, function(reply) {
                            for (var i = 0; i < ids.length; i++)
                                ids[i] = { id: ids[i] };
                            storage.update(0, ids, []);
                            vacationIndex = newVacationIndex;
                        });
                });
        });
    MenuNodes.createSmallButton(movemenu, "mailfilter.move.down",
        _("Down"), getFullImgSrc("img/mail/email_priolow.gif"),
        getFullImgSrc("img/mail/email_priolow.gif"),
        function() {
            if (selection.count != 1) return;
            storage.newIterate(selection.getSelected(), emptyFunction,
                function(idx, rule) {
                    if (idx >= storage.ids.length - 1) return;
                    var ids = new Array(storage.ids.length);
                    for (var i = 0; i < ids.length; i++)
                        ids[i] = storage.ids[i];
                    var id = ids[idx];
                    ids[idx] = ids[idx + 1];
                    ids[idx + 1] = id;
                    var newVacationIndex = vacationIndex;
                    if (vacationIndex == idx) newVacationIndex++;
                    else if (vacationIndex == idx + 1) newVacationIndex--;
                    ox.JSON.put(AjaxRoot +
                        "/mailfilter?action=reorder&session=" + session,
                        ids, function(reply) {
                            for (var i = 0; i < ids.length; i++)
                                ids[i] = { id: ids[i] };
                            storage.update(0, ids, []);
                            vacationIndex = newVacationIndex;
                        });
                });
        });
    addMenuNode(movemenu.node, MenuNodes.FIXED, 32);
    changeDisplay(node.id, "mailfilter.move");

    register("OX_SELECTED_ITEMS_CHANGED", function() {
        var moveenabled = selection.count == 1;
        var deleteenabled = selection.count >= 1;
        if (grid.storage && moveenabled) {
            grid.storage.newIterate(selection.getSelected(), emptyFunction,
                checkFlags, updateMenu);
        } else updateMenu();
        function checkFlags(i, data) {
            var f = data.flags;
            for (var i = 0; i < f.length; i++) {
                if (f[i] == "spam") {
                    moveenabled = false;
                    deleteenabled = false;
                    break;
                }
            }
        }
        function updateMenu() {
            menuglobalzaehler = 0;
            menuarrows[node.id]["mailfilter.rules"] = [];
            menuarrows[node.id]["mailfilter.move"] = [];
            menu_display_contents(node.id, "mailfilter.rules", deleteenabled,
                "mailfilter.rules.delete");
            menuglobalzaehler = 0;
            menu_display_contents(node.id, "mailfilter.move", moveenabled,
                "mailfilter.move.up");
            menu_display_contents(node.id, "mailfilter.move", moveenabled,
                "mailfilter.move.down");
        }
    });

    var group = new ox.Configuration.Group(_("Rule Details"));
    split.addWidget(group, ox.Configuration.Group.NoField);
    
    group.addWidget(new ox.UI.Input(_("Name")), "rulename");
    
    group = new ox.Configuration.Group();
    split.addWidget(group, "test");
    
    group.addWidget(new ox.UI.Text(
        _("For an incoming message that matches:")));
    
    var cond_operator = new ox.UI.ComboBox("");
    cond_operator.setEntries(["anyof", "allof"],
                             [_("any"), _("all")]);
    group.addWidget(cond_operator, "id");

    group.addWidget(new ox.UI.Text(_("Of the following conditions:")));
    
    var delete_buttons = {};
    
    /**
     * Returns a new instance with the specified name.
     * @class A controller which wraps values as single-element arrays.
     * Throws ox.UI.Controller.InvalidData when supplied with an array with
     * multiple elements.
     * @param {String} name The field name of the wrapped value.
     * @type ArrayWrapper
     * @return A new instance of its class.
     */
    function ArrayWrapper(name) {
        return {
            get: function(data, value) { data[name] = [value]; },
            set: function(data) {
                var value = data[name];
                if (!value || !value.length) return;
                if (value.length > 1) throw new ox.UI.Controller.InvalidData();
                return value[0];
            }
        };
    }
    
      ////////////////////
     //   Conditions   //
    ////////////////////
    
    cond_group.addElement = function() {
        var index = cond_group.children.length;

        var hlayout = new ox.Configuration.HLayout();
        cond_group.addWidget(hlayout);
    
        var condition = new ox.UI.ComboBox("");
        condition.width = "16em";
        condition.setEntries(conditions, cond_names);
        condition.default_value = "true";
        condition.changed = function() {
            var index = condition.combobox.getSelectedIndex();
            (cond_handlers[index] || trueHeaderHandler).changed({
                header: header,
                comparison: comparison,
                comp_value: comp_value,
                comp_size: comp_size,
                comp_date: comp_date
            });
        };
        hlayout.addWidget(condition, "id");
        
        var header = new ox.UI.Input("");
        hlayout.addWidget(header, ArrayWrapper("headers"));
        
        var comparison = new ox.UI.ComboBox("");
        hlayout.addWidget(comparison, "comparison");
        
        var comp_value = new ox.UI.Input("");
        hlayout.addWidget(comp_value, ArrayWrapper("values"));
        
        var comp_size = new ox.UI.Input("");
        comp_size.default_value = 0;
        hlayout.addWidget(comp_size, {
            get: function(data, value) { data.size = parseInt(value, 10); },
            set: function(data) { return data.size; }
        });
        
        var comp_date = new DatePicker("");
        hlayout.addWidget(comp_date, "datevalue");
        
        var comp_delete = new ox.UI.Widget();
        comp_delete.addContent = function() {
            var button = newnode("img", { cursor: "pointer" }, {
                src: getFullImgSrc("img/menu/delete.gif")
            });
            var i18nNode = new I18nNode(_("Delete this condition"));
            i18nNode.update = function() { this.node.title = this.callback(); };
            i18nNode.node = button;
            addDOMEvent(button, "click", function() {
                cond_group.deleteWidget(hlayout);
            });
            this.node = this.parent.addRow(button, true);
            this.initialized = true;
        };
        hlayout.addWidget(comp_delete);
        
        condition.set("true");
    };
    group.addWidget(cond_group, "tests");

    var cond_add = new ox.UI.Button(_("Add condition"));
    cond_add.click = cond_group.addElement;
    group.addWidget(cond_add);
    
    split.addWidget(new ox.UI.Text(_("Do the following:")));
    
      /////////////////
     //   Actions   //
    /////////////////

    action_group = new ox.Configuration.ArrayGroup("");
    action_group.addElement = function() {

        var hlayout = new ox.Configuration.HLayout();
        action_group.addWidget(hlayout);

        var action = new ox.UI.ComboBox("");
        action.width = "18.5em";
        action.setEntries(actions, action_names);
        action.default_value = "keep";
        action.changed = function() {
            var index = action.combobox.getSelectedIndex();
            var name = action_handlers[index];
            var widgets = {
                folder: widget,
                address: addr,
                reason: reason,
                marks: marks,
                tags: tags,
                flags: flags
            };
            for (var i in widgets)
                if (i == name) widgets[i].show(); else widgets[i].hide();
        };
        hlayout.addWidget(action, "id");
        
        var widget = new ox.UI.Widget();
        widget.addContent = function() {
            var Self = this;
            this.input = newnode("div",
                { height: "1.25em", overflow: "hidden", whiteSpace: "nowrap" },
                { className: "font-color-default background-color-content " +
                             "smallpopupborder border-color-design" },
                [document.createTextNode("")]);
            var justOpened = false;
            addDOMEvent(this.input, "click", function(e) {
                if (Self.folderpopup.style.display == "none") {
                    removeChildNodes(Self.folderpopup);
                    if (!Self.enabled) return;
                    
                	ox.api.ui.selectFolder({
                    	type: "mail",
                    	callback: function(data) {
                            Self.set(data.id);
                            Self.folderpopup.style.display = "none";
                        },
                    	node: Self.folderpopup
                    });
                    
                    Self.folderpopup.style.display = "block";
                    justOpened = true;
                    addDOMEvent(body, "click", bodyClick);
                }
            });
            function bodyClick() {
                if (justOpened) {
                    justOpened = false;
                    return;
                }
                removeChildNodes(Self.folderpopup);
                Self.folderpopup.style.display = "none";
                removeDOMEvent(body, "click", bodyClick);
            }
            this.folderpopup = newnode("div",
                { position: "absolute", display: "none", zIndex: 21,
                  height: "10em", width: this.width || this.parent.childWidth,
                  overflow: "hidden" },
                { className: "background-color-content smallpopupborder " +
                             "border-color-design" });
            addDOMEvent(this.folderpopup, "click", function(e) {
                cancelBubbling(e);
            });
            this.node = this.parent.addCells("", newnode("div",
                { width: this.width || this.parent.childWidth }, 0,
                [this.input, this.folderpopup]));
            this.initialized = true;
        };

        widget.applyEnabled = function() {
            this.input.className = this.input.className.replace(
                this.isEnabled ? disabled_color : enabled_color,
                this.isEnabled ? enabled_color : disabled_color);
        };
        widget.set = function(value) {
            this.folderid = value;
            var Self = this;
            if (this.initialized) {
                // clear path field first
                Self.input.firstChild.data = "\u00a0";
                if (value) {
                	ox.api.folder.getPathString({
                	    folder: value,
                	    success: function (path) {
                	        // set path
                		    Self.input.firstChild.data = path;
                	    },
                	    error: function (data) {
                	        // folder does not exists (anymore)
                	        // show "raw" path
                	        Self.input.firstChild.data = value;
                	        // now inform the user but do not change the folder automatically
                	        ox.UINotifier.warn(format(
                                _("The selected filter rule refers to an unknown folder '%s'."), /*i18n*/
                                value
                            ));
                	        // prevent global error message
                	        return true;
                	    }
                	});
                }
            }
        };
        widget.get = function() {
            return this.folderid;
        };
        widget.width = "20em";
        widget.default_value = configGetKey("modules.mail.defaultFolder.inbox");
        hlayout.addWidget(widget, "into");
        
        var addr = new ox.UI.Input("");
        addr.hide();
        hlayout.addWidget(addr, "to");
        
        var reason = new ox.UI.Input("");
        reason.hide();
        hlayout.addWidget(reason, "text");
    
        var marks = new ox.UI.ComboBox("");
        marks.setEntries(["\\seen", "\\deleted"], [_("Read"), _("Deleted")]);
        marks.hide();
        hlayout.addWidget(marks, "marks");
        
        var tags = new ox.UI.Input("");
        tags.hide();
        hlayout.addWidget(tags, "tags");
        
        var flags = new ox.UI.Widget();
        flags.addContent = function() {
            var Self = this;
            this.input = newnode("div", { height: "1.25em" },
                { className: "font-color-default background-color-content " +
                             "smallpopupborder border-color-design" },
                [newnode("img", 0, { src: getFullImgSrc("img/menu/tag_1.gif") })]);
            var justOpened = false;
            addDOMEvent(this.input, "click", function(e) {
                if (Self.popup.style.display == "none") {
                    Self.popup.style.display = "block";
                    justOpened = true;
                    addDOMEvent(body, "click", bodyClick);
                }
            });
            function bodyClick() {
                if (justOpened) {
                    justOpened = false;
                    return;
                }
                Self.popup.style.display = "none";
                removeDOMEvent(body, "click", bodyClick);
            }
            this.popup = newnode("div",
                { position: "absolute", display: "none", zIndex: 21,
                  height: "10em", width: this.parent.childWidth,
                  overflow: "auto" },
                { className: "background-color-content smallpopupborder " +
                             "border-color-design" });
            addDOMEvent(this.popup, "click", function(e) {
                cancelBubbling(e);
            });
            for (var i = 1; i < 11; i++) {
                var div = newnode("div", 0, 0, [ newnode("img", 0, {
                    src: getFullImgSrc("img/menu/tag_" + i + ".gif")
                })]);
                addDOMEvent(div, "click", (function(color) {
                    return function() {
                        Self.input.firstChild.src =
                            getFullImgSrc("img/menu/tag_" + color + ".gif");
                        Self.value = color;
                        Self.popup.style.display = "none";
                    };
                })(i));
                this.popup.appendChild(div);
            }
            this.node = this.parent.addCells("", newnode("div",
                { width: this.parent.childWidth }, 0,
                [this.input, this.popup]));
            this.initialized = true;
        };
        flags.applyEnabled = function() {
            this.input.className = this.input.className.replace(
                this.isEnabled ? disabled_color : enabled_color,
                this.isEnabled ? enabled_color : disabled_color);
        };
        flags.set = function(value) {
            var match = /^\$cl_([1-9]|10)$/.exec(value);
            this.value = match && match[1] ? Number(match[1]) : 0;
            if (this.initialized) {
                this.input.firstChild.src = getFullImgSrc(this.value
                    ? "img/menu/tag_" + this.value + ".gif"
                    : "img/dummy.gif");
            }
        };
        flags.get = function() {
            return this.value ? "$cl_" + this.value : "";
        };
        flags.value = 1;
        flags.default_value = "$cl_1";
        hlayout.addWidget(flags, "flags");

        var action_delete = new ox.UI.Widget();
        action_delete.addContent = function() {
            var button = newnode("img", { cursor: "pointer" }, {
                src: getFullImgSrc("img/menu/delete.gif")
            });
            var i18nNode = new I18nNode(_("Delete this action"));
            i18nNode.update = function() { this.node.title = this.callback(); };
            i18nNode.node = button;
            addDOMEvent(button, "click", function() {
                action_group.deleteWidget(hlayout);
            });
            this.node = this.parent.addRow(button, true);
            this.initialized = true;
        };
        hlayout.addWidget(action_delete);
        
        hlayout.set(hlayout.default_value);
    };
    split.addWidget(action_group, "actioncmds");
    
    var action_add = new ox.UI.Button(_("Add action"));
    action_add.click = action_group.addElement;
    split.addWidget(action_add);
    
    var action_stop = new ox.UI.CheckBox(
        _("Process subsequent rules even when this rule matches"));
    split.addWidget(action_stop, "dontstop");

    var hr = new ox.UI.Widget();
    hr.addContent = function() {
        this.node = this.parent.addRow(newnode("hr", { marginRight: "20px" }),
                                       false);
    };
    split.addWidget(hr);
    
    var buttons = new ox.Configuration.HLayout();
    split.addWidget(buttons);
    
    var save_button = new ox.UI.Button(_("Save"));
    save_button.click = function() { split.saveView(); };
    buttons.addWidget(save_button);

    var cancel_button = new ox.UI.Button(_("Cancel"));
    cancel_button.click = function() { split.cancelView(); };
    buttons.addWidget(cancel_button);
    
    configure();
};

  ///////////////////////
 //   Configuration   //
///////////////////////

// Shared variables between configure() and initSplit().
var trueHeaderHandler;
var conditions = ["true"], cond_names = [""], cond_handlers = [];
var actions = [], action_names = [], action_handlers = [];
var cond_group = new ox.Configuration.ArrayGroup();

// Initially false, an array with waiting callbacks during a configuration,
// true when configuration completes.
var configured = false;

/**
 * Fetches the configuration from the server.
 * @param {Function} cont An optional callback which is called after
 * the configuration is complete.
 */
function configure(cont) {
    if (configured) {
        if (cont) {
            if (configured === true) cont(); else configured.push(cont);
        }
        return;
    } else {
        configured = [];
        if (cont) configured.push(cont);
    }
    
    var inverted_comparisons = {
        "is": { normal: _("Is exactly"),
                inverted: _("Is not exactly") },
        "contains": { normal: _("Contains"),
                      inverted: _("Does not contain") },
        "matches": { normal: _("Matches"),
                     inverted: _("Does not match") },
        "regex": { normal: _("Matches regex"),
                   inverted: _("Does not match regex") },
        "over": { normal: _("Is bigger than"),
                  inverted: _("Is at most") },
        "under": { normal: _("Is smaller than"),
                   inverted: _("Is at least") }
    };
    
    var all_comparisons = {};
    for (var i in inverted_comparisons)
        all_comparisons[i] = { normal: inverted_comparisons[i].normal };
    
    function HeaderHandler(test, name) {
        this.keys = test.comparison;
        this.name = name;
    }
    
    HeaderHandler.prototype.changed = function(cond) {
        var values = new Array(this.keys.length);
        for (var i = 0; i < this.keys.length; i++) {
            var comp = all_comparisons[this.keys[i]];
            values[i] = comp ? comp.normal : this.keys[i];
        }
        cond.comparison.setEntries(this.keys, values);
        cond.comparison.show();
        var widgets = {
            value: cond.comp_value,
            size: cond.comp_size,
            header: cond.header,
            date: cond.comp_date
        };
        for (var i in widgets)  
            widgets[i][this.name.indexOf(i) >= 0 ? "show" : "hide"]();
    };

    trueHeaderHandler = { changed: function(cond) {
        cond.header.hide();
        cond.comparison.hide();
        cond.comp_value.hide();
        cond.comp_size.hide();
        cond.comp_date.hide();
    } };
    
    var dateHandler = { changed: function(cond) {
        cond.header.hide();
        cond.comparison.setEntries(["ge", "le", "is"],
            [//#. inclusive range, so e. g. "after" would be wrong
             _("starts on"),
             //#. inclusive range, so e. g. "before" would be wrong
             _("ends on"),
             //#. On some date
             ("is on")]);
        cond.comparison.show();
        cond.comp_value.hide();
        cond.comp_size.hide();
        cond.comp_date.show();
    } };
    
    ox.JSON.get(AjaxRoot + "/mailfilter?action=config&session=" + session,
        function(reply) {
            conditions.length = cond_names.length = 0;
            for (var i in reply.data.tests) {
                var test = reply.data.tests[i];
                switch (test.test) {
                    case "header":
                        cond_names = cond_names.concat([_("Sender/From"),
                            _("Any recipient"), _("Subject"), _("Mailing list"),
                            _("To"), _("CC"), _("Header")]);
                        conditions = conditions.concat(["from", "tocc",
                            "subject", "mailinglist", "to", "cc", "header"]);
                        var headerHandler = new HeaderHandler(test, "value");
                        for (var i = 0; i < 6; i++)
                            cond_handlers.push(headerHandler);
                        cond_handlers.push(new HeaderHandler(test,
                                                             "header value"));
                        break;
                    case "body":
                        cond_names.push(_("Content"));
                        conditions.push("body");
                        cond_handlers.push(new HeaderHandler(test, "value"));
                        break;
                    case "size":
                        cond_names.push(_("Size"));
                        conditions.push("size");
                        cond_handlers.push(new HeaderHandler(test, "size"));
                        break;
                    case "true":
                        cond_names.push(_("All emails"));
                        conditions.push("true");
                        cond_handlers.push(trueHeaderHandler);
                        break;
                    case "envelope":
                        cond_names.push(_("Envelope-To"));
                        conditions.push("envelope");
                        cond_handlers.push(new HeaderHandler(test, "value"));
                        break;
                    case "currentdate":
                        cond_names.push(_("Date"));
                        conditions.push("currentdate");
                        cond_handlers.push(dateHandler);
                        break;
                    case "not":
                        all_comparisons = inverted_comparisons;
                }
            }
            for (var i = 0; i < cond_group.children.length; i++)
                cond_group.children[i].setEntries(conditions, cond_names);
            var defs = {
                keep: { name: _("Keep"), widget: "" },
                discard: { name: _("Discard"), widget : "" },
                redirect: { name: _("Redirect to"), widget: "address" },
                move: { name: _("Move to folder"), widget: "folder" },
                reject: { name: _("Reject with reason"), widget: "reason" }
            };
            for (var i in reply.data.actioncommands) {
                var act = reply.data.actioncommands[i];
                switch (act) {
                    case "addflags":
                        action_names = action_names.concat(
                            [_("Mark mail as"), _("Tag mail with"),
                             _("Flag mail with")]);
                        actions = actions.concat(["mark", "tag", "flag"]);
                        action_handlers = action_handlers.concat(
                            ["marks", "tags", "flags"]);
                        break;
                    default:
                        var def = defs[act];
                        if (def) {
                            action_names.push(def.name);
                            actions.push(act);
                            action_handlers.push(def.widget);
                        }
                }
            }
            var cbs = configured;
            configured = true;
            for (var i = 0; i < cbs.length; i++) cbs[i]();
        });
}

  /////////////////////////
 //   Vacation Notice   //
/////////////////////////

ox.JSON.get(AjaxRoot + "/mailfilter?action=config&session=" + session,
    function(reply) {
        var actions = reply.data.actioncommands;
        searchVacation: {
            for (var i = 0; i < actions.length; i++)
                if (actions[i] == "vacation") break searchVacation;
            return;
        }
        var vnode = new ox.Configuration.LeafNode("configuration/mail/vacation",
            _("Vacation Notice"));
        var page = new ox.Configuration.Page(vnode, _("Vacation Notice"));
        page.init = initVacationPage;
        page.load = loadVacationPage;
        page.save = saveVacationPage;
    });

function initVacationPage() {

    this.childWidth = "30em";
    
    var active = new ox.UI.CheckBox(_("Vacation notice is active"));
    this.addWidget(active, "active");
    
    var subject = new ox.UI.Input(_("Subject"));
    this.addWidget(subject, "subject");
    
    var text = new ox.UI.TextArea(_("Text"));
    text.height = "15em";
    this.addWidget(text, "text");

    var days = new ox.UI.ComboBox(
        _("Number of days between vacation notices to the same sender"));
    var keys = new Array(31), values = new Array(31);
    for (var i = 0; i < 31; i++) {
        keys[i] = i + 1;
        values[i] = noI18n(i + 1);
    }
    days.setEntries(keys, values);
    days.default_value = 7;
    this.addWidget(days, "days");
    
    // Addresses
    var addrs = configGetKey("modules.mail.addresses");
    if (addrs) {
        var addresses = new ox.Configuration.Group(_("E-mail addresses"));
        addresses.default_value = {};
        this.addWidget(addresses, {
            get: function(data, value) {
                data.addresses = [];
                for (var i in value) if (value[i]) data.addresses.push(i);
            },
            set: function(data) {
                if (!data.addresses) return;
                var value = {};
                for (var i = 0; i < data.addresses.length; i++) {
                    value[data.addresses[i]] = true;
                }
                return value;
            }
        });
        for (var i = 0; i < addrs.length; i++) {
            addresses.addWidget(new ox.UI.CheckBox(addrs[i]), addrs[i]);
            addresses.default_value[addrs[i]] = true;
        }
    } else {
        var addresses = new ox.UI.TextArea(_("E-mail addresses"));
        this.addWidget(addresses, {
            get: function(data, value) { data.addresses = value.split("\n"); },
            set: function(data) { return data.addresses.join("\n"); }
        });
    }
    
    // Time range, only if supported by the Sieve server.
    var self = this;
    configure(function() {
        for (var i = 0; i < conditions.length; i++) {
            if (conditions[i] == "currentdate") {
                enableRange();
                break;
            }
        }
    });
    function enableRange() {
        self.addWidget(new DatePicker(_("First day")), "start");
        self.addWidget(new DatePicker(_("Last day")), "end");
/*
        if (configGetKey("modules.calendar.module")) {
            self.addWidget(new ox.UI.CheckBox(_("Create an appointment")),
                           "appointment");
        }
*/
    }
    
};

function loadVacationPage(cont) {
    var Self = this;
    ox.JSON.get(AjaxRoot + "/mailfilter?action=list&flag=vacation&session=" +
        session, function(reply) {
            if (reply.data.length) {
                var d = reply.data[0];
                Self.id = d.id;
                var start = null, end = null;
                if (d.test.id == "allof") {
                    for (var i = 0; i < d.test.tests.length; i++) {
                        var test = d.test.tests[i];
                        if (test.id != "currentdate") continue;
                        switch (test.comparison) {
                            case "ge":
                                start = test.datevalue[0];
                                break;
                            case "le":
                                end = test.datevalue[0];
                                break;
                            case "is":
                                start = end = test.datevalue[0];
                        }
                    }
                }
                var a = d.actioncmds[0];
                cont({
                    active: d.active,
                    subject: a.subject,
                    text: a.text,
                    days: a.days,
                    addresses: a.addresses,
                    start: start,
                    end: end
                });
            } else {
                Self.id = undefined;
                cont({});
            }
        });
};

function saveVacationPage(data, cont) {
    var Self = this;
    var days = parseInt(data.days);
    if (days != data.days) {
        ox.Configuration.error(
            _("The repeat interval must be an integer number"));
        return;
    }
    if (!data.addresses.length) {
        ox.Configuration.error(
            _("Please select at least one E-Mail address."));
        return;
    }
    var tests = [];
    if (data.start) {
        tests.push({ id: "currentdate", comparison: "ge", datepart: "date",
                     datevalue: [data.start] }); 
    }
    if (data.end) {
        tests.push({ id: "currentdate", comparison: "le", datepart: "date",
                     datevalue: [data.end] }); 
    }
    var test;
    switch (tests.length) {
        case 0:
            test = { id: "true" };
            break;
        case 1:
            test = tests[0];
            break;
        case 2:
            test = { id: "allof", tests: tests };
    }
    var d = {
        rulename: _("Vacation Notice").toString(),
        active: data.active,
        flags: ["vacation"],
        test: test,
        actioncmds: [{
            id: "vacation",
            days: days,
            addresses: data.addresses,
            text: data.text
        }]
    };
    if (data.subject) d.actioncmds[0].subject = data.subject;

    if (Self.id == undefined) {
        ox.JSON.put(AjaxRoot + "/mailfilter?action=new&session=" + session, d,
            function(reply) {
                Self.id = reply.data;
                createAppointment();
            });
    } else {
        d.id = Self.id;
        ox.JSON.put(AjaxRoot + "/mailfilter?action=update&session=" + session,
            d, createAppointment);
    }
    function createAppointment() {
        if (data.appointment) {
            ox.JSON.put(AjaxRoot + "/calendar?action=new&session=" + session,
                { folder_id: configGetKey("folder.calendar"),
                  title: data.subject || data.text, 
                  start_date: data.start, end_date: data.end,
                  note: String(_("Created by the vacation notice")),
                  full_time: true, shown_as: 1, ignore_conflicts: true },
                finish);
        } else {
            finish();
        }
    }
    //TODO: gui config for this module.
    function saveConfig(reply) {
        if (reply) {
            configSetKey(["modules", "com.openexchange.mail.filter"],
                           { gui: { vacationAppointment: reply.data.id } });
            ox.JSON.put(AjaxRoot +
                "/config/modules/com.openexchange.mail.filter/gui?session=" +
                session,
                configGetKey(["modules", "com.openexchange.mail.filter", "gui"]),
                finish);
        } else {
            finish();
        }
    }
    function finish() {
        ox.Configuration.info(_("Your settings have been saved."));
        cont();
    }
};

  ///////////////////////////
 //   Mail context menu   //
///////////////////////////

function checkHeader(id, header) {
    var result = [];
    for (var i = 0; i < header.length; i++) {
        result.push({ id: id, comparison: "contains",
                      values: [header[i][1]] });
    }
    return result;
}

function checkName(header) {
    var result = [];
    for (var i = 0; i < header.length; i++) {
        var name = header[i][0] || header[i][1];
        if (name) result.push(name);
    }
    return result;
}

var entries = {
    subject: {
        text: _("Based on subject"),
        columns: ["subject"],
        tests: function(mail) {
            return [{
                id: "subject",
                comparison: "contains",
                values: [mail.subject]
            }];
        },
        name: function(mail) { return mail.subject; }
    },
    from: {
        text: _("Based on sender"),
        columns: ["from"],
        tests: function(mail) {
            return checkHeader("from", mail.from);
        },
        name: function(mail) {
            //#. Default name of a mail filter rule
            //#. %s is a list of senders 
            return format(_("From %s"),
                          //#. List separator
                          checkName(mail.from).join(String(_(", "))));
        }
    },
    tocc: {
        text: _("Based on recipients"),
        columns: ["to", "cc"],
        tests: function(mail) {
            return checkHeader("tocc", mail.to).concat(
                   checkHeader("tocc", mail.cc));
        },
        name: function(mail) {
            //#. Default name of a mail filter rule
            //#. %s is a list of receivers 
            return format(_("To %s"),
                checkName(mail.to).concat(checkName(mail.cc)).
                    //#. List separator
                    join(String(_(", "))));
        }
    },
    mailinglist: {
        text: _("Based on mailing list"),
        columns: ["headers"],
        tests: function(mail) {
            return     check(mail.headers["X-BeenThere"]).
                concat(check(mail.headers["X-Mailinglist"]));
            function check(header) {
                if (!header) return [];
                return [{ id: "mailinglist", comparison: "contains",
                          values: [header] }];
            }
        },
        name: function(mail) {
            //#. Default name of a mail filter rule
            //#. %s is the name of a mailing list 
            return format(_("Mailing list %s"),
                mail.headers["X-BeenThere"] || mail.headers["X-Mailinglist"]);
        }
    }
};

var filter_submenu = new PopupMenu();
for (var i in entries) entries[i] = addEntry(entries[i]);
function addEntry(entry) {
    var item = new MenuItem(entry.text, function() {
        var ctx = this.getContext();
        ox.UIController.setModule({
            module: "configuration",
            view: node.id,
            success: function() {
                OXCache.newRequest(null, "mail",
                    { columns: entry.columns,
                      objects: ctx.getSelected() },
                    null, function(data) {
                        configure(function() {
                            split.addNew({
                                rulename: entry.name(data.objects[0]),
                                active: true,
                                test: { id: "anyof",
                                        tests: entry.tests(data.objects[0]) }
                            });
                        });
                    });
            }
        });
    });
    item.hide();
    filter_submenu.addItem(item);
    return item;
}

var visibilityVerified = false;
var filter_menu = new MenuItem(_("Create a filter"), filter_submenu);
filter_menu.onShow = function() {
    var ctx = this.getContext();
    configure(function() {
        if (!visibilityVerified) {
            for (var i = 0; i < conditions.length; i++) {
                var entry = entries[conditions[i]];
                if (entry) entry.show();
            }
        }
        entries.mailinglist.disable();
        OXCache.newRequest(null, "mail",
            { columns: ["headers"],
              objects: ctx.getSelected() },
            null, function(data) {
                var headers = data.objects[0].headers;
                if (headers["X-BeenThere"] || headers["X-Mailinglist"]) {
                    entries.mailinglist.enable();
                }
            });
    });
};
globalContextMenus.mail.addItem(filter_menu);
