/**
 * 
 * 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 
 * 
 * @author Stefan Preuss <stefan.preuss@open-xchange.com>
 * 
 */

// take care of new/edit windows. in this case the base object ox 
// doesn't exists so we have to create it
if ("ox" in window === false) {
    ox = {};
}

ox.categories = { cache: [], utils: {}, ui: { temp: {}, add: {} } };

ox.categories.setCache = function(categories) {
    ox.categories.cache = categories;
};

/**
 * Returns a list of categories available in the internal cache
 * @returns {Array} array of category objects
 */
ox.categories.list = function() {
    return ox.categories.cache;
};

/**
 * Gets a category by name
 * @param name of the category
 * @returns {object} the category object
 */
ox.categories.get = function(name) {    
    // get all categories
    var categories = ox.categories.list();
    // loop over
    for (var i=0; i<categories.length; i++) {        
        // compare case-insensitive
        if (categories[i].name.toLowerCase() == name.toLowerCase()) {
            delete(categories[i].active); // just in case
            return categories[i];
        }
    }
    return null;
};

/**
 * Checks if a category exists
 * @param the name of the category
 * @returns {object} the category object
 */
ox.categories.exists = function(name) {
    return ox.categories.get(name) ? true : false;
};

/**
 * Adds a new category to the internal cache
 * @param {String} name = the name, {int} color = the color id
 * @returns {object} the category object
 */
ox.categories.add = function(name, color) {
    name = trimStr(name);
    // do already exists or is name empty?
    if (ox.categories.exists(name) || !name.length) {
        return null;
    }
    var category = { name: name, color: (color || 0) };
    ox.categories.cache.push(category);    
    return category;
};

/**
 * Edits a category in the internal cache
 * @param {String} id = identifier of the category (likely the name), 
 *           {String} name = the name, {int} color = the color id
 * @returns {object} the category object
 */
ox.categories.edit = function(id, name, color) {
    name = trimStr(name || "");
    // name empty?
    if (!name.length && !ox.categories.exists(id)) {
        return null;
    }
    var category = ox.categories.get(id);
    if (category) {
        delete(category.active); // just in case
        category.name = name;
        category.color = color;
    }
    return category;
};

/**
 * Removes a set of categories from the cache
 * @param {Array} an array of names which should be removed from 
 *           the internal cache
 */
ox.categories.remove = function(names) {
    if (typeof names != "object") {
        names = [ names ];
    }
    $l1 = names.length;
    for (var i=0; i < ox.categories.list().length; i++) {
        for (var ia=0; ia<$l1; ia++) {
            if (ox.categories.cache[i] && ox.categories.cache[i].name == names[ia]) {
                ox.categories.cache.splice(i,1);                
            }
        }
    }
};

/**
 * Saves the current categories to the users configuration
 */
ox.categories.save = function() {
    // without corewindow we have to leave
    if (!corewindow) {
        return;
    }
    corewindow.configuration_changed_fields.gui = true;
//    var cats = (ox.categories.list() || []), $l = cats.length, categories = [];
//    for (var i=0; i < $l; i++) {
//        categories.push(corewindow.clone(cats[i]));
//    }
    var categories = corewindow.clone(ox.categories.list() || [], window);
    corewindow.configSetKey("gui.categories.local", categories);    
    corewindow.triggerEvent("OX_Save_Configuration", false, true);
    
    // are we in a new/edit window? if so we are updating the cache in the 
    // corewindow. 
    // @todo: we should think about a smarter way to force an cache update at
    // the corewindow. could lead to problems if the user edit categories in the
    // corewindow at the same time!
//    if (corewindow.ox.Configuration) {
//        corewindow.ox.categories.cache = categories;
//    }
};

ox.categories.match = { ALL: 1, MATCH: 2, NON_MATCH: 4 };

ox.categories.getByString = function(names, match, noDuplicates) {    
    var list = [];
    var ids = names.split(","), $l = ids.length;
    for (var i=0; i < $l; i++) {
        var name = trimStr(ids[i]);        
        if (name != "" && (!noDuplicates || 
                !ox.categories.utils.existsInObject(name, list))) {
            var cat = ox.categories.get(name);
            switch (match) {
                case 4:
                    if (!cat) {
                        list.push({ name: name, color: 0 });
                    }
                    break;
                case 2:
                    if (cat === null){
                        break;
                    }
                    /* no break */
                default:
                    list.push(cat || { name: name, color: 0 });
                    break;
            }
        }
    }
//    list.sort(function(a,b) {
//        return (a.color < b.color ? 1 : -1);
//    });
    return list;
};

/**
 * Removes the given category out of the comma separated list of categories
 * @param {String} the name of the category which should be removed
 * @param {String} comma separated list of categories
 * @returns {String} a new list without the given category. note: any duplicated
 *             category will be filtered out as well!
 */
ox.categories.removeCategoryFromString = function(name, categories) {
    var cats = ox.categories.getByString(categories, ox.categories.match.ALL, true);
    var retVal = "";
    for (var i=0; i < cats.length; i++) {
        if (cats[i].name !== name) {
            retVal += cats[i].name + (i+1 < cats.length ? ", " : "");
        }
    }
    return retVal;
};

/**
 * Gets the first category from cache matching with one of the 
 * categories of the given string.
 * @param {String} single category name or likely a comma separated 
 *           list of categories 
 */
ox.categories.getFirstMatch = function(str) {
    var ids = (str || "").split(/,/);
    var i = 0, $l = ids.length;
    // loop over names
    for (; i < $l; i++) {
        var id = trimStr(ids[i]);
        if (ox.categories.exists(id)) {            
            return ox.categories.get(id); // match!
        }
    }    
    return { name: "", color: 0 }; // no match
};


/*** Categories Selection Dialog ***/

ox.categories.ui.open = function(tags, cb) {    
    var grid, color, tags = (tags || "");    
    ox.categories.ui.final_cb = cb;    
    
    // initiate grid
    if (!ox.categories.ui.grid) {        
        ox.categories.ui._initGrid();
        
        // add handler on delete button
        addDOMEvent($("categories_sel_but_delete-button"), "click", function(e) {
            var ids = ox.categories.ui.grid.selection.getSelected();
            if (ids && ids.length) {
                ox.categories.ui.temp.changed = true;
                ox.categories.ui.grid.storage.removeIDs(ids);
            }
            return false;
        });
        
        // add handler on rename button
        addDOMEvent($("categories_sel_but_rename-button"), "click", function(e) {
            var ids = ox.categories.ui.grid.selection.getSelected();
            if (ids && ids.length == 1) {
                var inplace, self = this;
                var node = ox.categories.ui.grid.rows[ox.categories.ui.grid.focus].cells[2];
                if (!node || !node.firstChild) {
                    return;
                }
                inplace = new inplaceEdit(
                        node, 
                        node.firstChild.data, 
                        function(new_name) {
                            ox.categories.ui.grid.storage.localUpdate(ids, function(index, object) {
                                if (object.name != new_name) {
                                    ox.categories.ui.temp.changed = true;
                                    object.name = new_name;
                                }
                                return object;
                            });
                            delete(self.inplace);
                        }
                );
                inplace.style.border = "1px black dotted";
                inplace.style.background = "white";
                inplace.on();
                cancelBubbling(e);
            }
            return false;
        });    
        
        // add handler on new button
        addDOMEvent($("categories_sel_but_new-button"), "click", function(e) {
            ox.categories.ui.add.open(null, function(category) {
                //does it exists in cache or in the grid? then return
                if (!category || ox.categories.exists(category.name) || 
                        ox.categories.ui.grid.storage.indices[category.name]) {
                    return;
                }
                category.active = true;
                ox.categories.ui.grid.storage.append([category]); // append to grid
                if (category.color !== 0) {
                    ox.categories.ui.temp.changed = true;
                }
            });
        });
    }
    
    // initiate color selection combo
    if (!ox.categories.ui.color) {
        var color = ox.categories.ui.getColorSelect();
        // color change event
        color.onChange = function(new_color, color_before) {
            var ids = ox.categories.ui.grid.selection.getSelected();
            // anything selected and new color is not 0
            if (ids && ids.length && new_color !== 0) {
                // updating grid storage
                ox.categories.ui.grid.storage.localUpdate(ids, function(index, object) {
                    if (object.color != new_color) {
                        object.color = new_color;
                        if (new_color !== 0) {
                            ox.categories.ui.temp.changed = true;
                        }
                    }
                    return object;
                });        
            } else if (new_color === 0) {
                // if color is 0 reset to the color before
                color.setKey(color_before, true);
            }
        };
        ox.categories.ui.color = color;
        $("categories_sel_color").appendChild(color.getDomNode());
    }
    // enable grid with storage
    grid = ox.categories.ui.grid;
    grid.enable(new Storage(0, [], 0, 0, 0, 0, function(data) { return data.name; }));
    //grid.storage.remove(0, grid.storage.ids.length);

    // find matching categories by the given list and mark them as selected
    var mCat = clone(ox.categories.getByString(tags, ox.categories.match.MATCH));
    var lCat = clone(ox.categories.list()), $l = lCat.length, $la = mCat.length;
    for (var i=0; i < $l; i++) {
        var t_cat = lCat[i];
        for (var ia=0; ia < $la; ia++) {
            var t_cat2 = mCat[ia];
            if (t_cat.name == t_cat2.name) {
                t_cat.active = true; // mark as active
                break;
            } else {
                delete(t_cat.active); // just in case
            }
        }
    }
    
    // getting non matching categories and mark them as active
    var nCat = clone(ox.categories.getByString(tags, ox.categories.match.NON_MATCH));
    var $l = nCat.length;
    for (var i=0; i < $l; i++) {
        nCat[i].active = true;
        lCat.push(nCat[i]);
    }
    
    // append concatenated tags to grid storage
    grid.storage.append(lCat);
        
    // show dialog
    ox.api.setModal(true);
//    showNode("modal-dialog");
//    $("modal-dialog").style.display="block";
    
    showNode("disable_confirm_window");
    $("disable_confirm_window").style.display="block";
        
    showNode("window_categories");
    centerPopupWindow($("window_categories"));
    $("window_categories").style.display="block";
};

ox.categories.ui.close = function(cancel) {
    var selCat = [], finCat = [];
    // get selected categories and disable grid
    if (ox.categories.ui.grid) {
        var grid = ox.categories.ui.grid;
        // iterate through storage and get selected cats 
        grid.storage.newIterate(grid.storage.ids, emptyFunction, function(id, obj) {
            if (obj) {                
                if (obj.active) {
                    selCat.push(obj);
                }
                if (obj.color !== 0) {
                    finCat.push(obj);
                }
                delete(obj.active); // just temp so delete
            }
        });
        grid.disable();
    }
    
    // rearrange order of categories in the way the user selected them
    var order = (ox.categories.ui.temp.clickOrder || []), $l = order.length, $la = selCat.length;
    for (var i=$l-1; i >= 0; i--) {
        for (var ia=0; ia < $la; ia++) {
            var object = selCat[ia];
            if (object.name == order[i]) {
                selCat.splice(ia,1);
                selCat.unshift(object);
            }
        }
    }
    // remove instance here to prevent a save trigger later!
    delete(ox.categories.ui.temp.clickOrder);
    
    // do we have a action or did the user just pressed cancel?
    if (!cancel) {    
        if (ox.categories.ui.temp.changed) {
            ox.categories.setCache(finCat);
            ox.categories.save();
        }
        
        // trigger given callback
        if (ox.categories.ui.final_cb) {
            ox.categories.ui.final_cb(selCat, ox.categories.utils.paramToString(selCat, "name"));
        }
    }
    // clean-up
    delete(ox.categories.ui.final_cb);
    delete(ox.categories.ui.temp);
    ox.categories.ui.temp = {};
    
    // close modal window
    if (this.closecb) { 
        globalalert.cbclose(); 
    }
    hideNode("window_categories");
    $("window_categories").style.display="none";
        
    hideNode("disable_confirm_window");
    $("disable_confirm_window").style.display="none";
    var childs=    $("modal-dialog").childNodes;
    var closeall=true;
    for(var i=0;i<childs.length;i++) {
        if(childs[i].tagName && childs[i].tagName == "DIV") {
            if(childs[i].style.display && childs[i].style.display!= "none" ) {
                closeall=false;
            }
        }
    }
    if (closeall) {
        ox.api.setModal(false);
//        hideNode("modal-dialog");
//        $("modal-dialog").style.display="none";
    }
};

/**
 * Creates a new color combobox
 * @return {ComboBox3} object 
 */
ox.categories.ui.getColorSelect = function() {
    var ncol = new ComboBox3(window, null, "10em", 0, true, null, 7);
    for (var i=0; i<=25; i++) {
        ncol.addElement(newnode("div", 0, 
                { className: "colorLabelCont bs ha colorLabel" + i }), i);
    }
    ncol.headTable.style.height="1.8em";
    return ncol;    
};

/*
 * Initiate a new grid for the category selection dialog
 * @private
 */
ox.categories.ui._initGrid = function() {
    // temporary store the click sequence to get the correct order 
    function changeOrder(id, action) {
        var order = (ox.categories.ui.temp.clickOrder || []), $l = order.length;
        for (var i=0; i < $l; i++) {
            if (order[i] == id) {
                order.splice(i,1);
                break;
            }
        }
        if (action) {
            order.push(id);
        }
        ox.categories.ui.temp.clickOrder = order;
    }
    function addCheckBox() {
        var checkbox = newnode("input", 0, { type: "checkbox", className: "noborder" });
        addDOMEvent(checkbox, "click", function() {
            // clicked on check-box 
            var ids = ox.categories.ui.grid.selection.getSelected();
            if (ids && ids.length) {
                // setting status in storage
                var obj = ox.categories.ui.grid.storage.data.data[ids[0]];
                if (obj.active) {
                    delete(obj.active);
                } else {
                    obj.active = true;
                }
                changeOrder(obj.name, obj.active);
            }
            return false;
        });
        return checkbox;
    }
    var self = this;
    var grid = new LiveGrid([
          {
              text: "&#xa0;",
              width: "2em",
              style: { paddingTop: "2px" },
              set: function (div, data) {
                    if (!div.firstChild) {
                        div.appendChild(addCheckBox());
                    }
                    div.firstChild.style.display = "";
                    div.firstChild.checked = data.active;
              },
              clear: function (div) {
                    if (!div.firstChild) {
                        div.appendChild(addCheckBox());
                    }
                    div.firstChild.style.display = "none";
              }
          },
          { 
              text: "&#xa0;",
              width: "2em",
              clear: function(div) {
                  if (div.firstChild) {
                      div.firstChild.className = "";
                  } else {
                      div.appendChild(
                              newnode("div", 0, { className: "colorLabelCont bg hb" }));
                  }
                },
              set: function(div, data) {
                  if (div.firstChild) {
                      div.firstChild.className = "colorLabelCont bg hb colorLabel"+data.color;
                  } else {
                      div.appendChild(
                              newnode("div", 0, 
                                      { className: "colorLabelCont bg hb colorLabel" + data.color }));
                  }
              }
          },
          {
              text: "Name",
              i18n: true,
              clear: LiveGrid.makeClear(""),
              set: function(div, data) {
                  if (div.firstChild) {
                      div.firstChild.data = data.name;
                  } else {
                      div.appendChild(document.createTextNode(data.name));
                  }
              }
          }
      ], new Selection());
    
    grid.lineHeight = 1.9;
    grid.rowHeight = 24;
    grid.emptylivegridtext = "";
    grid.events.register("Selected", function() {
        // get selected signature
        var ids = self.grid.selection.getSelected();        
        if (!ids || !ids.length) {            
            ox.categories.ui.color.setKey(0, true);
        } else {            
            var object = ox.categories.get(ids[0]);            
            if (ids.length && object) {
                ox.categories.ui.color.setKey(object.color, true);
            } else {                
                ox.categories.ui.color.setKey(0, true);
            }
        }        
    });
    grid.events.register("Activated", function(ids) {        
            if (ids && ids.length > 0) {
                ox.categories.ui.grid.storage.localUpdate(ids, function(index, object) {
                    if (object.active) {
                        delete(object.active);
                    } else {
                        object.active = true;
                    }
                    changeOrder(object.name, object.active);
                    return object;
                });
            }
        }
    );    
    $("categories_sel_grid_header").appendChild(grid.getHeader());
    grid.getTable($("categories_sel_grid"));
    ox.categories.ui.grid = grid;
};


/*** Categories Add/Edit Dialog ***/

ox.categories.ui.add.open = function(tag, cb) {
    var color;
    ox.categories.ui.add.final_cb = cb;
    
    // init color selection
    if (!ox.categories.ui.add.color) {
        color = ox.categories.ui.getColorSelect();
        $("categories_new_color").appendChild(color.getDomNode());
        color.setKey(0);
        ox.categories.ui.add.color = color;
    } else {
        color = ox.categories.ui.add.color;
    }
    
    if (tag) {
        color.setKey(tag.color || 0);
        $("categories_new_id").value = (tag.id || tag.name || "");
        $("categories_new_name").value = (tag.name || "");
    }
    
    if (!ox.api.isModal()) {
        ox.api.setModal(true);
//        showNode("modal-dialog");
//        $("modal-dialog").style.display="block";
        showNode("disable_confirm_window");
        $("disable_confirm_window").style.display="block";
    }
    
    showNode("window_categories_new");
    centerPopupWindow($("window_categories_new"));
    $("window_categories_new").style.display="block";
    $("categories_new_name").focus();
};

ox.categories.ui.add.close = function(cancel) {        
    if (!cancel && ox.categories.ui.add.final_cb) {
        ox.categories.ui.add.final_cb({ id: $("categories_new_id").value, 
            name: $("categories_new_name").value, color: ox.categories.ui.add.color.getKey() });
    }    
    $("categories_new_id").value = "";
    $("categories_new_name").value = "";
    ox.categories.ui.add.color.setKey(0);
    delete(ox.categories.ui.add.final_cb);
    
    if (this.closecb) { 
        globalalert.cbclose(); 
    }
    hideNode("window_categories_new");
    $("window_categories_new").style.display="none";
    // hide background only if the main selection dialog isn't visible
    if ($("window_categories") && $("window_categories").style.display == "none") {
        hideNode("disable_confirm_window");
        $("disable_confirm_window").style.display="none";
        var childs=    $("modal-dialog").childNodes;
        var closeall=true;
        for (var i=0; i < childs.length; i++) {
            if (childs[i].tagName && childs[i].tagName == "DIV") {
                if (childs[i].style.display && childs[i].style.display!= "none" ) {
                    closeall=false;
                }
            }
        }
        if (closeall) {
            ox.api.setModal(false);
//            hideNode("modal-dialog");
//            $("modal-dialog").style.display="none";
        }
    }
};

/*
 * draws a list of category divs to a given DOM node
 * @param {Array} list of category objects
 * @param {DOM} node to attach category divs
 * @param {Boolean} true = show just only color divs, without names
 */
ox.categories.ui.drawCategoriesList = function(categories, node, withoutNames) {
    var $l = categories.length;
    removeChildNodes(node);
    var cNode = newnode("div", { flt: "left", border: "1px solid #c0c0c0", minHeight: "10px",
        padding: "0 4px 0 4px", margin: "1px 1px 0 0" }, { className: "colorLabelCont bs" }, 
        [ newtext("\u00a0") ]);    
    for (var i=0; i < $l; i++) {
        var category = categories[i];
        var tn = cNode.cloneNode(true);        
        tn.className += " colorLabel" + category.color;
        if (!withoutNames) {
            tn.firstChild.data = category.name;
        }
        tn.title = (category.name || "");
        tn.onselectstart = function() { return false; };
        node.appendChild(tn);
    }
};

/*
 * attach an inplace input field to the given DOM node. the developer
 * needs to take care that this node got removed afterwards. the callback 
 * will be triggered as soon the user leave the element and/or hit enter
 * or esc. 
 * @param {DOM} node to attach the inplace input
 * @param {function} callback which receive the input value. if the user
 *           hit ESC the value will be null
 */
ox.categories.ui.attachInplaceInput = function(node, callback) {
    if (!node || !callback) {
        return;
    }
    var key, blur;
    var bNode = newnode("div", { flt: "left", border: "1px solid #c0c0c0", 
        padding: "0 4px 0 4px", margin: "1px 1px 0 0", MozUserSelect: "normal" }, 
        { className: "colorLabelCont bs" });
    var input = newnode("input", { width: "15em", border: "none" });
    bNode.appendChild(input);
    node.appendChild(bNode);
    function finalCb(value) {
        removeDOMEvent(input, "blur", blur);
        removeDOMEvent(input, "keypress", key);
        callback(value);
    }
    key = function(e) {
        switch (e.keyCode || e.which) {
            // enter
            case 13:
                finalCb(input.value);
                break;
            // esc
            case 27:
                finalCb(null);
                break;
            default:
                return;
        }
        return false;
    };
    blur = function(e) {
        finalCb(input.value);
    };
    addDOMEvent(input, "blur", blur);
    addDOMEvent(input, "keypress", key);
    
    // set focus and select
    input.focus();
    input.select();
};

register("Loaded", function () {
    addOnClose($("window_categories"), function() { 
        ox.categories.ui.close(true);
    });
    addOnClose($("window_categories_new"), function() { 
        ox.categories.ui.add.close(true);
    });
    
    // need to init cache here in the new/edit windows
    if (!ox.Configuration) {
        ox.categories.setCache(clone(configGetKey("gui.categories.local"), corewindow));
    }
});

register("OX_Configuration_Loaded_Complete", function() {
    ox.categories.setCache(configGetKey("gui.categories.local"));
});

corewindow.register("OX_Configuration_Changed", function(location) {
    if ("ox" in window) {
        if (!ox.Configuration) {
            ox.categories.setCache(clone(configGetKey("gui.categories.local"), corewindow));
        } else {
            ox.categories.setCache(configGetKey("gui.categories.local"));
        }
    }
});


/*** Categories Utils ***/

ox.categories.utils.migrateTags = function() {
    var tags = (configGetKey("gui.private_categories") || "").split(","), color = 1;    
    for (var i=0; i<tags.length; i++) {
        var name = trimStr(tags[i]);
        // only add tags where name not empty and if not exists 
        if (name.length !== 0 && ox.categories.exists(name) === false) {
            ox.categories.add(name, color++);
            if (color == 25) {
                color = 1;
            }
        }
    }
    delete(config.gui.private_categories);
    delete(config.gui.global_categories);
    ox.categories.save();
};

ox.categories.utils.paramToString = function(categories, parameter, delimeter) {
    var $l = categories.length, retVal = [];
    for (var i=0; i < $l; i++) {
        if (categories[i][parameter]) {
            retVal.push(categories[i][parameter]);
        }
    }
    return retVal.join(delimeter || ", ");
};

/**
 * Check if the given name already exists in the list of categories
 * @param {String} the name to check
 * @param {Array} list of category objects
 */
ox.categories.utils.existsInObject = function(name, categories) {
    var $l = categories.length;
    for (var i=0; i < $l; i++) {
        if (categories[i].name == name) {
            return true;
        }
    }
    return false;
};

fileloaded();