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

/** @class */
jscape.UploadForm = Class.extend(
    /** @lends {jscape.UploadForm.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e) {
        this.name = a || "";
        this.description = b || "";
        this.enabled = !!c;
        this.batch = !!d;
        this.fields = e || [];
    }
});
jscape.UploadForm.fromJSON = function(data) {
    return data ? new jscape.UploadForm(data.name, data.description, data.enabled, data.batch, $.map(data.fields || [], jscape.UploadForm.Input.fromJSON)) : null;
};
/** @class */
jscape.UploadForm.Input = Class.extend(
    /** @lends {jscape.UploadForm.Input.prototype} */
{
    /** @constructor */
    initialize: function(a, b, c, d, e, f) {
        this.type = a || jscape.UploadForm.Input.TYPE.TEXT;
        this.name = b || "";
        this.label = c || "";
        this.value = $.isArray(d) ? d.join(",") : d || "";
        this.required = e !== undefined ? !!e : true;
        this.html = f || "";
    },

    values: function() {
        return this.multiple() ? this.value.split(",") : [this.value];
    },

    multiple: function() {
        return $.inArray(this.type, [jscape.UploadForm.Input.TYPE.DROPDOWN, jscape.UploadForm.Input.TYPE.MULTISELECT, jscape.UploadForm.Input.TYPE.RADIO]) != -1;
    }
});
jscape.UploadForm.Input.TYPE = {TEXT: "TEXT", MEMO: "MEMO", DROPDOWN: "DROPDOWN", MULTISELECT: "MULTISELECT", RADIO: "RADIO"};
jscape.UploadForm.Input.fromJSON = function(data) {
    return new jscape.UploadForm.Input(data.input.type, data.input.name, data.input.label, data.input.value || data.input.values, data.input.required, data.html);
};

/** @class */
jscape.UploadSessionInfo = Class.extend(
    /** @lends {jscape.UploadSessionInfo.prototype} */
{
    /** @constructor */
    initialize: function (a, b, c, d, e) {
        this.id = a || "";
        this.state = b || jscape.UploadSessionInfo.Status.UNKNOWN;
        this.progress = parseInt(c) || 0;
        this.statusMessage = d || "";
        this.timeMessage = e || "";
    }
});
jscape.UploadSessionInfo.fromJSON = function(data) {
    return data ? new jscape.UploadSessionInfo(data.id, data.state, data.progress, data.statusMessage, data.timeMessage) : null;
};
jscape.UploadSessionInfo.Status = {QUEUED: "QUEUED", IN_PROGRESS: "IN_PROGRESS", CANCELED: "CANCELED", COMPLETE: "COMPLETE", ERROR: "ERROR", UNKNOWN: "UNKNOWN"};

/** @class */
jscape.storage.UploadFileController = Class.extend(
    /** @lends {jscape.storage.UploadFileController.prototype} */
{
    deferred: null,
    requests: null,
    cancelled: false,
    view: null,

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

    start: function(mode, files, filenames, items) {
        this.mode = mode;
        this.filenames = filenames || [];
        this.requests = [];
        this.cancelled = false;
        this.deferred = $.Deferred();
        var promise = this.deferred.promise();

        var folder = this.hasFolder(items);
        if (folder) {
            this.view && this.view.destroy();
            this.deferred.reject({message: window.R.string.STORAGE_ERROR_UPLOAD_FOLDER.supplant({0: folder}), force: true});
            this.deferred = null;
            return promise;
        }

        this._chooseFiles(files)
                .then($.proxy(function (files) {
                          if (files.length == 0) {
                              return $.Deferred().reject().promise();
                          }
                          this.files = files;
                          return this._uploadFormData(files);
                      }, this))
                .then($.proxy(function (metadata) {
                          this.metadata = metadata;
                          return this._confirmOverwrite(this.files);
                      }, this))
                .then($.proxy(function () { return this._doUpload(this.files, this.metadata); }, this))
                .done($.proxy(function () {
                          if (this.cancelled) {
                              this.deferred.reject();
                          } else {
                              this.deferred.resolve();
                          }
                      }, this))
                .fail($.proxy(function (info) { this.deferred.reject(info); }, this))
                .always($.proxy(function () {
                            this.view && this.view.destroy();
                            this.deferred = null;
                        }, this));

        return promise;
    },

    _chooseFiles: function(files) {
        var deferred = $.Deferred();
        if ("FileList" in window && files instanceof FileList) {
            deferred.resolve(files);
        } else {
            this.view = new jscape.storage.UploadFileView();
            this.view.show({onSubmit: function(dialog) { deferred.resolve(dialog.getFiles()); }});
        }
        return deferred.promise();
    },

    hasFolder: function(items) {
        for (var i = 0, size = (items && items.length) || 0; i < size; ++i) {
            var item = items[i];
            if (item.kind == "file") {
                var entry = item.getAsEntry ? item.getAsEntry() : item.webkitGetAsEntry ? item.webkitGetAsEntry() : undefined;
                if (entry && entry.isDirectory) {
                    return entry.name;
                }
            }
        }
        return null;
    },

    onSubmit: function (dialog) {
        var files = dialog.getFiles();
        if (files.length != 0) {
            this._confirmOverwrite(files)
                    .then($.proxy(function () { return this._doUpload(files); }, this))
                    .done($.proxy(function () { this.deferred.resolve(); }, this))
                    .fail($.proxy(function () { this.deferred.reject(); }, this))
                    .always($.proxy(function () {
                                dialog.destroy();
                                this.deferred = null;
                            }, this));
        } else {
            dialog.destroy();
            this.deferred.reject();
            this.deferred = null;
        }
    },

    onCancelUpload: function(sessionId) {
        this.cancelled = true;
        while (this.requests.length) {
            this.requests.pop().abort();
        }
        jscape.API.cancelUpload(sessionId);
    },

    _uploadFormData: function(files) {
        var controller = new jscape.storage.UploadFormController(this.uploadFormRequired);
        return controller.start($.map(files, function(file) { return file.name; }));
    },

    _doUpload: function (files, metadata) {
        if (files.length == 0) {
            return $.Deferred().reject().promise();
        }
        var meter = new jscape.storage.UploadProgressMeter();

        return jscape.API.startUpload()
                .then($.proxy(function (sessionId) {
                          if (sessionId) {
                              meter.start(sessionId, this);
                              var xhr = jscape.API.uploadFile(files, this.mode, sessionId, metadata);
                              this.requests.push(xhr);
                              return xhr;
                          } else {
                              return $.Deferred().reject({message: window.R.string.STORAGE_ERROR_UPLOAD, force: true}).promise();
                          }
                      }, this))
                .always($.proxy(function () {
                            this.requests = [];
                            meter.stop();
                            meter = null;
                        }, this));
    },

    _confirmOverwrite: function (files) {
        var duplicates = $.map(files, $.proxy(function(file) { return $.inArray(file.name, this.filenames) != -1 ? file.name : null; }, this));
        if (duplicates.length > 0) {
            return jscape.ConfirmDialog.confirm(window.R.string.APPLICATION_TITLE,
                                                window.R.string.STORAGE_CONFIRM_OVERWRITE_MESSAGE);
        }
        return $.Deferred().resolve().promise();
    }
});

/** @class */
jscape.storage.UploadProgressMeter = Class.extend(
    /** @lends {jscape.storage.UploadProgressMeter.prototype} */
{
    DEFAULT_DELAY: 1000,

    dialog: null,
    timer: null,
    stopped: false,
    delay: 0,

    start: function(id, listener) {
        this.sessionId = id;
        this.listener = listener || jscape.storage.UploadProgressMeter.LISTENER;
        this.stopped = false;
        this.delay = this.DEFAULT_DELAY;
        this.dialog = this._setupDialog().show(this);
        this._poll(0);

        return this;
    },

    stop: function() {
        this.stopped = true;
        this._stopPoll();
        if (this.dialog) {
            this.dialog.destroy();
            this.dialog = null;
        }
    },

    onCancel: function(dialog) {
        this.stop();
        this.listener.onCancelUpload(this.sessionId);
    },

    _poll: function(delay) {
        if (this.stopped) {
            return;
        }
        this._stopPoll();
        this.timer = setTimeout($.proxy(function() {
            this.timer = null;
            $(document).triggerHandler("_activity.user");
            jscape.API.uploadState(this.sessionId)
                    .done($.proxy(function(session) { this.delay = this.DEFAULT_DELAY; this._onChange(session); }, this))
                    .fail($.proxy(function() { this.delay *= 2; this._poll(this.delay); }, this));
        }, this), delay || 0);
    },

    _stopPoll: function() {
        if (this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }
    },

    _setupDialog: function() {
        var dialog = new jscape.storage.UploadProgressDialog();
        dialog.setProgress(0);
        return dialog;
    },

    _progress: function(session) {
        if (this.dialog && session) {
            this.dialog.setProgress(session.progress);
            this.dialog.setMessage(session.statusMessage, session.timeMessage);
        }
    },

    _onChange: function (session) {
        switch (session.state) {
            case jscape.UploadSessionInfo.Status.QUEUED:
            case jscape.UploadSessionInfo.Status.IN_PROGRESS:
                this._progress(session);
                this._poll(this.delay);
                break;
            case jscape.UploadSessionInfo.Status.COMPLETE:
            case jscape.UploadSessionInfo.Status.CANCELED:
            case jscape.UploadSessionInfo.Status.ERROR:
                this.stop();
                break;
            default:
                this._poll(this.delay);
        }
    }
});
jscape.storage.UploadProgressMeter.LISTENER = {
    onCancelUpload: $.noop
};

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

        this.bar = $("div[aria-label=progress-bar]", this.dialog).progressbar({width: "100%"});
        this.progress = $("span[aria-label=progress-message]", this.dialog);
        this.remaining = $("span[aria-label=remaining-message]", this.dialog);
    },

    setMessage: function(status, remaining) {
        this.progress.text(status || "");
        this.remaining.text(remaining || "");
    },

    setProgress: function(value) {
        this.bar.progressbar("setValue", value);
    },

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

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

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

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

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

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

    /** @constructor */
    initialize: function() {
        this.container = $("<div/>")
                .appendTo(document.body)
                .css({position: "absolute", left: "-100000px", visibility: "hidden"});
        this.choosers = [];
    },

    getFiles: function() {
        if (this.choosers.length) {
            var input = this.choosers.pop();
            return "FileList" in window ? input.files : input;
        }
        return [];
    },

    show: function (listener) {
        this.listener = listener || jscape.storage.UploadFileView.LISTENER;
        var self = this;
        this._createChooser()
                .one("change.upload", function () {
                         if ($(this).val()) {
                             self._onSelect$File.call(self, this);
                         }
                         self = null;
                     })[0].click();
    },

    hide: function() {
    },

    destroy: function() {
        while (this.choosers.length) {
            this.choosers.pop().off(".upload").remove();
        }
        this.choosers = null;
        this.container.empty().remove();
    },

    _createChooser: function() {
        return $('<input type="file" />').appendTo(this.container)
                .prop("multiple", true).prop("tabindex", -1).prop("size", 1)
                .on("keypress.upload", function (e) { e.stopPropagation(); this.blur(); });
    },

    _onSelect$File: function(chooser) {
        this.choosers.push(chooser);
        this.listener.onSubmit(this);
    }
});
jscape.storage.UploadFileView.LISTENER = {
    onSubmit: $.noop
};

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

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

    start: function(filenames) {
        this.deferred = $.Deferred();
        this.filenames = filenames || [];
        this.controllers = [];
        this.form = null;
        jscape.API.getUploadForms()
                .then($.proxy(function (forms) {
                          if (forms.length == 0) {
                              this.deferred.resolve({});
                              return $.Deferred().reject().promise();
                          }
                          return this._selectUploadForm(forms);
                      }, this))
                .then(function(name) { return jscape.API.uploadForm(name); })
                .done($.proxy(function(form) { this.form = form; this._setupDialog(form).show(this); }, this))
                .fail($.proxy(function() { this.deferred.reject(); this.deferred = null; }, this));
        return this.deferred.promise();
    },

    onSubmit: function(dialog) {
        var data = this._createData(dialog);
        dialog.destroy();
        this.deferred.resolve(data);
        this.deferred = null;
    },

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

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

    _selectUploadForm: function(forms) {
        var deferred = $.Deferred();
        if (this.required && forms.length == 1) {
            deferred.resolve(forms[0].name);
        } else {
            var dialog = new jscape.storage.UploadFormSelectionDialog();
            dialog.setForms(forms);
            dialog.show({
                onSubmit: function (dialog) { var name = dialog.getForm(); dialog.destroy(); deferred.resolve(name); },
                onCancel: $.proxy(this.onCancel, this),
                onCancelUpload: $.proxy(this.onCancelUpload, this)
            });
        }
        return deferred.promise();
    },

    _setupDialog: function(form) {
        var dialog = new jscape.storage.UploadFormDialog(this.required);
        dialog.setTitle((form.name + "").escapeXml());
        dialog.setDescription(form.description);

        for (var i = 0; i < form.fields.length; ++i) {
            var field = form.fields[i];
            dialog.addFormInput(field.html);
            var controller = this._createController(field, dialog.container());
            controller.start(field);
            this.controllers.push(controller);
        }

        return dialog.finish();
    },

    _createController: function(field, container) {
        var type = (field.type || "").toLowerCase();
        if ("text" == type || "memo" == type) {
            return new jscape.storage.FormInputController(new jscape.storage.StringFieldView(field.name, container));
        } else if ("dropdown" == type || "multiselect" == type) {
            return new jscape.storage.FormInputController(new jscape.storage.ComboboxFieldView(field.name, container));
        } else if ("radio" == type) {
            return new jscape.storage.FormInputController(new jscape.storage.RadioFieldView(field.name));
        }
        return null;
    },

    _createData: function(dialog) {
        var data = {};
        for (var i = 0; i < this.controllers.length; i++) {
            this.controllers[i].apply(data, "");
        }
        return {form: this.form.name, data: data};
    }
});

/** @class */
jscape.storage.UploadFormSelectionDialog = Class.extend(
    /** @lends {jscape.storage.UploadFormSelectionDialog.prototype} */
{
    /** @constructor */
    initialize: function() {
        this.dialog = $("#d-upload-formselection").clone().attr("id", "");
        this.dialog.dialog({
            width: "50%", 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)},
                {text: window.R.string.STORAGE_BUTTON_CANCEL_UPLOAD || "Cancel Upload", handler: $.proxy(this._onCancelUpload, this)}
            ],
            onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
        }).show();

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

        this.uploadForm = $("select[name=uploadform]", this.fieldset).combobox({required: true, editable: false, textField: "name", valueField: "name", onChange: $.proxy(this._onChange$Form, this), width: "60%", panelHeight: "auto"});
        this.description = $("span[aria-label=description]", this.fieldset);
    },

    getForm: function() {
        return this.uploadForm.combobox("getValue");
    },

    setForms: function(values) {
        this.uploadForm.combobox("loadData", values);
    },

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

    _onChange$Form: function(value) {
        if (value) {
            var form = this._formFor(value);
            if (form) {
                this.description.text(form.description);
            }
        }
    },

    _formFor: function (value) {
        var data = this.uploadForm.combobox("getData");
        var valueField = this.uploadForm.combobox("options").valueField;
        for (var i = 0, size = data.length; i < size; ++i) {
            var form = data[i];
            if (form[valueField] == value) {
                return form;
            }
        }
        return null;
    },

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

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

    _onCancelUpload: function () {
        this.hide();
        this.listener.onCancelUpload(this);
    },

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

/** @class */
jscape.storage.UploadFormDialog = Class.extend(
    /** @lends {jscape.storage.UploadFormDialog.prototype} */
{
    /** @constructor */
    initialize: function(required) {
        this.dialog = $("#d-upload-form").clone().attr("id", "");
        if (required) {
            this.dialog.dialog({
                width: 650, height: "auto", modal: true, resizable: false, closable: false, closed: true,
                buttons: [
                        {text: window.R.string.DIALOG_BUTTON_OK || "Ok", handler: $.proxy(this._onSubmit, this)},
                        {text: window.R.string.DIALOG_BUTTON_CANCEL_UPLOAD || "Cancel Upload", handler: $.proxy(this._onCancelUpload, this)}
                ]
            });
        } else {
            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)},
                        {text: window.R.string.STORAGE_BUTTON_CANCEL_UPLOAD || "Cancel Upload", handler: $.proxy(this._onCancelUpload, this)}
                ],
                onClose: $.proxy(function () {if (!this.closing) { this._onCancel(); }}, this)
            });
        }
        this.dialog.show();

        this.fieldset = $("<fieldset><legend class=\"hidden-label\"></legend></fieldset>").appendTo(this.dialog).first();
        this.header = $("<h6><span aria-label=\"description\"></span></h6>").appendTo(this.fieldset);
    },

    setTitle: function(value) {
        this.header.html(value + this.header.html());
    },

    setDescription: function(value) {
        this.header.find("[aria-label=description]").text(value);
    },

    addFormInput: function(html) {
        this.fieldset.append(html);
    },

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

    finish: function() {
        this.dialog.dialog("resize");
        this.fieldset.form("validate");
        this.fieldset.find("input:not(:disabled):first").focus();
        return this;
    },

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

    _onCancelUpload: function () {
        this.hide();
        this.listener.onCancelUpload(this);
    },

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

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

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

    start: function(field) {
        this.field = field;
        this._setupView(field);
        this.view.show(this);
    },

    apply: function(context, prefix) {
        if (this.view.valid()) {
            var key = (prefix || "") + this.field.name;
            context[key] = this.view.val();
        }
    },

    _setupView: function (field) {
        if (field && field.value) {
            if(field.multiple()) {
                var values = field.values();
                if (values.length != 0) {
                    this.view.val(values[0]);
                } else {
                    this.view.val(this.field.value);
                }
            } else {
                this.view.val(field.value);
            }
        }
    }
});

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

    /** @constructor */
    initialize: function(name, container) {
        this.input = $("[name=" + name + "]:input", container);
        this.input.validatebox();
        var opts = this.input.validatebox("options");
        var maxLength = parseInt(this.input.prop("maxlength"));
        if (opts.required && !isNaN(maxLength)) {
            if (maxLength > 0) {
                opts.validType = "length[1," + maxLength + "]";
            } else {
                opts.validType = "length[1," + Number.MAX_VALUE + "]";
                opts.invalidMessage = opts.missingMessage;
            }
        }
        var pattern = this.input.prop("pattern");
        if (pattern && pattern.trim().length != 0) {
            opts.validType = "pattern['" + pattern + "', 'g']";
            opts.invalidMessage = opts.missingMessage;
        }
    },

    show: function() {
        this.valid();
    },

    valid: function() {
        return this.input.validatebox("isValid");
    },

    val: function(value) {
        if (!arguments.length) {
            return this.input.is(":not(:disabled)") ? this.input.val() : null;
        }
        this.input.val(value).change();
        return this;
    }
});

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

    /** @constructor */
    initialize: function (name, container, autocomplete) {
        this.input = $("select[name=" + name + "]", container);
        this.input.combobox({
            onLoadSuccess: function(data) {
                var c = $(this);
                c.combobox("clear");
                if (data.length != 0 && data.length < 15) {
                    c.combobox("select", data[0].value);
                    c.combo("options").panelHeight = "auto";
                    c.combo("resize");
                }
            }
        });
        if (autocomplete) {
            this.input.combobox("autocomplete", true);
        }
    },

    show: function() {
        this.valid();
    },

    valid: function() {
        return this.input.combobox("isValid");
    },

    variants: function(values) {
        this.input.combobox("loadData", values);
    },

    val: function(value) {
        if (!arguments.length) {
            return this.input.combobox("options").multiple ? this.input.combobox("getValues") : this.input.combobox("getValue");
        }
        this.input.combobox("select", value);
        return this;
    }
});

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

    /** @constructor */
    initialize: function (name, container) {
        this.input = $("input[name=" + name + "]:radio", container);
    },

    show: function() {
    },

    valid: function() {
        return this.input.filter(":checked").length > 0;
    },

    variants: function(values) {
        this.input.combobox("loadData", values);
    },

    val: function(value) {
        if (!arguments.length) {
            return this.input.filter(":checked").val();
        }
        this.input.filter("[value=" + value + "]").prop("checked", true).change();
        return this;
    }
});
