// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var widget_1 = require('./widget');
var _ = require('underscore');
var $ = require('jquery');
require('jquery-ui/ui/widgets/slider');
var d3format = require('d3-format').format;
var IntModel = (function (_super) {
    __extends(IntModel, _super);
    function IntModel() {
        _super.apply(this, arguments);
    }
    IntModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'IntModel',
            value: 0,
            disabled: false,
            description: ''
        });
    };
    return IntModel;
}(widget_1.DOMWidgetModel));
exports.IntModel = IntModel;
var BoundedIntModel = (function (_super) {
    __extends(BoundedIntModel, _super);
    function BoundedIntModel() {
        _super.apply(this, arguments);
    }
    BoundedIntModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'BoundedIntModel',
            step: 1,
            max: 100,
            min: 0
        });
    };
    return BoundedIntModel;
}(IntModel));
exports.BoundedIntModel = BoundedIntModel;
var IntSliderModel = (function (_super) {
    __extends(IntSliderModel, _super);
    function IntSliderModel() {
        _super.apply(this, arguments);
    }
    IntSliderModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'IntSliderModel',
            _view_name: 'IntSliderView',
            orientation: 'horizontal',
            _range: false,
            readout: true,
            readout_format: 'd',
            slider_color: null,
            continuous_update: true
        });
    };
    IntSliderModel.prototype.initialize = function (attributes, options) {
        _super.prototype.initialize.call(this, attributes, options);
        this.on('change:readout_format', this.update_readout_format, this);
        this.update_readout_format();
    };
    IntSliderModel.prototype.update_readout_format = function () {
        this.readout_formatter = d3format(this.get('readout_format'));
    };
    return IntSliderModel;
}(BoundedIntModel));
exports.IntSliderModel = IntSliderModel;
var IntSliderView = (function (_super) {
    __extends(IntSliderView, _super);
    function IntSliderView() {
        _super.apply(this, arguments);
        this._parse_value = parseInt;
        this._range_regex = /^\s*([+-]?\d+)\s*[-:]\s*([+-]?\d+)/;
    }
    IntSliderView.prototype.render = function () {
        /**
         * Called when view is rendered.
         */
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-hbox');
        this.el.classList.add('widget-hslider');
        this.label = document.createElement('div');
        this.el.appendChild(this.label);
        this.label.classList.add('widget-label');
        this.label.style.display = 'none';
        (this.$slider = $('<div />'))
            .slider({
            slide: this.handleSliderChange.bind(this),
            stop: this.handleSliderChanged.bind(this)
        })
            .addClass('slider');
        // Put the slider in a container
        this.slider_container = document.createElement('div');
        this.slider_container.classList.add('slider-container');
        this.slider_container.appendChild(this.$slider[0]);
        this.el.appendChild(this.slider_container);
        this.readout = document.createElement('div');
        this.el.appendChild(this.readout);
        this.readout.classList.add('widget-readout');
        this.readout.contentEditable = 'true';
        this.readout.style.display = 'none';
        this.listenTo(this.model, 'change:slider_color', function (sender, value) {
            this.$slider.find('a').css('background', value);
        });
        this.listenTo(this.model, 'change:description', function (sender, value) {
            this.updateDescription();
        });
        this.$slider.find('a').css('background', this.model.get('slider_color'));
        // Set defaults.
        this.update();
        this.updateDescription();
    };
    IntSliderView.prototype.updateDescription = function () {
        var description = this.model.get('description');
        if (description.length === 0) {
            this.label.style.display = 'none';
        }
        else {
            this.typeset(this.label, description);
            this.label.style.display = '';
        }
    };
    IntSliderView.prototype.update = function (options) {
        /**
         * Update the contents of this view
         *
         * Called when the model is changed.  The model may have been
         * changed by another view or by a state update from the back-end.
         */
        if (options === undefined || options.updated_view != this) {
            // JQuery slider option keys.  These keys happen to have a
            // one-to-one mapping with the corresponding keys of the model.
            var jquery_slider_keys = ['step', 'disabled'];
            var that = this;
            that.$slider.slider({});
            _.each(jquery_slider_keys, function (key, i) {
                var model_value = that.model.get(key);
                if (model_value !== undefined) {
                    that.$slider.slider('option', key, model_value);
                }
            });
            var max = this.model.get('max');
            var min = this.model.get('min');
            if (min <= max) {
                if (max !== undefined)
                    this.$slider.slider('option', 'max', max);
                if (min !== undefined)
                    this.$slider.slider('option', 'min', min);
            }
            var range_value = this.model.get('_range');
            if (range_value !== undefined) {
                this.$slider.slider('option', 'range', range_value);
            }
            // WORKAROUND FOR JQUERY SLIDER BUG.
            // The horizontal position of the slider handle
            // depends on the value of the slider at the time
            // of orientation change.  Before applying the new
            // workaround, we set the value to the minimum to
            // make sure that the horizontal placement of the
            // handle in the vertical slider is always
            // consistent.
            var orientation = this.model.get('orientation');
            var min = this.model.get('min');
            var max = this.model.get('max');
            if (this.model.get('_range')) {
                this.$slider.slider('option', 'values', [min, min]);
            }
            else {
                this.$slider.slider('option', 'value', min);
            }
            this.$slider.slider('option', 'orientation', orientation);
            var value = this.model.get('value');
            if (this.model.get('_range')) {
                // values for the range case are validated python-side in
                // _Bounded{Int,Float}RangeWidget._validate
                this.$slider.slider('option', 'values', value);
                this.readout.textContent = this.valueToString(value);
            }
            else {
                if (value > max) {
                    value = max;
                }
                else if (value < min) {
                    value = min;
                }
                this.$slider.slider('option', 'value', value);
                this.readout.textContent = this.valueToString(value);
            }
            if (this.model.get('value') != value) {
                this.model.set('value', value, { updated_view: this });
                this.touch();
            }
            // Use the right CSS classes for vertical & horizontal sliders
            if (orientation == 'vertical') {
                this.el.classList.remove('widget-hslider');
                this.el.classList.add('widget-vslider');
                this.el.classList.remove('widget-hbox');
                this.el.classList.add('widget-vbox');
            }
            else {
                this.el.classList.remove('widget-vslider');
                this.el.classList.add('widget-hslider');
                this.el.classList.remove('widget-vbox');
                this.el.classList.add('widget-hbox');
            }
            var readout = this.model.get('readout');
            if (readout) {
                this.readout.style.display = '';
                this.displayed.then(function () {
                    if (that.readout_overflow()) {
                        that.readout.classList.add('overflow');
                    }
                    else {
                        that.readout.classList.remove('overflow');
                    }
                });
            }
            else {
                this.readout.style.display = 'none';
            }
        }
        return _super.prototype.update.call(this);
    };
    /**
     * Returns true if the readout box content overflows.
     */
    IntSliderView.prototype.readout_overflow = function () {
        return this.readout.scrollWidth > this.readout.clientWidth;
    };
    /**
     * Write value to a string
     * @param  {number|number[]} value
     * @return {string}
     */
    IntSliderView.prototype.valueToString = function (value) {
        var format = this.model.readout_formatter;
        if (this.model.get('_range')) {
            return value.map(function (v) {
                return format(v);
            }).join('-');
        }
        else {
            return format(value);
        }
    };
    /**
     * Parse value from a string
     * @param  {string} text
     * @return {number|number[]} value
     */
    IntSliderView.prototype.stringToValue = function (text) {
        if (this.model.get('_range')) {
            // range case
            // ranges can be expressed either 'val-val' or 'val:val' (+spaces)
            var match = this._range_regex.exec(text);
            if (match) {
                return [this._parse_value(match[1]), this._parse_value(match[2])];
            }
            else {
                return null;
            }
        }
        else {
            return this._parse_value(text);
        }
    };
    IntSliderView.prototype.events = function () {
        return {
            // Dictionary of events and their handlers.
            'slide': 'handleSliderChange',
            'slidestop': 'handleSliderChanged',
            'blur [contentEditable=true]': 'handleTextChange',
            'keydown [contentEditable=true]': 'handleKeyDown'
        };
    };
    IntSliderView.prototype.handleKeyDown = function (e) {
        if (e.keyCode == 13) {
            e.preventDefault();
            this.handleTextChange();
        }
    };
    IntSliderView.prototype.handleTextChange = function () {
        /**
         * this handles the entry of text into the contentEditable label
         * first, the value is checked if it contains a parseable number
         *      (or pair of numbers, for the _range case)
         * then it is clamped within the min-max range of the slider
         * finally, the model is updated if the value is to be changed
         *
         * if any of these conditions are not met, the text is reset
         *
         * the step size is not enforced
         */
        var value = this.stringToValue(this.readout.textContent);
        var vmin = this.model.get('min');
        var vmax = this.model.get('max');
        if (this.model.get('_range')) {
            // reject input where NaN or lower > upper
            if (value === null ||
                isNaN(value[0]) ||
                isNaN(value[1]) ||
                (value[0] > value[1])) {
                this.readout.textContent = this.valueToString(this.model.get('value'));
            }
            else {
                // clamp to range
                value = [Math.max(Math.min(value[0], vmax), vmin),
                    Math.max(Math.min(value[1], vmax), vmin)];
                if ((value[0] != this.model.get('value')[0]) ||
                    (value[1] != this.model.get('value')[1])) {
                    this.readout.textContent = this.valueToString(value);
                    this.model.set('value', value, { updated_view: this });
                    this.touch();
                }
                else {
                    this.readout.textContent = this.valueToString(this.model.get('value'));
                }
            }
        }
        else {
            // single value case
            if (isNaN(value)) {
                this.readout.textContent = this.valueToString(this.model.get('value'));
            }
            else {
                value = Math.max(Math.min(value, vmax), vmin);
                if (value != this.model.get('value')) {
                    this.readout.textContent = this.valueToString(value);
                    this.model.set('value', value, { updated_view: this });
                    this.touch();
                }
                else {
                    this.readout.textContent = this.valueToString(this.model.get('value'));
                }
            }
        }
    };
    /**
     * Called when the slider value is changing.
     */
    IntSliderView.prototype.handleSliderChange = function (e, ui) {
        var actual_value;
        if (this.model.get('_range')) {
            actual_value = ui.values.map(this._validate_slide_value);
            this.readout.textContent = this.valueToString(actual_value);
        }
        else {
            actual_value = this._validate_slide_value(ui.value);
            this.readout.textContent = this.valueToString(actual_value);
        }
        // Only persist the value while sliding if the continuous_update
        // trait is set to true.
        if (this.model.get('continuous_update')) {
            this.handleSliderChanged(e, ui);
        }
    };
    /**
     * Called when the slider value has changed.
     *
     * Calling model.set will trigger all of the other views of the
     * model to update.
     */
    IntSliderView.prototype.handleSliderChanged = function (e, ui) {
        var actual_value;
        if (this.model.get('_range')) {
            actual_value = ui.values.map(this._validate_slide_value);
        }
        else {
            actual_value = this._validate_slide_value(ui.value);
        }
        this.model.set('value', actual_value, { updated_view: this });
        this.touch();
    };
    IntSliderView.prototype._validate_slide_value = function (x) {
        /**
         * Validate the value of the slider before sending it to the back-end
         * and applying it to the other views on the page.
         */
        return Math.floor(x);
    };
    return IntSliderView;
}(widget_1.DOMWidgetView));
exports.IntSliderView = IntSliderView;
var IntTextModel = (function (_super) {
    __extends(IntTextModel, _super);
    function IntTextModel() {
        _super.apply(this, arguments);
    }
    IntTextModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'IntTextModel',
            _view_name: 'IntTextView'
        });
    };
    return IntTextModel;
}(IntModel));
exports.IntTextModel = IntTextModel;
var IntTextView = (function (_super) {
    __extends(IntTextView, _super);
    function IntTextView() {
        _super.apply(this, arguments);
        this._parse_value = parseInt;
    }
    IntTextView.prototype.render = function () {
        /**
         * Called when view is rendered.
         */
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-hbox');
        this.el.classList.add('widget-text');
        this.label = document.createElement('div');
        this.el.appendChild(this.label);
        this.label.className = 'widget-label';
        this.label.style.display = 'none';
        this.textbox = document.createElement('input');
        this.textbox.setAttribute('type', 'text');
        this.textbox.className = 'form-control';
        this.el.appendChild(this.textbox);
        this.listenTo(this.model, 'change:description', function (sender, value) {
            this.updateDescription();
        });
        this.update(); // Set defaults.
        this.updateDescription();
    };
    IntTextView.prototype.updateDescription = function () {
        var description = this.model.get('description');
        if (description.length === 0) {
            this.label.style.display = 'none';
        }
        else {
            this.typeset(this.label, description);
            this.label.style.display = '';
        }
    };
    IntTextView.prototype.update = function (options) {
        /**
         * Update the contents of this view
         *
         * Called when the model is changed.  The model may have been
         * changed by another view or by a state update from the back-end.
         */
        if (options === undefined || options.updated_view != this) {
            var value = this.model.get('value');
            if (this._parse_value(this.textbox.value) !== value) {
                this.textbox.value = value.toString();
            }
            var disabled = this.model.get('disabled');
            if (disabled) {
                this.textbox.setAttribute('disabled', disabled);
            }
            else {
                this.textbox.removeAttribute('disabled');
            }
        }
        return _super.prototype.update.call(this);
    };
    IntTextView.prototype.events = function () {
        return {
            // Dictionary of events and their handlers.
            'keydown input': 'handleKeyDown',
            'keypress input': 'handleKeypress',
            'keyup input': 'handleChanging',
            'paste input': 'handleChanging',
            'cut input': 'handleChanging',
            // Fires only when control is validated or looses focus.
            'change input': 'handleChanged'
        };
    };
    /**
     * Handle key down
     *
     * Stop propagation so the event isn't sent to the application.
     */
    IntTextView.prototype.handleKeyDown = function (e) {
        e.stopPropagation();
    };
    /**
     * Handles key press
     *
     * Stop propagation so the event isn't sent to the application.
     */
    IntTextView.prototype.handleKeypress = function (e) {
        e.stopPropagation();
    };
    IntTextView.prototype.handleChanging = function (e) {
        /**
         * Handles and validates user input.
         *
         * Try to parse value as an int.
         */
        var numericalValue = 0;
        var trimmed = e.target.value.trim();
        if (trimmed === '') {
            return;
        }
        else {
            if (!(['-', '-.', '.', '+.', '+'].indexOf(trimmed) >= 0)) {
                numericalValue = this._parse_value(e.target.value);
            }
        }
        // If parse failed, reset value to value stored in model.
        if (isNaN(numericalValue)) {
            e.target.value = this.model.get('value');
        }
        else if (!isNaN(numericalValue)) {
            if (this.model.get('max') !== undefined) {
                numericalValue = Math.min(this.model.get('max'), numericalValue);
            }
            if (this.model.get('min') !== undefined) {
                numericalValue = Math.max(this.model.get('min'), numericalValue);
            }
            // Apply the value if it has changed.
            if (numericalValue != this.model.get('value')) {
                // Calling model.set will trigger all of the other views of the
                // model to update.
                this.model.set('value', numericalValue, { updated_view: this });
                this.touch();
            }
        }
    };
    IntTextView.prototype.handleChanged = function (e) {
        /**
         * Applies validated input.
         */
        if (e.target.value.trim() === '' || e.target.value !== this.model.get('value')) {
            e.target.value = this.model.get('value');
        }
    };
    return IntTextView;
}(widget_1.DOMWidgetView));
exports.IntTextView = IntTextView;
var ProgressModel = (function (_super) {
    __extends(ProgressModel, _super);
    function ProgressModel() {
        _super.apply(this, arguments);
    }
    ProgressModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'ProgressModel',
            _view_name: 'ProgressView',
            orientation: 'horizontal',
            bar_style: ''
        });
    };
    return ProgressModel;
}(BoundedIntModel));
exports.ProgressModel = ProgressModel;
var ProgressView = (function (_super) {
    __extends(ProgressView, _super);
    function ProgressView() {
        _super.apply(this, arguments);
    }
    ProgressView.prototype.render = function () {
        /**
         * Called when view is rendered.
         */
        var orientation = this.model.get('orientation');
        var className = orientation === 'horizontal' ? 'widget-hprogress'
            : 'widget-vprogress';
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add(className);
        this.label = document.createElement('div');
        this.el.appendChild(this.label);
        this.label.classList.add('widget-label');
        this.label.style.display = 'none';
        this.progress = document.createElement('div');
        this.progress.classList.add('progress');
        this.progress.style.position = 'relative';
        this.el.appendChild(this.progress);
        this.bar = document.createElement('div');
        this.bar.classList.add('progress-bar');
        this.bar.style.position = 'absolute';
        this.bar.style.bottom = '0px';
        this.bar.style.left = '0px';
        this.progress.appendChild(this.bar);
        // Set defaults.
        this.update();
        this.updateDescription();
        this.listenTo(this.model, 'change:bar_style', this.update_bar_style);
        this.listenTo(this.model, 'change:description', function (sender, value) {
            this.updateDescription();
        });
        this.update_bar_style();
    };
    ProgressView.prototype.updateDescription = function () {
        var description = this.model.get('description');
        if (description.length === 0) {
            this.label.style.display = 'none';
        }
        else {
            this.typeset(this.label, description);
            this.label.style.display = '';
        }
    };
    ProgressView.prototype.update = function () {
        /**
         * Update the contents of this view
         *
         * Called when the model is changed.  The model may have been
         * changed by another view or by a state update from the back-end.
         */
        var value = this.model.get('value');
        var max = this.model.get('max');
        var min = this.model.get('min');
        var orientation = this.model.get('orientation');
        var percent = 100.0 * (value - min) / (max - min);
        if (orientation === 'horizontal') {
            this.el.classList.remove('widget-vbox');
            this.el.classList.remove('widget-vprogress');
            this.el.classList.add('widget-hbox');
            this.el.classList.add('widget-hprogress');
            this.bar.style.width = percent + '%';
            this.bar.style.height = '100%';
        }
        else {
            this.el.classList.remove('widget-hbox');
            this.el.classList.remove('widget-hprogress');
            this.el.classList.add('widget-vbox');
            this.el.classList.add('widget-hprogress');
            this.bar.style.width = '100%';
            this.bar.style.height = percent + '%';
        }
        return _super.prototype.update.call(this);
    };
    ProgressView.prototype.update_bar_style = function () {
        var class_map = {
            success: ['progress-bar-success'],
            info: ['progress-bar-info'],
            warning: ['progress-bar-warning'],
            danger: ['progress-bar-danger']
        };
        this.update_mapped_classes(class_map, 'bar_style', this.bar);
    };
    return ProgressView;
}(widget_1.DOMWidgetView));
exports.ProgressView = ProgressView;
var PlayModel = (function (_super) {
    __extends(PlayModel, _super);
    function PlayModel() {
        _super.apply(this, arguments);
    }
    PlayModel.prototype.defaults = function () {
        return _.extend(_super.prototype.defaults.call(this), {
            _model_name: 'PlayModel',
            _view_name: 'PlayView',
            _playing: false,
            interval: 100
        });
    };
    PlayModel.prototype.initialize = function (attributes, options) {
        _super.prototype.initialize.call(this, attributes, options);
        this.on('change:_playing', function () {
            if (this.get('_playing')) {
                this.loop();
            }
        }, this);
    };
    PlayModel.prototype.loop = function () {
        if (this.get('_playing')) {
            var next_value = this.get('value') + this.get('step');
            if (next_value < this.get('max')) {
                this.set('value', next_value);
                window.setTimeout(this.loop.bind(this), this.get('interval'));
            }
            else {
                this.set('value', this.get('min'));
                this.set('_playing', false);
            }
            this.save_changes();
        }
    };
    PlayModel.prototype.stop = function () {
        this.set('_playing', false);
        this.set('value', this.get('min'));
        this.save_changes();
    };
    PlayModel.prototype.pause = function () {
        this.set('_playing', false);
        this.save_changes();
    };
    PlayModel.prototype.play = function () {
        this.set('_playing', true);
        this.save_changes();
    };
    return PlayModel;
}(BoundedIntModel));
exports.PlayModel = PlayModel;
var PlayView = (function (_super) {
    __extends(PlayView, _super);
    function PlayView() {
        _super.apply(this, arguments);
    }
    PlayView.prototype.render = function () {
        this.el.classList.add('jupyter-widgets');
        this.el.classList.add('widget-hbox');
        this.el.classList.add('widget-play');
        this.playButton = document.createElement('button');
        this.pauseButton = document.createElement('button');
        this.stopButton = document.createElement('button');
        this.playButton.className = 'jupyter-button';
        this.pauseButton.className = 'jupyter-button';
        this.stopButton.className = 'jupyter-button';
        this.el.appendChild(this.playButton); // Toggle button with playing
        this.el.appendChild(this.pauseButton); // Disable if not playing
        this.el.appendChild(this.stopButton); // Disable if not playing
        var playIcon = document.createElement('i');
        playIcon.className = 'fa fa-play';
        this.playButton.appendChild(playIcon);
        var pauseIcon = document.createElement('i');
        pauseIcon.className = 'fa fa-pause';
        this.pauseButton.appendChild(pauseIcon);
        var stopIcon = document.createElement('i');
        stopIcon.className = 'fa fa-stop';
        this.stopButton.appendChild(stopIcon);
        this.playButton.onclick = this.model.play.bind(this.model);
        this.pauseButton.onclick = this.model.pause.bind(this.model);
        this.stopButton.onclick = this.model.stop.bind(this.model);
        this.listenTo(this.model, 'change:_playing', this.update_playing);
        this.update_playing();
    };
    PlayView.prototype.update_playing = function () {
        var playing = this.model.get('_playing');
        if (playing) {
            this.pauseButton.disabled = false;
            this.playButton.classList.add('mod-active');
        }
        else {
            this.pauseButton.disabled = true;
            this.playButton.classList.remove('mod-active');
        }
    };
    return PlayView;
}(widget_1.DOMWidgetView));
exports.PlayView = PlayView;
