//////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2014-present, Egret Technology.
//  All rights reserved.
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the Egret nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY EGRET AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
//  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//  IN NO EVENT SHALL EGRET AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA,
//  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////////////

namespace eui {

    /**
     * @private
     */
    export const enum Keys {
        clickOffsetX,
        clickOffsetY,
        moveStageX,
        moveStageY,
        touchDownTarget,
        animation,
        slideDuration,
        pendingValue,
        slideToValue,
        liveDragging
    }

    /**
     * The SliderBase class lets users select a value by moving a slider thumb between
     * the end points of the slider track.
     * The current value of the slider is determined by the relative location of
     * the thumb between the end points of the slider,
     * corresponding to the slider's minimum and maximum values.
     * The SliderBase class is a base class for HSlider and VSlider.
     *
     * @event eui.UIEvent.CHANGE_START Dispatched when the scroll position is going to change
     * @event eui.UIEvent.CHANGE_END Dispatched when the scroll position changed complete
     * @event egret.Event.CHANGE Dispatched when the scroll position is changing
     *
     * @see eui.HSlider
     * @see eui.VSlider
     *
     * @version Egret 2.4
     * @version eui 1.0
     * @platform Web,Native
     * @language en_US
     */
    /**
     * 滑块控件基类，通过使用 SliderBase 类，用户可以在滑块轨道的端点之间移动滑块来选择值。
     * 滑块的当前值由滑块端点（对应于滑块的最小值和最大值）之间滑块的相对位置确定。
     * SliderBase 类是 HSlider 和 VSlider 的基类。
     *
     * @event eui.UIEvent.CHANGE_START 滚动位置改变开始
     * @event eui.UIEvent.CHANGE_END 滚动位置改变结束
     * @event egret.Event.CHANGE 滚动位置改变的时候
     *
     * @see eui.HSlider
     * @see eui.VSlider
     *
     * @version Egret 2.4
     * @version eui 1.0
     * @platform Web,Native
     * @language zh_CN
     */
    export class SliderBase extends Range {
        /**
         * Constructor
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 创建一个 SliderBase 实例
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public constructor() {
            super();
            this.$SliderBase = {
                0: 0,        //clickOffsetX,
                1: 0,        //clickOffsetY,
                2: 0,        //moveStageX,
                3: 0,        //moveStageY,
                4: null,     //touchDownTarget
                5: null,     //animation,
                6: 300,      //slideDuration,
                7: 0,        //pendingValue
                8: 0,        //slideToValue,
                9: true,     //liveDragging
            };
            this.maximum = 10;
            this.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onTouchBegin, this);
        }

        /**
         * @private
         */
        $SliderBase:Object;

        /**
         * [SkinPart] Highlight of track.
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * [SkinPart] 轨道高亮显示对象。
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public trackHighlight:egret.DisplayObject = null;
        /**
         * [SkinPart] Thumb display object.
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * [SkinPart]滑块显示对象。
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public thumb:eui.UIComponent = null;

        /**
         * [SkinPart] Track display object.
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * [SkinPart]轨道显示对象。
         * @skinPart
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public track:eui.UIComponent = null;

        /**
         * Duration in milliseconds for the sliding animation when you tap on the track to move a thumb.
         *
         * @default 300
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 在轨道上单击以移动滑块时，滑动动画持续的时间（以毫秒为单位）。设置为0将不执行缓动。
         *
         * @default 300
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public get slideDuration():number {
            return this.$SliderBase[Keys.slideDuration];
        }

        public set slideDuration(value:number) {
            this.$SliderBase[Keys.slideDuration] = +value || 0;
        }

        /**
         * Converts a track-relative x,y pixel location into a value between
         * the minimum and maximum, inclusive.
         *
         * @param x The x coordinate of the location relative to the track's origin.
         * @param y The y coordinate of the location relative to the track's origin.
         * @return A value between the minimum and maximum, inclusive.
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 将相对于轨道的 x,y 像素位置转换为介于最小值和最大值（包括两者）之间的一个值。
         *
         * @param x 相对于轨道原点的位置的x坐标。
         * @param y 相对于轨道原点的位置的y坐标。
         * @return 介于最小值和最大值（包括两者）之间的一个值。
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        protected pointToValue(x:number, y:number):number {
            return this.minimum;
        }

        /**
         * Specifies whether live dragging is enabled for the slider. If true, sets the value
         * and values properties and dispatches the change event continuously as
         * the user moves the thumb.
         *
         * @default true
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 如果为 true，则将在沿着轨道拖动滑块时，而不是在释放滑块按钮时，提交此滑块的值。
         *
         * @default true
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public get liveDragging():boolean {
            return this.$SliderBase[Keys.liveDragging];
        }

        public set liveDragging(value:boolean) {
            this.$SliderBase[Keys.liveDragging] = !!value;
        }


        /**
         * The value the slider will have when the touch is end.
         * This property is updated when the slider thumb moves, even if <code>liveDragging</code> is false.<p/>
         * If the <code>liveDragging</code> style is false, then the slider's value is only set
         * when the touch is end.
         *
         * @default 0
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 触摸结束时滑块将具有的值。
         * 无论 liveDragging 是否为 true，在滑块拖动期间始终更新此属性。
         * 而 value 属性在当 liveDragging 为 false 时，只在触摸释放时更新一次。
         *
         * @default 0
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        public get pendingValue():number {
            return this.$SliderBase[Keys.pendingValue];
        }

        public set pendingValue(value:number) {
            value = +value || 0;
            let values = this.$SliderBase;
            if (value === values[Keys.pendingValue])
                return;
            values[Keys.pendingValue] = value;
            this.invalidateDisplayList();
        }

        /**
         * @inheritDoc
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         */
        protected setValue(value:number):void {
            this.$SliderBase[Keys.pendingValue] = value;
            super.setValue(value);
        }


        /**
         * @inheritDoc
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         */
        protected partAdded(partName:string, instance:any):void {
            super.partAdded(partName, instance);

            if (instance == this.thumb) {
                this.thumb.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onThumbTouchBegin, this);
                this.thumb.addEventListener(egret.Event.RESIZE, this.onTrackOrThumbResize, this);
            }
            else if (instance == this.track) {
                this.track.addEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onTrackTouchBegin, this);
                this.track.addEventListener(egret.Event.RESIZE, this.onTrackOrThumbResize, this);
            }
            else if (instance === this.trackHighlight) {
                this.trackHighlight.touchEnabled = false;
                if (egret.is(this.trackHighlight, "egret.DisplayObjectContainer")) {
                    (<egret.DisplayObjectContainer> this.trackHighlight).touchChildren = false;
                }
            }
        }

        /**
         * @inheritDoc
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         */
        protected partRemoved(partName:string, instance:any):void {
            super.partRemoved(partName, instance);

            if (instance == this.thumb) {
                this.thumb.removeEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onThumbTouchBegin, this);
                this.thumb.removeEventListener(egret.Event.RESIZE, this.onTrackOrThumbResize, this);
            }
            else if (instance == this.track) {
                this.track.removeEventListener(egret.TouchEvent.TOUCH_BEGIN, this.onTrackTouchBegin, this);
                this.track.removeEventListener(egret.Event.RESIZE, this.onTrackOrThumbResize, this);
            }
        }

        /**
         * @private
         * 滑块或轨道尺寸改变事件
         */
        private onTrackOrThumbResize(event:egret.Event):void {
            this.updateSkinDisplayList();
        }


        /**
         * Handle touch-begin events on the scroll thumb. Records the touch begin point in clickOffset.
         *
         * @param The <code>egret.TouchEvent</code> object.
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 滑块触摸开始事件，记录触碰开始的坐标偏移量。
         *
         * @param event 事件 <code>egret.TouchEvent</code> 的对象.
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        protected onThumbTouchBegin(event:egret.TouchEvent):void {
            let values = this.$SliderBase;
            if (values[Keys.animation] && values[Keys.animation].isPlaying)
                this.stopAnimation();

            let stage = this.$stage;
            stage.addEventListener(egret.TouchEvent.TOUCH_MOVE, this.onStageTouchMove, this);
            stage.addEventListener(egret.TouchEvent.TOUCH_END, this.onStageTouchEnd, this);

            let clickOffset = this.thumb.globalToLocal(event.stageX, event.stageY, egret.$TempPoint);

            values[Keys.clickOffsetX] = clickOffset.x;
            values[Keys.clickOffsetY] = clickOffset.y;
            UIEvent.dispatchUIEvent(this, UIEvent.CHANGE_START);
        }

        /**
         * @private
         * 舞台上触摸移动事件
         */
        private onStageTouchMove(event:egret.TouchEvent):void {
            let values = this.$SliderBase;
            values[Keys.moveStageX] = event.$stageX;
            values[Keys.moveStageY] = event.$stageY;
            let track = this.track;
            if (!track)
                return;
            let p = track.globalToLocal(values[Keys.moveStageX], values[Keys.moveStageY], egret.$TempPoint);
            let newValue = this.pointToValue(p.x - values[Keys.clickOffsetX], p.y - values[Keys.clickOffsetY]);
            newValue = this.nearestValidValue(newValue, this.snapInterval);
            this.updateWhenTouchMove(newValue);
            event.updateAfterEvent();
        }

        /**
         * Capture touch-move events anywhere on or off the stage.
         * @param newValue new value
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 监听舞台的触碰移动事件。
         * @param newValue 新的值
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        protected updateWhenTouchMove(newValue:number):void {
            if (newValue != this.$SliderBase[Keys.pendingValue]) {
                if (this.liveDragging) {
                    this.setValue(newValue);
                    this.dispatchEventWith(egret.Event.CHANGE);
                }
                else {
                    this.pendingValue = newValue;
                }
            }
        }

        /**
         * Handle touch-end events anywhere on or off the stage.
         *
         * @param The <code>egret.Event</code> object.
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 触摸结束事件
         *
         * @param event 事件 <code>egret.Event</code> 的对象。
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        protected onStageTouchEnd(event:egret.Event):void {
            let stage:egret.Stage = event.$currentTarget;
            stage.removeEventListener(egret.TouchEvent.TOUCH_MOVE, this.onStageTouchMove, this);
            stage.removeEventListener(egret.TouchEvent.TOUCH_END, this.onStageTouchEnd, this);
            UIEvent.dispatchUIEvent(this, UIEvent.CHANGE_END);
            let values = this.$SliderBase;
            if (!this.liveDragging && this.value != values[Keys.pendingValue]) {
                this.setValue(values[Keys.pendingValue]);
                this.dispatchEventWith(egret.Event.CHANGE);
            }
        }

        /**
         * @private
         * 当在组件上按下时记录被按下的子显示对象
         */
        private onTouchBegin(event:egret.TouchEvent):void {
            this.$stage.addEventListener(egret.TouchEvent.TOUCH_END, this.stageTouchEndHandler, this);
            this.$SliderBase[Keys.touchDownTarget] = <egret.DisplayObject> (event.$target);
        }

        /**
         * @private
         * 当结束时，若不是在 touchDownTarget 上弹起，而是另外的子显示对象上弹起时，额外抛出一个触摸单击事件。
         */
        private stageTouchEndHandler(event:egret.TouchEvent):void {
            let target:egret.DisplayObject = event.$target;
            let values = this.$SliderBase;
            event.$currentTarget.removeEventListener(egret.TouchEvent.TOUCH_END, this.stageTouchEndHandler, this);
            if (values[Keys.touchDownTarget] != target && this.contains(<egret.DisplayObject> (target))) {
                egret.TouchEvent.dispatchTouchEvent(this, egret.TouchEvent.TOUCH_TAP, true, true,
                    event.$stageX, event.$stageY, event.touchPointID);
            }
            values[Keys.touchDownTarget] = null;
        }


        /**
         * @private
         * 动画播放更新数值
         */
        $animationUpdateHandler(animation:sys.Animation):void {
            this.pendingValue = animation.currentValue;
        }

        /**
         * @private
         * 动画播放完毕
         */
        private animationEndHandler(animation:sys.Animation):void {
            this.setValue(this.$SliderBase[Keys.slideToValue]);
            this.dispatchEventWith(egret.Event.CHANGE);
            UIEvent.dispatchUIEvent(this, UIEvent.CHANGE_END);
        }

        /**
         * @private
         * 停止播放动画
         */
        private stopAnimation():void {
            this.$SliderBase[Keys.animation].stop();
            this.setValue(this.nearestValidValue(this.pendingValue, this.snapInterval));
            this.dispatchEventWith(egret.Event.CHANGE);
            UIEvent.dispatchUIEvent(this, UIEvent.CHANGE_END);
        }

        /**
         * Handle touch-begin events for the slider track. We
         * calculate the value based on the new position and then
         * move the thumb to the correct location as well as
         * commit the value.
         * @param The <code>egret.TouchEvent</code> object.
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language en_US
         */
        /**
         * 轨道的触碰开始事件。我们会在这里根据新的坐标位置计算value，然后移动滑块到当前位置。
         *
         * @param event 事件 <code>egret.TouchEvent</code> 的对象.
         *
         * @version Egret 2.4
         * @version eui 1.0
         * @platform Web,Native
         * @language zh_CN
         */
        protected onTrackTouchBegin(event:egret.TouchEvent):void {
            let thumbW = this.thumb ? this.thumb.width : 0;
            let thumbH = this.thumb ? this.thumb.height : 0;
            let offsetX = event.$stageX - (thumbW / 2);
            let offsetY = event.$stageY - (thumbH / 2);
            let p = this.track.globalToLocal(offsetX, offsetY, egret.$TempPoint);

            let rangeValues = this.$Range
            let newValue = this.pointToValue(p.x, p.y);
            newValue = this.nearestValidValue(newValue, rangeValues[sys.RangeKeys.snapInterval]);

            let values = this.$SliderBase;
            if (newValue != values[Keys.pendingValue]) {
                if (values[Keys.slideDuration] != 0) {
                    if (!values[Keys.animation]) {
                        values[Keys.animation] = new sys.Animation(this.$animationUpdateHandler, this);
                        values[Keys.animation].endFunction = this.animationEndHandler;
                    }
                    let animation = values[Keys.animation];
                    if (animation.isPlaying)
                        this.stopAnimation();
                    values[Keys.slideToValue] = newValue;
                    animation.duration = values[Keys.slideDuration] *
                        (Math.abs(values[Keys.pendingValue] - values[Keys.slideToValue]) / (rangeValues[sys.RangeKeys.maximum] - rangeValues[sys.RangeKeys.minimum]));
                    animation.from = values[Keys.pendingValue];
                    animation.to = values[Keys.slideToValue];
                    UIEvent.dispatchUIEvent(this, UIEvent.CHANGE_START);
                    animation.play();
                }
                else {
                    this.setValue(newValue);
                    this.dispatchEventWith(egret.Event.CHANGE);
                }
            }
        }

    }

}
