jscape.domain = jscape.domain || {};

/** @class */
jscape.Group = Class.extend(
    /** @lends {jscape.Group.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c) {
        this.name = a || "";
        this.resources = b || [];
        this.tags = c || [];
    }
});
jscape.Group.fromJSON = function(data) {
    return data ? new jscape.Group(data.name, $.map(data.resources || [], jscape.VirtualFile.fromJSON), data.tags) : null;
};

/** @class */
jscape.domain.GroupsModule = jscape.domain.DomainModule.extend(
    /** @lends {jscape.domain.GroupsModule.prototype} */
{
    onCreate: function () {
        this._super.apply(this, arguments);

        this.controller = new jscape.domain.GroupsController();
        this.controller.onCreate(this.managementService, this.domainName);
    },

    onStart: function () {
        this.controller.onStart();
    },

    onResume: function() {
        this.controller.onResume();
    }
});

/** @class */
jscape.domain.GroupsController = jscape.ui.DatagridController.extend(
    /** @lends {jscape.domain.GroupsController.prototype} */
{
    view: null,

    /** @constructor */
    initialize: function() {
        this._initValidators();
    },

    onCreate: function (managementService, domainName) {
        this.managementService = managementService;
        this.domainName = domainName;

        this.addController = new jscape.domain.AddGroupController();
        this.editController = new jscape.domain.EditGroupController();
        this.copyController = new jscape.domain.CopyGroupController();
        this.usersController = new jscape.domain.GroupUsersController();
        this.deleteController = new jscape.domain.DeleteGroupController();
    },

    onStart: function () {
        if (this.view == null) {
            this.view = new jscape.domain.GroupsView();
        }
        this.view.show(this);
    },

    onResume: function() {
        this.view.refresh();
    },

    onRefresh: function (params) {
        params = jscape.Page.requestParameters(params);
        return this.managementService.listGroups(this.domainName, params.startIndex, params.count, params.query)
                .then(jscape.Page.datagridFormatter());
    },

    onAddGroup: function() {
        this.addController.start(this.managementService, this.domainName)
                .done($.proxy(this._groupAdded, this));
    },

    onCopyGroup: function(group) {
        this.copyController.start(this.managementService, this.domainName, group.name)
                .done($.proxy(this._groupAdded, this));
    },

    onEditGroup: function(group) {
        var index = $.inArray(group, this.view.getGroups());
        if (index != -1) {
            this.editController.start(this.managementService, this.domainName, group)
                    .done($.proxy(this._groupEdited, this, index));
        }
    },

    onDeleteGroup: function(groups) {
        this.deleteController.start(this.managementService, this.domainName, groups)
                .done($.proxy(this._groupDeleted, this, groups));
    },

    onGroupAccounts: function(group) {
        this.usersController.start(this.managementService, this.domainName, group.name);
    },

    _groupAdded: function(group) {
        this._recordAdded(group);
    },

    _groupEdited: function(index, group) {
        this._recordEdited(group);
    },

    _groupDeleted: function (groups) {
        this._recordDeleted(groups);
    },

    _initValidators: function() {
        var v = new jscape.Validator();
        v.nonEmptyRule("group_name_nonempty", window.R.string.GROUPS_ERROR_BAD_NAME);
        v.rule("group_unique", $.proxy(this._groupNotExists, this), window.R.string.GROUPS_ERROR_ALREADY_EXIST);
        v.nonEmptyRule("group_path_nonempty", window.R.string.GROUPS_ERROR_BAD_PATH);
        v.nonEmptyRule("group_realpath_nonempty", window.R.string.GROUPS_ERROR_BAD_REAL_PATH);
    },

    _groupNotExists: function(groupName) {
        groupName = (groupName || "").toUpperCase();
        var values = $.map(this.view.getGroups(), function(group) { return group.name.toUpperCase(); });
        return $.inArray(groupName, values) == -1;
    }
});

/** @class */
jscape.domain.GroupsView = jscape.ui.DatagridView.extend(
    /** @lends {jscape.domain.GroupsView.prototype} */
{
    selection: null,

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

        this.fieldset = $("fieldset[name=groupsfields]");

        this.data = $("table[role=grid][aria-label=groups]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns: true,
            singleSelect: true,
            pagination: true,
            search: true,
            remoteSearch: true,
            sortName: "name",
            idField: "name",
            columns: [[
                {field: "name", title: $("thead th:nth-child(1)", this.data).text(), sortable: true, width: "50%"},
                {field: "tags", title: $("thead th:nth-child(2)", this.data).text(), width: "50%", formatter: function(value) { return String($.isArray(value) ? value.join(", ") : value).escapeXml(); }}
            ]],
            onSelect: $.proxy(this._adjustRowSelection, this),
            onUnselect: $.proxy(this._adjustRowSelection, this),
            onDblClickRow: $.proxy(this._onClick$EditButton, this),
            loader: $.proxy(this._onLoad$Data, this),
            onBeforeLoad: function() { return !initializing; },
            onLoadSuccess: $.proxy(this._adjustRowSelection, this)
        });

        this.addButton = $("a[role=button][name=add]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$AddButton, this)});
        this.editButton = $("a[role=button][name=edit]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$EditButton, this)});
        this.copyButton = $("a[role=button][name=copy]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$CopyButton, this)});
        this.usersButton = $("a[role=button][name=users]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$UsersButton, this)});
        this.deleteButton = $("a[role=button][name=delete]", this.fieldset).linkbutton({disabled: true, onClick: $.proxy(this._onClick$DeleteButton, this)});

        initializing = false;
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.GroupsView.LISTENER;
        this._adjustRowSelection();
    },

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

    _onLoad$Data: function(params, success, error) {
        this.listener.onRefresh(params).done(success).fail(error);
    },

    _onClick$AddButton: function () {
        this.listener.onAddGroup();
    },

    _onClick$EditButton: function () {
        this.listener.onEditGroup(this.selection[0]);
    },

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

    _onClick$CopyButton: function () {
        this.listener.onCopyGroup(this.selection[0]);
    },

    _onClick$UsersButton: function () {
        this.listener.onGroupAccounts(this.selection[0]);
    },

    _adjustRowSelection: function () {
        this.selection = this.data.datagrid("getSelections");
        this._adjustButtonsState();
    },

    _adjustButtonsState: function() {
        this.editButton.linkbutton(this.selection.length == 1 ? "enable" : "disable");
        this.copyButton.linkbutton(this.selection.length == 1 ? "enable" : "disable");
        this.usersButton.linkbutton(this.selection.length == 1 ? "enable" : "disable");
        this.deleteButton.linkbutton(this.selection.length != 0 ? "enable" : "disable");
    }
});
jscape.domain.GroupsView.LISTENER = {
    onRefresh: $.noop,
    onAddGroup: $.noop,
    onEditGroup: $.noop,
    onCopyGroup: $.noop,
    onDeleteGroup: $.noop,
    onGroupAccounts: $.noop
};

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

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

    onSubmit: function(dialog) {
        var group = this._createGroup(dialog);
        dialog.destroy();

        this.managementService.addGroup(this.domainName, group)
                .done($.proxy(function(group) { this.deferred.resolve(group); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    onSelectPath: function(dialog) {
        new SelectPathController($.proxy(this.managementService.listFileSystem, this.managementService),
                                 $.proxy(this.managementService.createFileSystemDir, this.managementService))
                .selectDirectory()
                .done(function(entry) { dialog.setRealPath(entry.path); });
    },

    _createGroup: function(dialog) {
        var permissions = new jscape.AccessPermissions();
        var file = dialog.getReverseProxy() != null
                   ? jscape.VirtualFile.remote(dialog.getPath(), permissions, false, false, dialog.getReverseProxy())
                   : jscape.VirtualFile.local(dialog.getPath(), permissions, false, false, true, dialog.getRealPath(), true);

        return new jscape.Group(dialog.getGroupName(), [file], dialog.getTags());
    },

    _setupDialog: function () {
        var dialog = new jscape.domain.AddGroupDialog();
        this.managementService.getReverseProxyNames(this.domainName).done(function (names) { dialog.setReverseProxies(names); });
        this.managementService.getDomainTags(this.domainName).done(function(tags) { dialog.setAvailableTags(tags); });
        return dialog;
    }
});

/** @class */
jscape.domain.AddGroupDialog = Class.extend(
    /** @lends {jscape.domain.AddGroupDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-group-add").clone().attr("id", new Date().getTime().toString(36));
        this.dialog.dialog({
            width: 630, 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=addgroupfields]", this.dialog);

        this.name = $("input[name=name]", this.fieldset).textbox({required: true, validType: ["group_name_nonempty", "group_unique"], missingMessage: window.R.string.GROUPS_ERROR_BAD_NAME, width: "60%"});
        this.path = $("input[name=path]", this.fieldset).textbox({required: true, validType: "group_path_nonempty", missingMessage: window.R.string.GROUPS_ERROR_BAD_PATH, width: "60%"});

        this.type = $("input[name=type]:radio", this.fieldset);
        this.typeRealPath = this.type.filter("[value=realpath]");
        this.typeReverseProxy = this.type.filter("[value=reverseproxy]");
        this.typeRealPath.on("change", $.proxy(this._adjustFieldState, this));
        this.typeReverseProxy.on("change", $.proxy(this._adjustFieldState, this));

        this.realPath = $("input[name=realpath]", this.fieldset).textbox({required: true, validType: "group_realpath_nonempty", missingMessage: window.R.string.GROUPS_ERROR_BAD_REAL_PATH, width: "60%"});
        this.browseButton = $("a[role=button][name=browse]", this.fieldset).linkbutton({onClick: $.proxy(this._onClick$BrowseButton, this)});

        this.reverseProxy = $("select[name=reverseproxy]", this.fieldset);
        this.reverseProxy.combobox({
            required: true,
            editable: true,
            limitToList: true,
            disabled: true,
            missingMessage: window.R.string.GROUPS_ERROR_BAD_REVERSE_PROXY,
            width: "60%",
            panelHeight: "auto",
            onLoadSuccess: function() {
                var data = $(this).combobox("getData");
                var value = $(this).combobox("getValue") || (data.length && data[0].value);
                value && $(this).combobox("select", value);
            }
        });

        this.tags = $("select[name=tags]", this.fieldset).tagbox({limitToList: true, hasDownArrow: true, panelHeight: "auto", width: "60%", onLoadSuccess: function() { $(this).tagbox("clear"); }});
    },

    show: function(listener) {
        this.listener = listener || jscape.domain.AddGroupDialog.LISTENER;
        this.dialog.dialog("center").dialog("open");

        this.typeRealPath.prop("checked", true);
        this.setReverseProxies(null);
        this.fieldset.form("validate");
        this.name.textbox("textbox").focus();
    },

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

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

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

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

    getRealPath: function() {
        return this.typeRealPath.is(":checked") ? this.realPath.textbox("getValue") : null;
    },

    setRealPath: function(value) {
        this.typeRealPath.prop("checked", true).change();
        this.realPath.textbox("setValue", value).textbox("textbox").focus();
    },

    setReverseProxies: function(values) {
        if (values && values.length) {
            this.reverseProxy.combobox("loadData", values);
            this.typeReverseProxy.prop("disabled", false);
        } else {
            this.reverseProxy.combobox("disable").combobox("disableValidation");
            this.typeReverseProxy.prop("disabled", true);
        }
    },

    getReverseProxy: function() {
        return this.typeReverseProxy.is(":checked") ? this.reverseProxy.combobox("getValue") : null;
    },

    setAvailableTags: function (values) {
        this.tags.tagbox("loadData", values);
    },

    getTags: function () {
        return this.tags.tagbox("getValues");
    },

    setTags: function (values) {
        this.tags.tagbox("setValues", values);
    },

    _onClick$BrowseButton: function() {
        this.listener.onSelectPath(this);
    },

    _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;
    },

    _adjustFieldState: function() {
        if (this.typeRealPath.is(":checked")) {
            this.realPath.textbox("enable").textbox("enableValidation").textbox("isValid");
            this.browseButton.linkbutton("enable");
            this.reverseProxy.combobox("disable").combobox("disableValidation");
        } else if (this.typeReverseProxy.is(":checked")) {
            this.realPath.textbox("disable").textbox("disableValidation");
            this.browseButton.linkbutton("disable");
            this.reverseProxy.combobox("enable").combobox("enableValidation").combobox("isValid");
        }
    }
});
jscape.domain.AddGroupDialog.LISTENER = {
    onSelectPath: $.noop,
    onSubmit: $.noop,
    onCancel: $.noop
};

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

    /** @constructor */
    initialize: function () {
        this.vfsController = null;
    },

    start: function (managementService, domainName, group) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.groupName = group.name;
        this.deferred = $.Deferred();

        this.managementService.getGroup(this.domainName, this.groupName)
                .done($.proxy(function(group) { this._setupDialog(group).show(this); }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));

        return this.deferred.promise();
    },

    onSubmit: function (dialog) {
        var group = this._createGroup(dialog);
        dialog.destroy();

        this.managementService.setGroup(this.domainName, group.name, group)
                .done($.proxy(function() { this.deferred.resolve(group); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    },

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

    _createGroup: function(dialog) {
        return new jscape.Group(this.groupName, this.vfsController.getDescriptors(), dialog.getTags());
    },

    _setupDialog: function (group) {
        var dialog = new jscape.domain.EditGroupDialog();
        dialog.setGroupName(group.name);
        this.managementService.getDomainTags(this.domainName)
                .done(function (tags) {
                          dialog.setAvailableTags(tags);
                          dialog.setTags(group.tags);
                      });
        this.vfsController = new jscape.vfs.VirtualFilesController(dialog.container());
        this.vfsController.start(this.managementService, this.domainName, group.resources);
        return dialog;
    }
});

/** @class */
jscape.domain.EditGroupDialog = Class.extend(
    /** @lends {jscape.domain.EditGroupDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-group-edit").clone().attr("id", "");
        this.dialog.dialog({
            width: 650, 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.title = this.dialog.dialog("header").text() || "";

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

        this.tags = $("select[name=tags]", this.fieldset).tagbox({limitToList: true, hasDownArrow: true, panelHeight: "auto", width: "60%", onLoadSuccess: function() { $(this).tagbox("clear"); }});
    },

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

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

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

    container: function() {
        return this.fieldset;
    },

    setGroupName: function(name) {
        this.dialog.dialog("setTitle", this.title.supplant({0: name}));
    },

    setAvailableTags: function (values) {
        this.tags.tagbox("loadData", values);
    },

    getTags: function () {
        return this.tags.tagbox("getValues");
    },

    setTags: function (values) {
        this.tags.tagbox("setValues", values);
    },

    _onSubmit: function () {
        this.hide();
        this.listener.onSubmit(this);
    },

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

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

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

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

    onSubmit: function(dialog) {
        var targetGroupName = dialog.getGroupName();
        dialog.destroy();

        this.managementService.copyGroup(this.domainName, this.sourceGroupName, targetGroupName)
                .done($.proxy(function(group) { this.deferred.resolve(group); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this))
    },

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

    _setupDialog: function (groupName) {
        var dialog = new jscape.domain.CopyGroupDialog();
        dialog.setCopyGroup(groupName);
        return dialog;
    }
});

/** @class */
jscape.domain.CopyGroupDialog = Class.extend(
    /** @lends {jscape.domain.CopyGroupDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-group-copy").clone().attr("id", new Date().getTime().toString(36));
        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.title = this.dialog.dialog("header").text() || "";
        this.fieldset = $("fieldset[name=copygroupfields]", this.dialog);

        this.name = $("[name=name]", this.fieldset).textbox({required: true, validType: ["group_name_nonempty", "group_unique"], missingMessage: window.R.string.GROUPS_ERROR_BAD_NAME, width: "60%"});
    },

    show: function (listener) {
        this.listener = listener;
        this.dialog.dialog("center").dialog("open");
        this.fieldset.form("enableValidation");
        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");
    },

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

    setCopyGroup: function(value) {
        this.dialog.dialog("setTitle", this.title.supplant({0: value}));
    },

    _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.domain.DeleteGroupController = Class.extend(
    /** @lends {jscape.domain.DeleteGroupController.prototype} */
{
    groups: null,
    deferred: null,

    start: function(managementService, domainName, groups) {
        this.managementService = managementService;
        this.domainName = domainName;
        this.deferred = $.Deferred();
        this._confirmDelete(groups)
                .done($.proxy(this._deleteConfirmed, this, groups))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
        return this.deferred.promise();
    },

    _confirmDelete: function(groups) {
        var text = $.map(groups, function(group) { return group.name; }).join(", ");
        return jscape.ConfirmDialog.confirm(
                window.R.string.GROUPS_CONFIRM_DELETE_TITLE,
                window.R.string.GROUPS_CONFIRM_DELETE_MESSAGE.supplant({0: text}));
    },

    _deleteConfirmed: function (groups) {
        $.when.apply(jQuery, $.map(groups, $.proxy(function(group) { return this.managementService.deleteGroup(this.domainName, group.name); }, this)))
                .done($.proxy(function() { this.deferred.resolve(groups); this.deferred = null; }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
    }
});

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

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

    onSubmit: function(dialog) {
        var users = dialog.getAssignedUsers();
        dialog.destroy();

        this.managementService.setGroupUsers(this.domainName, this.groupName, users)
                .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;
    },

    _setupDialog: function () {
        var dialog = new jscape.domain.GroupUsersDialog();
        dialog.setGroupName(this.groupName);

        $.when(this.managementService.getAccountNames(this.domainName),
               this.managementService.getGroupUsers(this.domainName, this.groupName))
                .done(function (accountNames, groupUsers) {
                          dialog.setUsers(accountNames);
                          dialog.setAssignedUsers(groupUsers);
                      })
                .fail($.proxy(function () {
                          this.deferred.reject();
                          this.deferred = null;
                      }, this));
        return dialog;
    }
});

/** @class */
jscape.domain.GroupUsersDialog = Class.extend(
    /** @lends {jscape.domain.GroupUsersDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-group-users").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=groupusersfields]", this.dialog);
        this.data = $("table[role=grid][aria-label=users]", this.fieldset);
        this.data.datagrid({
            fit: true,
            fitColumns:true,
            singleSelect: false,
            checkOnSelect: true,
            selectOnCheck: true,
            remoteSort: false,
            columns: [[
                {field: "_ck", checkbox: true},
                {field: "value", width: 100, formatter: function (_, row) {return String(row || "").escapeXml(); }}
            ]]
        });

        this.title = this.dialog.dialog("header").text();
    },

    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");
    },

    setGroupName: function (name) {
        this.dialog.dialog("setTitle", this.title.supplant({0: name}));
    },

    setUsers: function (values) {
        this.data.datagrid("loading")
                .datagrid("loadData", values.sort())
                .datagrid("loaded");
    },

    getAssignedUsers: function () {
        return this.data.datagrid("getSelections");
    },

    setAssignedUsers: function (values) {
        this.data.datagrid("clearSelections");

        if (values && values.length) {
            this.data.datagrid("loadData", this.data.datagrid("getData").sort(function (o1, o2) {
                var s1 = $.inArray(o1, values) != -1;
                var s2 = $.inArray(o2, values) != -1;
                return s1 && !s2 ? -1 : !s1 && s2 ? 1 : (o1 == o2 ? 0 : o1 > o2 ? 1 : -1);
            }));

            var rows = this.data.datagrid("getRows");
            for (var i = 0; i < values.length; i++) {
                var index = $.inArray(values[i], rows);
                this.data.datagrid("selectRow", index);
            }
        }
    },

    _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;
    }
});