// like 'Select' editor, but will always return a boolean (true or false)
    editors.BooleanSelect = editors.Select.extend({
        initialize: function(options) {
            options.schema.options = [
                { val: '1', label: 'Yes' },
                { val: '', label: 'No' }
            ];
            editors.Select.prototype.initialize.call(this, options);
        },
        getValue: function() {
            return !!editors.Select.prototype.getValue.call(this);
        },
        setValue: function(value) {
            value = value ? '1' : '';
            editors.Select.prototype.setValue.call(this, value);
        }
    });

    // like the 'Select' editor, except will always return a number (int or float)
    editors.NumberSelect = editors.Select.extend({
        getValue: function() {
            return parseFloat(editors.Select.prototype.getValue.call(this));
        },
        setValue: function(value) {
            editors.Select.prototype.setValue.call(this, parseFloat(value));
        }
    });

    // https://github.com/eternicode/bootstrap-datepicker/
    editors.DatePicker = editors.Text.extend({
        initialize: function(options) {
            editors.Text.prototype.initialize.call(this, options);
            this.$el.addClass('datepicker-input');
        },

        getValue: function() {
            var value = this.$el.val();
            if (value) {
                return moment(value, 'MM/DD/YYYY').format();
            } else {
                return '';
            }
        },

        setValue: function(value) {
            if (value) {
                var formatted = moment(value).utc().format('MM/DD/YYYY');
                this.$el.val(formatted);
            } else {
                this.$el.val('');
            }
        },

        render: function() {
            editors.Text.prototype.render.apply(this, arguments);
            this.$el.datepicker({
                autoclose: true
            });
            return this;
        }
    });

    // https://github.com/jonthornton/jquery-timepicker
    editors.TimePicker = editors.Text.extend({
        initialize: function(options) {
            editors.Text.prototype.initialize.call(this, options);
            this.$el.addClass('timepicker-input');
        },

        render: function() {
            editors.Text.prototype.render.apply(this, arguments);
            this.$el.timepicker({
                minTime: this.options.schema.minTime,
                maxTime: this.options.schema.maxTime
            });
            return this;
        },

        setValue: function(value) {
            if (!value) value = '';
            this.value = value;
            var ret = editors.Text.prototype.setValue.apply(this, arguments);
            return ret;
        }
    });

    // Show both a date and time field
    // https://github.com/eternicode/bootstrap-datepicker/
    // https://github.com/jonthornton/jquery-timepicker
    editors.DateTimePicker = editors.Base.extend({
        events: {
            'changeDate': 'updateHidden',
            'changeTime': 'updateHidden',
            'input input': 'updateHidden' // so that clearing time works
        },
        initialize: function(options) {
            options = options || {};
            editors.Base.prototype.initialize.call(this, options);
            
            // Option defaults
            this.options = _.extend({
                DateEditor: editors.DatePicker,
                TimeEditor: editors.TimePicker
            }, options);

            // Schema defaults
            this.schema = _.extend({
                minsInterval: 15,
                minTime: '4:00am',
                maxTime: '11:00pm'
            }, options.schema || {});

            this.dateEditor = new this.options.DateEditor(options);
            this.dateEditor.$el.removeAttr('name');

            var timeOptions = _(options).clone();
            timeOptions.schema = _(this.schema).clone();
            timeOptions.schema.editorAttrs.placeholder = 'Any time';
            timeOptions.model = null;
            this.timeEditor = new this.options.TimeEditor(timeOptions);
            this.timeEditor.$el.removeAttr('name');

            this.$hidden = $('<input>', { type: 'hidden', name: options.key });

            this.value = this.dateEditor.value;
            this.setValue(this.value);
        },

        getValue: function() {
            return this.$hidden.val();
        },

        parseTimeValue: function(value) {
            return time;
        },

        setValue: function(value) {
            this.dateEditor.setValue(value);
            // pull the time portion of an ISO formatted string
            var time = '';
            if (_.isString(value) && value.indexOf('T') !== -1) {
                var m = moment(value);
                time = m ? m.format('h:mma') : '';
            }
            this.timeEditor.setValue(time);
        },

        updateHidden: function() {
            // update the hidden input with the value we want the server to see
            // if a date and time were chosen, include ISO formatted datetime with TZ offset
            // if no time was chosen, include only the date
            var date = moment(this.dateEditor.getValue());
            var time = this.timeEditor.getValue() ? this.timeEditor.$el.timepicker('getTime') : null;
            if (date && time) {
                date.hours(time.getHours());
                date.minutes(time.getMinutes());
            }
            var value = date ? date.format() : '';
            if (value && !time) {
                value = value.substr(0, value.indexOf('T'));
            }
            this.$hidden.val(value);
        },

        render: function() {
            editors.Base.prototype.render.apply(this, arguments);

            this.$el.append(this.dateEditor.render().el);
            this.$el.append(this.timeEditor.render().el);
            this.updateHidden();
            this.$el.append(this.$hidden);
            return this;
        }
    });

    editors.Range = editors.Text.extend({
        events: _.extend({}, editors.Text.prototype.events, {
            'change': function(event) {
                this.trigger('change', this);
            }
        }),

        initialize: function(options) {
          editors.Text.prototype.initialize.call(this, options);

          this.$el.attr('type', 'range');
          
          if (this.schema.appendToLabel) {
              this.updateLabel();
              this.on('change', this.updateLabel, this);
          }
        },
        getValue: function() {
            var val = editors.Text.prototype.getValue.call(this);
            return parseInt(val, 10);
        },

        updateLabel: function() {
            _(_(function() {
                var $label = this.$el.parents('.bbf-field').find('label');
                $label.text(this.schema.title + ': ' + this.getValue() + (this.schema.valueSuffix || ''));
            }).bind(this)).defer();
        }
    });