# Source: ui/base/abstract-slider.js

    /*global require, exports, window*/

    /**
     * @module montage/ui/base/abstract-slider.reel
     * @requires montage/ui/component
     * @requires montage/ui/native-control
     * @requires montage/composer/press-composer
     */

    var AbstractControl = require("./abstract-control").AbstractControl,
        TranslateComposer = require("../../composer/translate-composer").TranslateComposer,
        KeyComposer = require("../../composer/key-composer").KeyComposer;

    /**
     * @class AbstractSlider
     * @extends AbstractControl
     */
    var AbstractSlider = exports.AbstractSlider = AbstractControl.specialize( /** @lends AbstractSlider# */ {

        // Life Cycle

        /**
         * @private
         */
        constructor: {
            value: function AbstractSlider() {
                if (this.constructor === AbstractSlider) {
                    throw new Error("AbstractSlider cannot be instantiated.");
                }

                //this is so that when we read properties from the dom they are not overwritten
                this._propertyNamesUsed = {};
                this.addOwnPropertyChangeListener("_sliderMagnitude", this);
                this.addOwnPropertyChangeListener("_min", this);
                this.addOwnPropertyChangeListener("_max", this);
                this.addOwnPropertyChangeListener("_value", this);
                this.addOwnPropertyChangeListener("_step", this);
                this.addOwnPropertyChangeListener("axis", this);
                this.axis = "horizontal";

                this.defineBinding( "classList.has('montage--disabled')", { "<-": "!enabled" });
                this.defineBinding( "classList.has('montage-Slider--active')", { "<-": "active" });
            }
        },

        enterDocument: {
            value: function (firstTime) {
                if (firstTime) {
                    this._translateComposer = new TranslateComposer();
                    this._translateComposer.identifier = "thumb";
                    this._translateComposer.axis = this.axis;
                    this._translateComposer.hasMomentum = false;
                    this.addComposerForElement(this._translateComposer, this._sliderThumbElement);

                    // check for transform support
                    if("webkitTransform" in this.element.style) {
                        this._transform = "webkitTransform";
                    } else if("MozTransform" in this.element.style) {
                        this._transform = "MozTransform";
                    } else if("oTransform" in this.element.style) {
                        this._transform = "oTransform";
                    } else {
                        this._transform = "transform";
                    }
                    // read initial values from the input type=range
                    var used = this._propertyNamesUsed;
                    if (!used._min) {
                        this.min = this.element.getAttribute('min') || this._min;
                    }
                    if (!used._max) {
                        this.max = this.element.getAttribute('max') || this._max;
                    }
                    if (!used._step) {
                        this.step = this.element.getAttribute('step') || this._step;
                    }
                    if (!used._value) {
                        this.value = this.element.getAttribute('value') || this._value;
                    }
                    delete this._propertyNamesUsed;

                    this.element.setAttribute("role", "slider");
                    this.element.tabIndex = "-1";

                    this._upKeyComposer = KeyComposer.createKey(this, "up", "increase");
                    this._downKeyComposer = KeyComposer.createKey(this, "down", "decrease");
                    this._rightKeyComposer = KeyComposer.createKey(this, "right", "increase");
                    this._leftKeyComposer = KeyComposer.createKey(this, "left", "decrease");
                }
            }
        },

        // @todo: Without prepareForActivationEvents, the _translateComposer does not work
        prepareForActivationEvents: {
            value: function () {
                this._translateComposer.addEventListener('translateStart', this, false);
                this._translateComposer.addEventListener('translate', this, false);
                this._translateComposer.addEventListener('translateEnd', this, false);

                // needs to be fixed for pointer handling
                this._sliderThumbElement.addEventListener("touchstart", this, false);
                document.addEventListener("touchend", this, false);
                this._sliderThumbElement.addEventListener("mousedown", this, false);
                document.addEventListener("mouseup", this, false);

                this._upKeyComposer.addEventListener("keyPress", this, false);
                this._downKeyComposer.addEventListener("keyPress", this, false);
                this._leftKeyComposer.addEventListener("keyPress", this, false);
                this._rightKeyComposer.addEventListener("keyPress", this, false);
            }
        },

        willDraw: {
            value: function () {
                this._sliderMagnitude = this._calculateSliderMagnitude();
            }
        },

        _previousPercentage: {
            value: null
        },

        draw: {
            value: function () {
                if (this.axis === "vertical") {
                    if (this._isUpdatingTranslate) {
                        this._sliderThumbElement.style[this._transform] =
                            "translate3d(0," +
                            (this._previousPercentage - this._valuePercentage) * this._sliderMagnitude * 0.01 +
                            "px,0)";
                        this._isUpdatingTranslate = false;
                    } else {
                        this._sliderThumbElement.style.top = (100 - this._valuePercentage) + "%";
                        this._sliderThumbElement.style.left = 0;
                        this._sliderThumbElement.style[this._transform] = "translate3d(0,0,0)";
                        this._previousPercentage = this._valuePercentage;
                    }
                } else {
                    if (this._isUpdatingTranslate) {
                        this._sliderThumbElement.style[this._transform] =
                            "translate3d(" +
                            (this._valuePercentage - this._previousPercentage) * this._sliderMagnitude * 0.01 +
                            "px,0,0)";
                        this._isUpdatingTranslate = false;
                    } else {
                        this._sliderThumbElement.style.left = this._valuePercentage + "%";
                        this._sliderThumbElement.style.top = 0;
                        this._sliderThumbElement.style[this._transform] = "translate3d(0,0,0)";
                        this._previousPercentage = this._valuePercentage;
                    }
                }
                this.element.setAttribute("aria-valuemax", this.max);
                this.element.setAttribute("aria-valuemin", this.min);
                this.element.setAttribute("aria-valuenow", this.value);
            }
        },

        // Event Handling

        acceptsActiveTarget: {
            value: true
        },

        handleTouchstart: {
            value: function (e) {
                this.active = true;
                this.element.focus();
                this._isUpdatingTranslate = true;
            }
        },

        handleTouchend: {
            value: function (e) {
                this.active = false;
            }
        },

        handleMousedown: {
            value: function (e) {
                this.active = true;
                this.element.focus();
                // gh-1304
                // I did some experimentation based on using -webkit-user-select on the body element. Apart form the obvious
                // browser compatibility problems, it made existing text selection pop in and out as the slider is
                // interacted with. I'm worried about the possible side effects, but this might be the only solution.
                // The problem it solves is more pressing than the potential downside at this point.
                e.preventDefault();
                this._isUpdatingTranslate = true;
            }
        },

        handleMouseup: {
            value: function (e) {
                this.active = false;
            }
        },

        _isUpdatingTranslate: {
            value: false
        },

        handleThumbTranslateStart: {
            value: function (e) {
                if(this.axis === "vertical") {
                    this._startTranslate = e.translateY;
                } else {
                    this._startTranslate = e.translateX;
                }
                this._startValue = this.value;
            }
        },

        handleThumbTranslate: {
            value: function (event) {
                if(this.axis === "vertical") {
                    this.value = this._startValue + ((this._startTranslate - event.translateY) / this._sliderMagnitude) * (this._max - this._min);
                } else {
                    this.value = this._startValue + ((event.translateX - this._startTranslate) / this._sliderMagnitude) * (this._max - this._min);
                }
                this._isUpdatingTranslate = true;
            }
        },

        handleThumbTranslateEnd: {
            value: function (e) {
                this.active = false;
                this._isUpdatingTranslate = false;
            }
        },

        _increase: {
            value: function () {
                var stepBase = (typeof this.min === "number") ? this.min : 0;
                var value = this.value - stepBase;
                var step =  this.step | (this.max-this.min)/100;
                if (value % step) {
                    if (value < 0) {
                        value -= value % step;
                    } else {
                        value += step - (value % step);
                    }
                } else {
                    value += step;
                }
                this.value = value + stepBase;
            }
        },

        _decrease: {
            value: function () {
                var stepBase = (typeof this.min === "number") ? this.min : 0;
                var value = this.value - stepBase;
                var step =  this.step | (this.max-this.min)/100;
                if (value % step) {
                    if (value > 0) {
                        value -= value % step;
                    } else {
                        value -= step + (value % step);
                    }
                } else {
                    value -= step;
                }
                this.value = value + stepBase;
            }
        },

        handleKeyPress: {
            value: function (event) {
                if (!this.enabled) {
                    return;
                }
                if(event.identifier === "increase") {
                    this._increase();
                } else if (event.identifier === "decrease") {
                    this._decrease();
                }

            }
        },

        surrenderPointer: {
            value: function (pointer, composer) {
                // If the user is sliding us then we do not want anyone using
                // the pointer
                return false;
            }
        },

        // Properties

        /**
         * This property is true when the slider is being interacted with, either through mouse click or touch event, otherwise false.
         * @type {boolean}
         * @default false
         */
        active: {
            value: false
        },

        _min: {
            value: 0
        },

        _max: {
            value: 100
        },

        _step: {
            value: "any"
        },

        min: {
            get: function () {
                return this._min;
            },
            set: function (value) {
                if (! isNaN(value = parseFloat(value))) {
                    if (this._min !== value) {
                        this._min = value;
                    }
                }
            }
        },

        max: {
            get: function () {
                return this._max;
            },
            set: function (value) {
                if (! isNaN(value = parseFloat(value))) {
                    if (this._max !== value) {
                        this._max = value;
                    }
                }
            }
        },

        step: {
            get: function () {
                return this._step;
            },
            set: function (value) {
                if (! isNaN(value = parseFloat(value)) && value >= 0) {
                    if (this._step !== value) {
                        this._step = value;
                    }
                }
            }
        },

        _value: {
            value: 50
        },

        value: {
            get: function () {
                return this._value;
            },
            set: function (value) {
                if (! isNaN(value = parseFloat(value))) {
                    if (value > this._max) {
                        value = this._max;
                    } else if (value < this._min) {
                        value = this._min;
                    }

                    if (this._value !== value) {
                        this._value = value;
                    }
                }
            }
        },

        /**
         * Enables or disables the Button from user input. When this property is
         * set to `false`, the "disabled" CSS style is applied to the button's DOM
         * element during the next draw cycle. When set to `true` the "disabled"
         * CSS class is removed from the element's class list.
         * @type {boolean}
         */
        enabled: {
            value: true
        },

        axis: {
            value: null
        },

        // Machinery

        _sliderThumbElement: {
            value: null
        },

        _translateComposer: {
            value: null
        },

        _transform: {
            value: null
        },

        _transition: {
            value: null
        },

        _sliderMagnitude: {
            value: null
        },

        _startTranslate: {
            value: null
        },

        _startValue: {
            value: null
        },

        _valuePercentage: {
            value: null
        },

        _calculateSliderMagnitude: {
            value: function () {
                var computedStyle = window.getComputedStyle(this._element);

                if(this.axis === "vertical") {
                    return (
                        this._element.clientHeight -
                        parseFloat(computedStyle.getPropertyValue("padding-top")) -
                        parseFloat(computedStyle.getPropertyValue("padding-bottom"))
                    );
                } else {
                    return (
                        this._element.clientWidth -
                        parseFloat(computedStyle.getPropertyValue("padding-left")) -
                        parseFloat(computedStyle.getPropertyValue("padding-right"))
                    );
                }
            }
        },

        handleAxisChange: {
            value: function () {
                if (this._translateComposer) {
                    this._translateComposer.axis = this.axis;
                }
                if(this.axis === "vertical") {
                    this.classList.add("montage-Slider--vertical");
                    this.classList.remove("montage-Slider--horizontal");
                } else {
                    this.classList.remove("montage-Slider--vertical");
                    this.classList.add("montage-Slider--horizontal");
                }
            }
        },

        _propertyRegex: {
            value: /_sliderMagnitude|_min|_max|_value|_step/
        },

        handlePropertyChange: {
            value: function (changeValue, key, object) {
                if(key.match(this._propertyRegex) !== null) {
                    if(this._propertyNamesUsed) {
                        this._propertyNamesUsed[key] = true;
                    }
                    //adjust the value
                    if (this._value <= this._min) {
                        //first the simple case
                        this._value = this._min;
                    } else {
                        var magnitude = this._value - this._min;
                        var remainder = magnitude % this._step;
                        if (remainder) {
                            //if we have a remainder then we need to adjust the value
                            // Inspired by http://www.w3.org/html/wg/drafts/html/master/forms.html#range-state-(type=range)
                            // if we are in the middle of two stepped value then go for the larger one.
                            var roundup = (remainder >= this._step * 0.5) && ((this._value - remainder) + this._step <= this._max);
                            if (roundup) {
                                this._value = (this._value - remainder) + this._step;
                            } else {
                                this._value = this._value - remainder;
                            }
                        }

                    }

                    //otherwise don't adjust the value just check it's within  min and max
                    if (this._value > this._max) {
                        this._value = this._max;
                    }

                    this._valuePercentage = ((this._value - this._min) * 100) / (this._max - this._min);
                    this.needsDraw = true;
                }
            }
        }

    });