var CanvasKit = (function(){

    var Easing = {
        Linear: {
            None: function(k) {
                return k
            }
        },
        Quadratic: {
            In: function(k) {
                return k * k
            },
            Out: function(k) {
                return k * (2 - k)
            },
            InOut: function(k) {
                if ((k *= 2) < 1) {
                    return 0.5 * k * k
                }
                return - 0.5 * (--k * (k - 2) - 1)
            }
        },
        Cubic: {
            In: function(k) {
                return k * k * k
            },
            Out: function(k) {
                return--k * k * k + 1
            },
            InOut: function(k) {
                if ((k *= 2) < 1) {
                    return 0.5 * k * k * k
                }
                return 0.5 * ((k -= 2) * k * k + 2)
            }
        },
        Quartic: {
            In: function(k) {
                return k * k * k * k
            },
            Out: function(k) {
                return 1 - (--k * k * k * k)
            },
            InOut: function(k) {
                if ((k *= 2) < 1) {
                    return 0.5 * k * k * k * k
                }
                return - 0.5 * ((k -= 2) * k * k * k - 2)
            }
        },
        Quintic: {
            In: function(k) {
                return k * k * k * k * k
            },
            Out: function(k) {
                return--k * k * k * k * k + 1
            },
            InOut: function(k) {
                if ((k *= 2) < 1) {
                    return 0.5 * k * k * k * k * k
                }
                return 0.5 * ((k -= 2) * k * k * k * k + 2)
            }
        },
        Sinusoidal: {
            In: function(k) {
                return 1 - Math.cos(k * Math.PI / 2)
            },
            Out: function(k) {
                return Math.sin(k * Math.PI / 2)
            },
            InOut: function(k) {
                return 0.5 * (1 - Math.cos(Math.PI * k))
            }
        },
        Exponential: {
            In: function(k) {
                return k === 0 ? 0 : Math.pow(1024, k - 1)
            },
            Out: function(k) {
                return k === 1 ? 1 : 1 - Math.pow(2, -10 * k)
            },
            InOut: function(k) {
                if (k === 0) {
                    return 0
                }
                if (k === 1) {
                    return 1
                }
                if ((k *= 2) < 1) {
                    return 0.5 * Math.pow(1024, k - 1)
                }
                return 0.5 * ( - Math.pow(2, -10 * (k - 1)) + 2)
            }
        },
        Circular: {
            In: function(k) {
                return 1 - Math.sqrt(1 - k * k)
            },
            Out: function(k) {
                return Math.sqrt(1 - (--k * k))
            },
            InOut: function(k) {
                if ((k *= 2) < 1) {
                    return - 0.5 * (Math.sqrt(1 - k * k) - 1)
                }
                return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1)
            }
        },
        Elastic: {
            In: function(k) {
                if (k === 0) {
                    return 0
                }
                if (k === 1) {
                    return 1
                }
                return - Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI)
            },
            Out: function(k) {
                if (k === 0) {
                    return 0
                }
                if (k === 1) {
                    return 1
                }
                return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1
            },
            InOut: function(k) {
                if (k === 0) {
                    return 0
                }
                if (k === 1) {
                    return 1
                }
                k *= 2;
                if (k < 1) {
                    return - 0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI)
                }
                return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1
            }
        },
        Back: {
            In: function(k) {
                var s = 1.70158;
                return k * k * ((s + 1) * k - s)
            },
            Out: function(k) {
                var s = 1.70158;
                return--k * k * ((s + 1) * k + s) + 1
            },
            InOut: function(k) {
                var s = 1.70158 * 1.525;
                if ((k *= 2) < 1) {
                    return 0.5 * (k * k * ((s + 1) * k - s))
                }
                return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2)
            }
        },
        Bounce: {
            In: function(k) {
                return 1 - TWEEN.Easing.Bounce.Out(1 - k)
            },
            Out: function(k) {
                if (k < (1 / 2.75)) {
                    return 7.5625 * k * k
                } else if (k < (2 / 2.75)) {
                    return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75
                } else if (k < (2.5 / 2.75)) {
                    return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375
                } else {
                    return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375
                }
            },
            InOut: function(k) {
                if (k < 0.5) {
                    return TWEEN.Easing.Bounce.In(k * 2) * 0.5
                }
                return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5
            }
        }
    }

    var _animates = [];
    var _timers   = [];

    function mix(x,y,a){
    	if (x instanceof Array) {
    		var v = [];
    		for(var i=0;i<x.length;i++){
    			v.push(x[i]*(1-a)+y[i]*a);
    		}
    		return v;
    	}else{
    		return x*(1-a)+y*a;
    	}
    }

	function convertColor(color,alpha){
		if (typeof color === 'number') {
			var r = 0xff & (color>>16),
				g = 0xff & (color>>8),
				b = 0xff & color,
				a = Math.max(0,Math.min(1,alpha));
			return 'rgba('+r+','+g+','+b+','+a+')';
		}
	}

	function reverseConvertColor(color){
		if (color) {
			if (typeof color === 'number') {
				return Math.max(0,Math.min(0xffffff,Math.round(color)));
			}else if(typeof color === 'string'){
				if (/^#([0-9a-f]{3}){1,2}$/gi.test(color)) {
					color = color.toLowerCase();
					var icolor = parseInt('0x'+color.substr(1));
					if (color.length == 4) { // #123 => #112233
						var r = 0xf & (icolor>>8),
							g = 0xf & (icolor>>4),
							b = 0xf & icolor;
						return ((r<<4)+r <<16) + ((g<<4)+g<<8) + (b<<4) +b;
					}else{
						return icolor;
					}
				}
			}
		}
		return 0x0;
	}

	function distance(a,b){
		return Math.sqrt((a[0]-b[0])*(a[0]-b[0])+(a[1]-b[1])*(a[1]-b[1]));
	}

	function extendClass(A,B,object){
		function _(object){
			for(var prop in object){
				this[prop] = object[prop];
			}
		}
		_.prototype = new B();
		A.prototype = new _(object instanceof Object?object:{});
	}

	function Timer(){
        this.elapsedTime = 0;
        this.interval    = 0;
        this._stop       = !0;
        this._speed      = 1;
        this._tick       = {duration:0,lastCallTime:0,fn:false};
	}

	Timer.prototype = {
		start: function(){
			if (!this._stop) {
				return;
			}
			this._stop       = !!0;
			this._startTime  = +new Date;
			this._lastTime   = this._startTime;
			_timers.push(this);
            if (this._tick.duration>0) {
                this._tick.lastCallTime = this.elapsedTime;
            }
            typeof this.begin == 'function' && this.begin();
			return this;
		},
        reset: function(){
            if (!this._stop) {
                this.elapsedTime = 0;
            }
            this._tick.duration = 0;
            this._tick.fn = false;
            this._tick.lastCallTime = 0;
        },
        repeat: function(duration, fn){
            duration = Math.round(+duration);
            if (duration>0 && typeof fn === 'function') {
                this._tick.duration = duration;
                this._tick.fn = fn;
                this._tick.lastCallTime = this._stop?0:this.elapsedTime;
            }
            return this;
        },
		hit: function(){
			var currentTime = +new Date;
		    this.interval = (currentTime - this._lastTime)*this._speed;
		    this.elapsedTime += this.interval;
            typeof this.update == 'function' && this.update(this.elapsedTime,this.interval);
		    this._lastTime = currentTime;
            if (this._tick.duration>0) {
                if (this.elapsedTime >= this._tick.lastCallTime+this._tick.duration) {
                    this._tick.fn.call(this);
                    this._tick.lastCallTime+=Math.round((this.elapsedTime-this._tick.lastCallTime)/this._tick.duration)*this._tick.duration;
                }
            }
		},
		stop: function(){
			this._stop = !0;
			for(var i=0; i<_timers.length; i++) {
				if(_timers[i] == this) {
					_timers.splice(i, 1);
					break;
				}
			}
			return this;
		}
	}

	function Canvas(a){
	}
	Canvas.prototype = {
        _init: function(a) {
            a = a || {},
            this._canvas = document.querySelector(a.target?a.target:"canvas"),
            this._context = this._canvas.getContext("2d"),
            this._pixelRatio = a.pixelRatio || ('ontouchstart' in window) ? function() {
		        var a = window.devicePixelRatio || 1
		          , c = this.webkitBackingStorePixelRatio
		           		|| this.mozBackingStorePixelRatio
		           		|| this.msBackingStorePixelRatio
		           		|| this.oBackingStorePixelRatio
		           		|| this.backingStorePixelRatio || 1;
		        return a / c
		    }.bind(this._canvas)() : 1,
            'width' in a && 'height' in a?this.setSize(a.width,a.height):this.setSize(this._canvas.offsetWidth,this._canvas.offsetHeight),
            this._canvas.style.padding = 0,
            this._canvas.style.margin = 0,
            this._canvas.style.border = 0,
            this._canvas.style.background = "transparent";
        },
        getContext: function() {
            return this._context;
        },
        getPixelRatio: function() {
            return this._pixelRatio;
        },
        setPixelRatio: function(a) {
            this._pixelRatio = a,
            this.setSize(this.getWidth(), this.getHeight())
        },
        setWidth: function(a) {
            this._width = this._canvas.width = a * this._pixelRatio,
            this._canvas.style.width = a + "px"
        },
        setHeight: function(a) {
            this._height = this._canvas.height = a * this._pixelRatio,
            this._canvas.style.height = a + "px"
        },
        getWidth: function() {
            return this._width
        },
        getHeight: function() {
            return this._height
        },
        setSize: function(a, b) {
            this.setWidth(a),
            this.setHeight(b)
        }
    }

    function Animate(opt){
        opt = opt || {};
        this.node = opt.node;
        this.timer = new Timer();
    }
    Animate.prototype = {
        update : function(){
            return (!('_update' in this)) || !!this._update.apply(this,[this.timer.interval, this.timer.elapsedTime]);
        },
        play : function(){
            return '_start' in this && this._start.apply(this,arguments),
            this.startTime = +new Date,
            _animates.push(this),
            this.timer.start(),
            this;
        },
        pause : function(){
            for(var i=0; i<_animates.length; i++) {
                if(_animates[i] == this) {
                    _animates.splice(i, 1);
                    break;
                }
            }
            return this;
        }
    }

    function Drawer(){
    	this.position = [0,0];
    	this.color    = 0x000000;
    	this.alpha    = 1;
    	this.shadow   = {enabled:false,offset:{x:0,y:0},blur:2,color:undefined,alpha:undefined};
    }

    Drawer.prototype = {
    	draw : function(){
    		'_draw' in this && this._draw.apply(this,arguments);
    	}
    }

    function Cirque(radius,tube, position,arc){
    	this.radius   = radius;
    	this.tube     = tube;
    	this.position = position instanceof Array?position:(position instanceof Object?[+position.x,+position.y]:[0,0]);
    	this.arc      = arc;
    }

    extendClass(Cirque, Drawer,{
    	_draw : function(context){
    		context.save();
			context.lineWidth = this.tube;
			context.strokeStyle = convertColor(this.color,this.alpha);
			context.beginPath();
			context.arc(this.position[0], this.position[1], this.radius, 0, this.arc , false);
			context.closePath();
			context.stroke();
			context.restore();
    	}
    });

    function Circle(radius, position, color){
    	this.radius   = radius;
    	this.position = position instanceof Array?position:(position instanceof Object?[+position.x,+position.y]:[0,0]);
    	this.color    = reverseConvertColor(color);
    }

    extendClass(Circle, Drawer, {
    	_draw : function(ctx){
    		ctx.save();

			if (this.shadow.enabled) {
				ctx.shadowOffsetX = this.shadow.offset.x;
			    ctx.shadowOffsetY = this.shadow.offset.y;
			    ctx.shadowBlur    = this.shadow.blur;
			    ctx.shadowColor   = convertColor(this.shadow.color===undefined?this.color:this.shadow.color,1); // 颜色
			}

			ctx.save();
			ctx.fillStyle = convertColor(this.color,this.alpha);

			ctx.beginPath();
			ctx.arc(this.position[0],this.position[1],this.radius,0,Math.PI*2,true);
			ctx.closePath();
			ctx.fill();
			ctx.restore();
    	},
    	rect : function(){
    		return {position:{x:-this.radius,y:-this.radius},size:{width:this.radius*2,height:this.radius*2}}
    	}
    })

    function Stage(a){
        this._init(a);
    	this._objects = [];
    }

    extendClass(Stage,Canvas,{
    	add : function(object){
    		this._objects.push(object);
    		return this;
    	},
    	draw : function(){
    		var count      = this._objects.length,
    		    pixelRatio = this.getPixelRatio(),
    		    context    = this.getContext(),
    		    width      = this.getWidth(),
    		    height     = this.getHeight();
    		context.clearRect(0, 0, width, height);
    		for(var i=0;i<count;i++){
    			this._objects[i].draw(context);
    		}
    	}
    });

    function    Renderer(stage){
    	this._stop      = true;
    	this._speed     = 1.5;
    	this._stage     = stage;
    	this._listeners = {};
    }

    Renderer.prototype = {
    	_draw : function(){
    		this._stage.draw();
    	},
    	setSpeed: function(speed){
    		this._speed = speed;
    	},
    	pause : function(){
    		this._stop = true;
    	},
    	on:function(type, fn){
            type = type.replace(/^\s*|\s*$/gi,'');
            if (/\s+/.test(type)) {
                type.split(/\s+/).forEach(function(v,k){
                    this.on(v, fn);
                },this)
                return this;
            }
            if (typeof this._listeners[type] === "undefined") {
                this._listeners[type] = [];
            }
            if (typeof fn === "function") {
                this._listeners[type].push(fn);
            }
            return this;
        },
        off:function(type, fn){
            type = type.replace(/^\s*|\s*$/gi,'');
            if (/\s+/.test(type)) {
                type.split(/\s+/).forEach(function(v,k){
                    this.on(v, fn);
                },this)
                return this;
            }
            var arrayEvent = this._listeners[type];
            if (typeof type === "string" && arrayEvent instanceof Array) {
                if (typeof fn === "function") {
                    // 清除当前type类型事件下对应fn方法
                    for (var i=0, length=arrayEvent.length; i<length; i+=1){
                        if (arrayEvent[i] === fn){
                            this._listeners[type].splice(i, 1);
                            break;
                        }
                    }
                } else {
                    // 如果仅仅参数type, 或参数fn邪魔外道，则所有type类型事件清除
                    delete this._listeners[type];
                }
            }
            return this;
        },
        trigger: function(type){
            type = type.replace(/^\s*|\s*$/gi,'');
            var args = Array.prototype.slice.call(arguments,1);
            args.unshift(new Event(type));
            var arrayEvent = this._listeners[type];
            if (arrayEvent instanceof Array) {
                for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                    if (typeof arrayEvent[i] === "function") {
                        arrayEvent[i].apply(this,args);
                    }
                }
            }
            return this;
        },
    	run : function(){
            if (!this._stop) {
                return;
            }
    		this._stop = false;
    		var self = this;
    		this._startTime = +new Date;
    		this._lastTime = this._startTime;
            this.elapsedTime = 0;
    		var tick = function() {
    			if (this._stop) {
    				return;
    			}
			    requestAnimationFrame(tick);
			    var currentTime = +new Date;
			    this.interval = (currentTime - this._lastTime)*this._speed;
			    this.elapsedTime += this.interval;
                for(var i = 0; i<_timers.length; i++){
                    (_timers[i]._speed = this._speed) && _timers[i].hit();
                }
                for(var i = 0; i<_animates.length; i++){
                    if(_animates[i].update()){

                    }
                }
			    this.trigger('update',this.interval,this.elapsedTime);
			　　this._draw();
				this._lastTime = currentTime;
			}.bind(this);
			tick();
    	}
    }
	return {
        convertColor        : convertColor,
        reverseConvertColor : reverseConvertColor,
        distance            : distance,
        mix                 : mix,
        extendClass         : extendClass,
        Easing              : Easing,
		Stage               : Stage,
		Cirque              : Cirque,
		Circle              : Circle,
        Timer               : Timer,
        Animate             : Animate,
		Renderer            : Renderer
	}
})();