jscape.storage = jscape.storage || {};

/** @class */
jscape.FileInfo = Class.extend(
    /** @lends {jscape.FileInfo.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f, g) {
        this.path = a == "" ? "/" : a + "";
        this.name = b || "";
        this.directory = !!c;
        this.size = parseInt(d) || 0;
        this.lastModified = parseInt(e) || 0;
        this.viewable = f != undefined ? !!f : false;
        this.permissions = g || new jscape.FileInfo.Permissions();
    }
});
jscape.FileInfo.isRoot = function(path) { return "" === path || "/" === path; };
jscape.FileInfo.isParentDir = function(path) { return ".." === path; };
jscape.FileInfo.fromJSON = function(data) {
    return data ? new jscape.FileInfo(data.path, data.name, data.directory, data.size, data.lastModified, data.viewable, jscape.FileInfo.Permissions.fromJSON(data.permissions)) : null;
};
jscape.FileInfo.fromString = function(path, directory) {
    return typeof path == "string" ? new jscape.FileInfo(path, path.substring(path.lastIndexOf("/") + 1), directory) : null;
};
/** @class */
jscape.FileInfo.Permissions = Class.extend(
    /** @lends {jscape.FileInfo.Permissions.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f) {
        this.fileUploading = a != undefined ? !!a : true;
        this.fileDeletion = b != undefined ? !!b : true;
        this.fileRenaming = c != undefined ? !!c : true;
        this.fileDownloading = d != undefined ? !!d : true;
        this.directoryMaking = e != undefined ? !!e : true;
        this.directoryDeletion = f != undefined ? !!f : true;
    }
});
jscape.FileInfo.Permissions.fromJSON = function (data) {
    return data
            ? new jscape.FileInfo.Permissions(data.fileUploading,
                                              data.fileDeletion,
                                              data.fileRenaming,
                                              data.fileDownloading,
                                              data.directoryMaking,
                                              data.directoryDeletion)
            : new jscape.FileInfo.Permissions();
};

/** @class */
jscape.TagCloudInfo = Class.extend(
    /** @lends {jscape.TagCloudInfo.prototype} */
{
    /** @constructor */
    initialize: function(a, b) {
        this.entries = a || [];
        this.total = parseInt(b) || 0;
    }
});
jscape.TagCloudInfo.fromJSON = function(data) {
    var total = 0;
    var entries = $.map(data || [], jscape.TagCloudInfo.Entry.fromJSON);
    $.each(entries, function(_, entry) { total += entry.relevance; });
    return new jscape.TagCloudInfo(entries, total);
};
/** @class */
jscape.TagCloudInfo.Entry = Class.extend(
    /** @lends {jscape.TagCloudInfo.Entry.prototype} */
{
    /** @constructor */
    initialize: function(a, b) {
        this.tag = a || "";
        this.relevance = parseInt(b) || 0;
    }
});
jscape.TagCloudInfo.Entry.fromJSON = function(data) {
    return data ? new jscape.TagCloudInfo.Entry(data.tag, data.relevance) : null;
};

/** @class */
jscape.AdHocTransferData = Class.extend(
    /** @lends {jscape.AdHocTransferData.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f, g, h) {
        this.sender = a || "";
        this.recipient = b || "";
        this.ccRecipient = c || "";
        this.bccRecipient = d || "";
        this.subject = e || "";
        this.message = f || "";
        this.date = parseInt(g) || null;
        this.files = h || [];
    }
});
jscape.AdHocTransferData.fromJSON = function(data) {
    return data ? new jscape.AdHocTransferData(data.sender, data.recipient, data.ccRecipient, data.bccRecipient, data.subject, data.message, $.map(data.files || [], jscape.AdHocTransferData.FileInfo.fromJSON)) : null;
};

/** @class */
jscape.EmailFileData = Class.extend(
    /** @lends {jscape.EmailFileData.prototype} */
{
    /** @constructor */
    initialize: function (a, b, c, d, e, f, g, h, i, j, k, l, m) {
        this.paths = a || [];
        this.sender = b || "";
        this.replyTo = c || "";
        this.recipients = this._parseEmailAddresses(d);
        this.ccRecipients = this._parseEmailAddresses(e);
        this.bccRecipients = this._parseEmailAddresses(f);
        this.subject = g || "";
        this.message = h || "";
        this.protection = i || null;
        this.password = j || null;
        this.expireAfterDays = parseInt(k) || -1;
        this.maxDownloads = parseInt(l) || -1;
        this.deleteAfterMaxDownloads = !!m;
    },

    _parseEmailAddresses: function(value) {
        return $.isArray(value) ? value : typeof value === "string" ? value.split(", ") : [];
    }
});
jscape.EmailFileData.fromJSON = function(data) {
    return data
           ? new jscape.EmailFileData(data.paths, data.sender, data.replyTo, data.recipients, data.ccRecipients, data.bccRecipients, data.subject, data.message, data.password, data.protection, data.expireAfterDays, data.maxDownloads, data.deleteAfterMaxDownloads)
           : new jscape.EmailFileData();
};


/** @class */
jscape.SearchQueryData = Class.extend(
    /** @lends {jscape.SearchQueryData.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f, g) {
        this.query = a || "";
        this.file = b || "";
        this.tag = c || "";
        this.size = $.isNumeric(d) ? d : null;
        this.sizePredicate = e || jscape.SearchQueryData.Predicate.EQUALS;
        this.modified = $.type(f) === "date" ? f.getTime() : null;
        this.modifiedPredicate = g || jscape.SearchQueryData.Predicate.EQUALS;
    }
});
jscape.SearchQueryData.Predicate = {LESS: "LESS", GREATER: "GREATER", EQUALS: "EQUALS"};
/** @class */
jscape.SearchQueryFormat = Class.extend(
    /** @lends {jscape.SearchQueryFormat.prototype} */
{
    /** @constructor */
    initialize: function(dateFormat) {
        function _formatPredicate(predicate) {
            switch (predicate) {
                case jscape.SearchQueryData.Predicate.LESS: return "<";
                case jscape.SearchQueryData.Predicate.GREATER: return ">";
                default : return "";
            }
        }
        function _parsePredicate(str) {
            switch (str) {
                case '<': return jscape.SearchQueryData.Predicate.LESS;
                case '>': return jscape.SearchQueryData.Predicate.GREATER;
                default : return jscape.SearchQueryData.Predicate.EQUALS;
            }
        }
        this.segments = [
            {
                format: function (q) {
                    return q && q.file ? "file:" + (q.file.indexOf(" ") != -1 ? "(" + q.file + ")" : q.file) : "";
                },
                parse: function (s, q) {
                    var re = /\bfile:([\w\-+.*]+|(\(([\w\-+.*\s]+)\)))\s?/;
                    var matches = re.exec(s);
                    if (matches != null) {
                        q.file = matches[3] ? matches[3] : matches[1];
                        s = s.replace(matches[0], "");
                    }
                    return s;
                }
            },
            {
                format: function(q) {
                    return q && q.tag ? "tag:" + (q.tag.indexOf(" ") != -1 ? "(" + q.tag + ")" : q.tag) : "";
                },
                parse: function(s, q) {
                    var re = /(\btag:\(([^)]+?)\))|(\btag:(\w+?)\b)/;
                    var matches = re.exec(s);
                    if (matches != null) {
                        q.tag = matches[4] ? matches[4] : matches[2];
                        s = s.replace(matches[0], "");
                    }
                    return s;
                }
            },
            {
                format: function (q) {
                    return q && $.isNumeric(q.size) ? "size:" + _formatPredicate(q.sizePredicate) + q.size : "";
                },
                parse: function (s, q) {
                    var re = /\bsize:([<>=]?)(\d+?)\b/;
                    var matches = re.exec(s);
                    if (matches != null) {
                        q.size = parseInt(matches[2]);
                        q.sizePredicate = _parsePredicate(matches[1]);
                        s = s.replace(matches[0], "");
                    }
                    return s;
                }
            },
            {
                format: function(q) {
                    return q && q.modified ? "modified:" + _formatPredicate(q.modifiedPredicate) + dateFormat.format(q.modified) : "";
                },
                parse: function(s, q) {
                    var re = /\bmodified:([<>=]?)([\w\/\-\.*]+)\s?/;
                    var matches = re.exec(s);
                    if (matches != null) {
                        q.modified = dateFormat.parse(matches[2]).getTime();
                        q.modifiedPredicate = _parsePredicate(matches[1]);
                        s = s.replace(matches[0], "");
                    }
                    return s;
                }
            }
        ];
    },

    format: function(query) {
        var tokens = [];
        for (var i = 0; i < this.segments.length; i++) {
            var entry = this.segments[i].format.call(this, query);
            if (entry) {
                tokens.push(entry);
            }
        }
        if (query.query) {
            tokens.push(query.query);
        }
        return tokens.join(" ");
    },

    parse: function(str) {
        var query = new jscape.SearchQueryData();
        if (str && str.trim().length) {
            for (var i = 0; i < this.segments.length; i++) {
                str = this.segments[i].parse(str, query);
            }
            query.query = str.trim();
        }
        return query;
    }
});

/** @class */
jscape.storage.FileTransferModule = jscape.ApplicationModule.extend(
    /** @lends {jscape.storage.FileTransferModule.prototype} */
{
    currentDir: null,
    navigateToPath: null,
    searching: false,
    uploadFormRequired: false,
    searchAllowed: false,
    viewAllowed: false,
    addContactAllowed: false,

    /** @constructor */
    initialize: function (fileUploadMetadataRequired, searchAllowed, viewAllowed, addContactAllowed) {
        this.uploadFormRequired  = !!fileUploadMetadataRequired;
        this.searchAllowed = !!searchAllowed;
        this.viewAllowed = !!viewAllowed;
        this.addContactAllowed = !!addContactAllowed;
    },

    onCreate: function(_, path, navigateToPath) {
        this._super.apply(this, arguments);

        if (!!navigateToPath) {
            this.navigateToPath = path;
            this.currentDir = jscape.FileInfo.fromString("");
        } else {
            this.currentDir = jscape.FileInfo.fromString(path, true);
            this.navigateToPath = null;
        }

        this.breadcrumbsView = new jscape.storage.BreadcrumbsView();
        this.listView = new jscape.storage.FileListView();

        if (this.searchAllowed) {
            this.searchView = new jscape.storage.FileSearchView();
            this.tagCloudView = new jscape.storage.TagCloudView();
        }

        this.viewController = new jscape.storage.ViewFileController();
        this.changeDirController = new jscape.storage.ChangeDirController();
        this.makeDirController = new jscape.storage.MakeDirController();
        this.deleteController = new jscape.storage.DeletePathController();
        this.emailFileController = new jscape.storage.EmailFileController(this.addContactAllowed);
        this.manageTagsController = new jscape.storage.TagFileController();
    },

    onStart: function() {
        this.breadcrumbsView.show(this);
        this.listView.show(this);

        if (this.searchAllowed) {
            this.searchView && this.searchView.show(this);
            this.tagCloudView && this.tagCloudView.show(this);
        }

        if (this.navigateToPath) {
            var path = this.navigateToPath;
            this.navigateToPath = null;
            this.onChangeDir(path).fail($.proxy(function() { this.onChangeDir("/"); }, this));
        } else {
            this.listView.refresh();
            jscape.API.pathInfo(this.currentDir.path)
                    .done($.proxy(function (info) {
                              this.currentDir = info;
                              this.listView.setPermissions(info.permissions);
                          }, this));
        }
    },

    onResume: function() {
        this._refreshFileList();
    },

    onRefresh: function (params) {
        params = jscape.Page.requestParameters(params);
        return jscape.API.listFiles(params.query)
                .then(jscape.Page.datagridFormatter())
                .done(function() { $(document).triggerHandler("_activity.user"); })
                .done($.proxy(function () {
                          if (this.searching) {
                              this.listView.showPathColumn();
                              this.breadcrumbsView.setMessage(window.R.string.STORAGE_MESSAGE_SEARCH_RESULTS);
                          } else {
                              this.listView.hidePathColumn();
                              this.currentDir && this.breadcrumbsView.setCurrentDir(this.currentDir.path);
                          }
                          this._refreshTagCloud();
                      }, this));
    },

    onUploadFile: function (mode, files, items) {
        var filenames = $.map(this.listView.getFileInfos(), function (info) { return !info.directory ? info.name : null; });
        var controller = new jscape.storage.UploadFileController(this.uploadFormRequired);
        return controller.start(mode, files, filenames, items)
                .done($.proxy(this._fireMessage, this, window.R.string.STORAGE_SUCCESS_UPLOAD))
                .done($.proxy(this._refreshFileList, this))
                .fail(function (error) {
                          if (error && error.force) {
                              $.messager.alert(window.R.string.APPLICATION_TITLE,
                                               error.message || window.R.string.STORAGE_ERROR_UPLOAD,
                                               "error");
                          }
                      });
    },

    onViewFile: function(file) {
        if (this.viewAllowed) {
            return this.viewController.start(file.path);
        }
        return $.Deferred().reject().promise();
    },

    onRenameFile: function (file, filename) {
        return jscape.API.renameFile(file.path, filename)
                .done($.proxy(function (target) {
                          this._fireMessage((file.directory
                                  ? window.R.string.STORAGE_SUCCESS_RENAME_DIR
                                  : window.R.string.STORAGE_SUCCESS_RENAME_FILE).supplant({0: file.path, 1: target.path}));
                      }, this))
                .always($.proxy(this._refreshFileList, this));
    },

    onMoveFile: function (files, target) {
        var paths = $.map(files, function(file) { return file.path; });
        return jscape.API.moveFile(paths, target.path)
                .done($.proxy(function () {
                          var message = paths.length == 1
                                        ? window.R.string.STORAGE_SUCCESS_MOVE.supplant({0: paths[0], 1: target.path})
                                        : window.R.string.STORAGE_SUCCESS_MOVE_BATCH.supplant({0: target.path});
                          this._fireMessage(message);
                      }, this))
                .always($.proxy(this._refreshFileList, this));
    },

    onChangeDir: function (file) {
        return this.changeDirController.start(file)
                .done($.proxy(function (info) {
                          this.currentDir = info;

                          if (this.searchAllowed) {
                              this.searchView.setQuery("");
                              this.searching = false;
                          }
                          this.listView.setPermissions(info.permissions);
                          this.listView.load({});
                      }, this));
    },

    onMakeDir: function () {
        return this.makeDirController.start()
                .done($.proxy(this._refreshFileList, this))
                .done($.proxy(function (path) {
                          this._fireMessage(window.R.string.STORAGE_SUCCESS_MAKE_DIR.supplant({0: path}));
                      }, this));
    },

    onDeleteFile: function (files) {
        return this.deleteController.start(files)
                .done($.proxy(this._refreshFileList, this))
                .done($.proxy(function () {
                          var message = files.length == 1
                                  ? (files[0].directory
                                  ? window.R.string.STORAGE_SUCCESS_DELETE_DIR
                                  : window.R.string.STORAGE_SUCCESS_DELETE_FILE).supplant({0: files[0].path})
                                  : window.R.string.STORAGE_SUCCESS_DELETE_BATCH;
                          this._fireMessage(message);
                      }, this));
    },

    onDownload: function(file) {
        return jscape.API.downloadFile(file.path);
    },

    onZipDownload: function(files) {
        return jscape.API.zipDownload($.map(files, function (file) { return file.path; }));
    },

    onEmailFile: function(files) {
        return this.emailFileController.start(files)
                .done($.proxy(this._fireMessage, this, window.R.string.STORAGE_SUCCESS_EMAIL_FILE));
    },

    onManageTags: function(files) {
        $(document).triggerHandler("_activity.user");

        return this.manageTagsController.start(files)
                .done($.proxy(this._fireMessage, this, window.R.string.STORAGE_SUCCESS_TAG_FILE))
                .done($.proxy(function () {
                          if (this.searching) {
                              this.listView.refresh();
                          } else {
                              this._refreshTagCloud();
                          }
                      }, this));
    },

    onRefreshCloud: function() {
        return jscape.API.tagCloud()
                .done($.proxy(function(data) { this.tagCloudView.loadData(data); }, this));
    },

    onTaggedFile: function(tag) {
        if (this.searchAllowed) {
            var query = new jscape.SearchQueryData(null, null, tag);
            this.searching = true;
            this.searchView.setQuery(query);
            this.listView.load(query);
        }
    },

    onSearch: function(query) {
        if (this.searchAllowed) {
            this.searching = true;
            this.listView.load(query);
        }
    },

    onSearchClear: function(view) {
        this.searching = false;
        this.listView.load({});
    },

    _refreshFileList: function() {
        if (this.searching) {
            setTimeout($.proxy(function() { this.listView.refresh(); }, this), 500);
        } else {
            this.listView.refresh();
        }
    },

    _refreshTagCloud: function() {
        if (this.tagCloudView) {
            setTimeout($.proxy(function() { this.tagCloudView.refresh(); }, this), 600);
        }
    },

    _fireMessage: function(message) {
        if (message) {
            $(document).triggerHandler("broadcast", [message]);
        }
    }
});

/** @class */
jscape.storage.FileListView = jscape.ui.DatagridView.extend(
    /** @lends {jscape.storage.FileListView.prototype} */
{
    SIZE_FACTOR: 1024,
    DEFAULT_PERMISSIONS: new jscape.FileInfo.Permissions(),

    sizeFormat: null,
    selection: null,
    editIndex: -1,

    /** @constructor */
    initialize: function() {
        var initializing = true;

        this.sizeFormat = new jscape.SizeFormat();

        this.fieldset = $("#viewpane");

        this.data = $("table[role=grid][aria-label=filelistinggrid]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            singleSelect: false,
            ctrlSelect: true,
            remoteSort: true,
            showFooter: true,
            autoRowHeight: false,
            pageSize: 100,
            /*view: $.fn.datagrid.defaults.scrollview,*/
            columns: [[
                    {field: "_ck", checkbox: true, width: 32, fixed: true},
                    {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: "40%", editor: "text", formatter: $.proxy(function(_, rowData) { return this._formatName(rowData); }, this), styler: $.proxy(this._styleName, this)},
                    {field: "path", title: $("thead th:nth-child(2)", this.data).text(), sortable: true, width: 300, fixed: true},
                    {field: "size", title: $("thead th:nth-child(3)", this.data).text(), sortable: true, width: 150, fixed: true, formatter: $.proxy(function(_, rowData) { return this._formatSize(rowData); }, this)},
                    {field: "lastModified", title: $("thead th:nth-child(4)", this.data).text(), sortable: true, width: 280, fixed: true, formatter: $.proxy(function(_, rowData) { return this._formatDate(rowData); }, this)}
            ]],
            rowEvents: $.extend({}, $.fn.datagrid.defaults.rowEvents, {
                mouseover: function (e) {
                    var tr = $(e.target).closest("tr.datagrid-row");
                    if (tr.length && tr.parent().length) {
                        var table = tr.closest("div.datagrid-view").children(".datagrid-f");
                        if (table && table.length) {
                            var data = $.data(table[0], "datagrid");
                            if (data.highlighting || data.resizing) {
                                return;
                            }
                            data.highlighting = true;
                            tr.addClass("datagrid-row-over");
                        }
                    }
                },
                mouseout: function (e) {
                    var tr = $(e.target).closest("tr.datagrid-row");
                    if (!e.relatedTarget || (tr.length && !$.contains(tr[0], e.relatedTarget))) {
                        var table = tr.closest("div.datagrid-view").children(".datagrid-f");
                        if (table && table.length) {
                            var data = $.data(table[0], "datagrid");
                            data.highlighting = false;
                            if (data.resizing) {
                                return;
                            }
                            tr.removeClass("datagrid-row-over");
                        }
                    }
                }
            }),
            onSelect: $.proxy(this._adjustRowSelection, this),
            onSelectAll: $.proxy(this._adjustRowSelection, this),
            onUnselect: $.proxy(this._adjustRowSelection, this),
            onUnselectAll: $.proxy(this._adjustRowSelection, this),
            onRowContextMenu: $.proxy(this._onRow$ContextMenu, this),
            onResize: $.proxy(this._onResize$FileList, this),
            onDragEnter: $.proxy(this._onMove$CheckFile, this),
            onDragOver: $.proxy(this._onMove$CheckFile, this),
            onBeforeDrag: $.proxy(this._onBeforeDrag$File, this),
            onBeforeDrop: $.proxy(this._onBeforeMove$File, this),
            onDrop: $.proxy(this._onMove$File, this),
            onCheck: $.proxy(this._onRow$Check, this),
            onClickRow: $.proxy(this._onClick$Row, this),
            onDblClickRow: $.proxy(function() { this.viewButton.click(); }, this),
            onAfterEdit: $.proxy(this._onEdit$After, this),
            loader: $.proxy(this._onLoad$Data, this),
            onBeforeLoad: function() { return !initializing; },
            onLoadSuccess: $.proxy(function(data) {
                this.data.datagrid("fixRowHeight").datagrid("enableDnd");
                this._adjustRowSelection();
            }, this)
        });
        this.data.datagrid("getPanel").addClass("filelist");

        var toolbar = $("#filetoolbar");
        this.uploadMode = $("#uploadmodemenu");
        this.uploadButton = this.uploadMode.length
                            ? $("a[role=button][name=upload]", toolbar).splitbutton({plain: false, menu: "#uploadmodemenu", onClick: $.proxy(this._onClick$UploadButton, this)})
                            : $("a[role=button][name=upload]", toolbar).linkbutton({onClick: $.proxy(this._onClick$UploadButton, this)});
        this.viewButton = $("a[role=button][name=view]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ViewButton, this)});
        this.renameButton = $("a[role=button][name=rename]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$RenameButton, this)});
        this.refreshButton = $("a[role=button][name=refresh]", toolbar).linkbutton({onClick: $.proxy(this._onClick$RefreshButton, this)});
        this.changeDirButton = $("a[role=button][name=cwd]", toolbar).linkbutton({onClick: $.proxy(this._onClick$ChangeDirButton, this)});
        this.makeDirButton = $("a[role=button][name=mkdir]", toolbar).linkbutton({onClick: $.proxy(this._onClick$MakeDirButton, this)});
        this.zipDownloadButton = $("a[role=button][name=zipdload]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$ZipDownloadButton, this)});
        this.deleteButton = $("a[role=button][name=delete]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$DeleteButton, this)});
        this.emailFileButton = $("a[role=button][name=emailfile]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$EmailFileButton, this)});
        this.tagsButton = $("a[role=button][name=tags]", toolbar).linkbutton({disabled: true, onClick: $.proxy(this._onClick$TagsButton, this)});

        $(document).off(".storage")
                .on("keydown.storage", {
                    113: this.renameButton,
                    116: this.refreshButton,
                    118: this.makeDirButton,
                    119: this.deleteButton
                }, $.proxy(this._onKeyDown, this));

        initializing = false;
    },

    show: function(listener) {
        this.listener = listener || jscape.storage.FileListView.LISTENER;
        this.editIndex = -1;
        this._adjustRowSelection();
        this._initUploadDropzone();
        return this;
    },

    refresh: function(callback) {
        this.data.datagrid("reload", callback);
    },

    load: function(query) {
        this.data.datagrid("load", query);
    },

    setPermissions: function(value) {
        this.permissions = value;
        this._adjustRowSelection();
    },

    hidePathColumn: function() {
        this.data.datagrid("hideColumn", "path");
    },

    showPathColumn: function() {
        this.data.datagrid("showColumn", "path");
    },

    getFileInfos: function() {
        return this.getRecords();
    },

    _uploadMode: function() {
        return this.uploadMode.length ? this.uploadMode.find(":checked").val() : null;
    },

    _contextMenu: function() {
        var items = [
            {name: "zipdownload", button: this.zipDownloadButton},
            {name: "emailfile", button: this.emailFileButton},
            {name: "tagfile", button: this.tagsButton},
            {name: "viewfile", button: this.viewButton},
            "-",
            {name: "rename", button: this.renameButton},
            {name: "delete", button: this.deleteButton}
        ];

        if (!this.contextMenu) {
            this.contextMenu = $("<div/>").appendTo(this.fieldset).menu({})
                    .off(".grid").on("contextmenu.grid", function(e) { e.preventDefault(); });
            this._createContextMenuItems(this.contextMenu, items);
        } else {
            this._updateContextMenuItems(this.contextMenu, items);
        }
        return this.contextMenu;
    },

    _createContextMenuItems: function(menu, items) {
        $.each(items, function (_, entry) {
            if (entry == "-") {
                menu.menu("appendItem", {separator: true});
            } else if ($.isPlainObject(entry)) {
                if (entry.name && entry.button && entry.button.length) {
                    var opts = entry.button.linkbutton("options");
                    menu.menu("appendItem", {name: entry.name, text: opts.text, handler: opts.onClick, disabled: opts.disabled});
                }
            }
        });
    },

    _updateContextMenuItems: function(menu, items) {
        $.each(items, function (_, entry) {
            if ($.isPlainObject(entry)) {
                if (entry.name && entry.button && entry.button.length) {
                    var opts = entry.button.linkbutton("options");
                    var item = menu.menu("findItem", opts.text);
                    if (item) {
                        menu.menu(opts.disabled ? "disableItem" : "enableItem", item.target);
                    }
                }
            }
        });
    },

    _initUploadDropzone: function () {
        var panel = this.data.datagrid("getPanel");
        if (panel.find(".dropzone").length != 0) {
            return;
        }

        panel.find(".datagrid-view")
                .append($("<div></div>").addClass("dropzone").text(window.R.string.STORAGE_MESSAGE_DROPZONE));

        $(window)
                .on("dragenter dragover", function (e) {
                        var dt = e.originalEvent.dataTransfer;
                        if ($.inArray("Files", dt.types) == -1) {
                            return;
                        }
                        e.stopPropagation();
                        e.preventDefault();
                        panel.find(".dropzone").addClass("hover");

                        if ($(e.target).hasClass("dropzone")) {
                            dt.effectAllowed = "copyMove";
                            dt.dropEffect = "move";
                        } else {
                            dt.effectAllowed = "none";
                            dt.dropEffect = "none";
                        }
                    })
                .on("dragleave", function (e) {
                        e.stopPropagation();
                        e.preventDefault();

                        if (e.originalEvent.clientX <= 0 || e.originalEvent.clientY <= 0) {
                            panel.find(".dropzone").removeClass("hover");
                        }
                    })
                .on("drop", $.proxy(function (e) {
                        if ($.inArray("Files", e.originalEvent.dataTransfer.types) == -1) {
                            return;
                        }
                        e.preventDefault();
                        e.stopPropagation();

                        if ($(e.target).hasClass("dropzone")) {
                            $(e.target).removeClass("hover");
                            this._onDrop$File(e.originalEvent.dataTransfer);
                        }
                    }, this));

        this._onResize$FileList();
    },

    _onResize$FileList: function () {
        var panel = this.data.datagrid("getPanel");
        var dz = panel.find(".dropzone");
        if (dz && dz.length) {
            dz.css("line-height", panel.css("height"))
                    .width(panel.width() - (dz.outerWidth() - dz.innerWidth()))
                    .height(panel.height() - (dz.outerWidth() - dz.innerWidth()));
        }
    },

    _formatName: function(info) {
        var name = (info.name || "").escapeXml();
        return jscape.FileInfo.isParentDir(info.path) ? window.R.string.STORAGE_PARENT_DIR_TEMPLATE.supplant({0: name}) : name;
    },

    _formatDate: function(info) {
        return info.lastModified ? window.moment(info.lastModified).format("ll LTS") : "";
    },

    _formatSize: function(info) {
        return info.type == "footer" ? "" : info.directory ? window.R.string.STORAGE_TYPE_DIRECTORY : this.sizeFormat.format(info.size);
    },

    _formatFooter: function(data) {
        var count = 0;
        var rows = data.rows.length ? data.rows : data.firstRows || [];
        for (var i = 0; i < rows.length; i++) {
            if (!jscape.FileInfo.isParentDir(rows[i].path)) {
                count++;
            }
        }
        return window.R.string.STORAGE_LISTING_RESULTS.supplant({0: count});
    },

    _styleName: function(name, info) {
        return info.type == "footer"
                ? {style: "font-weight: bold"}
                : jscape.FileInfo.isParentDir(info.path) ? {"class": "cdup"} : {"class": info.directory ? "folder" : "file"};
    },

    _adjustRowSelection: function() {
        this.selection = this.data.datagrid("getSelections");
        var hasParentDir = $.grep(this.selection, function(info) { return jscape.FileInfo.isParentDir(info.path); }).length != 0;
        var singleOperation = !hasParentDir && this.selection.length == 1;
        var batchOperation = !hasParentDir && this.selection.length != 0;
        var permissions = this.permissions || this.DEFAULT_PERMISSIONS;

        this.viewButton.linkbutton(singleOperation && this.selection[0].viewable ? "enable" : "disable");
        this.uploadButton.linkbutton(!!permissions.fileUploading ? "enable" : "disable");
        this.renameButton.linkbutton(!!permissions.fileRenaming && singleOperation ? "enable" : "disable");
        this.makeDirButton.linkbutton(!!permissions.directoryMaking ? "enable" : "disable");
        this.zipDownloadButton.linkbutton(!!permissions.fileDownloading && batchOperation ? "enable" : "disable");
        this.emailFileButton.linkbutton(batchOperation ? "enable" : "disable");
        this.tagsButton.linkbutton(batchOperation ? "enable" : "disable");

        var deleteAllowed = batchOperation;
        for (var i = 0; i < this.selection.length; ++i) {
            var directory = this.selection[i].directory;
            if ((directory && !permissions.directoryDeletion)
                || (!directory && !permissions.fileDeletion)) {
                deleteAllowed = false;
                break;
            }
        }
        this.deleteButton.linkbutton(deleteAllowed ? "enable" : "disable");
    },

    _onLoad$Data: function (params, success, error) {
        this.listener.onRefresh(params)
                .done(success)
                .done($.proxy(function (data) {
                          this._adjustRowSelection();//todo: check scroll view onLoadSuccess
                          this.data.datagrid("reloadFooter", [{name: this._formatFooter(data), type: "footer"}])
                                  .datagrid("mergeCells", {type: "footer", index: 0, field: "name", colspan: 4});
                      }, this))
                .fail(error);
    },

    _onClick$Row: function (index, row) {
        if (row.directory) {
            this.listener.onChangeDir(row);
        } else {
            this.listener.onDownload(row);
        }
    },

    _onClick$UploadButton: function() {
        this.listener.onUploadFile(this._uploadMode());
    },

    _onMove$CheckFile: function(target, source) {
        return target && !!target.directory;
    },

    _onBeforeDrag$File: function(rows) {
        var index = this.editIndex;
        if (index >= 0 && rows.length) {
            for (var i = 0; i < rows.length; i++) {
                if (this.data.datagrid("getRowIndex", rows[i]) == index) {
                    return false;
                }
            }
            this.editIndex = -1;
            this.data.datagrid("cancelEdit", index);
        }

        return true;
    },

    _onBeforeMove$File: function(target, source) {
        return !!source && !!target && target.path != source.path;
    },

    _onMove$File: function(target, source) {
        this.listener.onMoveFile(source, target);
    },

    _onDrop$File: function(dataTransfer) {
        var files = dataTransfer.files;
        if (files && files.length) {
            this.listener.onUploadFile(this._uploadMode(), files, dataTransfer.items);
        }
    },

    _onClick$ViewButton: function() {
        this.viewButton.linkbutton("disable");
        this.listener.onViewFile(this.selection[0])
                .always($.proxy(function() { this.viewButton.linkbutton("enable"); }, this));
    },

    _onRow$ContextMenu: function(e, index, row) {
        e.preventDefault();
        if (index >= 0) {
            this.data.datagrid("cancelEdit")
                    .datagrid("unselectAll")
                    .datagrid("selectRow", index);
            this._contextMenu().menu("show", {top: e.pageY, left: e.pageX})
        }
    },

    _onRow$Check: function(index) {
        if (this.editIndex != index) {
            this.data.datagrid("cancelEdit", this.editIndex);
            this.editIndex = -1;
        }
    },

    _onClick$RenameButton: function() {
        var index = this.editIndex;
        if (index != -1) {
            this.editIndex = -1;
            this.data.datagrid("cancelEdit", index);
        }
        index = this.data.datagrid("getRowIndex", this.selection[0]);
        if (index != -1) {
            this.data.datagrid("beginEdit", index);
            var editor = this.data.datagrid("getEditor", {index: index, field: "name"});
            if (editor) {
                this.editIndex = index;
                var input = $(editor.target);
                var grid = this.data;
                setTimeout(function() {
                    input.unbind(".rename").bind("keydown.rename", function(e) {
                        if (e.keyCode == 13) {
                            if ($(this).val()) {
                                grid.datagrid("endEdit", index);
                            } else {
                                grid.datagrid("cancelEdit", index);
                            }
                            return false;
                        } else if (e.keyCode == 27) {
                            grid.datagrid("cancelEdit", index);
                            return false;
                        }
                    });
                }, 0);
                input.focus();
            }
        }
    },

    _onEdit$After: function(index, row, changes) {
        if (changes && changes.name) {
            this.listener.onRenameFile(row, changes.name);
        }
    },

    _onClick$RefreshButton: function() {
        this.refresh();
    },

    _onClick$ChangeDirButton: function() {
        this.listener.onChangeDir();
    },

    _onClick$MakeDirButton: function() {
        this.listener.onMakeDir();
    },

    _onClick$ZipDownloadButton: function() {
        this.listener.onZipDownload(this.selection);
    },

    _onClick$DeleteButton: function() {
        this.listener.onDeleteFile(this.selection);
    },

    _onClick$EmailFileButton: function() {
        this.listener.onEmailFile(this.selection);
    },

    _onClick$TagsButton: function() {
        this.listener.onManageTags(this.selection);
    },

    _onKeyDown: function(e) {
        var button = typeof e.data == "object" && e.data.hasOwnProperty(e.which) ? e.data[e.which] : null;
        if (button && button.length && !button.linkbutton("options").disabled) {
            e.preventDefault();
            button.click();
            return false;
        }
    }
});
jscape.storage.FileListView.LISTENER = {
    onRefresh: $.noop,
    onViewFile: $.noop,
    onRenameFile: $.noop,
    onMoveFile: $.noop,
    onChangeDir: $.noop,
    onMakeDir: $.noop,
    onUploadFile: $.noop,
    onDownload: $.noop,
    onZipDownload: $.noop,
    onDeleteFile: $.noop,
    onEmailFile: $.noop,
    onManageTags: $.noop
};

/** @class */
jscape.storage.FileSearchView = Class.extend(
    /** @lends {jscape.storage.FileSearchView.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.format = new jscape.SearchQueryFormat({
            format: $.proxy(function(value) { return this.date.datebox("options").formatter($.isNumeric(value) ? new Date(value) : value); }, this),
            parse:$.proxy(function(str) { return this.date.datebox("options").parser(str); }, this)
        });

        this.fieldset = $("#filetoolbar");

        this.query = $("input[name=query]", this.fieldset).combo({
            editable: true,
            selectOnNavigation: false,
            keyHandler: $.extend({}, $.fn.combo.defaults.keyHandler, {enter: $.proxy(this._onClick$SearchButton, this), esc: function() {}, down: function() { $(this).combobox("showPanel"); }}),
            panelEvents: $.extend({}, $.fn.combo.defaults.panelEvents, {mousedown: $.noop}),
            delay: 5 * 60 * 1000,
            width: "60%",
            height: 28,
            panelHeight: "auto",
            icons: [{iconCls: "icon-clear", handler: $.proxy(this._onClick$ClearButton, this)}],
            onShowPanel: $.proxy(this._onShow$Panel, this),
            onHidePanel: $.proxy(this._onHide$Panel, this)
        });
        this.query.combo("textbox").bind("keyup change", $.proxy(this._adjustClearButtonState, this));

        this.searchButton = $("a[role=button][name=search]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$SearchButton, this)});
        this.advancedButton = $("button[name=advancedsearch]", this.advancedPanel).linkbutton({onClick: $.proxy(this._onClick$SearchButton, this)});

        this.advancedPanel = $("#advancedsearch").appendTo(this.query.combo("panel")).show();
        $("form", this.advancedPanel).form({ajax: true, onSubmit: $.proxy(function() { return false; }, this), novalidate: true});
        this.query.combo("getIcon", 1).tooltip({content: this.advancedPanel.attr("title"), showDelay: 0}).click(function() { $(this).tooltip("hide"); });
        this.advancedPanel.removeAttr("title");

        this.content = $("input[name=filecontent]", this.advancedPanel).textbox({width: "100%", height: 58, labelPosition: "top", doSize: false});
        this.filename = $("input[name=filename]", this.advancedPanel).textbox({width: "100%", height: 58, labelPosition: "top", doSize: false});
        this.tag = $("input[name=filetag]", this.advancedPanel).textbox({width: "100%", height: 58, labelPosition: "top", doSize: false});
        this.size = $("input[name=filesize]", this.advancedPanel).numberspinner({min: 0, width: 175, max: Math.pow(2, 31) - 1});
        this.sizePredicate = $("select[name=filesizepredicate]", this.advancedPanel).combobox({required: true, editable: false, width: 120, panelHeight: "auto"});
        this.sizeUnit = $("select[name=filesizeunit]", this.advancedPanel).combobox({required: true, editable: false, width: 70, panelHeight: "auto"});
        this.date = $("input[name=filedate]", this.advancedPanel).datebox({width: 175});
        this.datePredicate = $("select[name=filedatepredicate]", this.advancedPanel).combobox({required: true, editable: false, width: 120, panelHeight: "auto"});
    },

    getQuery: function() {
        return this.query.combo("getText");
    },

    setQuery: function(value) {
        if (typeof value === "string") {
            this.query.combo("setText", value);
        } else if (typeof value === "object") {
            this.query.combo("setText", this.format.format(value));
        }
        this.query.combo("textbox").change();
    },

    getContentQuery: function() {
        return this.content.textbox("getValue");
    },

    getFilenameQuery: function() {
        return this.filename.textbox("getValue")
    },

    getTagQuery: function () {
        return this.tag.textbox("getValue");
    },

    getSizeQuery: function() {
        return parseInt(this.size.numberspinner("getValue")) * parseInt(this.sizeUnit.combobox("getValue"));
    },

    getSizePredicate: function() {
        return this.sizePredicate.combobox("getValue");
    },

    getDateQuery: function() {
        var value = this.date.datebox("getValue");
        return value.trim().length != 0 ? this.date.datebox("options").parser(value) : null;
    },

    getDatePredicate: function() {
        return this.datePredicate.combobox("getValue");
    },

    show: function(listener) {
        this.listener = listener || jscape.storage.FileSearchView.LISTENER;
        this.query.combo("textbox").change();
        return this;
    },

    _onShow$Panel: function() {
        var query = this.format.parse(this.query.combobox("getText"));
        this.content.textbox("resize");
        this.filename.textbox("resize");
        this.tag.textbox("resize");

        if (query) {
            this.content.textbox("setValue", query.query);
            this.filename.textbox("setValue", query.file);
            this.tag.textbox("setValue", query.tag);
            if ($.isNumeric(query.size)) {
                var sizeUnit = this._sizeUnitFor(query.size);
                this.size.numberspinner("setValue", query.size / sizeUnit);
                this.sizePredicate.combobox("select", query.sizePredicate);
                this.sizeUnit.combobox("select", sizeUnit + "");
            }
            if ($.isNumeric(query.modified)) {
                var formatter = this.date.datebox("options").formatter;
                this.date.datebox("setValue", formatter.call(this.date, new Date(query.modified)));
                this.datePredicate.combobox("select", query.modifiedPredicate);
            }
        }

        setTimeout($.proxy(function() {
            this.advancedPanel.find("input:visible:first").focus().select();
        }, this), 0);
    },

    _onHide$Panel: function() {
        setTimeout($.proxy(function() {
            this.query.combobox("textbox").focus();
        }, this), 0);
    },

    _onClick$ClearButton: function() {
        $.each([this.content, this.filename, this.tag], function(_, entry) { entry.textbox("reset"); });
        $.each([this.sizePredicate, this.datePredicate], function(_, entry) { entry.combobox("reset"); });
        this.size.numberspinner("reset");
        this.date.datebox("reset");
        this.query.combo("hidePanel");
        this.setQuery("");

        this.listener.onSearchClear(this);
    },

    _onClick$SearchButton: function () {
        var query;
        if (this.query.combobox("panel").is(":visible")) {
            query = new jscape.SearchQueryData(this.getContentQuery(),
                                               this.getFilenameQuery(),
                                               this.getTagQuery(),
                                               this.getSizeQuery(),
                                               this.getSizePredicate(),
                                               this.getDateQuery(),
                                               this.getDatePredicate());
            this.query.combo("hidePanel");
            this.setQuery(query);
        } else {
            query = this.format.parse(this.getQuery());
        }
        this.listener.onSearch(query);
    },

    _sizeUnitFor: function(value) {
        var opts = this.sizeUnit.combobox("options");
        var units = $.map(this.sizeUnit.combobox("getData"), function(entry) { return parseInt(entry[opts.valueField]); }).sort();
        var unit = 1;
        while (units.length) {
            unit = units.pop();
            if (value % unit == 0) {
                break;
            }
        }
        return unit;
    },

    _adjustClearButtonState: function() {
        var query = this.query.combo("textbox").val();
        this.query.combo("getIcon", 0).css("visibility", query && query.length ? "visible" : "hidden");
    }
});
jscape.storage.FileSearchView.LISTENER = {
    onSearch: $.noop,
    onSearchClear: $.noop
};

/** @class */
jscape.storage.BreadcrumbsView = Class.extend(
    /** @lends {jscape.storage.BreadcrumbsView.prototype} */
{
    path: "",

    /** @constructor */
    initialize: function() {
        this.panel = $("#breadcrumbs");
    },

    show: function(listener) {
        this.listener = listener || jscape.storage.BreadcrumbsView.LISTENER;
        this.path = null;
    },

    setCurrentDir: function(path) {
        if (this.path != path) {
            this.path = path;
            this._adjustBreadcrumbs();
        }
    },

    setMessage: function(text) {
        this._clear();
        this.panel.text(text);
    },

    _onClick$Entry: function(path) {
        this.listener.onChangeDir(path);
    },

    _clear: function() {
        this.panel.filter("a").each(function(_, el) { el.unbind(".linkbutton") });
        this.panel.empty();
        this.path = null;
    },

    _adjustBreadcrumbs: function() {
        var currentDir = this.path;
        this._clear();
        var entries = jscape.FileInfo.isRoot(currentDir) ? ["/"] : currentDir.split("/");

        for (var i = 0, size = entries.length; i < size; ++i) {
            var text = entries[i];

            if (jscape.FileInfo.isRoot(text)) {
                this._addBreadCrumbEntry("/", "/");
            } else {
                var successor = entries.slice(0, i + 1).join("/");
                this._addBreadCrumbEntry(text, successor);
                if (i + 1 < size) {
                    $("<span>/</span>").appendTo(this.panel);
                }
            }
        }
    },

    _addBreadCrumbEntry: function(text, path) {
        return $("<a></a>").appendTo(this.panel)
                .linkbutton({
                                group: "breadcrumbs",
                                text: (text + "").escapeXml(),
                                plain: true,
                                selected: path == this.path,
                                onClick: $.proxy(this._onClick$Entry, this, path)
                            });
    }
});
jscape.storage.BreadcrumbsView.LISTENER = {
    onChangeDir: $.noop
};

/** @class */
jscape.storage.TagCloudView = Class.extend(
    /** @lends {jscape.storage.TagCloudView.prototype} */
{
    MIN_SIZE: 12,
    SIZE_FACTOR: 3,

    /** @constructor */
    initialize: function() {
        this.layout = $("#viewpane");
        this.panel = $("#tagcloud");
        this.cloud = $("<div class='tagcloud-tags'></div>").appendTo(this.panel);
    },

    show: function(listener) {
        this.listener = listener || jscape.storage.TagCloudView.LISTENER;
        return this;
    },

    refresh: function() {
        this.listener.onRefreshCloud();
    },

    loadData: function(data) {
        var c = this.layout.layout("panel", "center");
        var s = this.layout.layout("panel", "south");
        c.panel("resize", {height: s.panel("panel")._outerHeight() + c.panel("panel")._outerHeight()});
        s.panel("collapse").panel("close");

        if (data.total > 0) {
            this.cloud.empty();
            var quotedRe = /^"([^"]+)"$/;

            $.each(data.entries, $.proxy(function(_, entry) {
                var title = window.R.string.STORAGE_TAG_CLOUD_ENTRY_TITLE.supplant({0: entry.relevance});
                var text = quotedRe.test(entry.tag) ? entry.tag.replace(quotedRe, "$1") : entry.tag;
                var size = this.MIN_SIZE + (entry.relevance / data.total) * this.SIZE_FACTOR;
                $("<a/>").appendTo(this.cloud)
                        .attr("title", title).attr("alt", title)
                        .linkbutton({plain: true, text: text.escapeXml(), onClick: $.proxy(this._onClick$Tag, this, entry.tag)})
                        .find(".l-btn-text").css("font-size", size + "px");
            }, this));
            s.panel("expand").panel("open").panel("resize", {height: "auto"});
            c.panel("resize", {height: c.panel("panel")._outerHeight() - s.panel("panel")._outerHeight()});
            this.layout.layout("resize");
        }
    },

    _onClick$Tag: function(tag) {
        this.listener.onTaggedFile(tag);
    }
});
jscape.storage.TagCloudView.LISTENER = {
    onRefreshCloud: $.noop,
    onTaggedFile: $.noop
};

/** @class */
jscape.storage.ViewFileController = Class.extend(
    /** @lends {jscape.storage.ViewFileController.prototype} */
{
    deferred: null,

    initialize: function() {
        var self = this;
        $(document).on("loaderror.docviewer", function (e, error) {
            self.onLoadError.call(self, (error && error.message) || null);
        });
    },

    start: function(path) {
        this.deferred = $.Deferred();
        this.dialog = this._setupDialog(path);
        this.dialog.show(this, "/storage/viewer?" + $.param({file: path}));
        return this.deferred.promise();
    },

    onClose: function(dialog) {
        dialog && dialog.destroy();
        this.deferred.resolve();
        this.deferred = null;
    },

    onError: function(dialog, message) {
        this.onClose(dialog);
        $.messager.alert(window.R.string.APPLICATION_TITLE, message || window.R.string.STORAGE_ERROR_LOAD_VIEWER, "error");
    },

    onLoadError: function(message) {
        this.onError(this.dialog, message);
    },

    _setupDialog: function(path) {
        var dialog = new jscape.storage.ViewFileDialog();
        dialog.setPath(path);
        return dialog;
    }
});

/** @class */
jscape.storage.ViewFileDialog = Class.extend(
    /** @lends {jscape.storage.ViewFileDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        var initializing = true;

        this.dialog = $("<div/>").appendTo("body");
        this.dialog.dialog({
            width: "100%", height: "100%", border: "thin", draggable: false,
            modal: true, resizable: false, closable: true, closed: true,
            onClose: $.proxy(function () {if (!this.closing) { this._onClose(); }}, this),
            onBeforeLoad: function() { return !initializing; },
            onLoadError: $.proxy(this._onLoadError, this)
        }).show();

        initializing = false;
    },

    setPath: function(value) {
        this.dialog.dialog("setTitle", (value + "").escapeXml() || document.title);
    },

    show: function (listener, url) {
        this.listener = listener;
        this.dialog.dialog("open").dialog("center").dialog("refresh", url);
    },

    hide: function () {
        if (!this.dialog.dialog("options").closed) {
            this.closing = true;
            this.dialog.dialog("close");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
        this.dialog = null;
        this.listener = null;
    },

    _onLoadError: function(xhr, status, message) {
        this.hide();
        this.listener.onError(this, xhr ? xhr.responseText : message || status);
    },

    _onClose: function () {
        this.hide();
        this.listener.onClose(this);
    }
});

/** @class */
jscape.storage.ChangeDirController = Class.extend(
    /** @lends {jscape.storage.ChangeDirController.prototype} */
{
    deferred: null,

    start: function(info) {
        if (info instanceof jscape.FileInfo) {
            return jscape.API.changeDir(info.path);
        } else if (typeof info == "string") {
            return jscape.API.changeDir(info);
        }
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var path = dialog.getPath();
        dialog.destroy();
        jscape.API.changeDir(path)
                .done($.proxy(function(info) { this.deferred.resolve(info); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        return new jscape.storage.ChangeDirDialog();
    }
});

/** @class */
jscape.storage.ChangeDirDialog = Class.extend(
    /** @lends {jscape.storage.ChangeDirDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-storage-cwd").clone().attr("id", "");
        this.dialog.dialog({
            width: 500, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this)},
                {text: window.R.string.DIALOG_BUTTON_CANCEL || "Cancel", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=cwdfields]", this.dialog);

        this.path = $("input[name=path]", this.fieldset).textbox({required: true, validType: "non_empty", width: 390});
    },

    getPath: function() {
        return this.path.textbox("getValue");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").focus();
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});

/** @class */
jscape.storage.MakeDirController = Class.extend(
    /** @lends {jscape.storage.MakeDirController.prototype} */
{
    deferred: null,

    start: function() {
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var name = dialog.getName();
        dialog.destroy();
        jscape.API.makeDir(name)
                .done($.proxy(function() { this.deferred.resolve(name); this.deferred = null; name = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function() {
        return new jscape.storage.MakeDirDialog();
    }
});

/** @class */
jscape.storage.MakeDirDialog = Class.extend(
    /** @lends {jscape.storage.MakeDirDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-storage-mkdir").clone().attr("id", "");
        this.dialog.dialog({
            width: 500, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this)},
                {text: window.R.string.DIALOG_BUTTON_CANCEL || "Cancel", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=mkdirfields]", this.dialog);

        this.name = $("input[name=name]", this.fieldset).textbox({required: true, validType: "non_empty", width: 340});
    },

    getName: function() {
        return this.name.textbox("getValue");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.fieldset.find("input:first").focus();
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});

/** @class */
jscape.storage.DeletePathController = Class.extend(
    /** @lends {jscape.storage.DeletePathController.prototype} */
{
    start: function(files) {
        this.deferred = $.Deferred();
        var paths = $.map(files, function(file) { return file.path; });
        return this._confirmDelete(paths)
                .then(function() { return jscape.API.deleteFile(paths); });
    },

    _confirmDelete: function(files) {
        return jscape.ConfirmDialog.confirm(window.R.string.STORAGE_CONFIRM_DELETE_TITLE,
                                            window.R.string.STORAGE_CONFIRM_DELETE_MESSAGE);
    }
});

/** @class */
jscape.storage.TagFileController = Class.extend(
    /** @lends {jscape.storage.TagFileController.prototype} */
{
    deferred: null,

    start: function(files) {
        this.deferred = $.Deferred();
        this.paths = $.map(files, function(file) { return file.path; });
        if (this.paths.length == 1) {
            jscape.API.tagsFor(this.paths[0])
                    .done($.proxy(function(tags) { this._setupDialog(this.paths[0], tags).show(this); }, this))
                    .fail($.proxy(function () { this.deferred.reject(); this.deferred = null; }, this));
        } else {
            this._setupBatchDialog(this.paths).show(this);
        }
        return this.deferred.promise();
    },

    _setupDialog: function(path, tags) {
        var dialog = new jscape.storage.TagFileDialog();
        dialog.setPath(path);
        dialog.setTags(tags);
        return dialog;
    },

    _setupBatchDialog: function(paths) {
        var dialog = new jscape.storage.TagFileBatchDialog();
        dialog.setPaths(paths);
        return dialog;
    },

    onSubmit: function (dialog) {
        var tags = dialog.getTags();
        dialog.destroy();
        return jscape.API.tagFile(this.paths, tags)
                .done($.proxy(function () { this.deferred.resolve(); this.deferred = null; }, this))
                .fail($.proxy(function () { this.deferred.reject(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    }
});

/** @class */
jscape.storage.BaseTagDialog = Class.extend(
    /** @lends {jscape.storage.BaseTagDialog.prototype} */
{
    /** @constructor */
    initialize: function(dialog) {
        this.dialog = dialog.clone().attr("id", "");
        this.dialog.dialog({
            width: 530, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this)},
                {text: window.R.string.DIALOG_BUTTON_CANCEL || "Cancel", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=tagfilefields]", this.dialog);
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});

/** @class */
jscape.storage.TagFileDialog = jscape.storage.BaseTagDialog.extend(
    /** @lends {jscape.storage.TagFileDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this._super($("#d-storage-tagfile"));

        this.file = $("input[name=file]", this.fieldset).textbox({editable: false, readonly: true, width: "70%"});
        this.tags = $("input[name=keywords]", this.fieldset).textbox({width: "70%"});
    },

    getTags: function() {
        return this.tags.textbox("getValue");
    },

    setTags: function(value) {
        this.tags.textbox("setValue", value);
    },

    setPath: function(value) {
        this.file.textbox("setValue", value);
    },

    /**@override*/
    show: function(listener) {
        this._super(listener);
        this.tags.textbox("textbox").select().focus();
    }
});

/** @class */
jscape.storage.TagFileBatchDialog = jscape.storage.BaseTagDialog.extend(
    /** @lends {jscape.storage.TagFileDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this._super($("#d-storage-tagfilebatch"));

        this.files = $("input[name=files]", this.fieldset).textbox({editable: false, readonly: true, multiline: true, width: "70%"});
        this.tags = $("input[name=keywords]", this.fieldset).textbox({required: true, width: "70%"});
    },

    getTags: function() {
        return this.tags.textbox("getValue");
    },

    setPaths: function(values) {
        this.files.textbox("setValue", values.join("\r\n"));
    }
});

/** @class */
jscape.storage.EmailFileController = Class.extend(
    /** @lends {jscape.storage.EmailFileController.prototype} */
{
    addContactAllowed: false,
    deferred: null,

    initialize: function(addContactAllowed) {
        this.addContactAllowed = !!addContactAllowed;
        this._initValidators();
    },

    start: function(files) {
        this.deferred = $.Deferred();
        this.paths = $.map(files, function(file) { return file.path; });
        this._setupDialog(this.paths).show(this);
        return this.deferred.promise();
    },

    onAddContact: function(dialog) {
        if (!this.addContactAllowed) {
            return $.Deferred().reject().promise();
        }
        return new jscape.storage.AddContactController()
                .start()
                .done(function (contact) {
                          jscape.API.getContacts({sort: "NAME", order: "ASC"})
                                  .done(function (contacts) {
                                            dialog.setContacts(contacts);
                                            dialog.selectRecipient(contact);
                                        });
                      });
    },

    onSubmit: function(dialog) {
        return jscape.API.emailFile(this._createData(dialog))
                .done($.proxy(function() { dialog.destroy(); this.deferred.resolve(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _setupDialog: function(paths) {
        var dialog = new jscape.storage.EmailFileDialog(this.addContactAllowed);
        jscape.API.composeEmailFile(paths)
                .done($.proxy(function (data) {
                          if (data) {
                              dialog.setPaths(data.paths);
                              dialog.setSender(data.sender);
                              dialog.setReplyTo(data.replyTo);
                              dialog.setSubject(data.subject);
                              dialog.setExpires(data.expireAfterDays);
                          }
                      }, this))
                .fail($.proxy(function () { dialog.setPaths(paths); }, this));
        jscape.API.getContacts({sort: "NAME", order: "ASC"})
                .done(function (contacts) { dialog.setContacts(contacts); });
        return dialog;
    },

    _createData: function(dialog) {
        return new jscape.EmailFileData(this.paths,
                                        dialog.getSender(),
                                        dialog.getReplyTo(),
                                        dialog.getRecipient(),
                                        dialog.getCcRecipient(),
                                        dialog.getBccRecipient(),
                                        dialog.getSubject(),
                                        dialog.getMessage(),
                                        dialog.getProtection(),
                                        dialog.getPassword(),
                                        dialog.getExpires(),
                                        dialog.getMaxDownloads(),
                                        dialog.isDeleteAfterMaxDownloads()
        );
    },

    _initValidators: function () {
        var v = new jscape.Validator();
        v.rule("password_compliance", $.proxy(this._passwordCompliant, this), "Password is not compliant.");
    },

    _passwordCompliant: function (value, params) {
        var accountName = params && params.length ? params[0] : null;
        var result = jscape.API.testPasswordCompliance(accountName, value);
        if ($.isPlainObject(result)) {
            if (result.valid === false && result.message) {
                new jscape.Validator().ruleMessage("password_compliance", result.message);
            }
            return !!result.valid;
        }
        return true;
    }
});

/** @class */
jscape.storage.EmailFileDialog = Class.extend(
    /** @lends {jscape.storage.EmailFileDialog.prototype} */
{
    /** @constructor */
    initialize: function(addContactAllowed) {
        var okButtonId = Math.random().toString(36).substr(2);
        this.dialog = $("#d-storage-adhoc").clone().attr("id", "");
        this.dialog.dialog({
            width: 800, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this), id: okButtonId},
                {text: window.R.string.DIALOG_BUTTON_CANCEL || "Cancel", handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).keydown($.proxy(function (e) {
            if (e.keyCode == 27) {
                e.stopPropagation();
                this._onCancel();
            }
        }, this)).show();
        this.okButton = $("#" + okButtonId);

        this.fieldset = $("fieldset[name=adhoctransferfields]", this.dialog);

        this.recipient = $("input[name=to]", this.fieldset).tagbox({
            required: true,
            editable: true,
            limitToList: false,
            separator: ",",
            validType: ["non_empty", "multi_value['email',',']"],
            valueField: "id",
            textField: "displayName",
            val: $.proxy(this._valueFor, this),
            parser: $.proxy(this._parseContactData, this),
            formatter: $.proxy(this._formatContactRow, this),
            filter: $.proxy(this._filterByEmailAddress, this),
            tagFormatter: $.proxy(this._formatTag, this),
            tagStyler: $.proxy(this._styleTag, this),
            hasDownArrow: true,
            width: "60%",
            panelHeight: "auto",
            panelMaxHeight: 400,
            icons: !!addContactAllowed ? [{iconCls: "icon-add", handler: $.proxy(this._onClick$AddContactButton, this)}] : undefined,
            tipPosition: "right",
            onChange: $.proxy(function() {
                this.recipient.tagbox("textbox").validatebox("options").required = this.recipient.tagbox("getValues").length == 0;
                this.recipient.tagbox("isValid");
            }, this),
            invalidMessage: window.R.string.STORAGE_ERROR_BAD_RECIPIENT
        });
        this.ccRecipient = $("input[name=cc]", this.fieldset).tagbox({
            required: false,
            editable: true,
            limitToList: false,
            separator: ",",
            validType: ["non_empty", "multi_value['email',',']"],
            valueField: "id",
            textField: "displayName",
            val: $.proxy(this._valueFor, this),
            parser: $.proxy(this._parseContactData, this),
            formatter: $.proxy(this._formatContactRow, this),
            filter: $.proxy(this._filterByEmailAddress, this),
            tagFormatter: $.proxy(this._formatTag, this),
            tagStyler: $.proxy(this._styleTag, this),
            hasDownArrow: true,
            width: "70%",
            panelHeight: "auto",
            invalidMessage: window.R.string.STORAGE_ERROR_BAD_RECIPIENT
        });
        this.ccRecipientRow = $("#ccrecipients", this.fieldset).hide();
        this.addCcButton = $("a[role=button][name=addcc]", this.fieldset).linkbutton({plain: true, onClick: $.proxy(function() {
            this.ccRecipientRow.show();
            this.addCcButton.tooltip("destroy").remove();
            this.ccRecipient.tagbox("resize").tagbox("textbox").focus();
            delete this.addCcButton;
            if (!this.hasOwnProperty("addBccButton")) {
                this.recipient.tagbox("resize", "70%");
            }
        }, this)});
        this.addCcButton.tooltip({content: this.addCcButton.attr("title"), showDelay: 0}).attr("title", "");

        this.bccRecipient = $("input[name=bcc]", this.fieldset).tagbox({
            required: false,
            editable: true,
            limitToList: false,
            separator: ",",
            validType: ["non_empty", "multi_value['email',',']"],
            valueField: "id",
            textField: "displayName",
            val: $.proxy(this._valueFor, this),
            parser: $.proxy(this._parseContactData, this),
            formatter: $.proxy(this._formatContactRow, this),
            filter: $.proxy(this._filterByEmailAddress, this),
            tagFormatter: $.proxy(this._formatTag, this),
            tagStyler: $.proxy(this._styleTag, this),
            hasDownArrow: true,
            width: "70%",
            panelHeight: "auto",
            invalidMessage: window.R.string.STORAGE_ERROR_BAD_RECIPIENT
        });
        this.bccRecipientRow = $("#bccrecipients", this.fieldset).hide();
        this.addBccButton = $("a[role=button][name=addbcc]", this.fieldset).linkbutton({plain: true, onClick: $.proxy(function() {
            this.bccRecipientRow.show();
            this.addBccButton.tooltip("destroy").remove();
            this.bccRecipient.tagbox("resize").tagbox("textbox").focus();
            delete this.addBccButton;
            if (!this.hasOwnProperty("addCcButton")) {
                this.recipient.tagbox("resize", "70%");
            }
        }, this)});
        this.addBccButton.tooltip({content: this.addBccButton.attr("title"), showDelay: 0}).attr("title", "");

        this.sender = $("input[name=from]", this.fieldset).textbox({required: true, editable: true, validType: "email", width: "60%", tipPosition: "bottom", invalidMessage: window.R.string.STORAGE_ERROR_BAD_SENDER});
        this.replyTo = $("input[name=replyto]", this.fieldset).textbox({required: false, editable: true, validType: "email", width: "70%"});
        this.replyToRow = $("#replytoaddress", this.fieldset).hide();
        this.addReplyToButton = $("a[role=button][name=addreplyto]", this.fieldset).linkbutton({plain: true, onClick: $.proxy(function() {
            this._onClick$AddReplyToButton();
            this.replyTo.textbox("textbox").focus();
        }, this)});
        this.addReplyToButton.tooltip({content: this.addReplyToButton.attr("title"), showDelay: 0}).attr("title", "");

        this.subject = $("input[name=subject]", this.fieldset).textbox({required: true, validType: "non_empty", width: "70%", invalidMessage: window.R.string.STORAGE_ERROR_BAD_SUBJECT});
        this.message = $("textarea[name=message]", this.fieldset).textbox({multiline: true, width: "70%", height: 140});
        this.files = $("textarea[name=filelist]", this.fieldset).textbox({editable: false, readonly: true, multiline: true, width: "70%", height: 75});
        this.protection = $("select[name=protectwith]", this.fieldset).combobox({editable: false, onChange: $.proxy(function(value) {
            var enable = "CUSTOM" === value;
            this.password.textbox(enable ? "enable" : "disable").textbox(enable ? "enableValidation" : "disableValidation").textbox("isValid");
        }, this), width: 300, panelHeight: "auto"});
        this.password = $("input[name=custompwd]", this.fieldset).textbox({required: true, validType: "password_compliance", width: 160});

        this.useExpires = $("input[name=useexpires]:checkbox", this.fieldset).change($.proxy(function() {
            var enabled = this.useExpires.is(":checked");
            this.expires.combobox(enabled ? "enable" : "disable").combobox(enabled ? "enableValidation" : "disableValidation");
        }, this));
        this.expires = $("select[name=expires]", this.fieldset).combobox({required: true, disabled: true, width: 60});

        this.useMaxDownloads = $("input[name=usemaxdloads]:checkbox", this.fieldset).change($.proxy(function() {
            var enabled = this.useMaxDownloads.is(":checked");
            this.maxDownloads.numberspinner(enabled ? "enable" : "disable").numberspinner(enabled ? "enableValidation" : "disableValidation");
            this.deleteAfterMaxDownloads.prop("disabled", !enabled);
        }, this));
        this.maxDownloads = $("input[name=maxdownloads]", this.fieldset).numberspinner({required: true, disabled: true, min: 1, width: 60});
        this.deleteAfterMaxDownloads = $("input[name=deleteaftermaxdownloads]:checkbox", this.fieldset);
    },

    setPaths: function(values) {
        this.files.textbox("setValue", (values || []).join("\r\n"));
    },

    setContacts: function(values) {
        this.recipient.tagbox("loadData", values);
        this.ccRecipient.tagbox("loadData", values);
        this.bccRecipient.tagbox("loadData", values);
    },

    getRecipient: function() {
        return this._parseContactData(this.recipient);
    },

    selectRecipient: function(value) {
        this.recipient.tagbox("select", value);
    },

    getCcRecipient: function() {
        return this.ccRecipient.length ? this._parseContactData(this.ccRecipient) : null;
    },

    getBccRecipient: function() {
        return this.bccRecipient.length ? this._parseContactData(this.bccRecipient) : null;
    },

    getSender: function() {
        return this.sender.textbox("getValue");
    },

    setSender: function(value) {
        this.sender.textbox("setValue", value);

        if (this.sender.textbox("isValid")) {
            this.sender.textbox("readonly", true).textbox("disable");
        } else {
            this.sender.textbox("readonly", false).textbox("enable");
        }
    },

    getReplyTo: function() {
        return this.replyTo.length ? this.replyTo.textbox("getValue") : null;
    },

    setReplyTo: function(value) {
        if (value) {
            if (!this.replyTo.is(":visible")) {
                this._onClick$AddReplyToButton();
            }
            this.replyTo.textbox("setValue", value);
        }
    },

    getSubject: function() {
        return this.subject.textbox("getValue");
    },

    setSubject: function(value) {
        this.subject.textbox("setValue", value);
    },

    getMessage: function() {
        return this.message.textbox("getValue");
    },

    getProtection: function() {
        return this.protection.length ? this.protection.combobox("getValue") : null;
    },

    getPassword: function() {
        return this.password.length && !this.password.textbox("options").disabled ? this.password.textbox("getValue") : null;
    },

    getExpires: function() {
        return this.useExpires.is(":checked") ? parseInt(this.expires.combobox("getValue")) : null;
    },

    setExpires: function(value) {
        value = parseInt(value);
        if (!isNaN(value)) {
            this.expires.combobox("select", value);
        }
        this.useExpires.prop("checked", !isNaN(value)).change();
    },

    getMaxDownloads: function() {
        return this.useMaxDownloads.is(":checked") ? parseInt(this.maxDownloads.numberspinner("getValue")) : null;
    },

    setMaxDownloads: function(value) {
        value = parseInt(value);
        if (isNaN(value)) {
            this.useMaxDownloads.prop("checked", false).change();
        } else {
            this.useMaxDownloads.prop("checked", true).change();
            this.maxDownloads.numberspinner("setValue", value);
        }
    },

    isDeleteAfterMaxDownloads: function() {
        return this.deleteAfterMaxDownloads.is(":checked:not(:disabled)");
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.useExpires.change();
        this.useMaxDownloads.change();
        this.fieldset.form("validate");
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.hide();
        this.dialog.dialog("destroy");
    },

    _filterByEmailAddress: function(query, row) {
        query = query.toLowerCase();
        return row && ((row.name || "").toLowerCase().indexOf(query) != -1 || row.email.toLowerCase().indexOf(query) != -1);
    },

    _formatTag: function(value, row) {
        return row ? this._escapeXml(row.name ? row.name : row.email) : value;
    },

    _styleTag: function() {
        return "color:#222";
    },

    _escapeXml: function(value) {
        var entities = {"<": "&lt;", ">": "&gt;", "&": "&amp;", '"': "&quot;"};
        return value.replace(/[<>&"]/g, function (entry) { return entities[entry]});
    },

    _formatContactRow: function (contact) {
        return contact.name && contact.name.trim().length
                ? this._escapeXml(contact.name) + "&nbsp;<span style=\"color:#777\">" + this._escapeXml("<" + contact.email + ">") + "</span>"
                : this._escapeXml(contact.email);
    },

    _parseContactData: function(jq) {
        var data = jq.tagbox("getData");
        return $.map(jq.tagbox("getValues"), function(value) {
            for (var i = 0; i < data.length; i++) {
                if (data[i].id == value) {
                    return data[i].email;
                }
            }
            return value;
        });
    },

    _valueFor: function (jq) {
        var t = $(jq).parent().prev();
        var parser = t.tagbox("options").parser;
        var values = typeof parser == "function" ? parser(t) : t.tagbox("getValues");
        if ($(jq).is(":focus")) {
            values.push($(jq).val());
        }
        return values.join(t.tagbox("options").separator);
    },

    _onClick$AddContactButton: function(e) {
        e.stopPropagation();
        this.listener.onAddContact(this);
    },

    _onClick$AddReplyToButton: function() {
        this.replyToRow.show();
        this.addReplyToButton.tooltip("destroy").remove();
        delete this.addReplyToButton;
        this.replyTo.textbox("resize");
        this.sender.textbox("resize", "70%");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.okButton.linkbutton("disable");
            this.listener.onSubmit(this)
                    .fail($.proxy(function() { this.okButton.linkbutton("enable"); }, this));
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});

/** @class */
jscape.storage.AddContactController = Class.extend(
    /** @lends {jscape.storage.AddContactController.prototype} */
{
    deferred: null,

    start: function() {
        this.deferred = $.Deferred();
        this._setupDialog().show(this);
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var data = this._createData(dialog);
        dialog.destroy();

        jscape.API.addContact(data)
                .done($.proxy(function(contact) { this.deferred.resolve(contact); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

    onCancel: function(dialog) {
        dialog.destroy();
        this.deferred.reject();
        this.deferred = null;
    },

    _createData: function(dialog) {
        return {name: dialog.getName(), email: dialog.getEmail(), company: dialog.getCompany()};
    },

    _setupDialog: function () {
        return new jscape.storage.AddContactDialog();
    }
});

/** @class */
jscape.storage.AddContactDialog = Class.extend(
    /** @lends {jscape.storage.AddContactDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-contacts-add").clone().attr("id", "");
        this.dialog.dialog({
            width: 530, height: "auto",
            modal: true, resizable: false, closable: true, closed: true,
            buttons: [
                {text: window.R.string.DIALOG_BUTTON_OK, handler: $.proxy(this._onSubmit, this)},
                {text: window.R.string.DIALOG_BUTTON_CANCEL, handler: $.proxy(this._onCancel, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

        this.fieldset = $("fieldset[name=contactfields]", this.dialog);

        this.name = $("[name=name]", this.fieldset).textbox({required: true, validType: "non_empty", missingMessage: window.R.string.STORAGE_ERROR_BAD_CONTACT_NAME, invalidMessage: window.R.string.STORAGE_ERROR_BAD_CONTACT_NAME, width: "70%"});
        this.email = $("[name=email]", this.fieldset).textbox({required: true, validType: "multi_value['email',',']", missingMessage: window.R.string.STORAGE_ERROR_BAD_CONTACT_EMAIL, invalidMessage: window.R.string.STORAGE_ERROR_BAD_CONTACT_EMAIL, width: "70%"});
        this.company = $("[name=company]", this.fieldset).textbox({required: false, width: "70%"});
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("validate");
        this.name.textbox("textbox").focus();
    },

    hide: function () {
        if (!this._closed()) {
            this.closing = true;
            this.dialog.dialog("close");
            this.fieldset.form("disableValidation");
            this.closing = false;
        }
    },

    destroy: function() {
        this.dialog.dialog("destroy");
    },

    getName: function() {
        return this.name.textbox("getValue");
    },

    getEmail: function() {
        return this.email.textbox("getValue");
    },

    getCompany: function() {
        return this.company.textbox("getValue");
    },

    _onSubmit: function () {
        if (this.fieldset.form("validate")) {
            this.hide();
            this.listener.onSubmit(this);
        }
    },

    _onCancel: function () {
        this.hide();
        this.listener.onCancel(this);
    },

    _closed: function () {
        return !!this.dialog.dialog("options").closed;
    }
});