/**
 * 
 * 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 Matthias Biggeleben <matthias.biggeleben@open-xchange.com>
 * 
 */

/*jslint bitwise: false, nomen: false, onevar: false, plusplus: false, regexp: false, white: true, browser: true, devel: true, evil: true, forin: true, undef: true, eqeqeq: true, immed: true */

/*global ox, jQuery, window, console, newWindow, urlify, activefolder : true, activemodule : true,
corewindow, json, JSON, AjaxRoot, session, OXMailMapping, _, triggerEvent, 
register, unregister, currentCalendarView, configGetKey, config : true,
mail_printSingleMail, activeYear, activeMonth, activeDay, calendar_print,
getDayInSameWeek, calendar_getAllFoldersAttribute, getWWStartTime, getWWEndTime, 
showNode, changeView, mail_accounts, escapeRegExp, newAlert, debug, expectI18n,
configuration_askforSave, isEmpty, configSetKey, configContainsKey, configRemoveKey, logout_location,
formatError, newConfirm, AlertPopup, Join, internalCache, getNewFolderPerms,
nativeJSON, localStorage, newServerError, setLoading, setLoadingCursor */

//--------------------------------------------------------------------------------------------------
//     _  __                      _  _____ _      _       _     _                             _     
//    | |/ /                     | |/ ____| |    (_)     | |   | |                           | |    
//    | ' / ___  ___ _ __        | | (___ | |     _ _ __ | |_  | |__   __ _ _ __  _ __  _   _| |    
//    |  < / _ \/ _ \ '_ \   _   | |\___ \| |    | | '_ \| __| | '_ \ / _` | '_ \| '_ \| | | | |    
//    | . \  __/  __/ |_) | | |__| |____) | |____| | | | | |_  | | | | (_| | |_) | |_) | |_| |_|    
//    |_|\_\___|\___| .__/   \____/|_____/|______|_|_| |_|\__| |_| |_|\__,_| .__/| .__/ \__, (_)    
//                  | |                                                    | |   | |     __/ |      
//                  |_|                                                    |_|   |_|    |___/       
//    ... pretty please... with sugar on top....
//    JSLINT (http://www.jslint.com/) should not report any error for api.js.
//--------------------------------------------------------------------------------------------------

if (window.ox === undefined) {
    // required to run in "lightweight" windows
    /**
     * @name ox
     * @namespace
     */
    window.ox = {};
}

/**
 * @name ox.browser
 * @namespace
 */
ox.browser = (function () {
    // adopted from prototype.js
    var ua = navigator.userAgent;
    var isOpera = Object.prototype.toString.call(window.opera) === "[object Opera]";
    var webkit = ua.indexOf('AppleWebKit/') > -1;
    var chrome = ua.indexOf('Chrome/') > -1;
    return {
        /** @lends ox.browser */
        /** is IE? */
        IE:     !!window.attachEvent && !isOpera,
        /** is Opera? */
        Opera:  isOpera,
        /** is WebKit? */
        WebKit: webkit,
        /** Safari */
        Safari: webkit && !chrome,
        /** Safari */
        Chrome: webkit && chrome,
        /** is Gecko/Firefox? */
        Gecko:  ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1
    };
}());

/**
 * @name ox.test
 * @namespace
 */
ox.test = (function () {

    var $ = jQuery;
    
    return {
        /** @lends ox.test */
        
        /**
         * Execute JavaScript code via eval() in current window (helps SELENIUM tests).
         * Otherwise, internal tests, e.g. using "object.constructor", will fail due to permission problems.
         * @param {string} code JavaScript code
         * @returns {Object} Result of eval()
         * @example
         * // API call inside SELENIUM unit test
         * ox.test.evalScript("ox.api.calendar.compose({ data: { title: 'Test' } });");
         */
        evalScript: function (code) {
            return eval(code);
        },
        
        /**
         * Trigger an event in a nested window. First parameter is the window's GUID, second the event name.
         * Any further parameter is passed to the corresponding event handlers.
         * @param {string} guid Window GUID. "OX.#" refers to last window.
         * @param {string} name Event name
         * @example
         * ox.test.trigger("OX.1", "OX_Cancel_Object");
         */
        trigger: function (guid, name) {
            // exists?
            if (ox.api.window.exists(guid)) {
                // get arguments
                var args = $.makeArray(arguments);
                // remove guid
                args.shift();
                // get window & trigger event
                var w = ox.api.window.get(guid).window;
                w.triggerEvent.apply(w, args);
            }
        },
        
        /**
         * Trigger save event in a window (convenience function)
         * @param {string} guid Window GUID. "OX.#" refers to last window.
         * @example
         * // save object
         * ox.test.triggerSave("OX.1");
         */
        triggerSave: function (guid) {
            this.trigger(guid, ox.api.event.common.SaveObject);
        },
        
        /**
         * Trigger cancel event in a window (convenience function)
         * @param {string} guid Window GUID. "OX.#" refers to last window.
         * @example
         * // cancel object (might trigger dialog "Do you really want to cancel...?")
         * ox.test.triggerCancel("OX.1");
         */
        triggerCancel: function (guid) {
            this.trigger(guid, ox.api.event.common.CancelObject);
        }
    };
}());

/**
 * @name ox.util
 * @namespace
 */
ox.util = (function () {
    
    // deserialize
    var deserialize = function (str, delimiter) {
        var pairs = (str || "").split(delimiter === undefined ? "&" : delimiter);
        var i = 0, $l = pairs.length, pair, obj = {}, d = decodeURIComponent;
        for (; i < $l; i++) {
            pair = pairs[i];
            var keyValue = pair.split(/\=/), key = keyValue[0], value = keyValue[1];
            if (key !== "" || value !== undefined) {
                obj[d(key)] = d(value);
            }
        }
        return obj;
    };
    
    // get hash & query
    var queryData = deserialize(document.location.search.substr(1), /&/);
    var hashData = deserialize(document.location.hash.substr(1), /&/);
    
    return {
        
        /**
         * @param {string} [name] Name of the hash parameter
         * @returns {Object} Value or all values
         */
        getHash: function (name) {
            return name === undefined ? hashData : hashData[name];
        },
    
        /**
         * @param name {string} [Name] of the query parameter
         * @returns {Object} Value or all values
         */
        getParam: function (name) {
            return name === undefined ? queryData : queryData[name];
        },
        
        /**
          * Get a new empty array (constructed by current window; useful in IE for cross-window issues)
          * @returns {Array} New empty array
          * @example
          * var tmp = ox.util.getArray(); // simply returns []
          */
        getArray: function () {
            return [];
        },
        
        /**
         * Serialize object (key/value pairs) to fit into URLs (e.g. a=1&b=2&c=HelloWorld)
         * @param {Object} obj Object to serialize
         * @param {string} [delimiter] Delimiter
         * @returns {string} Serialized object
         * @example
         * ox.util.serialize({ a: 1, b: 2, c: "text" });
         */
        serialize: function (obj, delimiter) {
            var tmp = [], e = encodeURIComponent, id;
            if (typeof obj === "object") {
                for (id in (obj || {})) {
                    if (obj[id] !== undefined) {
                        tmp.push(e(id) + "=" + e(obj[id]));
                    }
                }
            }
            return tmp.join(delimiter === undefined ? "&" : delimiter);
        },
        
        /**
         * Deserialize object (key/value pairs)
         * @param {string} str String to deserialize
         * @param {string} [delimiter] Delimiter
         * @returns {Object} Deserialized object
         * @function
         * @name ox.util.deserialize
         * @example
         * ox.util.deserialize("a=1&b=2&c=text");
         */
        deserialize: deserialize,
        
        /**
         * This function simply writes its parameters to console.
         * Useful to debug callbacks, e.g. event handlers.
         * @example
         * ox.api.calendar.httpGet({ params: { id: 158302 }, success: ox.util.inspect });
         */
        inspect: function () {
            var args = jQuery.makeArray(arguments); // $ is not jQuery here
            args.unshift("Inspect");
            console.debug.apply(console, args);
        },
        
        /**
         * Create absolute URL
         * @param {string} url (relative/absolute) URL
         * @returns {string} Absolute URL
         * @example
         * ox.util.getAbsoluteURL("index.html"); // returns protocol://host/path/index.html
         * ox.util.getAbsoluteURL("/index.html"); // returns protocol://host/index.html
         * ox.util.getAbsoluteURL("http://localhost/foo/index.html"); // returns http://localhost/foo/index.html
         */
        getAbsoluteURL: function (url) {
            // change?
            if (url.search(/^http/) !== -1) {
                return url;
            } else {
                var l = window.location;
                if (url.substr(0, 1) === "/") {
                    // add protocol & host
                    return l.protocol + "//" + l.host + url;
                } else {
                    // strip file & inject version
                    return l.href.replace(/\/[^\/]+$/, "/") + ox.api.window.core.oxProductInfo.build + "/" + url;
                }
            }
        },
        
        /**
         * Check whether an object is an array
         * @param {Object} obj The object to test
         * @returns {boolean} True/False
         */
        isArray: function (obj) {
            // we cannot use 'elem.constructor === array' under SELENIUM (permission denied error).
            // so, we simply rely on the existence of length, splice, and push.
            // We even cannot test if they are functions (does not work in IE).
            // For the same reason, Object.prototype.toString.call(elem) == "[Object Array]" does not 
            // work across windows. However, a browser thinks that an object is an array if it 
            // has "length" and "splice". So we just look for those properties.
            return obj && obj.length !== undefined && obj.splice !== undefined && obj.push !== undefined;
        },
        
        /**
         * Check whether an object is an array
         * @param {Object} obj The object to test
         * @returns {boolean} True/False
         */
        isFunction: (function () {
            return function (obj) {
                // fix checks across window boundaries
                return obj && obj.apply !== undefined && obj.call !== undefined;
            };
        }()),
        
        /**
         * Simple test to check whether an object is an instance of Window or a DOM node
         * @param {Object} obj The object to test
         * @returns {boolean} True/False
         */
        isDOMObject: function (obj) {
            // not null, not undefined, but DOM node or window 
            return obj && (obj.nodeType || obj.setInterval);
        },
        
        /**
         * Simple test to check whether an object is a plain object
         * @param {Object} obj The object to test
         * @returns {Boolean} True/False
         */
        isPlainObject: function (obj) {
            // jQuery provides a more precise method but this one works across windows
            var toString = Object.prototype.toString;
            return obj && toString.call(obj) === "[object Object]" && !obj.nodeType && !obj.setInterval;
        },
        
        /**
         * Call function if first parameter is a function (simplifies callbacks)
         * @param {function ()} fn Callback
         */
        call: function (fn) {
            if (typeof fn === "function" || (ox.browser.IE === true && ox.util.isFunction(fn))) {
                var i = 1, $l = arguments.length, args = [];
                for (; i < $l; i++) {
                    args.push(arguments[i]);
                }
                return fn.apply(fn, args);
            }
        },
        
        /**
         * Call "new" in other windows
         */
        create: function () {
            // create new helper class
            var Fn = function () {};
            // get arguments
            var args = jQuery.makeArray(arguments);
            // get class
            var Class = args.shift();
            // inherit
            Fn.prototype = Class.prototype;
            // create object
            var obj = new Fn();
            // apply
            Class.apply(obj, args);
            // return new instance
            return obj;
        },
        
        /**
         * Return the first parameter that is not undefined
         */
        firstOf: function () {
            var args = jQuery.makeArray(arguments), i = 0, $l = args.length;
            for (; i < $l; i++) {
                if (args[i] !== undefined) {
                    return args[i];
                }
            }
            return undefined;
        },
        
        /**
         * Return current time as timestamp
         * @returns {long} Timestamp
         */
        now: function () {
            return new Date().getTime();
        },
        
        /**
         * Get flat list of all keys (deep)
         */
        keys: function (obj, list) {
            // loop
            var tmp = list || [], deep = list !== false, id, o;
            if (obj) {
                for (id in obj) {
                    // add
                    tmp.push(id);
                    // recursion
                    if (deep) {
                        o = obj[id];
                        if (typeof o === "object" && o !== null) {
                            ox.util.keys(o, tmp);
                        }
                    }
                }
            }
            return tmp;
        },
    
        /**
         * Get flat list of all values
         */
        values: function (obj) {
            // loop
            var tmp = [], id;
            if (obj) {
                for (id in obj) {
                    // add
                    tmp.push(obj[id]);
                }
            }
            return tmp;
        },
    
        /**
         * Clone object
         * @param {Object} elem Object to clone
         * @returns {Object} Its clone
         */
        clone: function clone(elem) {
            if (typeof elem !== "object") {
                return elem;
            } else {
                var isArray = ox.util.isArray;
                var isDOMObject = ox.util.isDOMObject;
                var subclone = function (elem) {
                    if (!elem) {
                        return null;
                    } else {
                        var tmp = isArray(elem) ? [] : {}, prop, i;
                        for (i in elem) {
                            prop = elem[i];
                            tmp[i] = typeof prop === "object" && !isDOMObject(prop) ? subclone(prop) : prop;
                        }
                        return tmp;
                    }
                };
                return subclone(elem);
            }
        },
        
        /**
         * Identity function. Returns the object as is.
         */
        identity: function (o) {
            return o;
        }
    };
    
}());

/**
 * IE specific fix for function.apply across window boundaries
 */
if (ox.browser.IE) {
    
    // adopted from Ash Searle's nice work on Function.apply (See http://hexmen.com/blog/)
    
    // overwrite apply
    Function.prototype.apply = function (scope, list) {
        
        // get proper scope
        scope = (scope === null || scope === undefined) ? window : scope;
        
        // the function to apply (magic magic)
        scope.houdini = this;
        
        // copy arguments
        var i = 0, args = [], $l = (list || []).length;
        for (; i < $l; i++) {
            args.push("list[" + i + "]");
        }
        
        // eval function call
        try {
            var retval = eval("scope.houdini(" + args.join(", ") + ")");
            delete scope.houdini;
            return retval;
        } catch (e) {
            if (debug) {
                console.error("Error in Function.prototype.apply!");
            }
            return retval;
        }
    };
}

/** @namespace */
ox.api = (function () {
    
    // jquery ref
    var $ = jQuery;
    
    // classes
    var Dispatcher = null;
    
    // api refs
    var that;
    var dispatcherRegistry = null;
    
    // debug
    var debugEnabled = false;
    var debug = !debugEnabled ? $.noop : function () {
        console.debug.apply(console, arguments);
    };
    
    // shortcuts
    var clone = ox.util.clone;
    var isFunction = ox.util.isFunction;
    
    // default window options
    var defaultWindowOptions = {
        resizable: "yes",
        menubar: "no",
        toolbar: "no",
        status: "no",
        location: "no",
        scrollbars: "no"
    };
    
    // default window functions
    var defaultWindowFactory, defaultWindowCloser, defaultWindowSelector;
    
    /**
     * Default window factory (can be overridden).
     * A window factory is a function that produces a new window (e.g. via window.open) or
     * an iframe. The function receives an options objects. After creating the new window,
     * the function calls registerWindow(guid, window) and calls the callback function options.success
     * with the same two parameters: 1) the new window object, and 2) the GUID of that window.
     * @public
     * @param {Object} opt Factory options
     * @param {string} opt.url Window URL
     * @param {string} opt.guid Window GUID
     * @param {Object} opt.options Window options
     */
    var windowFactory = defaultWindowFactory = function (opt) {
        if (opt.guid !== undefined) {
            // get url
            var url = ox.util.getAbsoluteURL(opt.url);
            // event
            triggerEvent("OX_Window_Open", {
                guid: opt.guid,
                url: url
            });
            // open
            var win = newWindow(ox.util.getAbsoluteURL(opt.url), that.window.serializeOptions(opt.options), opt.id);
            /// register window
            that.window.register(opt.guid, win);
            // set guid
            win.guid = opt.guid;
            // focus
            win.focus();
            // continuation
            $(win).one("load", function(e) {
                opt.success(win, opt.guid, opt);
            });
        } else {
            console.error("windowFactory: guid is missing!");
        }
    };
    
    /**
     * Default window closer (can be overridden)
     * @public
     */
    var windowCloser = defaultWindowCloser = function (obj) {
        obj.window.close();
    };
    
    /**
     * Default window selector (can be overriden)
     * @public
     */
    var windowSelector = defaultWindowSelector = function (obj, guid) {
        obj.window.focus();
    };
    
    /**
     * Default window positioner
     * @name ox.api-windowPositioner
     * @public
     * @returns {Object} Bounds
     * @example
     * function () {
     *   return { x: 0, y: 0, width: 400, height: 300 };
     * }
     */
    var windowPositioner = function () {
        // width & height
        var width = screen.availWidth <= 1024 ? 800 : 1000;
        var height = screen.availHeight <= 800 ? 700 : Math.max(700, $(document).height());
        // return bounds
        return {
            x: Math.round((screen.width - width) / 2),
            y: Math.round((screen.height - height) / 2) - 25,
            width: width,
            height: height
        };
    };
    
    /**
     * Internal window preprocessor
     * @name ox.api-constructWindow
     * @function
     * @public
     * @param {string} urlPrefix URL path
     * @param {Object} o Options
     * @param {Object} o.data Data passed to the new window
     * @param {Object} o.params Params added to the url as hash (#anchor)
     * @param {function ()} o.success Called when the new window is created
     * @param {function ()} o.closer Custom closer function (called when closing the window)
     * @param {Object} o.bounds Window bounds {@link ox.api-windowPositioner}
     * @param {Object} o.options Window options {@link ox.api.setDefaultWindowOptions}
     * @returns {string} New window GUID
     */
    var constructWindow = function (urlPrefix, o) {
        // set data
        var guid = o.params.guid = that.window.setData(o.data);
        // build url
        var url = urlPrefix + "#" + ox.util.serialize(o.params);
        // open new window
        var opt = {
            guid: guid,
            url: url,
            options: $.extend(o.bounds, o.options),
            success: o.success, 
            closer: o.closer,
            id: o.id
        };
        debug("constructWindow", opt);
        windowFactory(opt);
        return guid;
    };
    
    // process options
    var processOptions = function (d, o) {
        // general defaults
        var defaults = {
            params: {},
            options: {},
            data: {},
            bounds: windowPositioner(),
            success: $.noop,
            closer: $.noop
        };
        // merge general defaults, local default, and specific options
        return $.extend(true, defaults, d || {}, o || {});
    };
    
    // process select options
    var processSelectOptions = function (module, o) {
        var defaultFolder = module === "mail" ? configGetKey("mail.folder.inbox") : configGetKey("folder")[module];
        var opt = $.extend({
            module: module,
            folder: defaultFolder,
            id: undefined,
            success: $.noop
        }, o || {});
        // copy
        opt.folder_id = opt.folder;
        return opt;
    };
    
    var processSelect = function (module, options) {
        // options
        var o = processSelectOptions(module, options);
        // cont
        var cont = function () {
            // change view (always; all users have mail)
            changeView("mail");
            // trigger event
            triggerEvent("OX_Direct_Linking", module, o);
            // call back
            o.success(o);
        };
        // check for folder first
        if (o.folder !== undefined) {
            ox.api.folder.get({
                folder: o.folder,
                success: cont
            });
        } else {
            cont();
        }
    };

    var processHTTPOptions = function (module, defaultParams, options, defaultOptions) {
        // options
        var o = $.extend(defaultOptions || {}, options || { params: {} });
        // params
        o.params = $.extend(defaultParams || {}, o.params || {});
        // set module
        o.module = module;
        // done
        return o;
    };

    // is nested window?
    var isNested = function () {
        // already in core window? (we rely on this simple variable not to mess up SELENIUM tests)
        return window.oxCoreWindow === true ? false : true;
    };
    
    // get core window
    var getCore = function () {
        return isNested() ? (window.opener || window.parent).ox.api.window.core : window;
    };
    
    // helper for setModal
    var modal = false;
    
    // callback once
    var once = (function () {
        
        var callbacks = {};
        
        return function (options) {
            
            // options.success must be a function
            if (ox.util.isFunction(options.success)) {
                
                // get id
                var name, tmp = [], id;
                for (name in options) {
                    if (typeof options[name] === "string" || typeof options[name] === "number") {
                        tmp.push(options[name] + "");
                    }
                }
                id = tmp.join("-");
                
                // first?
                if (callbacks[id] === undefined) {
                    // overwrite options.success
                    var success = options.success;
                    options.success = function () {
                        // bypass arguments to first callback
                        success.apply(window, arguments);
                        // loop over consecutive callbacks
                        var i = 0, list = callbacks[id], $l = list.length;
                        for (; i < $l; i++) {
                            list[i].apply(window, arguments);
                        }
                        // remove queue
                        delete callbacks[id];
                    };
                    // create queue
                    callbacks[id] = [];
                    return false;
                } else {
                    callbacks[id].push(options.success);
                    return true;
                }
            }
        };
    }());
    
    that = {
        /** @lends ox.api */

        /**
         * @name ox.api.event
         * @namespace
         */
        event: {
        
            /**
             * Register for global event
             * @param {string} name Event name
             * @param {function ()} fn Event handler
             * @example
             * ox.api.event.register(ox.api.event.common.ViewChanged, ox.util.inspect);
             */
            register: function (name, fn) {
                register(name, fn);
            },

            /**
             * Unregister for global event
             * @param {string} name Event name
             * @param {function ()} fn Event handler
             * @example
             * ox.api.event.unregister(ox.api.event.common.ViewChanged, ox.util.inspect);
             */
            unregister: function (name, fn) {
                unregister(name, fn);
            },
            
            /**
             * Trigger an event. First parameter is the event name.
             * Any further parameter is passed to the corresponding event handlers.
             * @param {string} name Event name
             */
            trigger: function (name) {
                triggerEvent.apply(window, arguments);
            },
            
            /**
             * A list of common events used throughout the UI
             * @name ox.api.event.common
             * @namespace
             */
            common: {
                /** @type string */
                ViewChanged: "OX_View_Changed",
                /** @type string */
                Refresh: "OX_Refresh",
                /** @type string */
                ConfigurationLoadedComplete: "OX_Configuration_Loaded_Complete",
                /** @type string */
                ConfigurationChanged: "OX_Configuration_Changed",
                /** @type string */
                LanguageChanged: "LanguageChanged",
                /** @type string */
                Logout: "Logout",
                /** @type string */
                SaveObject: "OX_SAVE_OBJECT",
                /** @type string */
                CancelObject: "OX_Cancel_Object",
                /** @type string */
                NewUnreadMail: "OX_New_Unread_Mail",
                /** @type string */
                Ready: "Ready"
            },
            
            dispatcherRegistry: (function () {
                
                /**
                 * @name ox.api.event.DispatcherRegistry
                 */
                var DispatcherRegistry = function () {
                    
                    var dispatchers = [];
                    
                    this.add = function (dispatcher) {
                        // add?
                        if ($.inArray(dispatcher, dispatchers) === -1) {
                            dispatchers.push(dispatcher);
                        }
                    };
                    
                    this.list = function () {
                        return dispatchers;
                    };
                };
                
                // remove ALL handlers on window unload
                var fnUnload = function () {
                    // vars
                    var i = 0, list, $l, dispatcher, type, h, guid;
                    // loop over all dispatchers
                    list = ox.api.event.dispatcherRegistry.list();
                    // add core window dispatchers as well
                    if (isNested()) {
                        // concat
                        list.concat(getCore().ox.api.event.dispatcherRegistry.list());
                    }
                    for ($l = list.length; i < $l; i++) {
                        dispatcher = list[i];
                        // loop over all handlers
                        for (type in dispatcher.handlers) {
                            h = dispatcher.handlers[type];
                            for (guid in h) {
                                if (h[guid].window === window) {
                                    try {
                                        // remove handler
                                        delete h[guid];
                                    } catch (e) {
                                    }
                                }
                            }
                        }
                    }
                };
                
                jQuery(window).unload(fnUnload);
                
                // need this before ox.api is ready, so set local var as well
                return (dispatcherRegistry = new DispatcherRegistry());
                
            }()),
            
            Dispatcher: (function () {
                
                var guid = 1;
                
                var trim = function (type) {
                    // events should be string and lower case
                    return (type + "").toLowerCase().replace(/(^\s+|\s+$)/g, "");
                };
                
                var trimSplit = function (type) {
                    type = trim(type);
                    // split?
                    return type.search(/\s/) > -1 ? type.split(/\s+/) : type;
                };
                
                /**
                 * @name ox.api.event.Dispatcher
                 * @param {Object} target Target object
                 * @class Event Dispatcher
                 */
                Dispatcher = function (target) {
                    
                    this.handlers = {};
                    this.data = {};
                    this.has = false;
                    
                    this.enabled = true;
                    this.paused = false;
                    this.queue = {};
                    
                    this.queueTimer = null;
                    
                    // target is private (to prevent recursion)
                    this.getTarget = function () {
                        return target;
                    };
                    
                    // add to registry
                    dispatcherRegistry.add(this);
                };
                
                // extend
                Dispatcher.prototype = {
                    /** @lends ox.api.event.Dispatcher.prototype */
                        
                    /**
                     * Bind event
                     * @param {string} type Event name
                     * @param {Object} [data] Bound data
                     * @param {function (data, type)} fn Event handler
                     * @param {window} [win] Current DOM window
                     * @param {boolean} [atomic] React on single events or the last one
                     */
                    bind: function (type, data, fn, win, atomic) {
                        
                        // trim
                        type = trimSplit(type);
                        
                        var self = this;
                        
                        // multiple types?
                        if ($.isArray(type)) {
                            $.each(type, function (i, type) {
                                self.bind(type, data, fn);
                            });
                            return;
                        }
                        
                        if (isFunction(data)) {
                            atomic = win;
                            win = fn;
                            fn = data;
                            data = undefined;
                        }
                        
                        // new queue?
                        if (this.handlers[type] === undefined) {
                            this.handlers[type] = {};
                        }
                        
                        var h = this.handlers[type];
                        
                        // add guid
                        if (fn.oxGuid === undefined) {
                            fn.oxGuid = guid++;
                        }
                        
                        // add event once
                        if (h[fn.oxGuid] === undefined) {
                            // add
                            h[fn.oxGuid] = {
                                fn: fn,
                                data: data || {},
                                atomic: atomic !== undefined ? atomic : true,
                                window: win || window 
                            };
                            // heuristic
                            this.has = true;
                        }
                    },
                    
                    /**
                     * Unbind event
                     * @param {string} type Event name
                     * @param {function ()} fn Event handler
                     */
                    unbind: function (type, fn) {
                        
                        // trim
                        type = trimSplit(type);
                        
                        // multiple types?
                        if ($.isArray(trim)) {
                            var self = this;
                            $.each(type, function (i, type) {
                                self.unbind(type, fn);
                            });
                            return;
                        }
                        
                        // get handlers
                        var h = this.handlers[type] || {};
                        
                        
                        // prevent IE from throwing unnecessary errors
                        try {
                            // remove listener
                            delete h[fn.oxGuid];
                        } catch (e) {
                        }
                    },
                    
                    /**
                     * Trigger event
                     * @param {string} type Event name
                     * @param {Object} [data] Event data
                     */
                    trigger: function (type, data) {
                        
                        if (this.has === false || this.enabled === false) {
                            return;
                        }
                        
                        var self = this, id;
                        
                        // trim
                        type = trimSplit(type);
                        
                        // multiple types?
                        if ($.isArray(type)) {
                            $.each(type, function (i, type) {
                                self.trigger(type, data);
                            });
                            return;
                        }
                        
                        // get handlers
                        var h = this.handlers[trim(type)] || {};
                        
                        // call handler
                        var call = function (handler) {
                            // not defined in closed window?
                            if (handler.window.closed === false) {
                                // get data
                                var d = $.extend({}, handler.data, data || {});
                                // execute function
                                handler.fn.call(self.getTarget(), d, type);
                            } else {
                                // remove handler
                                self.unbind(type, handler.fn);
                            }
                        };
                        
                        // process handler
                        var process = function (handler) {
                            if (self.paused === false && handler.atomic === true) {
                                // call
                                call(handler);
                            } else {
                                // enqueue (latest call per type)
                                var guid = handler.fn.oxGuid;
                                // clear?
                                if (self.queue[guid]) {
                                    clearTimeout(self.queue[guid]);
                                }
                                // add new timeout
                                self.queue[guid] = setTimeout(function () {
                                    // call
                                    call(handler);
                                }, 10);
                            }
                        };
                        
                        // loop
                        for (id in h) {
                            process(h[id]);
                        }
                    },
                    
                    /**
                     * List all event handlers
                     * @param {string} [type] List only events of given type
                     * @returns {Object} Bound handlers
                     */
                    list: function (type) {
                        return type === undefined ? this.handlers : this.handlers[type];
                    },
                    
                    /**
                     * Get the number of bound handlers
                     * @return {number} Number of bound handlers
                     */
                    numHandlers: function () {
                        var i = 0, id;
                        for (id in this.handlers) {
                            i++;
                        }
                        return i;
                    },
                    
                    /**
                     * Disable dispatcher
                     */
                    disable: function () {
                        this.enabled = false;
                    },

                    /**
                     * Enable dispatcher
                     */
                    enable: function () {
                        this.enabled = true;
                    },
                    
                    pause: function () {
                        //this.paused = true;
                    },
                    
                    resume: function () {
//                        // paused?
//                        if (this.paused === true) {
//                            // process queue
//                            var type, item;
//                            for (type in this.queue) {
//                                item = this.queue[type];
//                                item.fn.call(this.target, item.data, item.type);
//                            }
//                            this.queue = {};
//                            this.paused = false;
//                        }
                    }
                };
                
                return Dispatcher;
                
            }())
        },
        
        /**
         * @name ox.api.config
         * @namespace
         */
        config: (function () {
            
            return {
                /** @lends ox.api.config */
                
                /**
                 * Get configuration value
                 * @param {string} path Configuration path
                 * @param {Object} [defaultValue] Default value to be returned if the path does not exist 
                 * @returns {Object} Value
                 * @example
                 * // get team view zoom level
                 * ox.api.config.get("gui.calendar.teamview.zoom");
                 * // use default value if the configuration does not contain the given path
                 * ox.api.config.get("gui.calendar.teamview.zoom", 100);
                 */
                get: function (path, defaultValue) {
                    if (!path) { // undefined, null, ""
                        return config;
                    } else {
                        if (defaultValue === undefined) {
                            return configGetKey(path);
                        } else {
                            return this.contains(path) ? configGetKey(path) : defaultValue;
                        }
                    }
                },
                
                /**
                 * Set configuration value
                 * @param {string} path Configuration path
                 * @param {Object} value Value
                 * @example
                 * // set "save configuration after logout" to "true"
                 * ox.api.config.set("gui.global.save", 1);
                 */
                set: function (path, value) {
                    if (path) {
                        configSetKey(path, value);
                    }
                },
                
                /**
                 * Remove configuration value
                 * @param {string} path Configuration path
                 * @example
                 * // remove config parameter "save configuration after logout"
                 * ox.api.config.remove("gui.global.save");
                 */
                remove: function (path) {
                    if (path) {
                        configRemoveKey(path);
                    }
                },
                
                /**
                 * Check if configuration contains a specific path
                 * @param {string} path Configuration path
                 * @returns {boolean} True/False
                 * @example
                 * ox.api.config.contains("gui.global.save");
                 */
                contains: function (path) {
                    return configContainsKey(path);
                },
                
                /**
                 * Save configuration
                 * @param {Object} [options] Options
                 * @param {Object} [options.force] Force save
                 * @param {Object} [options.silent] If true no message will be shown
                 * @param {function (data)} [options.success] Success handler
                 * @param {function (error)} [options.error] Error handler
                 * @example
                 * // save configuration
                 * ox.api.config.save({
                 *   success: function () {
                 *      alert("Configuration saved!");
                 *   }
                 * });
                 */
                save: function (options) {
                    // defaults
                    var opt = $.extend({
                        force: true,
                        silent: true,
                        success: $.noop,
                        error: $.noop
                    }, options || {});
                    // this is done via an event
                    ox.api.event.trigger(
                        "OX_Save_Configuration", opt.force, opt.silent, 
                        opt.success, opt.error
                    );
                },
                
                load: function (path, success) {
                    // path?
                    if (isFunction(path)) {
                        success = path;
                        path = undefined;
                    }
                    // load configuration
                    ox.api.http.GET({
                        module: path !== undefined ? path : "config",
                        appendColumns: false,
                        processData: false,
                        success: function (data) {
                            // only remember full config
                            if (path === undefined) {
                                config = data !== undefined ? data.data : {};
                            }
                            ox.util.call(success, data.data);
                        }
                    });
                }
            };
        }()),

        /**
         * @name ox.api.ui
         * @namespace
         */
        ui: (function () {
            
            // modules container pre-filled with standard ox modules disabled
            var registeredModules = [
                { name: "portal", title: _("Start"), priority: 10, disabled: true },
                { name: "mail", title: _("E-Mail"), priority: 20, disabled: true },
                { name: "calendar", title: _("Calendar"), priority: 30, disabled: true, 
                    icons: [ "img/calendar/dayicons/mod_calendar-" + (new Date()).getUTCDate() + ".png", 
                             "img/calendar/mod_calendar_d.png" ] },
                { name: "contacts", title: _("Contacts"), priority: 40, disabled: true },
                { name: "tasks", title: _("Tasks"), priority: 50, disabled: true },
                { name: "infostore", title: _("Infostore"), priority: 60, disabled: true },
                { name: "configuration", title: _("Configuration"), priority: 70, disabled: true }
            ];
            
            return {
                /** @lends ox.api.ui */
                
                /**
                 * Registers a new module
                 * @param {Object} module object
                 */
                registerModule: function (module) {
                    // register only if not exists
                    if (module && !this.moduleExists(module.name)) {
                        registeredModules.push(module);
                        // resort by priority
                        registeredModules.sort(function (a, b) { 
                            return a.priority - b.priority; 
                        });
                    }
                },
                
                /**
                 * Checks if a module exists under the given ID
                 * @param {string} the module ID
                 * @returns {Boolean}
                 * @example
                 * ox.api.ui.moduleExists(id);
                 */
                moduleExists: function (id) {
                    return this.getModule(id) !== null; 
                },
                
                /**
                 * Lists all registered modules
                 * @returns {Object} A list of registered modules
                 * @example
                 * ox.api.ui.listModules();
                 */
                listModules: function () {
                    return registeredModules;
                },
                
                /**
                 * Returns the module object stored under the given id,
                 * otherwise null will be returned
                 * @param {string} the module ID
                 * @returns {Object} the Modul object
                 */
                getModule: function (id) {
                    var i;
                    for (i in registeredModules) {
                        if (registeredModules[i].name === id) {
                            return registeredModules[i];
                        }
                    }
                    return null;
                },
                
                /**
                 * Set active module
                 * @param {string} name Module name, e.g. mail, calendar etc.
                 * @param {function ()} cont Continuation
                 * @param {string} folder Folder Id
                 * @example
                 * ox.api.ui.setModule("calendar"); // switch to calendar
                 */
                setModule: function (name, cont, folder) {
                    // is module?
                    if (this.isModule(name)) {
                        // options
                        var options = {
                            module: name,
                            success: cont
                        };
                        // folder?
                        if (folder !== undefined) {
                            options.folder = folder;
                        }
                        ox.UIController.setModule(options);
                    }
                },
                
                /**
                 * Checks if given name is valid module
                 * @returns {boolean} True/False
                 */
                isModule: function (name) {
                    // get config ref
                    var module = ox.api.config.get("modules." + name);
                    return name === "configuration" || (module !== null && module.module === true);
                },
                
                /**
                 * Get active module
                 * @returns {string} Active module
                 * @example
                 * // get current module, e.g. mail, calendar, etc.
                 * ox.api.ui.getActiveModule();
                 */
                getActiveModule: function () {
                    return ox.UIController.getModule();
                },
                
                /**
                 * Set active folder
                 * @param {string} folder Folder Id
                 * @param {function ()} [cont] Continuation
                 * @param {boolean} scrollTo Scroll to folder item
                 */
                setFolder: function (folder, cont, scrollTo) {
                    
                    // get folder first
                    ox.api.folder.get({
                        folder: folder,
                        success: function (data) {
                            // options
                            var options = {
                                folder: folder,
                                success: cont
                            };
                            // switch module?
                            if (data.module !== activemodule) {
                                options.module = data.module;
                            } else {
                                // speak to new UIController
                                ox.UIController.setFolder(options);
                            }
                        }
                    });
                },
                
                /**
                 * Get active folder
                 * @returns {string} Active folder
                 */
                getFolder: function () {
                    return ox.UIController.getFolder();
                },
                
                /**
                 * Logout
                 * @param {Object} [options] Logout options
                 * @param {string} [options.location] Location to redirect to after logout
                 * @param {boolean} [options.force] If true no checks are done before, e.g. save configuration
                 * @example
                 * // logout
                 * ox.api.ui.logout({
                 *   location: "http://www.google.de", // landing page after logout
                 *   force: true // don't try to save configuration before logout
                 * });
                 */
                logout: function (options) {
                    // defaults
                    var opt = $.extend({
                        location: logout_location.format(),
                        force: false
                    }, options || {});
                    // api call
                    var processLogout = function () {
                        ox.api.http.GET({
                            module: "login",
                            params: {
                                action: "logout"
                            },
                            // on success or error
                            complete: function (data) {
                                // remove unload handlers
                                window.onbeforeunload = null;
                                $(window).unbind("beforeunload");
                                // redirect
                                window.setTimeout(function () { 
                                    window.location.replace(opt.location); 
                                }, 0);
                            }
                        });
                    };
                    // force logout?
                    if (opt.force === true) {
                        // go!
                        processLogout();
                    } else {
                        // check things first
                        var save = ox.api.config.get("gui.global.save", 1);
                        // save configuration before every logout?
                        if (save === 0) {
                            // no!
                            processLogout();
                        } else {
                            // yes!
                            // replace with 1 (might contain deprecated values)
                            ox.api.config.set("gui.global.save", 1);
                            // save configuration
                            ox.api.config.save({
                                success: function (data) {
                                    // logout
                                    processLogout();
                                },
                                error: function (error) {
                                    // could not save configuration?
                                    if (error !== null && error.code === "SES-0203") {
                                        // session expired, so...
                                        processLogout();
                                    } else {
                                        var msg = error ? "\n\n" + formatError(error) : "";
                                        newConfirm(
                                            _("Server Error:"),
                                            _("Your configuration settings could not be saved. All your changes will be lost. Do you really want to continue?") + msg, /*i18n*/ 
                                            AlertPopup.YESNO, null, null, 
                                            processLogout, // yes 
                                            $.noop // no
                                        );
                                    }
                                }
                            });
                        }
                    }
                }
            };
        }()),
        
        /**
         * @name ox.api.window
         * @namespace
         */
        window: (function () {
            
            // globally unique identifier
            var GUID = 1;
            
            var newGUID = function () {
                return "OX." + (GUID++);
            };
            
            var getGUID = function (obj) {
                if (typeof obj === "string") {
                    return obj === "OX.#" ? "OX." + (GUID - 1) : obj;
                } else {
                    return obj ? obj.guid : undefined;
                }
            };
            
            // window registry
            var windows = { "OX.0": { window: window } };
            // window data
            var windowData = {};
            // is embedded
            var isEmbedded = false;
            
            return {
                /** @lends ox.api.window */
                
                /**
                 * Returns true if current window is a nested window.
                 * @name ox.api.window.isNested
                 * @type Bool
                 */
                isNested: isNested(),
                
                isEmbedded: function () {
                    return isEmbedded;
                },
                
                /**
                 * Reference to topmost core window
                 * @name ox.api.window.core
                 * @type {Window}
                 */
                core: getCore(),
                
                /**
                 * Sets a new window factory consisting of an opening and a closing function. See {@link ox.api-windowFactory}
                 * @param opener {function ()} The opening function
                 * @param closer {function ()} The closing function
                 * @param selector {function ()} The selector/focus function
                 * @param embedded {Boolean} true if window factory uses iframes instead of popup windows (useful for testing)
                 * @example
                 * ox.api.setWindowFactory(
                 *   // opener
                 *   function (opt) {
                 *     // open
                 *     var win = window.open(opt, "", ox.api.serializeWindowOptions(opt.options));
                 *     // register window
                 *     ox.api.registerWindow(opt.guid, win);
                 *     // set guid
                 *     win.guid = opt.guid;
                 *     // continuation
                 *     opt.success(win, opt.guid);
                 *   },
                 *   // closer
                 *   function (obj) {
                 *     // call native function "close"
                 *     obj.window.close();
                 *   }
                 * );
                 */
                setFactory: function (opener, closer, selector, embedded) {
                    if (opener !== undefined && closer !== undefined && selector !== undefined) {
                        windowFactory = opener;
                        windowCloser = closer;
                        windowSelector = selector;
                        isEmbedded = !!embedded;
                    } else {
                        console.error("setFactory(opener, closer, selector): One function is missing");
                    }
                },
                
                /**
                 * Reset window factory to defaults
                 */
                resetFactory: function () {
                    windowFactory = defaultWindowFactory;
                    windowCloser = defaultWindowCloser;
                    windowSelector = defaultWindowSelector;
                    isEmbedded = false;
                },
                
                /**
                 * @description Set a window positioner
                 * @param {function ()} fn A function that returns the window bounds
                 * @see ox.api-windowPositioner
                 */
                setPositioner: function (fn) {
                    windowPositioner = fn;
                },
                
                /**
                 * Define new window defaults
                 * @param {Object} options Default options
                 * @example
                 * ox.api.setDefaultWindowOptions({
                 *   resizable: "yes",
                 *   menubar: "no",
                 *   toolbar: "no",
                 *   status: "no",
                 *   location: "yes",
                 *   scrollbars: "no"
                 * });
                 */
                setDefaultOptions: function (options) {
                    defaultWindowOptions = options;
                },
                
                /**
                 * Serialize window options
                 * @param {Object} o Window options (e.g. width, height, scrollbars etc.)
                 * @returns {string} Serialized string
                 */
                serializeOptions: function (o) {
                    // merge (take care not to extend the defaults permanently)
                    var opt = $.extend({}, defaultWindowOptions, o || {});
                    // copy bounds
                    if (o) {
                        opt.width = o.width;
                        opt.height = o.height;
                        opt.left = o.x;
                        opt.top = o.y;
                    }
                    // serialize
                    return ox.util.serialize(opt, ",");
                },
                
                /**
                 * Register a new window.
                 * @param {string} guid The GUID of the window
                 * @param {Window} window The new window object
                 * @param {Object} options Further information to be stored (extends internal registry object)
                 * @returns {Object} The internal registry object
                 */
                register: function (guid, window, options) {
                    windows[guid] = $.extend({ window: window }, options);
                    return windows[guid];
                },
                
                /**
                 * Check if a window is registered under the given GUID
                 * @param {string} guid Window GUID. "OX.#" refers to last window.
                 * @returns {Boolean}
                 */
                exists: function (guid) {
                    return windows[getGUID(guid)] !== undefined;
                },
                
                /**
                 * Get window from internal window registry
                 * @param {string} guid Window GUID. "OX.#" refers to last window.
                 * @returns {Object} Internal registry object
                 * @example
                 * ox.api.window.get("OX.1");
                 */
                get: function (guid) {
                    guid = getGUID(guid);
                    return windows[guid] !== undefined ? windows[guid] : null;
                },
                
                /**
                 * Get all windows from internal window registry
                 * @returns {Object} All open windows
                 * @example
                 * var windows = ox.api.window.getAll();
                 */
                getAll: function () {
                    return windows;
                },
                
                /**
                 * Passes data to a window
                 * @param {Object} data Data
                 * @param {string} [guid] Existing GUID (to update data)
                 * @returns {string} New/Existing GUID
                 * @example
                 * var guid = ox.api.window.setData({ hello: "world" });
                 */
                setData: function (data, guid) {
                    if (data !== undefined) {
                        guid = getGUID(guid);
                        // exists?
                        if (guid === undefined || windowData[guid] === undefined) {
                            // get new GUID
                            guid = newGUID();
                        }
                        debug("setData", guid, data);
                        windowData[guid] = { data: data || {} };
                    }
                    return guid;
                },
                
                /**
                 * Get window-specific data
                 * @param {string|Window} GUID
                 * @returns {Object} Data
                 * @example
                 * var data = ox.api.window.getData("OX.1");
                 * alert(data);
                 */
                getData: function (obj) {
                    // get guid
                    var guid = getGUID(obj);
                    if (guid !== undefined) {
                        // get data
                        if (isNested()) {
                            return this.core.ox.api.window.getData(guid);
                        } else {
                            var d = windowData[guid];
                            debug("getData", guid, d.data, "identifiedBy", obj);
                            return clone(d.data);
                        }
                    } else {
                        console.error("getData: guid is missing in given window!");
                    }
                },
                
                /**
                 * Open a new window using the current window factory
                 * @param {string} path URL Path
                 * @param {Object} [options] Options
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.window.open("test.html", { options: { locationbar: true, top: 100, left: 100, width: 400, height: 300 } });
                 */
                open: function (path, options) {
                    return constructWindow(path, processOptions({}, options));
                },
                
                /**
                 * Close a window
                 * @param {Window|string} obj window or GUID
                 * @param {Boolean} noConfirm If true, the user will not be asked
                 * @example
                 * ox.api.window.close("OX.1");
                 * ox.api.window.close("OX.#"); // close last window
                 */
                close: function (obj, noConfirm) {
                    // get guid
                    var guid = getGUID(obj), data = windows[guid];
                    var ctrl = data.window.windowController;
                    // defined?
                    if (data !== undefined) {
                        // managed?
                        if (ctrl && !noConfirm) {
                            ctrl.close();
                        } else {
                            // destruct?
                            if (ctrl) {
                                ctrl.destruct();
                            }
                            // close!
                            windowCloser(data, guid);
                            data.window = null; // don't leak
                            delete windows[guid];
                            delete windowData[guid];
                        }
                    }
                },
                
                /**
                 * Select (i.e. focus) a window
                 * @param {Window|string} obj window or GUID
                 * @example
                 * ox.api.window.select("OX.1");
                 * ox.api.window.select("OX.#"); // select last window
                 */
                select: function (obj) {
                    // get guid
                    var guid = getGUID(obj);
                    // defined?
                    if (windows[guid] !== undefined) {
                        windowSelector(windows[guid], guid);
                    }
                },
                
                /**
                 * (Re-)select last window
                 * @example
                 * ox.api.window.selectLast();
                 * // same as
                 * ox.api.window.select("OX.#");
                 */
                selectLast: function () {
                    this.select("OX.#");
                },
                
                /**
                 * Returns last GUID
                 * @returns {string} GUID
                 * @example
                 * var guid = ox.api.window.getLastGUID();
                 */
                getLastGUID: function () {
                    return "OX." + (GUID - 1);
                },
                
                /**
                 * Returns next GUID
                 * @returns {string} GUID
                 */
                getNextGUID: function () {
                    return "OX." + (GUID);
                },
                
                /**
                 * Returns number of windows
                 */
                count: function () {
                    var i = 0, id;
                    for (id in windows) {
                        i++;
                    }
                    return i;
                }
            };
        }()),
        
        /**
         * Switch to modal mode
         * @param {Boolean} flag On/Off
         */
        setModal: function (flag) {
            // copy nodes from allnodes hash
            showNode("modal-dialog-decorator");
            showNode("modal-dialog");
            // show/hide?
            if (flag) {
                // show
                $("#modal-dialog-decorator").show();
                $("#modal-dialog").show();
            } else {
                // hide
                $("#modal-dialog-decorator").hide();
                $("#modal-dialog").hide();
            }
            // remember
            modal = !!flag;
        },
        
        isModal: function () {
            return modal;
        },
        
        /**
         * @name ox.api.mail
         * @namespace
         */
        mail: (function () {
        
            var open = function (options) {
                // options
                var opt = processOptions({
                    params: {
                        folder: activefolder === undefined ? "default0/INBOX" : activefolder,
                        id: undefined,
                        account: undefined,
                        noimg: !corewindow.loadblockedimages,
                        action: ""
                    },
                    message: undefined,
                    closer: function (opt) {
                        that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                    }
                }, options);
                var p = opt.params;
                // message object?
                if (opt.message !== undefined) {
                    p.folder = opt.message.folder_id;
                    p.id = opt.message.id;
                }
                // fix account
                if (p.account === undefined) {
                    p.account = corewindow.getMailAccountIdByFolder(p.folder);
                }
                if ((p.id + "").match(/^default/)) {
                    p.account = corewindow.getMailAccountIdByFolder(p.id);
                }
                // construct
                return constructWindow("newMail.html", opt);
            };
            
            return {
                /** @lends ox.api.mail */
                
                /**
                 * Select an email (changes view & folder)
                 * @param {Object} options Options
                 * @param {string} options.id Id
                 * @param {string} [options.folder] Folder
                 * @param {function ()} [options.success] Callback
                 * @example
                 * ox.api.mail.select({ id: "4451" });
                 * ox.api.mail.select({ id: "4451", folder: "default0/INBOX" });
                 */
                select: function (options) {
                    processSelect("mail", options);
                },
                
                /**
                 * Generic open function
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 */
                open: function (options) {
                    return open(options);
                },

                /**
                 * Open compose dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog 
                 * ox.api.mail.compose();
                 * 
                 * // open dialog with subject set
                 * ox.api.mail.compose({ data: { subject: "Hello World" } });
                 * 
                 * // open dialog with subject, recipients, and mail text set
                 * ox.api.mail.compose({
                 *   data: {
                 *     to: [["Max Mustermann", "max.mustermann@example.org"], ["John Doe", "john.doe@exmaple.org"]],
                 *     subject: "Meeting Minutes",
                 *     priority: 2,
                 *     mailtext: "Hi Max and John,&lt;br/>&lt;br/>enclosed you'll find the meeting minutes from this morning.&lt;br/>&lt;br/>Cheers,&lt;br/>Lila", 
                 *     disp_notification_to: true
                 *   }
                 * });
                 */
                compose: function (options) {
                    return open($.extend(true, options || {}, { params: { action: "" }}));
                },

                /**
                 * Open "forward" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.forward({ params: { id: 4415 }});
                 */
                forward: function (options) {
                    return open($.extend(true, options || {}, { params: { action: "forward" }}));
                },

                /**
                 * Open "reply" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.reply({ params: { id: 4415 }});
                 */
                reply: function (options) {
                    return open($.extend(true, options || {}, { params: { action: "reply" }}));
                },

                /**
                 * Open "replyAll" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.replyAll({ params: { id: 4415 }});
                 */
                replyAll: function (options) {
                    return open($.extend(true, options || {}, { params: { action: "replyAll" }}));
                },

                /**
                 * Open "edit draft" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.draft({ params: { id: 4415 }});
                 */
                draft: function (options) {
                    return open($.extend(true, options || {}, { params: { action: "draft" }}));
                },

                /**
                 * Open detail view
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.view({params: { id: 4415 }});
                 * ox.api.mail.view({params: { id: 4415, folder: "default0/INBOX" }});
                 */
                view: function (options) {
                    // options
                    var opt = processOptions({
                        params: {
                            id: undefined,
                            folder: "default0/INBOX",
                            sid: undefined
                        },
                        closer: function (opt) {
                            that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                        }
                    }, options);
                    // set type
                    // opt.params.type = "isNestedMessage";
                    // construct
                    return constructWindow("detailMail.html", opt);
                },
                
                /**
                 * Open source view
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.mail.showSource({ params: { id: 4415 }});
                 */
                showSource: function (options) {
                    // options
                    var opt = processOptions({
                        params: {
                            triggerevent: "OX_Popup_Cb",
                            folder: "default0/INBOX"
                        },
                        mail: undefined,
                        options: { scrollbars: "yes" },
                        closer: function (opt) {
                            that.window.close(that.window.get(opt.guid).window);
                        }
                    }, options);
                    // handler
                    var handler = function (win) {
                        // get mail source
                        json.get(AjaxRoot + "/mail?" + 
                            ox.util.serialize({ action: "get", session: session, folder: opt.params.folder, id: opt.params.id, src: "true" }),
                            null,
                            function (response) {
                            unregister('OX_Popup_Cb', handler);
                            if (win.closed) {
                                return;
                            }
                            if (opt.mail) {
                                OXMailMapping.editObjectsInternal([opt.mail], null, true, [{ flag: 32, bool: true }]);
                            }
                            var text = _("Unable to display E-Mail source."); /*i18n*/
                            if (response.data) {
                                text = response.data.replace(/\r/g, ""); // IE does not like CRs here
                            }
                            // show source
                            var doc = win.document, area;
                            $("body", doc).css({
                                margin: "0px",
                                padding: "10px",
                                backgroundColor: "lightyellow",
                                fontFamily: "Arial, Helvetica, sans-serif",
                                fontSize: "9pt",
                                borderTop: "5px solid #fc0"
                            }).append(
                                $("<div/>", doc).css({ 
                                    position: "absolute",
                                    top: "5px", 
                                    left: "0px", 
                                    right: "10px", 
                                    height: "25px", 
                                    lineHeight: "25px",
                                    textAlign: "right"
                                }).append(
                                    $("<a/>", doc).attr(
                                       { href: "#" }
                                    ).text(
                                       _("Close") + ""
                                    ).bind(
                                        "click",
                                        function (e) {
                                            ox.api.window.close(win.url.guid);
                                        }
                                    )
                                )
                            ).append(
                                $("<div/>", doc).css({
                                    position: "absolute",
                                    top: "30px",
                                    left: "0px",
                                    right: "0px",
                                    bottom: "0px",
                                    padding: "10px"
                                }).append(
                                    area = $("<textarea/>", doc).css({
                                        width: "100%",
                                        height: "100%",
                                        backgroundColor: "lightyellow",
                                        border: "0px none",
                                        whiteSpace: "pre"
                                    }).val(text)
                                )
                            );
                            // focus textarea
                            area.focus();
                        });
                    };
                    // talk via event
                    register('OX_Popup_Cb', handler);
                    // construct
                    return constructWindow("popup_trigger_cb.html", opt);
                },
                
                /**
                 * Open print view
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @example
                 * ox.api.mail.print({ id: 4415 });
                 */
                print: function (options) {
                    // options
                    var opt = processOptions({
                        id: undefined,
                        folder: "default0/INBOX",
                        closer: function (opt) {
                            ox.api.window.close(opt.guid);
                        }
                    }, options);
                    // get mail
                    this.httpGet({
                        params: {
                            id: opt.id,
                            folder: opt.folder,
                            view: "noimg"
                        },
                        success: function (data) {
                            // copy property
                            data.attachments_html_noimage = data.attachments;
                            // print (global function in js/mail.js)
                            mail_printSingleMail(data, opt.closer);
                        }
                    });
                },
                
                /**
                 * Get mail (HTTP API)
                 * @param {Object} [options] Options
                 */
                httpGet: function (options) {
                    // options
                    var opt = options || { params: {} };
                    // prepare options
                    ox.api.http.GET({
                        module: "mail",
                        params: {
                            action: "get",
                            id: opt.params.id,
                            folder: opt.params.folder || "default0/INBOX",
                            view: undefined
                        },
                        success: opt.success || $.noop,
                        error: opt.error || $.noop,
                        appendColumns: false
                    });
                },
                
                get: function (folder, id, success) {
                    // get cache
                    var cache = ox.api.cache.mail, key = folder + "." + id;
                    // cache miss?
                    if (!cache.contains(key)) {
                        // cache miss!
                        ox.api.http.GET({
                            module: "mail",
                            params: { 
                                action: "get",
                                folder: folder,
                                id: id
                            },
                            success: function (data, timestamp) {
                                // add to cache
                                cache.add(data, timestamp);
                                ox.util.call(success, data);
                            }
                        });
                    } else {
                        var data = cache.get(folder);
                        ox.util.call(success, data);
                        return data;
                    }
                },
                
                all: function (folder, options) {
                    // options
                    var opt = options || { params: {} };
                    // defaults
                    opt.params = $.extend({
                        action: "all",
                        folder: folder,
                        columns: "600,601",
                        sort: "610",
                        order: "desc"
                    }, opt.params || {});
                    // get cache
                    var cache = ox.api.cache.mailFolder;
                    // cache key
                    var key = folder + ".(" + opt.params.columns + ")." + opt.params.sort + "." + opt.params.order;
                    // cache miss?
                    if (!cache.contains(key)) {
                        // cache miss!
                        ox.api.http.GET({
                            module: "mail",
                            params: opt.params,
                            appendColumns: false,
                            success: function (data, timestamp) {
                                // add to mailFolder cache?
                                if (opt.params.columns.search(/600/) > -1) {
                                    cache.add(key, data);
                                }
                                // add to mail cache?
                                if (opt.params.columns === ox.api.http.getAllColumns("mail").join(",")) {
                                    // cache only for default columns
                                    ox.api.cache.mail.addArray(data, timestamp);
                                }
                                ox.util.call(opt.success, data);
                            }
                        });
                    } else {
                        var data = cache.get(key);
                        ox.util.call(opt.success, data);
                        return data;
                    }
                },
                
                list: function (options) {
                    // options
                    var opt = options || { data: [], params: {} };
                    // data
                    opt.data = opt.data || [];
                    // default params
                    opt.params = $.extend({
                        action: "list",
                        columns: "600,601,602,603,604,605,606,607"
                    }, opt.params || {});
                    // go!
                    ox.api.http.PUT({
                        module: "mail",
                        data: opt.data,
                        params: opt.params,
                        appendColumns: false,
                        success: function (data, timestamp) {
                            // loop over data to make objects
                            var i = 0, list = data.data, $i = list.length, tmp = [];
                            var columns = opt.params.columns.split(/,/);
                            var make = ox.api.http.makeObject;
                            for (; i < $i; i++) {
                                tmp.push(make(list[i], "mail", columns));
                            }
                            // continue
                            ox.util.call(opt.success, tmp);
                        }
                    });
                }
            };
        }()),
        
        /**
         * @namespace
         * @name ox.api.calendar
         */
        calendar: (function () {
            
            var fix = function (o) {
                var p = o.params;
                p.folder_id = p.folder_id !== undefined ? o.folder_id : p.folder;
                delete p.folder;
                // recurrence position set?
                if (p.recurrence_position !== undefined && p.recurrence_position > 0) {
                    p.singleappointment = "yes";
                }
                return o;
            };
            
            var open = function (options) {
                // options
                var opt = processOptions({
                    params: {
                        modul: "new",
                        folder: config.folder.calendar,
                        view: currentCalendarView,
                        folderOwner: undefined,
                        start_date: new Date().getTime(),
                        end_date: "",
                        full_time: false,
                        id: undefined
                    },
                    closer: function (opt) {
                        that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                    }
                }, options);
                // construct
                return constructWindow("newAppointment.html", fix(opt));
            };
            
            return {
                /** @lends ox.api.calendar */
                
                /**
                 * Select an appointment
                 * @param {Object} options Options
                 * @param {string} options.id Id
                 * @param {string} [options.folder] Folder
                 * @param {function ()} [options.success] Callback
                 * @example
                 * ox.api.calendar.select({ id: "158402" });
                 */
                select: function (options) {
                    processSelect("calendar", options);
                },
                
                /**
                 * Open compose dialog. Fields are described <a href="http://oxpedia.org/index.php?title=HTTP_API#DetailedTaskAndAppointmentData">here</a>.
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @param {Object} [options.data] Data. Prefilled fields.
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog
                 * ox.api.calendar.compose();
                 * 
                 * // open dialog with fields set
                 * ox.api.calendar.compose({
                 *   data: {
                 *     title: "Meeting",
                 *     location: "Berlin",
                 *     start_date: 1280919600000, // UTC
                 *     end_date: 1280937600000, // UTC
                 *     private_flag: false,
                 *     shown_as: 3,
                 *     full_time: false,
                 *     categories: "Meeting, Business",
                 *     note: "Please be prepared for the meeting.",
                 *     alarm: 300 // = 300 seconds = 5 minutes
                 *   }
                 * });
                 *
                 * // how to deal with timestamps
                 * ox.api.calendar.compose({
                 *   data: {
                 *     title: "Do not use local time!",
                 *     start_date: Date.UTC(2010, 0, 1, 12, 0, 0), // shows 12:00 PM
                 *     end_date: Date.UTC(2010, 0, 1, 14, 0, 0), // shows 02:00 PM (14:00)
                 *   }
                 * });
                 * 
                 * // open dialog with exemplary success handler
                 * ox.api.calendar.compose({
                 *   data: {
                 *     title: "Using the success handler",
                 *   },
                 *   success: function (win) {
                 *     // focus core window
                 *     window.focus()
                 *     // say hello
                 *     alert("Hello");
                 *     // focus new window via select
                 *     ox.api.window.select(ox.api.window.getLastGUID()); // e.g. "OX.1"
                 *   }
                 * });
                 */
                compose: function (options) {
                    // get next guid
                    var guid = ox.api.window.getNextGUID();
                    // get options
                    var opt = $.extend(true, options || {}, { params: { modul: "new" }});
                    // continuation
                    var cont = function (folder) {
                        // for private or shared folders add owner to participant list
                        if (ox.api.folder.is("private|shared", folder)) {
                            opt.params.folderOwner = folder.created_by;
                        } else {
                            opt.params.folderOwner = configGetKey('identifier');
                        }
                        // go!
                        open(opt);
                    };
                    // get folder data (default is active folder)
                    var id = opt.params.folder !== undefined ? opt.params.folder : (activefolder ? activefolder : config.folder.calendar);
                    // first try
                    ox.api.folder.get({
                        folder: id,
                        success: function (data) {
                            // is calendar?
                            if (data.module === "calendar") {
                                // ok
                                cont(data);
                            } else {
                                // fail?
                                if (opt.params.folder !== undefined) {
                                    // yes
                                    triggerEvent("OX_New_Error", 4, _("Can not create appointment in a folder other than appointment folder")); /*i18n*/
                                } else {
                                    // try default calendar folder
                                    ox.api.folder.get({
                                        folder: config.folder.calendar,
                                        success: cont
                                    });
                                }
                            }
                        }
                    });
                    // return potential GUID
                    return guid;
                },
                
                /**
                 * Open edit dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.calendar.edit({ params: { id: 158302 }}); // edit appointment
                 * ox.api.calendar.edit({ params: { id: 158374, recurrence_position: 0 }}); // edit series
                 * ox.api.calendar.edit({ params: { id: 158374, recurrence_position: 1 }}); // edit exception (1st appointment of series)
                 * ox.api.calendar.edit({ params: { id: 158374, recurrence_position: 5 }}); // edit exception (5th appointment of series)
                 */
                edit: function (options) {
                    return open($.extend(true, options || {}, { params: { modul: "edit" }}));
                },

                /**
                 * Open print dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.calendar.print(); // prints day view (today)
                 * ox.api.calendar.print({ view: "month" }); // prints month view (current month)
                 * ox.api.calendar.print({ view: "week", year: 2010, month: 1 }); // prints month view (January 2010)
                 */
                print: function (options) {
                    
                    // options
                    var opt = processOptions({
                        view: "day",
                        day: activeDay,
                        month: activeMonth + 1,
                        year: activeYear,
                        options: { scrollbars: "yes" },
                        closer: function (opt) {
                            ox.api.window.close(opt.guid);
                        }
                    }, options);
                    // get start date
                    var date = new Date(Date.UTC(opt.year, opt.month - 1, opt.day)), start, days;
                    var self = this;
                    // printer
                    var print = function (template, start, end, numdays) {
                        // handler
                        var success = opt.success;
                        opt.success = function (win, guid) {
                            // get via HTTP API
                            self.httpPrint({
                                params: {
                                    start: start,
                                    end: end,
                                    template: template,
                                    folder: calendar_getAllFoldersAttribute() ? undefined : activefolder,
                                    work_week_day_amount: numdays ? numdays : undefined
                                },
                                success: function (response) {
                                    // still open?
                                    if (win && !window.closed) {
                                        // insert response
                                        win.document.open();
                                        // remove window.print from onload
                                        response = response.replace(/window\.print\(\)/, "");
                                        // firefox likes this
                                        if (ox.browser.Gecko) {
                                            response = response.replace(/<\/html>/, '<script type="text/javascript">window.print();</script>');
                                        }
                                        win.document.write(response);
                                        win.document.close();
                                        // for IE/Safari
                                        if (ox.browser.IE || ox.browser.WebKit) {
                                            setTimeout(function () {
                                                win.print();
                                            }, 0);
                                        }
                                        // continue
                                        success(win, guid);
                                    }
                                }
                            });
                        };
                        // open dummy window
                        return constructWindow("newInfoItemHidden.html", opt);
                    };
                    // process view
                    switch (opt.view) {
                    case "day":
                        start = date.getTime();
                        return print("cp_dayview_table.tmpl", start, start + 864e5, null);
                    case "workweek":
                        start = getDayInSameWeek(date, configGetKey("gui.calendar.workweek.startday")) * 864e5;
                        days = configGetKey("gui.calendar.workweek.countdays");
                        return print("cp_weekview_table.tmpl", start, start + days * 864e5, days);
                    case "week":
                        start = getDayInSameWeek(date, 1) * 864e5; // 1 = Monday
                        return print("cp_weekview_table.tmpl", start, start + 6048e5, null);
                    case "month":
                        return print("cp_monthview_table.tmpl", Date.UTC(opt.year, opt.month - 1, 1), Date.UTC(opt.year, opt.month, 1), null);
                    }
                },
                
                /**
                 * Get calendar object (HTTP API)
                 * @param {Object} options Request options
                 * @param {Object} options.params Request parameters
                 * @param {function (response)} options.success Success handler
                 * @param {function (response)} options.error Error handler
                 * @example
                 * ox.api.calendar.httpGet({ params: { id: 158302 } });
                 * ox.api.calendar.httpGet({ params: { id: 158302 }, success: ox.util.inspect });
                 */
                httpGet: function (options) {
                    ox.api.http.GET(processHTTPOptions("calendar", {
                        action: "get",
                        folder: config.folder.calendar,
                        id: undefined
                    }, options));
                },
                
                /**
                 * Get all calendar objects in a folder (HTTP API)
                 * @param {Object} options Request options#
                 * @example
                 * ox.api.calendar.httpGetAll();
                 * ox.api.calendar.httpGetAll({ folder: 4384 });
                 */
                httpGetAll: function (options) {
                    var now = (new Date()).getTime();
                    ox.api.http.GET(processHTTPOptions("calendar", {
                        action: "all",
                        folder: config.folder.calendar,
                        start: now,
                        end: now + 1000 * 3600 * 24 * 7
                    }, options));
                },
                
                /**
                 * Print calendar via template (HTTP API)
                 */
                httpPrint: function (options) {
                    var now = (new Date()).getTime();
                    ox.api.http.GET(processHTTPOptions("printCalendar", {
                        start: now,
                        end: now + 864e5, // day
                        template: "cp_dayview_table.tmpl", // day
                        folder: undefined, // = all folders
                        work_day_start_time: getWWStartTime(),
                        work_day_end_time: getWWEndTime(),
                        work_week_day_amount: undefined
                    }, options, { 
                        dataType: "text",
                        appendColumns: false
                    }));
                }
            };
            
        }()),
        
        /**
         * @namespace
         * @name ox.api.contact
         */
        contact: (function () {
            
            var open = function (file, options) {
                // options
                var opt = processOptions({
                    params: {
                        modul: "new",
                        currentView: undefined,
                        folder: config.folder.contacts, // = personal folder. 6 = global address book
                        session: session
                    },
                    closer: function (opt) {
                        that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                    }
                }, options);
                // construct
                return constructWindow(file, opt);
            };
            
            return {
                /** @lends ox.api.contact */
                
                /**
                 * Select a contact
                 * @param {Object} options Options
                 * @param {string} options.id Id
                 * @param {string} [options.folder] Folder
                 * @param {function ()} [options.success] Callback
                 * @example
                 * ox.api.contact.select({ id: "93437" });
                 */
                select: function (options) {
                    processSelect("contacts", options);
                },
                
                /**
                 * Open "compose new contact" dialog. . Fields are described <a href="http://oxpedia.org/index.php?title=HTTP_API#DetailedContactData">here</a>.
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog
                 * ox.api.contact.compose();
                 * 
                 * // open dialog with predefined fields
                 * ox.api.contact.compose({
                 *   data: {
                 *     title: "Mr.", first_name: "Max", last_name: "Mustermann",
                 *     company: "Mustermann Inc.", street_business: "Musterstr. 11",
                 *     postal_code_business: "12345", city_business: "Berlin", state_business: "Berlin",
                 *     country_business: "DE", telephone_business1: "+49 1234 5678",
                 *     fax_business: "+49 1234 56990", email1: "max.mustermann@example.org",
                 *     url: "http://www.mustermann.inc", number_of_employees: "6", department: "IT",
                 *     note: "Business Contact, Mustermann Inc.", birthday: 147913200000
                 *   }
                 * });
                 */
                compose: function (options) {
                    return open("newContact.html", $.extend(true, options || {}, { params: { modul: "new" }}));
                },

                /**
                 * Open "compose distribution list" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog
                 * ox.api.contact.composeDistributionList();
                 * 
                 * // open dialog with prefilled fields
                 * ox.api.contact.composeDistributionList({
                 *   data: {
                 *     title: "My List", 
                 *     distribution_list: [
                 *       { display_name: "Max Mustermann", mail: "max.muster@example.org"}, 
                 *       { display_name: "John Doe", mail: "john.doe@example.org" }
                 *     ]
                 *   }
                 * });
                 */
                composeDistributionList: function (options) {
                    return open("newDistributionList.html", $.extend(true, options || {}, { params: { modul: "new" }}));
                },
                
                /**
                 * Open "edit contact" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.contact.edit({ params: { id: 73303 }});
                 * ox.api.contact.edit({ params: { id: 73303, folder: 4385 }}); // private contact in private contact folder (default)
                 * ox.api.contact.edit({ params: { id: 29, folder: 6 }}); // 6 = global address book
                 */
                edit: function (options) {
                    return open("newContact.html", $.extend(true, options || {}, { params: { modul: "edit" }}));
                },
                
                /**
                 * Open "edit distribution list" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.contact.editDistributionList({ params: { id: 115787 }});
                 * ox.api.contact.editDistributionList({ params: { id: 115787, folder: 4385 }});
                 */                
                editDistributionList: function (options) {
                    return open("newDistributionList.html", $.extend(true, options || {}, { params: { modul: "edit" }}));
                },

                /**
                 * Open dialog to create new contact based on existing contact
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.contact.duplicate({ params: { id: 29, folder: 6 }}); // 6 = global address book
                 */                
                duplicate: function (options) {
                    return open("newContact.html", $.extend(true, options || {}, { params: { modul: "duplicate" }}));
                },

                /**
                 * Open dialog to create new distribution list based on existing list
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.contact.duplicateDistributionList({ params: { id: 115787 }});
                 */
                duplicateDistributionList: function (options) {
                    return open("newDistributionList.html", $.extend(true, options || {}, { params: { modul: "duplicate" }}));
                },
                
                /**
                 * Search contacts by their email address
                 * @param {Array} list A list of strings
                 * @param {function ()} cont Success handler
                 */
                searchByMail: (function () {
                    
                    var addressMap = {}, contacts = {};
                    
                    return function (opt) {
                        // get columns
                        var columns = opt.columns || ox.api.http.getAllColumns("contacts");
                        var list = opt.data || [];
                        // pause
                        ox.api.http.pause();
                        // local success
                        var success = function (responses) {
                            // loop over responses
                            var i = 0, data = responses.data, $i = data.length, r, key, contact;
                            var map = function (address, key) {
                                address = (address || "").toLowerCase();
                                if (address !== "") {
                                    // global address book (6) dominates other folders
                                    if (addressMap[address] === undefined) {
                                        addressMap[address] = [];
                                    }
                                    if (key.folder === 6) {
                                        addressMap[address].unshift(key);
                                    } else {
                                        addressMap[address].push(key);
                                    }
                                }
                            };
                            for (; i < $i; i++) {
                                contact = ox.api.http.makeObject(data[i], "contacts", columns);
                                // get key
                                key = contact.folder_id + "." + contact.id;
                                contacts[key] = contact;
                                // mapping
                                map(contact.email1, key);
                                map(contact.email2, key);
                                map(contact.email3, key);
                            }
                        };
                        // loop over addresses
                        var i = 0, $i = list.length, address, count = 0;
                        for (; i < $i; i++) {
                            address = list[i];
                            // not known?
                            if (addressMap[address] === undefined) {
                                // search for contact
                                ox.api.http.PUT({
                                    module: "contacts",
                                    params: {
                                        action: "search"
                                    },
                                    data: {
                                        email1: address,
                                        email2: address,
                                        email3: address,
                                        orSearch: true//,
                                        //folder: [6] // GAB only
                                    },
                                    success: success
                                });
                                // inc
                                count++;
                            }
                        }
                        // resume
                        ox.api.http.resume(function () {
                            ox.util.call(opt.success, {
                                map: addressMap,
                                contacts: contacts
                            });
                        });
                    };
                }())
            };
            
        }()),
        
        /**
         * @namespace
         * @name ox.api.task
         */
        task: (function () {
            
            var open = function (options) {
                // options
                var opt = processOptions({
                    params: {
                        modul: undefined,
                        folder: configGetKey("folder.tasks"),
                        session: session
                    },
                    closer: function (opt) {
                        that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                    }
                }, options);
                // construct
                return constructWindow("newTask.html", opt);
            };
            
            return {
                /** @lends ox.api.task */
                
                /**
                 * Select a task
                 * @param {Object} options Options
                 * @param {string} options.id Id
                 * @param {string} [options.folder] Folder
                 * @param {function ()} [options.success] Callback
                 * @example
                 * ox.api.task.select({ id: "3810" });
                 */
                select: function (options) {
                    processSelect("tasks", options);
                },
                
                /**
                 * Open "compose new task" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog
                 * ox.api.task.compose();
                 * 
                 * // open dialog with predefined fields
                 * ox.api.task.compose({
                 *   data: {
                 *     title: "Setup new Hardware",
                 *     start_date: 1280919600000,
                 *     end_date: 1280937600000,
                 *     priority: 3,
                 *     note: "We need to setup the new hardware. Be prepared.",
                 *     alarm: 1280937600000
                 *   }
                 * });
                 */
                compose: function (options) {
                    return open($.extend(true, options || {}, { params: { modul: undefined }}));
                },
                
                /**
                 * Open "edit task" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.task.edit({ params: { id: 3616 }});
                 */
                edit: function (options) {
                    return open($.extend(true, options || {}, { params: { modul: "edit" }}));
                },
                
                /**
                 * Open dialog to create new task based on existing task
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.task.duplicate({ params: { id: 3616 }});
                 */
                duplicate: function (options) {
                    return open($.extend(true, options || {}, { params: { modul: "duplicate" }}));
                }
            };
        }()),
        
        /**
         * @namespace
         * @name ox.api.infostore
         */
        infostore: (function () {
            
            var fix = function (options) {
                options.params.edit_id = options.params.id;
                delete options.params.id;
                return options;
            };
            
            var open = function (options) {
                // options
                var opt = processOptions({
                    params: {
                        id: undefined,
                        folder: configGetKey("folder.infostore")
                    },
                    closer: function (opt) {
                        that.window.get(opt.guid).window.triggerEvent("OX_Cancel_Object");
                    }
                }, options);
                // construct
                return constructWindow("newInfoItemMain.html", fix(opt));
            };
            
            return {
                /** @lends ox.api.infostore */
                
                /**
                 * Select an infoitem
                 * @param {Object} options Options
                 * @param {string} options.id Id
                 * @param {string} [options.folder] Folder
                 * @param {function ()} [options.success] Callback
                 * @example
                 * ox.api.infostore.select({ id: "16560" });
                 */
                select: function (options) {
                    processSelect("infostore", options);
                },
                
                /**
                 * Open compose dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * // open simple dialog
                 * ox.api.infostore.compose();
                 * 
                 * // prefill some fields
                 * ox.api.infostore.compose({
                 *   data: {
                 *     title: "Exemplary document",
                 *     description: "Latest documentation", 
                 *     categories: ["Important", "Internal", "Business"]
                 *   }
                 * });
                 */
                compose: function (options) {
                    return open($.extend(true, options || {}, { params: { id: -1 }}));
                },
                
                /**
                 * Open "compose from attachment" dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.infostore.composeFromAttachment({
                 *   params: {
                 *     attachmail: 1,
                 *     filename: "test.doc",
                 *     mailid: 4711,
                 *     mailfolder: "default0/INBOX"
                 *   },
                 *   success: function (win) {
                 *     // do something on success
                 *   }
                 * });
                 */
                composeFromAttachment: function (options) {
                    return open($.extend(true, options || {}, { params: { id: -1 }}));
                },
                
                /**
                 * Open edit dialog
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @returns {string} New window GUID
                 * @example
                 * ox.api.infostore.edit({ params: { id: 16560 }});
                 */
                edit: function (options) {
                    return open(options);
                },
                
                /**
                 * Download file
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @example
                 * ox.api.infostore.download({ id: 16449 });
                 */
                download: function (options) {
                    // options
                    var opt = $.extend({
                        id: 0,
                        filename: ""
                    }, options || {});
                    // continuation
                    var cont = function () {
                        // url parts
                        var parts = [AjaxRoot, "infostore", opt.filename];
                        // url params
                        var params = {
                            action: "document",
                            id: opt.id,
                            content_type: "application/octet-stream",
                            content_disposition: "attachment",
                            session: session
                        };
                        // firefox?
                        if (ox.browser.Gecko) {
                            // firefox handles content dispostion properly, so we do not
                            // need to overwrite the content type. By doing this way, the 
                            // user can open OR download the file. Otherwise, users are only
                            // asked to download the file
                            delete params.content_type;
                        }
                        // create url
                        var url = parts.join("/") + "?" + ox.util.serialize(params);
                        // create hidden frame
                        var iframe = $("<iframe/>", { src: url }).css({
                            display: "none",
                            width: "0px",
                            height: "0px"
                        }).one("load", function (e) {
                            // remove frame immediately
                            setTimeout(function () {
                                iframe.remove();
                            }, 10);
                        }).appendTo(document.body);
                    };
                    // missing filename? (just in IE)
                    if (ox.browser.IE && opt.filename === undefined) {
                        // get object via HTTP API
                        ox.api.http.GET({
                            module: "infostore",
                            params: {
                                action: "get",
                                id: opt.id,
                                columns: "702"
                            },
                            success: function (response) {
                                opt.filename = response.filename;
                                cont();
                            }
                        });
                    } else {
                        cont();
                    }
                },
                
                /*
                 * Open/Download behavior of browsers:
                 * 
                 * 1) Chrome (not WebKit) just offers downloads
                 * 2) FF/Safari/IE all behave identically
                 * 3) IE does not like "inline" disposition with content_type 
                 *    "application/octet-stream" (DOCX issue)
                 */
                
                /**
                 * Open file
                 * @param {Object} [options] Options {@link ox.api-constructWindow}
                 * @example
                 * ox.api.infostore.open({ id: 16449 });
                 */
                open: function (options) {
                    // options
                    var opt = $.extend({
                        id: 0,
                        filename: ""
                    }, options || {});
                    // continuation
                    var cont = function () {
                        // url parts
                        var parts = [AjaxRoot, "infostore", opt.filename];
                        // url params
                        var params = {
                            action: "document",
                            id: opt.id,
                            content_disposition: "inline",
                            session: session
                        };
                        // create url
                        var url = parts.join("/") + "?" + ox.util.serialize(params);
                        var options = that.window.serializeOptions({ width: 1000, height: 600 });
                        // open window (not via window factory; just a popup)
                        var w = window.open(url, "", options);
                        if (w && w.moveTo) {
                            w.moveTo((screen.availWidth - 1000) / 2 >> 0, (screen.availHeight - 600) / 2 >> 0);
                        }
                    };
                    // missing filename? (just in IE)
                    if (ox.browser.IE && opt.filename === undefined) {
                        // get object via HTTP API
                        ox.api.http.GET({
                            module: "infostore",
                            params: {
                                action: "get",
                                id: opt.id,
                                columns: "702"
                            },
                            success: function (response) {
                                opt.filename = response.filename;
                                cont();
                            }
                        });
                    } else {
                        cont();
                    }
                }
            };
        }()),
        
        /**
         * @namespace
         * @name ox.api.folder
         */
        folder: (function () {
            
            register("OX_Refresh", function () {
                ox.api.folder.refresh();
            });
            
            // check permission
            var perm = function (nBits, nOffset) {
                return (nBits >> nOffset) & (nOffset >= 28 ? 1 : 127);
            };

            // remember last timestamp
            var TIME = (new Date()).getTime();
            
            var clock = function () {
                TIME = ox.util.now();
            };
            
            // clear folder (used by "clear" and "remove")
            var clear = function (id, cont) {
                // get folder
                ox.api.folder.get({
                    folder: id,
                    success: function (data) {
                        // get module
                        var module = data.module;
                        // list all items
                        if (ox.api[module] && typeof ox.api[module].all === "function") {
                            // list all elements (id only)
                            ox.api[module].all(id, {
                                success: function (data) {
                                    // transform response
                                    var list = $.map(data, function (o, i) {
                                        return o.id;
                                    });
                                    // remove from cache
                                    if (module === "mail") {
                                        ox.api.cache.mail.remove(list);
                                        ox.api.cache.mailFolder.grepRemove(id);
                                    }
                                    // continue
                                    ox.util.call(cont);
                                }
                            });
                        }
                    }
                });
            };
            
            var notExists = {};
            
            var api = {
                /** @lends ox.api.folder */
                
                /**
                 * Get root folders
                 */
                getRootFolders: function (options) {
                    var opt = options || {};
                    opt.folder = "0";
                    this.getSubFolders(opt);
                },
                
                /**
                 * Get subfolders
                 */
                getSubFolders: function (options) {
                    // options
                    var opt = $.extend({
                        folder: "0",
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        all: false,
                        event: true,
                        cache: true,
                        storage: undefined
                    }, options || {});
                    // get cache
                    var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                    // cache miss?
                    if (opt.cache === false || !cache.isComplete(opt.folder)) {
                        // cache miss!
                        ox.api.http.GET({
                            module: "folders",
                            appendColumns: true,
                            params: { 
                                action: "list",
                                parent: opt.folder,
                                all: opt.all === true ? 1 : 0,
                                tree: opt.tree
                            },
                            success: function (data, timestamp) {
                                // fix folder_id bug
                                var i = 0, $l = data.length, obj;
                                for (; i < $l; i++) {
                                    obj = data[i];
                                    obj.folder_id = opt.folder; // ox.util.firstOf(obj.folder_id, obj.folder);
                                }
                                // add to cache
                                cache.removeChildren(opt.folder);
                                cache.addArray(data);
                                cache.setComplete(opt.folder);
                                // trigger
                                if (opt.event === true) {
                                    api.dispatcher.trigger("update *", {
                                        why: "folder.subfolders",
                                        id: opt.folder
                                    });
                                }
                                // cont
                                return ox.util.call(opt.success, data);
                            },
                            error: function (error) {
                                // not exists?
                                if (error) {
                                    switch (error.code) {
                                    case "FLD-0008":
                                    case "FLD-0003":
                                    case "IMAP-1002":
                                        // remember
                                        notExists[opt.folder] = true;
                                        // remove from cache to avoid future errors
                                        cache.remove(opt.folder);
                                        cache.setComplete(opt.folder, false);
                                        break;
                                    }
                                }
                                return ox.util.call(opt.error, error);
                            }
                        });
                    } else {
                        // cache hit
                        var data = cache.children(opt.folder);
                        ox.util.call(opt.success, data);
                        return data;
                    }
                },
                
                /**
                 * Get specific folder
                 */
                get: function (options) {
                    // options
                    var opt = $.extend({
                        folder: "0",
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        event: false,
                        cache: true,
                        storage: undefined
                    }, options || {});
                    // get cache
                    var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                    // cache miss?
                    if (opt.cache === false || !cache.contains(opt.folder)) {
                        // not exists?
                        if (notExists[opt.folder] === true) {
                            // error
                            ox.util.call(opt.error, { code: "FLD-0008", error: "" });
                        } else {
                            // cache miss!
                            ox.api.http.GET({
                                module: "folders",
                                params: { 
                                    action: "get", 
                                    id: opt.folder,
                                    tree: opt.tree
                                },
                                success: function (data, timestamp) {
                                    // add to cache
                                    cache.add(data, opt.cache === false ? undefined : timestamp);
                                    // trigger
                                    if (opt.event === true) {
                                        api.dispatcher.trigger("update *", {
                                            why: "folder.get",
                                            id: opt.folder
                                        });
                                    }
                                    // cont
                                    return ox.util.call(opt.success, data);
                                },
                                error: function (error, status) {
                                    // not exists?
                                    if (error) {
                                        switch (error.code) {
                                        case "FLD-0008":
                                        case "FLD-0003":
                                        case "IMAP-1002":
                                            // remember
                                            notExists[opt.folder] = true;
                                            // remove from cache to avoid future errors
                                            cache.remove(opt.folder);
                                            cache.setComplete(opt.folder, false);
                                            break;
                                        }
                                    }
                                    return ox.util.call(opt.error, error, status);
                                }
                            });
                        }
                    } else {
                        // cache hit (implies we have the parents too)
                        var data = cache.get(opt.folder);
                        ox.util.call(opt.success, data);
                        return data;
                    }
                },
                
                exists: function (options) {
                    // options
                    var opt = $.extend({
                        folder: "0",
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        cache: true,
                        storage: undefined,
                        error: $.noop
                    }, options || {});
                    // wrappers
                    var yes = function () {
                        ox.util.call(opt.success, true);
                    };
                    var no = function (error, status) {
                        if (!error.status && (error.code === "FLD-0008" ||
                                              error.code === "FLD-0003")) {
                            ox.util.call(opt.success, false);
                            return true; // to prevent global error handling
                        }
                        ox.util.call(opt.error, error, status);
                    };
                    // get cache
                    var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                    // cache miss?
                    if (opt.cache === false || !cache.contains(opt.folder)) {
                        // get folder
                        ox.api.folder.get({
                            folder: opt.folder,
                            tree: opt.tree,
                            success: yes,
                            error: no
                        });
                    } else {
                        yes();
                    }
                },
                
                getMultiple: function (options) {
                    // options
                    var opt = $.extend({
                        list: [],
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        cache: true,
                        event: true,
                        storage: undefined
                    }, options || {});
                    // return value
                    var result = {};
                    // is array?
                    if (ox.util.isArray(opt.list)) {
                        // get cache
                        var cache = opt.storage || ox.api.cache["folder" + (opt.tree || 0)];
                        // loop
                        var i = 0, $l = opt.list.length, requests = [], id;
                        var columns = ox.api.http.getAllColumns("folders").join(",");
                        for (; i < $l; i++) {
                            id = opt.list[i] + "";
                            // split into cache hit/miss
                            if (opt.cache === true && cache.contains(id)) {
                                result[id] = cache.get(id);
                            } else if (notExists[id] !== true) {
                                requests.push({
                                    action: "get",
                                    module: "folders",
                                    id: id,
                                    columns: columns,
                                    tree: opt.tree
                                });
                            }
                        }
                        // success handler
                        var cont = function (folders) {
                            // loop over folders
                            var i = 0, $l = folders.length, folder, id;
                            for (; i < $l; i++) {
                                // get folder
                                folder = folders[i];
                                // error?
                                if (folder.error !== undefined) {
                                    notExists[requests[i].id] = true;
                                } else {
                                    id = folder.data.id + "";
                                    if (result[id] === undefined) {
                                        result[id] = folder.data;
                                        // add to cache? (might be missing due to server error, e.g. "folder does not exist)
                                        if (folder.data !== undefined) {
                                            cache.add(folder.data, folder.timestamp);
                                        }
                                    }
                                }
                            }
                            // trigger
                            if (opt.event === true) {
                                api.dispatcher.trigger("update *", {
                                    why: "folder.get.multiple"
                                });
                            }
                            // cont
                            return ox.util.call(opt.success, result);
                        };
                        // http?
                        if (requests.length > 0) {
                            ox.api.http.PUT({
                                module: "multiple",
                                data: requests,
                                appendColumns: false,
                                params: {
                                    "continue": true
                                },
                                success: cont
                            });
                        } else {
                            // full cache hit
                            cont(result);
                        }
                    }
                    return result;
                },
                
                /**
                 * Get parents
                 */
                getParents: function (options) {
                    
                    // options
                    var opt = $.extend({
                        folder: "0",
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        cache: true,
                        event: true,
                        storage: undefined
                    }, options || {});
                    
                    // once!
//                    if (once(opt)) {
//                        return;
//                    }
//TODO
                    
                    // get cache
                    var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                    // all in cache?
                    var data = cache.get(opt.folder), cacheHit = false, tmp = [];
                    // loop
                    while (data) {
                        tmp.push(data);
                        if (data.folder_id === "0") { // is of type string!
                            cacheHit = true;
                            break;
                        }
                        data = cache.get(data.folder_id);
                    }
                    
                    if (opt.cache === true && cacheHit === true) {
                        // complete cache hit
                        ox.util.call(opt.success, tmp);
                    } else {
                        // cache miss!
                        ox.api.http.GET({
                            module: "folders",
                            appendColumns: true,
                            params: {
                                action: "path", 
                                id: opt.folder,
                                tree: opt.tree
                            },
                            success: function (data, timestamp) {
                                // add to cache
                                cache.addArray(data, timestamp);
                                // trigger
                                if (opt.event === true) {
                                    // this does not trigger a "modify"
                                    api.dispatcher.trigger("update *", {
                                        why: "folder.parents",
                                        id: opt.folder
                                    });
                                }
                                // cont
                                return ox.util.call(opt.success, data);
                            },
                            error: function (data) {
                                // cont
                                return ox.util.call(opt.error, data);
                            }
                        });
                    }
                },
                
                getAllVisible: function (type, success) {
                    if (typeof type === "string") {
                        // get cache
                        var tree = ox.api.config.get("modules.folder.tree", 0);
                        var cache = ox.api.cache["folder" + tree];
                        var flat = ox.api.cache.folderFlat;
                        // cache miss?
                        if (!flat.contains(type)) {
                            // cache miss
                            ox.api.http.GET({
                                module: "folders",
                                appendColumns: true,
                                params: { 
                                    tree: tree,
                                    action: "allVisible",
                                    content_type: type
                                },
                                success: function (data, timestamp) {
                                    // make objects
                                    var id;
                                    for (id in data) {
                                        var i = 0, folders = data[id], $l = folders.length;
                                        for (; i < $l; i++) {
                                            // replace by object
                                            folders[i] = ox.api.http.makeObject(folders[i], "folders");
                                            // add to folder cache
                                            cache.add(folders[i], timestamp);
                                        }
                                    }
                                    // add to simple cache
                                    flat.add(type, data, timestamp);
                                    // trigger
                                    if (true) { // TODO: use options
                                        api.dispatcher.trigger("update *", {
                                            why: "folder.allVisible",
                                            type: type
                                        });
                                    }
                                    // call back
                                    return ox.util.call(success, data);
                                }
                            });
                        } else {
                            // cache hit
                            return ox.call(success, flat.get(type));
                        }
                    }
                },
                
                /**
                 * Update folder
                 */
                update: function (options) {
                    // options
                    var opt = $.extend({
                        folder: undefined,
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        data: undefined,
                        event: true,
                        local: false,
                        storage: undefined,
                        filter: ox.util.identity
                    }, options || {});
                    // folder?
                    if (opt.folder !== undefined) {
                        // get folder first (need some of its data)
                        ox.api.folder.get({
                            folder: opt.folder,
                            tree: opt.tree,
                            cache: true,
                            event: false,
                            storage: opt.storage,
                            success: function (folder) {
                                // get cache
                                var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                                // only local changes? (e.g. for updating read/unread counter)
                                if (opt.local === true) {
                                    // merge changes and original data
                                    folder = opt.filter($.extend(folder, opt.data));
                                    // update cache
                                    cache.add(folder);
                                    // trigger
                                    if (opt.event === true) {
                                        api.dispatcher.trigger("update modify *", {
                                            why: "folder.update.local",
                                            id: opt.folder,
                                            folder: folder
                                        });
                                    }
                                    // continuation
                                    ox.util.call(opt.success, folder);
                                    
                                } else if (opt.data !== undefined) {
                                    // update folder on server (unless no changes are given)
                                    ox.api.http.PUT({
                                        module: "folders",
                                        params: {
                                            action: "update", 
                                            id: opt.folder,
                                            tree: opt.tree
                                        },
                                        data: opt.data || {},
                                        appendColumns: false,
                                        processData: false,
                                        success: function (response) {
                                            // (new) id
                                            var id = response.data;
                                            // get updated folder
                                            ox.api.folder.get({
                                                folder: id,
                                                event: false,
                                                cache: false,
                                                storage: opt.storage,
                                                success: function (data) {
                                                    var why, type;
                                                    // id changed? (rename of mail folders behaves like a folder move)
                                                    if (opt.folder !== id) {
                                                        // get proper cache
                                                        var cache = opt.storage || ox.api.cache["folder" + opt.tree];
                                                        // remove children
                                                        cache.removeChildren(opt.folder, true);
                                                        cache.remove(opt.folder);
                                                        cache.removeChildren(opt.folder_id, true);
                                                        // why
                                                        why = "folder.move";
                                                        type = "move modify *";
                                                    } else {
                                                        why = "folder.update";
                                                        type = "update modify *";
                                                    }
                                                    // trigger
                                                    if (opt.event === true) {
                                                        api.dispatcher.trigger(type, {
                                                            why: why,
                                                            id: id,
                                                            oldId: opt.folder,
                                                            folder: data
                                                        });
                                                    }
                                                    // continuation
                                                    return ox.util.call(opt.success, data);
                                                }
                                            });
                                        },
                                        error: function (error) {
                                            // continuation
                                            return ox.util.call(opt.error, error);
                                        }
                                    });
                                }
                            }
                        });
                    }
                },
                
                /**
                 * Get updated folders
                 * @param {String} [folder] If specified, only the children of
                 * the folder with this ID will be returned.
                 * @param {function()} [success] The callback which is called
                 * when the update finishes.
                 */
                refresh: function (options) {
                    // options
                    var opt = $.extend({
                        folders: [],
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        event: true,
                        storage: undefined
                    }, options || {});
                    // params
                    var params = { 
                        action: "updates",
                        timestamp: TIME,
                        tree: opt.tree
                    };
                    // get updates
                    ox.api.http.GET({
                        module: "folders",
                        appendColumns: true,
                        params: params,
                        success: function (data, timestamp) {
                            // get proper cache
                            var cache = opt.storage || ox.api.cache["folder" + opt.tree], i, $l;
                            // add updated and remove deleted folders
                            for (i = 0, $l = data.length; i < $l; i++) {
                                if (typeof data[i] === "object") {
                                    cache.add(data[i], timestamp);
                                } else {
                                    var deleted = cache.get(data[i]);
                                    if (deleted) {
                                        cache.setComplete(deleted.folder_id, false);
                                        cache.remove(deleted);
                                    }
                                }
                            }
                            var values, keys, id;
                            // all or some folders?
                            if (opt.folders.length === 0) {
                                // invalidate all cached remote and unified
                                // mail folders, as well as unsubscribed folders
                                values = cache.values();
                                for (i = 0, $l = values.length; i < $l; i++) {
                                    var f = values[i];
                                    // remove:
                                    // 1) Root folder "1"
                                    // 2) all internal, unsubscribed mail folders
                                    // 3) all external accounts
                                    if (f.id === "1" || /^default[1-9]/.test(f.id) ||
                                        /^default0/.test(f.id) && !f.subscribed && !f.subscr_subflds) {
                                        id = ox.util.firstOf(f.folder_id, f.folder);
                                        cache.setComplete(id, false);
                                        cache.removeChildren(id, true);
                                    }
                                }
                                // update all cached local mail folders
                                values = $.grep(cache.values(), function (folder) {
                                    return folder.id.match(/^default0/) &&
                                        (folder.subscribed === true ||
                                         folder.subscr_subflds === true);
                                });
                                keys = $.map(values, function (folder) {
                                    return folder.id;
                                });
                                // clear flat caches
                                ox.api.cache.folderFlat.remove("calendar");
                                ox.api.cache.folderFlat.remove("contacts");
                                ox.api.cache.folderFlat.remove("tasks");
                            } else {
                                // get only specified folders
                                keys = opt.folders;
                            }
                            // pause
                            ox.api.http.pause();
                            // loop
                            var refreshed = {};
                            var markAsRefreshed = function (i, folder) {
                                refreshed[folder.id] = true;
                            };
                            for (i = 0, $l = keys.length; i < $l; i++) {
                                id = keys[i];
                                // load sub folders only if this has been done before
                                if (cache.isComplete(id)) {
                                    ox.api.folder.getSubFolders({ folder: id, tree: opt.tree, cache: false });
                                    $.each(cache.children(id), markAsRefreshed);
                                }
                            }
                            // loop again for missing single folders
                            for (i = 0; i < $l; i++) {
                                id = keys[i];
                                // not refreshed as child of a folder?
                                if (refreshed[id] === undefined) {
                                    // refresh single folder
                                    ox.api.folder.get({ folder: id, tree: opt.tree, cache: false });
                                }
                            }
                            // resume
                            ox.api.http.resume(function () {
                                // remember time
                                clock();
                                // trigger
                                if (opt.event === true) {
                                    api.dispatcher.trigger("refresh modify *", {
                                        why: "folder.refresh"
                                    });
                                }
                                // continuation
                                ox.util.call(opt.success, {});
                            });
                        }
                    });
                },
                
                /**
                 * Moves a folder
                 * @param {Object} folder The folder to move.
                 * @param {Object} target The new parent of the moved folder.
                 */
                move: function (folder, target) {
                    api.canMove(folder, target, function () {
                        // remember id
                        var id = folder.id;
                        // update folder
                        ox.api.folder.update({
                            folder: id,
                            data: {
                                folder_id: target.id
                            },
                            event: false,
                            success: function (data) {
                                
                                // get proper cache
                                var tree = ox.api.config.get("modules.folder.tree", 0);
                                var cache = ox.api.cache["folder" + tree];
                                // remove children
                                cache.removeChildren(id, true);
                                cache.remove(id);
                                cache.removeChildren(folder.folder_id, true);
                                cache.removeChildren(target.folder_id, true);
                                
                                // reload parent folders
                                var join = new Join(function () {
                                    // trigger global event TODO: of no interest if it works!
                                    // triggerEvent("OX_New_Info", 4, _("Folder was moved successfully") /*i18n*/);
                                    // trigger local event
                                    ox.api.folder.dispatcher.trigger("move modify *", {
                                        why: "folder.move",
                                        oldId: id,
                                        id: data.id,
                                        folder: data,
                                        target: target.id
                                    });
                                });
                                var folderReady = join.add();
                                var targetReady = join.add();
                                ox.api.folder.get({
                                    folder: folder.folder_id,
                                    cache: false,
                                    success: folderReady
                                });
                                ox.api.folder.get({
                                    folder: target.folder_id,
                                    cache: false,
                                    success: targetReady
                                });
                            }
                        });
                    }, $.noop);
                },
                
                /**
                 * Create folder
                 */
                create: function (options) {
                    
                    // options
                    var opt = $.extend({
                        folder: undefined,
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        event: true
                    }, options || {});

                    // default data
                    opt.data = $.extend({
                        module: "mail",
                        title: expectI18n(_("New folder")),
                        subscribed: 1,
                        permissions: undefined
                    }, opt.data || {});
                    
                    var index = 0, nextTitle, cont;
                    
                    // find next title
                    nextTitle = function () {
                        // get sub folders
                        ox.api.folder.getSubFolders({
                            folder: opt.folder,
                            tree: opt.tree,
                            event: false,
                            success: function (data) {
                                // grep titles
                                var titles = $.map(data, function (folder) {
                                    return folder.title;
                                });
                                // count up
                                var title;
                                while (true) {
                                    index++;
                                    title = opt.data.title;
                                    if (index > 1) {
                                        title += " " + index;
                                    }
                                    if ($.inArray(title, titles) === -1) {
                                        cont(title);
                                        break;
                                    }
                                }
                            } 
                        });
                    };
                    
                    // continuation
                    cont = function (title) {
                        // set title
                        opt.data.title = title;
                        // get parent folder to inherit permissions
                        ox.api.folder.get({
                            folder: opt.folder,
                            event: false,
                            success: function (parent) {
                                // inherit rights only if folder isn't a system folder (type 5)
                                if (opt.data.permissions === undefined) {
                                    opt.data.permissions = getNewFolderPerms(parent, parent.type !== 5);
                                }
                                // add admin rights (unless parent folder is shared)
                                if (parent.type !== 3) {
                                    // remove user permissions
                                    var p = opt.data.permissions, i = 0, $l = p.length;
                                    var entity = ox.api.config.get("identifier");
                                    for (; i < $l; i++) {
                                        if (p[i].entity === entity) {
                                            p.splice(i, 1);
                                            break;
                                        }
                                    }
                                    // add admin rights
                                    p.push({
                                        group: false,
                                        bits: 403710016,
                                        entity: entity
                                    });
                                }
                                // go!
                                ox.api.http.PUT({
                                    module: "folders",
                                    params: {
                                        action: "new", 
                                        folder_id: opt.folder,
                                        tree: opt.tree
                                    },
                                    data: opt.data,
                                    appendColumns: false,
                                    success: function (data) {
                                        // pause
                                        ox.api.http.pause();
                                        // refresh parent folder
                                        ox.api.folder.get({
                                            folder: opt.folder,
                                            cache: false,
                                            event: false
                                        });
                                        // make sure new folder is not marked as "not existing"
                                        delete notExists[data.data];
                                        // get new folder
                                        ox.api.folder.get({
                                            folder: data.data,
                                            tree: opt.tree,
                                            cache: false,
                                            event: false,
                                            success: function (data, timestamp) {
                                                // clear flat list caches TODO: smarter
                                                ox.api.cache.folderFlat.remove(opt.data.module);
                                                // trigger
                                                if (opt.event === true) {
                                                    ox.api.folder.dispatcher.trigger("create modify *", {
                                                        why: "folder.create",
                                                        id: data.id,
                                                        folder: data
                                                    });
                                                }
                                                // continue
                                                ox.util.call(opt.success, data);
                                            }
                                        });
                                        // resume
                                        ox.api.http.resume(function () {
                                        });
                                    },
                                    error: function (error) {
                                        if (error.code === "FLD-0012") {
                                            nextTitle();
                                            return true;
                                        } else {
                                            ox.util.call(opt.error, error);
                                        }
                                    }
                                });
                            }
                        });
                    };
                    
                    nextTitle();
                },
                
                /**
                 * Remove folder
                 * @param options
                 * @returns
                 */
                remove: function (options) {
                    
                    var opt = $.extend({
                        id: undefined,
                        event: true,
                        tree: ox.api.config.get("modules.folder.tree", 0)
                    }, options || {});
                    
                    // get folder first. we need its module
                    ox.api.folder.get({
                        folder: opt.id,
                        tree: opt.tree,
                        event: false,
                        success: function (folder) {
                            // go!
                            ox.api.http.PUT({
                                module: "folders",
                                params: {
                                    action: "delete",
                                    tree: opt.tree
                                },
                                data: [opt.id],
                                success: function (data) {
                                    var parent = folder.folder_id || folder.folder;
                                    function updateHasChildren(cache) {
                                        var parentObj = cache.get(parent);
                                        if (parentObj && !cache.children(parent).length) {
                                            parentObj.subfolders = false;
                                        }
                                    }
                                    // remove from all caches TODO: smarter
                                    ox.api.cache.folder0.remove(opt.id);
                                    ox.api.cache.folder0.setComplete(opt.id, false);
                                    ox.api.cache.folder0.setComplete(parent, false);
                                    updateHasChildren(ox.api.cache.folder0);
                                    ox.api.cache.folder1.remove(opt.id);
                                    ox.api.cache.folder1.setComplete(opt.id, false);
                                    ox.api.cache.folder1.setComplete(parent, false);
                                    updateHasChildren(ox.api.cache.folder1);
                                    ox.api.cache.folderFlat.remove(folder.module);
                                    // trigger
                                    api.dispatcher.trigger("remove modify *", {
                                        why: "folder.remove",
                                        id: opt.id,
                                        oldId: opt.id,
                                        folder: folder
                                    });
                                    // continuation
                                    ox.util.call(opt.success, data, folder);
                                },
                                error: function (error) {
                                    ox.util.call(opt.error, error);
                                }
                            });
                        }
                    });
                },
                
                /**
                 * Clear folder
                 */
                clear: function (id, success) {
                    // use internal helper (clears caches only)
                    clear(id, function () {
                        // new clear
                        ox.api.http.PUT({
                            module: "folders",
                            appendColumns: false,
                            params: { 
                                action: "clear",
                                tree: ox.api.config.get("modules.folder.tree", 0)
                            },
                            data: [id],
                            success: function (data, timestamp) {
                                // update caches
                                var caches = ["folder0", "folder1"], i = 0, $i = caches.length;
                                var cache, folder;
                                for (; i < $i; i++) {
                                    // get cache
                                    cache = ox.api.cache[caches[i]];
                                    // update folder attribute
                                    if ((folder = cache.get(id))) {
                                        folder.subfolders = false;
                                    }
                                    // remove sub folders
                                    cache.removeChildren(id, true);
                                }
                                // trigger
                                api.dispatcher.trigger("clear update *", {
                                    why: "folder.clear",
                                    id: id
                                });
                                // continue
                                ox.util.call(success, data);
                            }
                        });
                    });
                },
                
                /**
                 * Checks whether a folder can be moved into another folder.
                 * @param {String} folder The folder to be moved.
                 * @param {String} target The new parent folder of the moved
                 * folder.
                 * @param {function()} success A callback function which is
                 * called if the folder can be moved into the target folder.
                 * @param {function()} failure A callback functino which is
                 * called if the folder can not be moved into the target folder.
                 */
                canMove: function (folder, target, success, failure) {
                    if (folder.folder_id === target.id) {
                        return failure();
                    }
                    // Prevent moving into folder itself
                    if (folder.id === target.id) {
                        return failure();
                    }
                    // Prevent moving shared folders 
                    if (folder.type === 3 || target.type === 3) {
                        return failure();
                    }
                    // Prevent moving system folders
                    if (folder.type === 5) {
                        return failure();
                    }
                    // Prevent moving private folders to other folders than
                    // private folders
                    if (folder.type === 1 && target.type !== 1 && target.id !== 1 && 
                            (configGetKey("modules.folder.tree") !== 1 || target.type !== 7)) {
                        return failure();
                    }
                    // Prevent moving public folders to other folders than
                    // public folders
                    if (folder.type === 2 && target.type !== 2 && !(target.id in { 2: 1, 10: 1, 15: 1 })) {
                        return failure();
                    }
                    // Prevent moving folders to other not allowed modules
                    if (folder.module !== target.module) {
                        if (folder.module === "infostore") {
                            return failure();
                        }
                        var pim = { tasks: 1, calendar: 1, contacts: 1 };
                        if (configGetKey("modules.folder.tree") === 1) {
                            pim.mail = 1;
                        } else {
                            if (folder.module === "mail" && target.module !== "system") {
                                return failure();
                            }
                        }
                        if ((folder.module in pim) && !(target.module in pim) && target.module !== "system") {
                            return failure();
                        }
                    }
                    
                    // Check rights Admin right source folder and create
                    // subfolders in target
                    if (perm(folder.own_rights, 28) !== 1 || perm(target.own_rights, 0) < 4) {
                        return failure();
                    }

                    // Check target is subfolder of folder
                    this.getParents({
                        folder: target.id,
                        success: function (ancestors) {
                            var i;
                            for (i = 0; i < ancestors.length; i++) {
                                if (ancestors[i].id === folder.id) {
                                    return failure();
                                }
                            }
                            success();
                        }
                    });
                },
                
                /**
                 * Can?
                 */
                can: function (action, data) {
                    // check multiple folder?
                    var result = true;
                    if (ox.util.isArray(data)) {
                        // loop over folders
                        var i = 0, $l = data.length;
                        for (; i < $l && result; i++) {
                            // for multiple folders, all folders must satisfy the condition
                            result = result && ox.api.folder.can(action, data[i]);
                        }
                        return result;
                    }
                    // get rights & common flags
                    var rights = data.own_rights;
                    var isSystem = data.standard_folder || this.is("system", data);
                    var isAdmin = perm(rights, 28) === 1;
                    var isMail = data.module === "mail";
                    // switch
                    switch (action) {
                    case "read":
                        // can read?
                        // 256 = read own, 512 = read all, 8192 = admin
                        //return (rights & 256 || rights & 512 || rights & 8192) > 0;
                        return perm(rights, 7) > 0;
                    case "write":
                        // can write?
                        return perm(rights, 0) >= 2;
                    case "rename":
                        // can rename?
                        if (!isAdmin || isSystem) {
                            // missing admin privileges or system folder
                            result = false;
                        } else {
                            if (!isMail) {
                                result = true;
                            } else {
                                // default folder cannot be renamed
                                result = !this.is("defaultfolder", data);
                            }
                        }
                        return result;
                    case "delete":
                        // must be admin; system and default folder cannot be deleted
                        return isAdmin && !isSystem && !this.is("defaultfolder", data);
                    case "import":
                        // import data
                        return (rights & 127) >= 2 && this.is("calendar|contacts|tasks", data);
                    case "export":
                        // export data (not allowed for shared folders)
                        return !this.is("shared", data) && this.is("contacts|calendar", data);
                    case "empty":
                        // empty folder
                        return (rights >> 21 & 127) && this.is("mail", data);
                    case "viewproperties":
                        // view properties
                        return !isMail && !this.is("account", data) && (data.capabilities & 1);
                    default:
                        return false;
                    }
                },
                
                is: function (type, data) {
                    // split?
                    if (type.search(/\|/) > -1) {
                        var types = type.split(/\|/), result = false;
                        var i = 0, $l = types.length;
                        for (; i < $l; i++) {
                            if (this.is(types[i], data)) {
                                result = true;
                                break;
                            }
                        }
                        return result;
                    } else {
                        // is?
                        switch (type) {
                        case "private":
                            return data.type === 1;
                        case "public":
                            return data.type === 2;
                        case "shared":
                            return data.type === 3;
                        case "system":
                            return data.type === 5;
                        case "mail":
                            return data.module === "mail";
                        case "calendar":
                            return data.module === "calendar";
                        case "contacts":
                            return data.module === "contacts";
                        case "tasks":
                            return data.module === "tasks";
                        case "infostore":
                            return data.module === "infostore";
                        case "account":
                            return data.module === "system" && (data.id + "").match(/^default(\d+)?/);
                        case "unifiedmail":
                            var match = (data.id + "").match(/^default(\d+)/);
                            // is account? (unified inbox is not a usual account)
                            return match && !ox.api.cache.account.contains(match[1]);
                        case "external":
                            return (data.id + "").match(/^default[1-9]/) &&
                                !this.is("unifiedmail", data);
                        case "defaultfolder":
                            // get default folder
                            var id, folders = ox.api.config.get("mail.folder");
                            for (id in folders) {
                                if (folders[id] === data.id) {
                                    return true;
                                }
                            }
                            return false;
                        case "published":
                            if (data["com.openexchange.publish.publicationFlag"]) {
                                return true; // published
                            }
                            if (data.permissions.length <= 1) {
                                return false; // not shared
                            }
                            // only shared BY me, not TO me
                            return data.type === 1 || data.type === 7 ||
                                (data.module === "infostore" && data.created_by === ox.api.config.get("identifier"));
                        }
                    }
                },
                
                /**
                 * Get folder path as string
                 * @param {string} folder Folder Id
                 * @param {string} [separator] Separator. Default is "/"
                 * @param {function (string)} Success handler
                 */
                getPathString: function (options) {
                    // options
                    var opt = $.extend({
                        folder: "0",
                        tree: ox.api.config.get("modules.folder.tree", 0),
                        separator: "/"
                    }, options || {});
                    // get folder path
                    this.getParents({
                        folder: opt.folder,
                        tree: opt.tree,
                        success: function (data) {
                            // strip "IPM-Root" (only for tree=1)
                            if (opt.tree === 1) {
                                data.pop();
                            }
                            // transform & call back
                            ox.util.call(opt.success, ox.api.folder.derive("path.string", data, opt.separator));
                        },
                        error: function (data) {
                            // cont
                            return ox.util.call(opt.error, data);
                        }
                    });
                },
                
                /**
                 * Derive folder data
                 */
                derive: function (type, data) {

                    // which type?
                    switch (type) {
                    
                    case "path.string":
                        return (function () {
                            // get titles and join array
                            var separator = arguments[2] !== undefined ? arguments[2] : "/";
                            return ox.api.folder.derive("path.titles", data).join(separator);
                        }());
                        
                    case "path.titles":
                        return (function () {
                            // loop
                            var i = 0, $l = data.length, tmp = [];
                            for (; i < $l; i++) {
                                // add title to array
                                tmp.push(data[i].title);
                            }
                            // reverse array
                            tmp.reverse();
                            return tmp;
                        }());
                        
                    case "stats":
                        return (function (tree) {
                            // figures
                            var total = 0, unread = 0, folders = 0;
                            // loop over cache
                            var cache = ox.api.cache["folder" + tree];
                            var i = 0, keys = cache.keys(), $l = keys.length, folder;
                            for (; i < $l; i++) {
                                folder = cache.get(keys[i]);
                                total += folder.total || 0;
                                unread += folder.unread || 0;
                                folders += 1;
                            }
                            return {
                                total: total,
                                unread: unread,
                                folders: folders
                            };
                        }(data !== undefined ? data : ox.api.config.get("modules.folder.tree")));
                    
                    case "bits":
                        
                        return perm(ox.util.firstOf(data.own_rights, data, 0), arguments[2] || 0);
                    
                    case "permissions":
                        
                        return (function (nBits, nOffset) {
                            // helper
                            var f = function (mask, strings) {
                                nBits = (nBits >> nOffset) & mask;
                                return { string: _(strings[nBits]), bit: nBits };
                            };
                            // which offset?
                            switch (nOffset) {
                            case 0:
                                return f(127, {
                                    0: "None", /*i18n*/
                                    1: "Visible folder", /*i18n*/
                                    2: "Create objects", /*i18n*/
                                    4: "Create subfolders", /*i18n*/
                                    64: "Maximum"/*i18n*/
                                });
                            case 7:
                                return f(127, {
                                    0: "None", /*i18n*/
                                    1: "Read own", /*i18n*/
                                    2: "Read all", /*i18n*/
                                    64: "Maximum" /*i18n*/
                                });
                            case 14:
                                return f(127, {
                                    0: "None", /*i18n*/
                                    1: "Modify own", /*i18n*/
                                    2: "Modify all", /*i18n*/
                                    64: "Maximum" /*i18n*/
                                });
                            case 21:
                                return f(127, {
                                    0: "None", /*i18n*/
                                    1: "Delete own", /*i18n*/
                                    2: "Delete all", /*i18n*/
                                    64: "Maximum" /*i18n*/
                                });
                            case 28:
                            case 29:
                                return f(1, {
                                    0: "No", /*i18n*/
                                    1: "Yes" /*i18n*/
                                });
                            }
                        }(data.own_rights || data || 0, arguments[2] || 0));
                        
                    case "owner":
                        
                        return (function (folder, fn) {
                            // get user data
                            internalCache.getUsers([folder.created_by], function (data) {
                                ox.util.call(fn, data[folder.created_by]);
                            });
                        }(arguments[1], arguments[2]));
                        
                    case "accountId":
                        
                        return (function (folder) {
                            // regex
                            if (String(folder).match(/^default(\d+)?/) !== null) {
                                return parseInt(String(folder).match(/^default(\d+)?/)[1], 10);
                            } else {
                                return undefined;
                            }
                        }(arguments[1]));
                    }
                }
            };
            
            // add dispatcher
            api.dispatcher = !isNested() ? new Dispatcher(api) : null;
            
            return api;
            
        }()),
        
        /**
         * @namespace
         * @name ox.api.user
         */
        user: (function () {
            
            return {
                /** @lends ox.api.user */
                
                /**
                 * Get user
                 */
                get: function (id, success) {
                    // params
                    if (isFunction(id)) {
                        success = id;
                        id = undefined;
                    }
                    if (id === undefined || id === null) {
                        id = ox.api.config.get("identifier");
                    }
                    // get
                    ox.api.http.GET({
                        module: "user",
                        params: { 
                            action: "get",
                            id: id
                        },
                        success: function (data, timestamp) {
                            ox.util.call(success, data);
                        }
                    });
                },
                
                /**
                 * Get multiple users
                 */
                getMultiple: function (list, options) {
                    // options
                    var opt = $.extend({
                        success: $.noop,
                        columns: undefined
                    }, options || {});
                    // get
                    ox.api.http.PUT({
                        module: "user",
                        params: {
                            action: "list",
                            columns: opt.columns ? opt.columns : ox.api.http.getAllColumns("user", true)
                        },
                        data: list,
                        processData: opt.processData === false ? false : true,
                        success: function (data, timestamp) {
                            ox.util.call(opt.success, data);
                        },
                        error: function (error) {
                            ox.util.call(opt.error, error);
                        }
                    });
                }
            };
        }()),
        
        /**
         * @namespace
         * @name ox.api.account
         */
        account: (function () {
            
            var processAccounts = function (data) {
                
                // not array?
                if (!$.isArray(data)) {
                    data = [data];
                }
                
                var separator = ox.api.config.get("modules.mail.defaultseparator", "/");
                
                // processor
                var process = function (account, id, title) {
                    account[id + "_fullname"] = account[id + "_fullname"] || "default" + account.id + separator + (account[id] || title);
                };
                // loop
                var i = 0, $l = data.length, account;
                for (; i < $l; i++) {
                    account = data[i];
                    process(account, "trash", "Trash");
                    process(account, "sent", "Sent");
                    process(account, "drafts", "Drafts");
                    process(account, "spam", "Spam");
                    process(account, "confirmed_spam", "Confirmed Spam");
                    process(account, "confirmed_ham", "Confirmed Ham");
                }
                return data;
            };
            
            return {
                /** @lends ox.api.account */
                
                /**
                 * Get mail account
                 */
                get: function (id, success) {
                    // get cache
                    var cache = ox.api.cache.account;
                    // cache miss?
                    if (!cache.contains(id)) {
                        // cache miss
                        ox.api.http.GET({
                            module: "account",
                            params: { 
                                action: "get",
                                id: id
                            },
                            success: function (data, timestamp) {
                                data = processAccounts(data);
                                cache.add(data, timestamp);
                                ox.util.call(success, data);
                            }
                        });
                    } else {
                        // cache hit
                        ox.call(success, cache.get(id));
                    }
                },
                
                /**
                 * Get all mail accounts
                 */
                all: function (success) {
                    // get cache
                    var cache = ox.api.cache.account;
                    // cache miss?
                    if (!cache.isComplete("all")) {
                        // cache miss
                        ox.api.http.GET({
                            module: "account",
                            appendColumns: true,
                            params: { 
                                action: "all"
                            },
                            success: function (data, timestamp) {
                                data = processAccounts(data);
                                cache.addArray(data, timestamp);
                                cache.setComplete("all");
                                ox.util.call(success, data);
                            }
                        });
                    } else {
                        // cache hit
                        var accounts = cache.values();
                        ox.call(success, accounts);
                        return accounts;
                    }
                },
                
                /**
                 * Create mail account
                 */
                create: function (options) {
                    // options
                    var opt = $.extend({
                        data: {},
                        success: $.noop
                    }, options || {});
                    // go!
                    ox.api.http.PUT({
                        module: "account",
                        appendColumns: false,
                        params: {
                            action: "new"
                        },
                        data: opt.data,
                        success: function (data, timestamp) {
                            // process data
                            data = processAccounts(data.data)[0];
                            // fix property
                            data.pop3_expunge_on_quit = !data.pop3_expunge_on_quit;
                            // add to cache
                            ox.api.cache.account.add(data, timestamp);
                            // additionally, folder "1" has a new child
                            ox.api.cache.folder0.setComplete("1", false);
                            ox.api.cache.folder1.setComplete("1", false);
                            // cont
                            ox.util.call(opt.success, data);
                        }
                    });
                },
                
                /**
                 * Remove mail account
                 */
                remove: function (options) {
                    // options
                    var opt = $.extend({
                        id: undefined,
                        success: $.noop
                    }, options || {});
                    // go!
                    ox.api.http.PUT({
                        module: "account",
                        appendColumns: false,
                        params: { 
                            action: "delete"
                        },
                        data: [parseInt(opt.id, 10)], // must be an array container a number (not a string)
                        success: function (data, timestamp) {
                            // remove from cache
                            ox.api.cache.account.remove(opt.id);
                            // additionally, the children of folder 1 become invalid, since the account has gone
                            ox.api.cache.folder0.setComplete("1", false);
                            ox.api.cache.folder1.setComplete("1", false);
                            // remove folders
                            ox.api.cache.folder0.removeChildren("default" + opt.id, true); // deep
                            ox.api.cache.folder0.remove("default" + opt.id);
                            ox.api.cache.folder1.removeChildren("default" + opt.id, true); // deep
                            ox.api.cache.folder1.remove("default" + opt.id);
                            // cont
                            ox.util.call(opt.success, data);
                        }
                    });
                }
            };
            
        }()),

        /**
         * @namespace
         * @name ox.api.cache
         */
        cache: (function () {
            
            // default key generator
            var defaultKeyGenerator = function (data) {
                if (typeof data === "object" && data) {
                    return (data.folder || data.folder_id) + "." + data.id;
                } else {
                    return "";
                }
            };
            
            /**
             *  @class CacheStorage
             */
            
            var CacheStorage = (function () {
                 
                // native JSON
                var JSON = nativeJSON;
                
                // persistent storage?
                var hasLocalStorage = false;
                try {
                    // supported by browser and explicitly activated?
                    hasLocalStorage = ox.util.getParam("persistence") === "true" && "localStorage" in window && window.localStorage !== null;
                } catch (e) {
                }
                
                return function (name, persistent) {
                    
                    // init fast storage
                    var id = "cache." + (name || "");
                    var fast = {};
                    
                    // use persistent storage?
                    var persist = hasLocalStorage && persistent === true;
                    
                    // copy from persistent storage? (much faster than working on local storage)
                    if (persist) {
                        // loop over all keys
                        var i = 0, $i = localStorage.length;
                        var reg = new RegExp("^cache\\." + name + "\\."), key;
                        for (; i < $i; i++) {
                            // get key by index
                            key = localStorage.key(i);
                            // match?
                            if (reg.test(key)) {
                                fast[key.replace(reg, "")] = JSON.parse(localStorage.getItem(key));
                            }
                        }
                    }
                    
                    this.clear = function () {
                        fast = {};
                        // persistent clear
                        if (persist) {
                            // lazy update
                            setTimeout(function () {
                                // loop over all keys
                                var i = 0, reg = new RegExp("^cache\\." + name + "\\."), key;
                                while (i < localStorage.length) {
                                    // get key by index
                                    key = localStorage.key(i);
                                    // match?
                                    if (reg.test(key)) {
                                        localStorage.removeItem(key);
                                    } else {
                                        i++;
                                    }
                                }
                            }, 0);
                        }
                    };
                    
                    this.get = function (key) {
                        return fast[key + ""];
                    };
                    
                    this.set = function (key, data) {
                        // fast set
                        fast[key + ""] = data;
                        // persistent set
                        if (persist) {
                            // stringify now
                            var str = JSON.stringify(data);
                            // lazy update
                            setTimeout(function () {
                                localStorage.setItem(id + "." + key, str);
                            }, 0);
                        }
                    };
                    
                    this.contains = function (key) {
                        return fast[key + ""] !== undefined;
                    };
                    
                    this.remove = function (key) {
                        // fast remove
                        delete fast[key + ""];
                        // persistent remove
                        if (persist) {
                            // lazy update
                            setTimeout(function () {
                                localStorage.removeItem(id + "." + key);
                            }, 0);
                        }
                    };
                    
                    this.keys = function () {
                        var tmp = [], key;
                        for (key in fast) {
                            tmp.push(key);
                        }
                        return tmp;
                    };
                };
                
            }());
            
            /**
             *  @class Simple Cache
             */
            var SimpleCache = function (name, persistent) {
                
                // private fields
                var index = new CacheStorage(name + ".index", persistent);
                var isComplete = new CacheStorage(name + ".isComplete", persistent);
                
                // dispatcher
                var dispatcher = this.dispatcher = new Dispatcher(this);
                
                // clear cache
                this.clear = function () {
                    index.clear();
                    isComplete.clear();
                    dispatcher.trigger("clear *", {});
                };
                
                this.add = function (key, data, timestamp) {
                    // timestamp
                    timestamp = timestamp !== undefined ? timestamp : ox.util.now();
                    // add/update?
                    if (!index.contains(key) || timestamp >= index.get(key).timestamp) {
                        // type
                        var type = !index.contains(key) ? "add modify *" : "update modify *";
                        // set
                        index.set(key, {
                            data: data,
                            timestamp: timestamp
                        });
                        // trigger
                        dispatcher.trigger(type, {
                            key: key,
                            data: data,
                            timestamp: timestamp
                        });
                    }
                };
                
                // get from cache
                this.get = function (key) {
                    var data = index.get(key);
                    return data !== undefined ? data.data : undefined;
                };
                
                // get timestamp of cached element
                this.time = function (key) {
                    return index.contains(key) ? index.get(key).timestamp : 0;
                };
                
                // private
                var remove = function (key) {
                    if (index.contains(key)) {
                        var o = index.get(key);
                        index.remove(key);
                        dispatcher.trigger("remove modify *", {
                            key: key,
                            data: o.data,
                            timestamp: o.timestamp
                        });
                    }
                };
                
                // remove from cache (key|array of keys)
                this.remove = function (key) {
                    // is array?
                    if ($.isArray(key)) {
                        var i = 0, $l = key.length;
                        for (; i < $l; i++) {
                            remove(key[i]);
                        }
                    } else {
                        remove(key);
                    }
                };
                
                // grep remove
                this.grepRemove = function (pattern) {
                    var i = 0, keys = index.keys(), $i = keys.length;
                    var key, reg = new RegExp(pattern);
                    for (; i < $i; i++) {
                        key = keys[i];
                        if (reg.test(key)) {
                            remove(key);
                        }
                    }
                };
                
                // list keys
                this.keys = function () {
                    return index.keys();
                };

                // grep keys
                this.grepKeys = function (pattern) {
                    var i = 0, keys = index.keys(), $i = keys.length;
                    var tmp = [], key, reg = new RegExp(pattern);
                    for (; i < $i; i++) {
                        key = keys[i];
                        if (reg.test(key)) {
                            tmp.push(key);
                        }
                    }
                    return tmp;
                };
                
                // grep contained keys
                this.grepContains = function (list) {
                    var i = 0, $l = list.length, tmp = [];
                    for (; i < $l; i++) {
                        if (this.contains(list[i])) {
                            tmp.push(list[i]);
                        }
                    }
                    return tmp;
                };

                // list values
                this.values = function () {
                    var i = 0, keys = index.keys(), $i = keys.length;
                    var tmp = [], key;
                    for (; i < $i; i++) {
                        key = keys[i];
                        tmp.push(index.get(key).data);
                    }
                    return tmp;
                };
                
                // get size
                this.size = function () {
                    return index.keys().length;
                };
                
                // contains
                this.contains = function (key) {
                    return index.contains(key);
                };
                
                // explicit cache
                this.setComplete = function (key, flag) {
                    isComplete.set(key, flag === undefined ? true : !!flag);
                };
                
                this.isComplete = function (key) {
                    return isComplete.get(key) === true;
                };
            };
            
            /**
             *  @class Flat Cache
             */
            var FlatCache = function (name, persistent, keyGenerator) {
                
                // inherit
                SimpleCache.call(this, name, persistent);
                
                // key generator
                this.keyGenerator = isFunction(keyGenerator) ? keyGenerator : defaultKeyGenerator;
                
                // add to cache
                var add = this.add;
                this.add = function (data, timestamp) {
                    // get key
                    var key = this.keyGenerator(data) + "";
                    add.call(this, key, data, timestamp);
                    return key;
                };
                
                // add to cache
                this.addArray = function (data, timestamp) {
                    if (ox.util.isArray(data)) {
                        timestamp = timestamp !== undefined ? timestamp : ox.util.now();
                        var i = 0, $l = data.length;
                        for (; i < $l; i++) {
                            this.add(data[i], timestamp);
                        }
                    }
                };
            };
            
            /**
             *  @class Folder Cache
             *  @augments FlatCache
             */
            var FolderCache = function (name, persistent) {
                
                // inherit
                FlatCache.call(this, name, persistent, function (data) {
                    return data.id;
                });
                
                // private
                var children = new CacheStorage(name + ".children", persistent);
                
                // override "add"
                var add = this.add;
                this.add = function (data, timestamp, parent) {
                    // add to cache
                    var key = add.call(this, data, timestamp);
                    // get parent id (to string)
                    var p = ox.util.firstOf(data.folder_id, data.folder) + "";
                    var list = children.get(p) || [];
                    // add/replace
                    var pos = $.inArray(key, list);
                    if (pos === -1) {
                        // add
                        list.push(key);
                    } else {
                        // replace
                        list.splice(pos, 1, key);
                    }
                    children.set(p, list);
                };
                
                var clear = this.clear;
                this.clear = function () {
                    children.clear();
                    clear.call(this);
                };
                
                // super class' remove
                var remove = this.remove;
                
                var removeChild = function (key) {
                    // remove from all children list
                    var data = this.get(key), p, pos, list;
                    if (data !== undefined) {
                        // get parent id (to string)
                        p = ox.util.firstOf(data.folder_id, data.folder) + "";
                        list = children.get(p) || [];
                        // remove from list
                        pos = $.inArray(key, list);
                        if (pos > -1) {
                            list.splice(pos, 1);
                        }
                        children.set(p, list);
                    }
                    // remove its own children
                    this.removeChildren(key);
                    // remove element
                    remove.call(this, key); 
                };
                
                this.remove = function (key) {
                    // is array?
                    if ($.isArray(key)) {
                        var i = 0, $l = key.length;
                        for (; i < $l; i++) {
                            removeChild.call(this, key[i]);
                        }
                    } else {
                        removeChild.call(this, key);
                    }
                };
                
                this.removeChildren = function (parent, deep) {
                    // has children?
                    if (deep === true && children.contains(parent)) {
                        // loop
                        var i = 0, keys = children.get(parent), $l = keys.length;
                        for (; i < $l; i++) {
                            // remove its own children
                            if (keys[i] !== parent) {
                                this.removeChildren(keys[i], true);
                            }
                            // remove element
                            remove.call(this, keys[i]);
                        }
                    }
                    // remove entry
                    children.remove(parent);
                    // mark as incomplete
                    this.setComplete(parent, false);
                };
                
                this.children = function (parent) {
                    var tmp = [];
                    // exists?
                    if (children.contains(parent)) {
                        // loop
                        var i = 0, keys = children.get(parent), $l = keys.length;
                        for (; i < $l; i++) {
                            tmp.push(this.get(keys[i]));
                        }
                    }
                    return tmp;
                };
                
                this.parents = function () {
                    return children.keys();
                };
                
                // helps debugging
                this.inspect = function (key) {
                    return {
                        keys: this.grepKeys(key),
                        children: this.children(key),
                        complete: this.isComplete(key)
                    };
                };
            };
            
            var caches = {
                /** @lends ox.api.cache */
                
                /**
                 * Folder cache (0 = tree0, 1 = tree1)
                 */
                folder0: new FolderCache("folder0", true), // use default key generator
                folder1: new FolderCache("folder1", true), // use default key generator
                subscribe: new FolderCache("", false), // use default key generator
                
                /**
                 * Visible folder cache
                 */
                folderFlat: new SimpleCache("folderFlat", true),
                
                /**
                 * Flat caches
                 */
                mail: new FlatCache("", false),
                mailHeader: new SimpleCache("", false),
                mailFolder: new SimpleCache("", false),
                calendar: new FlatCache("", false),
                contacts: new FlatCache("", false),
                tasks: new FlatCache("", false),
                infostore: new FlatCache("", false),
                
                /**
                 * Account cache
                 */
                account: new FlatCache("account", true, function (data) {
                    // get key
                    return data && data.id !== undefined ? data.id : "";
                })
            };
            
            return !isNested() ? caches : null;
            
        }()),
        
        /**
         * @namespace
         * @name ox.api.http
         */
        http: (function () {
            
            // default columns for each module
            var idMapping = {
                "common" : {
                    "1" : "id",
                    "2" : "created_by",
                    "3" : "modified_by",
                    "4" : "creation_date",
                    "5" : "last_modified",
                    "100" : "categories",
                    "101" : "private_flag",
                    "102" : "color_label",
                    "104" : "number_of_attachments",
                    "20" : "folder_id"
                },
                "mail" : {
                    "102" : "color_label",
                    "600" : "id",
                    "601" : "folder_id",
                    "602" : "attachment",
                    "603" : "from",
                    "604" : "to",
                    "605" : "cc",
                    "606" : "bcc",
                    "607" : "subject",
                    "608" : "size",
                    "609" : "sent_date",
                    "610" : "received_date",
                    "611" : "flags",
                    "612" : "level",
                    "613" : "disp_notification_to",
                    "614" : "priority",
                    "615" : "msgref",
                    "651" : "flag_seen",
                    "652" : "account_name"
                },
                "contacts" : {
                    "500" : "display_name",
                    "501" : "first_name",
                    "502" : "last_name",
                    "503" : "second_name",
                    "504" : "suffix",
                    "505" : "title",
                    "506" : "street_home",
                    "507" : "postal_code_home",
                    "508" : "city_home",
                    "509" : "state_home",
                    "510" : "country_home",
                    "511" : "birthday",
                    "512" : "marital_status",
                    "513" : "number_of_children",
                    "514" : "profession",
                    "515" : "nickname",
                    "516" : "spouse_name",
                    "517" : "anniversary",
                    "518" : "note",
                    "519" : "department",
                    "520" : "position",
                    "521" : "employee_type",
                    "522" : "room_number",
                    "523" : "street_business",
                    "525" : "postal_code_business",
                    "526" : "city_business",
                    "527" : "state_business",
                    "528" : "country_business",
                    "529" : "number_of_employees",
                    "530" : "sales_volume",
                    "531" : "tax_id",
                    "532" : "commercial_register",
                    "533" : "branches",
                    "534" : "business_category",
                    "535" : "info",
                    "536" : "manager_name",
                    "537" : "assistant_name",
                    "538" : "street_other",
                    "539" : "city_other",
                    "540" : "postal_code_other",
                    "541" : "country_other",
                    "542" : "telephone_business1",
                    "543" : "telephone_business2",
                    "544" : "fax_business",
                    "545" : "telephone_callback",
                    "546" : "telephone_car",
                    "547" : "telephone_company",
                    "548" : "telephone_home1",
                    "549" : "telephone_home2",
                    "550" : "fax_home",
                    "551" : "cellular_telephone1",
                    "552" : "cellular_telephone2",
                    "553" : "telephone_other",
                    "554" : "fax_other",
                    "555" : "email1",
                    "556" : "email2",
                    "557" : "email3",
                    "558" : "url",
                    "559" : "telephone_isdn",
                    "560" : "telephone_pager",
                    "561" : "telephone_primary",
                    "562" : "telephone_radio",
                    "563" : "telephone_telex",
                    "564" : "telephone_ttytdd",
                    "565" : "instant_messenger1",
                    "566" : "instant_messenger2",
                    "567" : "telephone_ip",
                    "568" : "telephone_assistant",
                    "569" : "company",
                    //"570" : "image1",
                    "571" : "userfield01",
                    "572" : "userfield02",
                    "573" : "userfield03",
                    "574" : "userfield04",
                    "575" : "userfield05",
                    "576" : "userfield06",
                    "577" : "userfield07",
                    "578" : "userfield08",
                    "579" : "userfield09",
                    "580" : "userfield10",
                    "581" : "userfield11",
                    "582" : "userfield12",
                    "583" : "userfield13",
                    "584" : "userfield14",
                    "585" : "userfield15",
                    "586" : "userfield16",
                    "587" : "userfield17",
                    "588" : "userfield18",
                    "589" : "userfield19",
                    "590" : "userfield20",
                    "592" : "distribution_list",
                    "594" : "number_of_distribution_list",
                    "596" : "contains_image1",
                    "597" : "image_last_modified",
                    "598" : "state_other",
                    "599" : "file_as",
                    "104" : "number_of_attachments",
                    "601" : "image1_content_type",
                    "602" : "mark_as_distributionlist",
                    "605" : "default_address",
                    "524" : "internal_userid",
                    "606" : "image1_url"
                },
                "calendar" : {
                    "200" : "title",
                    "201" : "start_date",
                    "202" : "end_date",
                    "203" : "note",
                    "204" : "alarm",
                    "207" : "recurrence_position",
                    "208" : "recurrence_date_position",
                    "209" : "recurrence_type",
                    "212" : "days",
                    "213" : "days_in_month",
                    "214" : "month",
                    "215" : "interval",
                    "216" : "until",
                    "220" : "participants",
                    "221" : "users",
                    "400" : "location",
                    "401" : "full_time",
                    "402" : "shown_as"
                },
                "infostore" : {
                    "700" : "title",
                    "701" : "url",
                    "702" : "filename",
                    "703" : "file_mimetype",
                    "704" : "file_size",
                    "705" : "version",
                    "706" : "description",
                    "707" : "locked_until",
                    "708" : "file_md5sum",
                    "709" : "version_comment",
                    "710" : "current_version",
                    "711" : "number_of_versions"
                },
                "task" : {
                    "200" : "title",
                    "201" : "start_date",
                    "202" : "end_date",
                    "203" : "note",
                    "204" : "alarm",
                    "209" : "recurrence_type",
                    "212" : "days",
                    "213" : "days_in_month",
                    "214" : "month",
                    "215" : "internal",
                    "216" : "until",
                    "220" : "participants",
                    "221" : "users",
                    "300" : "status",
                    "301" : "percent_completed",
                    "302" : "actual_costs",
                    "303" : "actual_duration",
                    "305" : "billing_information",
                    "307" : "target_costs",
                    "308" : "target_duration",
                    "309" : "priority",
                    "312" : "currency",
                    "313" : "trip_meter",
                    "314" : "companies",
                    "315" : "date_completed"
                },
                "folders" : {
                    "1" : "id",
                    "2" : "created_by",
                    "3" : "modified_by",
                    "4" : "creation_date",
                    "5" : "last_modified",
                    "6" : "last_modified_utc",
                    "20" : "folder_id",
                    "300" : "title",
                    "301" : "module",
                    "302" : "type",
                    "304" : "subfolders",
                    "305" : "own_rights",
                    "306" : "permissions",
                    "307" : "summary",
                    "308" : "standard_folder",
                    "309" : "total",
                    "310" : "new",
                    "311" : "unread",
                    "312" : "deleted",
                    "313" : "capabilities",
                    "314" : "subscribed",
                    "315" : "subscr_subflds",
                    "316" : "standard_folder_type",
                    "3010" : "com.openexchange.publish.publicationFlag",
                    "3020" : "com.openexchange.subscribe.subscriptionFlag",
                    "3030" : "com.openexchange.folderstorage.displayName"
                },
                "user" : {
                    "610" : "aliases",
                    "611" : "timezone",
                    "612" : "locale",
                    "613" : "groups",
                    "614" : "contact_id",
                    "615" : "login_info"
                },
                "account": {
                    "1001": "id",
                    "1002": "login",
                    "1003": "password",
                    "1004": "mail_url",
                    "1005": "transport_url",
                    "1006": "name",
                    "1007": "primary_address",
                    "1008": "spam_handler",
                    "1009": "trash",
                    "1010": "sent",
                    "1011": "drafts",
                    "1012": "spam",
                    "1013": "confirmed_spam",
                    "1014": "confirmed_ham",
                    "1015": "mail_server",
                    "1016": "mail_port",
                    "1017": "mail_protocol",
                    "1018": "mail_secure",
                    "1019": "transport_server",
                    "1020": "transport_port",
                    "1021": "transport_protocol",
                    "1022": "transport_secure",
                    "1023": "transport_login",
                    "1024": "transport_passord",
                    "1025": "unified_inbox_enabled",
                    "1026": "trash_fullname",
                    "1027": "sent_fullname",
                    "1028": "drafts_fullname",
                    "1029": "spam_fullname",
                    "1030": "confirmed_spam_fullname",
                    "1031": "confirmed_ham_fullname",
                    "1032": "pop3_refresh_rate",
                    "1033": "pop3_expunge_on_quit",
                    "1034": "pop3_delete_write_through",
                    "1035": "pop3_storage ",
                    "1036": "pop3_path",
                    "1037": "personal"
                }
            };
            
            // extend with commons (not all modules use common columns, e.g. folders)
            $.extend(idMapping.contacts, idMapping.common);
            $.extend(idMapping.calendar, idMapping.common);
            $.extend(idMapping.infostore, idMapping.common); 
            delete idMapping.infostore["101"]; // not "common" here (exception)
            delete idMapping.infostore["104"];
            $.extend(idMapping.task, idMapping.common);
            $.extend(idMapping.user, idMapping.contacts, idMapping.common);
            
            // get all columns of a module
            var getAllColumns = function (module, join) {
                // get ids
                var ids = idMapping[module];
                // flatten this array
                var tmp = [], column;
                for (column in ids) {
                    tmp.push(column);
                }
                tmp.sort(function (a, b) {
                    return a - b;
                });
                return join === true ? tmp.join(",") : tmp;
            };
            
            // transform arrays to objects
            var makeObject = function (data, module, columns) {
                // primitive types may be mixed with column arrays
                // e. g. deleted objects from action=updates.
                if (typeof data !== "object") {
                    return data;
                }
                // columns set?
                columns = columns !== undefined ? columns : getAllColumns(module);
                // get ids
                var ids = idMapping[module];
                var obj = {}, i = 0, $l = data.length;
                // loop through data
                for (; i < $l; i++) {
                    // get id
                    var id = ids[columns[i]];
                    // extend object
                    obj[id] = data[i];
                }
                return obj;
            };
            
            var processOptions = function (options, type) {
                // defaults
                var o = $.extend({
                    module: "",
                    params: {},
                    data: {},
                    dataType: "json",
                    success: $.noop,
                    error: $.noop,
                    complete: $.noop,
                    appendColumns: type === "GET" ? false : true,
                    appendSession: true,
                    processData: true,
                    cursor: true
                }, options || {});
                // support classic error handling
                var errorCallback = o.error;
                o.error = function (error, xhr) {
                    var handled;
                    if (xhr) {
                        handled = errorCallback(xhr.statusText, xhr.status);
                    } else {
                        handled = errorCallback(error);
                    }
                    if (handled !== true) {
                        if (xhr) {
                            JSON.errorHandler(xhr.statusText, xhr.status);
                        } else {
                            JSON.errorHandler(error);
                        }
                    }
                };
                // prepend root
                o.url = AjaxRoot + "/" + o.module;
                // add session
                if (o.appendSession === true) {
                    o.params.session = session;
                }
                // add columns
                if (o.appendColumns === true && o.params.columns === undefined) {
                    o.params.columns = getAllColumns(o.module).join(",");
                }
                // data & body
                if (type === "GET" || type === "POST") {
                    // GET & POST
                    o.data = o.params;
                } else {
                    // PUT & DELETE
                    o.url += "?" + ox.util.serialize(o.params);
                    o.original = o.data;
                    o.data = JSON.serialize(o.data);
                    o.processData = false;
                    o.contentType = "text/javascript";
                }
                // done
                return o;
            };
            
            var processData = function (data, module, columns) {
                // not array or no columns given?
                if (!$.isArray(data) || !columns) {
                    // typically from "action=get" (already sanitized)
                    return data;
                } else {
                    // POST/PUT - sanitize data
                    var i = 0, $l = data.length, sanitized = [];
                    var columnList = columns.split(",");
                    for (; i < $l; i++) {
                        sanitized.push(makeObject(data[i], module, columnList));
                    }
                    return sanitized;
                }
            };
            
            var processResponse = function (response, o) {
                // process response
                if (response && response.error !== undefined && response.data === undefined) {
                    // server error
                    o.error(response);
                } else {
                    // handle warnings
                    if (response && response.error !== undefined) {
                        ox.UINotifier.warn(formatError(response));
                    }
                    
                    // success
                    if (o.dataType === "json" && o.processData === true) {
                        // variables
                        var data = [], timestamp;
                        // response? (logout e.g. hasn't any)
                        if (response) {
                            // multiple?
                            if (o.module === "multiple") {
                                var i = 0, $l = response.length, tmp;
                                for (; i < $l; i++) {
                                    // time
                                    timestamp = response[i].timestamp !== undefined ? response[i].timestamp : ox.util.now();
                                    // data/error
                                    if (response[i].data !== undefined) {
                                        // data
                                        tmp = processData(response[i].data, o.data[i].module, o.data[i].columns);
                                        data.push({ data: tmp, timestamp: timestamp });
                                        
                                        // handle warnings within multiple
                                        if (response[i].error !== undefined) {
                                            newServerError(response[i]);
                                        }
                                    } else {
                                        // error
                                        data.push({ error: response[i], timestamp: timestamp });
                                    }
                                }
                                o.success(data);
                            } else {
                                data = processData(response.data, o.module, o.params.columns);
                                timestamp = response.timestamp !== undefined ? response.timestamp : ox.util.now();
                                o.success(data, timestamp);
                            }
                        } else {
                            o.success({}, ox.util.now());
                        }
                    } else {
                        // e.g. plain text
                        o.success(response || "");
                    }
                }
            };
            
            // internal queue
            var paused = false;
            var queue = [];
            
            var ajax = function (options, type) {
                // process options
                var o = processOptions(options, type);
                // paused?
                if (paused === true) {
                    queue.push(o);
                    return;
                }
                // trigger event
                if (++JSON.count === 1) {
                    setLoading(true);
                }
                if (o.cursor && ++JSON.cursorCount === 1) {
                    setLoadingCursor(true);
                }
                // ajax
                $.ajax({
                    // type (GET, POST, PUT, ...)
                    type: type,
                    // url
                    url: o.url,
                    // data
                    data: o.data,
                    dataType: o.dataType,
                    contentType: o.contentType || "application/x-www-form-urlencoded",
                    // handler
                    success: function (response) {
                        processResponse(response, o);
                    },
                    error: function (xhr) {
                        o.error({}, xhr);
                    },
                    complete: function (xhr) {
                        if (--JSON.count === 0) {
                            setLoading(false);
                        }
                        if (o.cursor && --JSON.cursorCount === 0) {
                            setLoadingCursor(false);
                        }
                        o.complete({}, xhr);
                    }
                });
            };
            
            return {
                /** @lends ox.api.http */
                
                /**
                 * Send a GET request
                 * @param {Object} options Request options
                 * @param {string} options.module Module, e.g. folder, mail, calendar etc.
                 * @param {Object} options.params URL parameters
                 * @example
                 * ox.api.http.GET({ module: "mail", params: { action: "all", folder: "default0/INBOX" }});
                 */
                GET: function (options) {
                    ajax(options, "GET");
                },
                
                /**
                 * Send a POST request
                 * @param {Object} options Request options
                 * @param {string} options.module Module, e.g. folder, mail, calendar etc.
                 * @param {Object} options.params URL parameters
                 */
                POST: function (options) {
                    ajax(options, "POST");
                },

                /**
                 * Send a PUT request
                 * @param {Object} options Request options
                 * @param {string} options.module Module, e.g. folder, mail, calendar etc.
                 * @param {Object} options.params URL parameters
                 */
                PUT: function (options) {
                    ajax(options, "PUT");
                },
                
                /**
                 * Send a DELETE request
                 * @param {Object} options Request options
                 * @param {string} options.module Module, e.g. folder, mail, calendar etc.
                 * @param {Object} options.params URL parameters
                 */
                DELETE: function (options) {
                    ajax(options, "DELETE");
                },
                
                /**
                 * Get all columns of a module
                 * @param {string} module Module name
                 * @returns {Array} All columns 
                 */
                getAllColumns: getAllColumns,
                
                /**
                 * Transform objects with array-based columns into key-value-based columns
                 * @param {Array} data Data
                 * @param {string} module Module name
                 * @param {Array} columns Columns
                 * @returns {Object} Transformed object
                 */
                makeObject: makeObject,
                
                /**
                 * Collect requests
                 */
                pause: function () {
                    paused = true;
                },
                    
                /**
                 * Resume HTTP API. Send all queued requests as one multiple
                 */
                resume: function (cont, error) {
                    if (paused === true) {
                        // look for nested multiple requests
                        var i = 0, $l = queue.length, req, q = [], tmp, o, size = [];
                        for (; i < $l; i++) {
                            // get request
                            req = queue[i];
                            // multiple?
                            if (req.module === "multiple") {
                                var j = 0, $lj = req.original.length, sub;
                                for (; j < $lj; j++) {
                                    // create proper data structure
                                    sub = req.original[j];
                                    o = {
                                        module: sub.module,
                                        params: sub
                                    };
                                    delete o.params.module;
                                    delete o.params["continue"];
                                    // add handler
                                    o.success = j === 0 ? req.success : $.noop;
                                    o.error = j === 0 ? req.error : $.noop;
                                    o.complete = j === 0 ? req.complete : $.noop;
                                    // add
                                    q.push(o);
                                }
                                size.push($lj);
                            } else {
                                q.push(req);
                                size.push(1);
                            }
                        }
                        // create multiple request
                        i = 0;
                        $l = q.length; 
                        tmp = [];
                        for (; i < $l; i++) {
                            // get request
                            req = q[i];
                            // remove session
                            delete req.params.session;
                            // build request
                            o = $.extend(req.params, { module: req.module, data: req.original });
                            // action?
                            if (req.params.action !== undefined) {
                                o.action = req.params.action;
                            }
                            // add
                            tmp.push(o);
                        }
                        // clear queue & remove "paused" flag
                        queue = [];
                        paused = false;
                        // send PUT
                        if (tmp.length > 0) {
                            this.PUT({
                                module: "multiple",
                                "continue": true,
                                data: tmp,
                                appendColumns: false,
                                success: function (data) {
                                    // orchestrate callbacks and their data
                                    var i = 0, j = 0, $l = data.length, range;
                                    while (i < $l) {
                                        // get data range
                                        range = size[j] > 1 ? data.slice(i, i + size[j]) : data[i];
                                        // call
                                        processResponse(range, q[i]);
                                        // ox.util.call(q[i].success, range);
                                        // inc
                                        i = i + size[j];
                                        j = j + 1;
                                    }
                                    // continuation
                                    ox.util.call(cont);
                                },
                                error: function (data) {
                                    return ox.util.call(error, data);
                                }
                            });
                        } else {
                            // continuation
                            ox.util.call(cont);
                        }
                    }
                }
            };
        }())
    };
    
    // local functions (no bubbling)
    var local = (function () {
        var localFn = {};
        return {
            add: function () {
                var args = $.makeArray(arguments), i = 0, $l = args.length;
                for (; i < $l; i++) {
                    localFn[args[i]] = true;
                }
                return this;
            },
            has: function (fn) {
                return localFn[fn] !== undefined;
            }
        };
    }());
    
    local.add(
        that.window.getData,
        that.getHash,
        that.getParam,
        that.setModal,
        that.http,
        that.event.Dispatcher,
        that.event.dispatcherRegistry
    );

    // wrap functions?
    if (that.window.isNested) {

        // wrap!
        var wrap = function (fn, scope, getArray) {
            return function () {
                // get arguments
                var tmp = $.makeArray(arguments), i = 0, $l = tmp.length;
                var args = getArray($l);
                // process arguments
                for (; i < $l; i++) {
                    // clone object except for DOM objects
                    if (!ox.util.isDOMObject(tmp[i])) {
                        args[i] = clone(tmp[i]);
                    }
                }
                // call
                return fn.apply(scope, args);
            };
        };
        var recurse = function (here, there, getArray) {
            // is object?
            if ($.isPlainObject(here) && there !== undefined) {
                // loop over properties
                var id;
                for (id in here) {
                    // replace by reference
                    if (here[id] === null && there[id] !== undefined) {
                        here[id] = there[id];
                    } else {
                        // cannot check there[id] via jQuery's isFunction()!
                        if (!local.has(here[id]) && isFunction(here[id]) && there[id] !== undefined) {
                            // replace function with wrapper
                            here[id] = wrap(there[id], there, getArray);
                        } else {
                            recurse(here[id], there[id], getArray);
                        }
                    }
                }
            }
        };
        var ref = that.window.core.ox;
        recurse(that, ref.api, ref.util.getArray);
    } 
    
    // done. return
    return that;
    
}());

// window controller class

var WindowController = function () {
    
    var $ = jQuery, self = this;
        
    var handlers = {
        // before unload ("hard" ask)
        beforeunload: $.noop,
        // ask user ("soft" ask)
        confirm: null,
        // unload
        unload: $.noop,
        // close
        close: $.noop
    };
    
    // public: set
    this.set = function (name, fn) {
        handlers[name] = fn;
    };
    
    var unload = function () {
        // bind close button to "Window Close" events
        ox.api.window.core.triggerEvent("OX_Window_Close", { handle: url.guid });
        // handler
        handlers.unload();
    };
    
    var destruct = function () {
        // prevent memory leaks
        for (var id in window) {
            if (id !== "location" && id !== "guid" && id !== "close") {
                try {
                    delete window[id];
                } catch (e) {
                }
            }
        }
        // clear here
        destruct = unload = null;
        handlers = {};
    };
    
    // public: close
    this.close = function (noConfirm) {
        if (!noConfirm && handlers.confirm) {
            // confirm by user
            handlers.confirm();
        } else {
            $(window).unbind("beforeunload unload");
            // unload
            unload();
            // close handler
            handlers.close();
            ox.api.window.close(url.guid, true);
        }
    };
    
    // public: destruct
    this.destruct = function () {
        $(window).unbind("beforeunload unload");
        unload();
        destruct();
    };
    
    // bind handlers
    $(window).bind("beforeunload", function () {
        return handlers.beforeunload();
    });
    
    $(window).bind("unload", this.destruct);
};
