/*!
 * 
 * 这是一个迷你版avalon,支持IE9+
 */
(function () {
    var avalon = function () {
    }
    window.avalon = avalon
    var vmodels = avalon.vmodels = {}
    avalon.define = function (obj) {
        return vmodels[obj.$id] = Observer(obj)
    }

    avalon.mix = function (a, b) {
        for (var i in b) {
            a[i] = b[i]
        }
        return a
    }

    avalon.noop = avalon

    var rhashcode = /\d\.\d{4}/
    avalon.makeHashCode = function (prefix) {
        /* istanbul ignore next*/
        prefix = prefix || 'avalon'
        /* istanbul ignore next*/
        return String(Math.random() + Math.random()).replace(rhashcode, prefix)
    }
    var hasConsole = typeof console === 'object'
    avalon.config = function fn(obj) {
        avalon.mix(fn, obj)
    }
    avalon.config({
        debug: 1
    })
    avalon.log = function () {
        if (hasConsole && avalon.config.debug) {
            // http://stackoverflow.com/questions/8785624/how-to-safely-wrap-console-log
            Function.apply.call(console.log, console, arguments)
        }
    }
    var rword = /[^, ]+/g
    avalon.oneObject = function (array, val) {
        if (typeof array === 'string') {
            array = array.match(rword) || []
        }
        var result = {},
                value = val !== void 0 ? val : 1
        for (var i = 0, n = array.length; i < n; i++) {
            result[array[i]] = value
        }
        return result
    }
    
    avalon.quote = JSON.stringify

   function startWith(long, short){
       return long.indexOf(short) === 0
   }
    function isArray(a) {
        return Array.isArray(a)
    }
    function isObservable(key, value) {
        return (typeof value !== 'function') && key.charAt(0) !== '$'
    }
    function isObject(a) {
        return a && typeof a === 'object'
    }
    function createFragment() {
        return document.createDocumentFragment()
    }
    function createAnchor(nodeValue) {
        return document.createComment(nodeValue)
    }
    function copy(target) {
        var ret
        if (isArray(target)) {
            ret = target.slice(0)
        } else if (isObject(target)) {
            ret = avalon.mix({}, target)
        }

        return ret || target
    }
    function replaceNode(newNode, oldNode) {
        oldNode.parentNode.replaceChild(newNode, oldNode)
        return newNode
    }

    avalon.each = function (a, fn) {
        if (isArray(a)) {
            a.forEach(function (el, index) {
                fn(index, el)
            })
        } else {
            for (var i in a) {
                fn(i, a[i])
            }
        }
    }

    var delayCompile = {}
    avalon.directives = {}
    avalon.directive = function (name, opts) {
        avalon.directives[name] = opts
        if (opts.delay) {
            delayCompile[name] = 1
        }
    }
    //============ Observer 模块 ==========

    function createObserver(target, ret) {
        if (isObject(target)) {
            return target.$events ? target : new Observer(target, ret)
        }
        if (ret) {
            return target
        }
    }
    function Observer(data, vm) {
        if (isArray(data)) {
            vm = observeArray(data)
        } else {
            vm = observeObject(data)
        }
        if(vm)
        vm.$events.__dep__ = new Depend()
        return vm
    }

    function observeObject(object) {
        var core = {} //events
        var state = {}
        var props = {}
        for (var key in object) {
            var val = object[key]
            if (isObservable(key, val)) {
                state[key] = createAccessor(key, val, core)
            } else {
                props[key] = val
            }
        }

        addMoreProps(props, object, state, core)
        var observe = {}
        observe = createViewModel(observe, state, props)
        for (var i in props) {
            observe[i] = props[i]
        }
        core.observe = observe
        return observe
    }

    function observeItemObject(before, after) {
        var core = before.$events
        var state = before.$accessor
        var object = after.data
        delete after.data
        var props = after
        for (var key in object) {
            state[key] = createAccessor(key, object[key], core)
        }

        addMoreProps(props, object, state, core)
        var observe = {}
        observe = createViewModel(observe, state, props)
        for (var i in props) {
            observe[i] = props[i]
        }
        core.observe = observe
        if (!core.__dep__) {
            core.__dep__ = new Depend()
        }
        return observe
    }
    var modelAccessor = {
        get: function () {
            return toJson(this)
        },
        set: avalon.noop,
        enumerable: false,
        configurable: true
    }
    
    function toJson(val) {
        if (isArray(val)) {
            var array = []
            for (var i = 0; i < val.length; i++) {
                array[i] = toJson(val[i])
            }
            return array
        } else if (isObject(val)) {
            var obj = {}
            for (i in val) {
                if (val.hasOwnProperty(i)) {
                    var value = val[i]
                    obj[i] = value && value.nodeType ? value : toJson(value)
                }
            }
            return obj
        } else {
            return val
        }
    }

    function createViewModel(a, b, c) {
        return Object.defineProperties(a, b)
    }
    
    function observeArray(array, rewrite, ret) {
    function createAccessor(key, val, core) {
        var value = val
        var childOb = createObserver(val)
        return {
            get: function Getter() {
                var ret = value
                if (Depend.watcher) {
                    core.__dep__.collect()
                    if (childOb && childOb.$events) {
                        childOb.$events.__dep__.collect()
                    }
                }
                return ret
            },
            set: function Setter(newValue) {
                var oldValue = value
                if (newValue === oldValue) {
                    return
                }
                core.__dep__.beforeNotify()
                value = newValue
                childOb = createObserver(newValue)
                core.__dep__.notify()
            },
            enumerable: true,
            configurable: true
        }
    }


    function addMoreProps(props, object, state, core) {
        var hash = avalon.makeHashCode('$')
        state.$model = modelAccessor
        avalon.mix(props, {
            $id: object.$id || hash,
            $events: core,
            $hashcode: hash,
            $accessor: state
        })
    }
    //============ 监控数组   ==========
    var ap = Array.prototype
    var __array__ = {}
    var __method__ = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
    function rewriteArrayMethods(array) {
        /* istanbul ignore else */
        for (var i in __array__) {
            array[i] = __array__[i]
        }
        array.$events = {} // 以后自动加上    
    }

    __method__.forEach(function (method) {
        var original = ap[method]
        __array__[method] = function () {
            // 继续尝试劫持数组元素的属性
            var args = []
            var size = this.length
            var core = this.$events
            for (var i = 0; i < arguments.length; i++) {
                args.push(arguments[i])
            }
            core.__dep__.beforeNotify()
            var result = original.apply(this, args)
            var inserts = []
            switch (method) {
                case 'push':
                case 'unshift':
                    inserts = args
                    break
                case 'splice':
                    inserts = args.slice(2)
                    break
            }
            if (inserts && inserts) {
                inserts = observeArray(inserts, 1, 1)
            }
            
            core.__dep__.notify({
                method: method,
                args: args
            })
            return result
        }
    })

    //============== Depend ============

    var guid = 0
    /**
     * 依赖收集类 用于联结 VM 与 Watcher
     */
    function Depend() {
        this.watchers = []
        this.guid = guid++
    }

    /**
     * 当前收集依赖的订阅模块 watcher
     * @type  {Object}
     */
    Depend.watcher = null
    var dp = Depend.prototype
    /**
     * 添加依赖订阅
     * @param  {Object}  watcher
     */
    dp.addWatcher = function (watcher) {
        this.watchers.push(watcher)
    }

    /**
     * 移除依赖订阅
     * @param  {Object}  watcher
     */
    dp.removeWatcher = function (watcher) {
        var index = this.watchers.indexOf(watcher)
        if (index > -1) {
            this.watchers.splice(index, 1)
        }
    }

    /**
     * 为 watcher 收集当前的依赖
     */
    dp.collect = function () {
        if (Depend.watcher) {
            Depend.watcher.addDepend(this)
        }
    }

    /**
     * 依赖变更前调用方法,用于旧数据的缓存处理
     */
    dp.beforeNotify = function () {
        this.watchers.forEach(function (watcher) {
            watcher.beforeUpdate()
        })
    }

    /**
     * 依赖变更,通知每一个订阅了该依赖的 watcher
     * @param  {Object}  args  [数组操作参数信息]
     */
    dp.notify = function (args) {
        var guid = this.guid
        this.watchers.forEach(function (watcher) {
            watcher.update(args, guid)
        })
    }

    //============ Watcher模块 ============

    /** 
     * 遍历对象/数组每一个可枚举属性
     * @param  {Object|Array}  target  [遍历值/对象或数组]
     * @param  {Boolean}       root    [是否是根对象/数组]
     */
    var walkedObs = []
    function walkThrough(target, root) {
        var events = target && target.$events

        var guid = events && events.__dep__.guid

        if (guid) {
            if (walkedObs.indexOf(guid) > -1) {
                return
            } else {
                walkedObs.push(guid)
            }
        }

        avalon.each(target, function (key, value) {
            walkThrough(value, false)
        })
        if (root) {
            walkedObs.length = 0
        }
    }
    /**
     * 用户watch回调及页面上的指令都会转换它的实例
     * @param {type} vm
     * @param {type} desc
     * @param {type} callback
     * @param {type} context
     * @returns {Watcher}
     */

    function Watcher(vm, desc, callback, context) {
        this.vm = vm
        avalon.mix(this, desc)
        this.callback = callback
        this.context = context || this
        // 依赖 id 缓存
        this.depIds = []
        this.newDepIds = []
        this.shallowIds = []
        // 依赖实例缓存
        this.depends = []
        this.newDepends = []
        var expr = desc.expr
        var preSetFunc = typeof expr === 'function'
        // 缓存取值函数
        this.getter = preSetFunc ? expr : createGetter(expr)
        // 缓存设值函数(双向数据绑定)
        this.setter = this.type === 'duplex' ? createSetter(expr) : null
        // 缓存表达式旧值
        this.oldVal = null
        // 表达式初始值 & 提取依赖
        this.value = this.get()
    }

    var wp = Watcher.prototype
    /**
     * 获取取值域
     * @return  {Object}
     */
    wp.getScope = function () {
        return this.context.scope || this.vm
    }

    wp.getValue = function () {
        var scope = this.getScope()
        try {
            return this.getter.call(scope, scope)
        } catch (e) {
            avalon.log(this.getter + 'exec error')
        }
    }

    wp.setValue = function (value) {
        var scope = this.getScope()
        if (this.setter) {
            this.setter.call(scope, scope, value)
        }
    }
    wp.get = function () {
        var value
        this.beforeGet()
        value = this.getValue()
        // 深层依赖获取
        if (this.deep) {
            // 先缓存浅依赖的 ids
            this.shallowIds = copy(this.newDepIds)
            walkThrough(value, true)
        }

        this.afterGet()
        return value
    }

    wp.beforeGet = function () {
        Depend.watcher = this
    }

    wp.addDepend = function (depend) {
        var guid = depend.guid
        var newIds = this.newDepIds
        if (newIds.indexOf(guid) < 0) {
            newIds.push(guid)
            this.newDepends.push(depend)
            if (this.depIds.indexOf(guid) < 0) {
                depend.addWatcher(this)
            }
        }
    }

    wp.removeDepends = function (filter) {
        var self = this
        this.depends.forEach(function (depend) {
            if (filter) {
                if (filter.call(self, depend)) {
                    depend.removeWatcher(self)
                }
            } else {
                depend.removeWatcher(self)
            }
        })
    }

    wp.afterGet = function () {
        Depend.watcher = null
        // 清除无用的依赖
        this.removeDepends(function (depend) {
            return this.newDepIds.indexOf(depend.guid) < 0
        })
        // 重设依赖缓存
        this.depIds = copy(this.newDepIds)
        this.newDepIds.length = 0
        this.depends = copy(this.newDepends)
        this.newDepends.length = 0
    }

    wp.beforeUpdate = function () {
        this.oldVal = copy(this.value)
    }

    wp.update = function (args, guid) {
        var oldVal = this.oldVal
        var newVal = this.value = this.get()
        var callback = this.callback
        if (callback && (oldVal !== newVal)) {
            var fromDeep = this.deep && this.shallowIds.indexOf(guid) < 0
            callback.call(this.context, newVal, oldVal, fromDeep, args)
        }
    }

    wp.destroy = function () {
        this.value = null
        this.removeDepends()
        if (this._destroy) {
            this._destroy()
        }
        for (var i in this) {
            delete this[i]
        }
    }

    //========== 渲染模块 =========
    function isDirective(directive) {
        return /^(?:\:|ms-)\w+/.test(directive)
    }

    function delayCompileNodes(dirs) {
        for (var i in delayCompile) {
            if (('ms-' + i) in dirs) {
                return true
            }
        }
    }
    var regMustache = /\{\{.+\}\}/
    var rcolon = /^(:|ms)-/
    function getRawBindings(node) {
        if (node.nodeType === 1) {
            var attrs = node.attributes
            var props = {}, has = false
            for (var i = 0, n = attrs.length; i < n; i++) {
                var attr = attrs[i]
                if (attr.specified) {
                    var name = attr.name
                    if (name.charAt(0) === ':') {
                        name = name.replace(rcolon, 'ms-')
                    }
                    if (startWith(name,'ms-')) {
                        props[name] = attr.value
                        has = true
                    }

                }
            }
            if (attrs['is']) {
                if (!props['ms-widget']) {
                    props['ms-widget'] = '{}'
                }
                has = true
            }

            return has ? props : false
        } else if (node.nodeType === 3) {
            if (regMustache.test(node.nodeValue)) {
                return {
                    nodeValue: node.nodeValue
                }
            }
        } else if (node.nodeType === 8) {
            if (startWith(node.nodeValue,'ms-for:')) {
                var nodes = []
                var deep = 1
                var begin = node
                var expr = node.nodeValue.replace('ms-for:', '')
                node.nodeValue = 'msfor:' + expr
                while (node = node.nextSibling) {
                    nodes.push(node)

                    if (node.nodeType === 8) {
                        if (startWith(node.nodeValue,'ms-for:')) {
                            deep++
                        } else if (startWith(node.nodeValue,'ms-for-end:')) {
                            deep--
                            if (deep === 0) {
                                node.nodeValue = 'msfor-end:'
                                nodes.pop()
                            }
                        }
                    }

                }
                var f = createFragment()
                nodes.forEach(function (n) {
                    f.appendChild(n)
                })
                this.queue.push([
                    f, this.vm, {'ms-for': expr}, begin
                ])

            }
        }
    }
    function emptyNode(a) {
        var f = createFragment()
        while (a.firstChild) {
            f.appendChild(a.firstChild)
        }
        return f
    }
    avalon.scan = function (node, vm) {
        return new Render(node, vm)
    }
    var eventMap = avalon.oneObject('animationend,blur,change,input,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scan,scroll,submit')

    function Render(node, vm) {
        this.node = node
        this.vm = vm
        this.queue = []
        this.directives = []
        this.init()
    }

    var cp = Render.prototype
    cp.init = function () {
        this.fragment = emptyNode(this.node)
        this.getBindings(this.fragment, true)
    }

    cp.getRawBindings = getRawBindings
    cp.getBindings = function (element, root) {
        var childNodes = element.childNodes
        var scope = this.vm
        var dirs = this.getRawBindings(element)
        if (dirs) {
            this.queue.push([element, scope, dirs])
        }
        if (!/style|textarea|xmp|script|template/i.test(element.nodeName)
                && childNodes
                && childNodes.length
                && !delayCompileNodes(dirs || {})
                ) {
            for (var i = 0; i < childNodes.length; i++) {
                this.getBindings(childNodes[i], false)
            }
        }
        if (root) {
            this.compileBindings()
        }
    }

    cp.compileBindings = function () {
        this.queue.forEach(function (tuple) {
            this.parseBindings(tuple)
        }, this)
        this.node.appendChild(this.fragment)
    }

    /**
     * 将收集到的绑定属性进行深加工,最后转换为watcher
     * @param   {Array}  tuple  [node, scope, dirs]
     */
    cp.parseBindings = function (tuple) {
        var node = tuple[0]
        var scope = tuple[1]
        var dirs = tuple[2]
        if ('nodeValue' in dirs) {
            this.parseText(node, dirs, scope)
        } else if (!('ms-skip' in dirs)) {
            var uniq = {}, bindings = []
            var directives = avalon.directives
            for (var name in dirs) {
                var value = dirs[name]
                var rbinding = /^(\:|ms\-)\w+/
                var match = name.match(rbinding)
                var arr = name.replace(match[1], '').split('-')

                if (eventMap[arr[0]]) {
                    arr.unshift('on')
                }
                if (arr[0] === 'on') {
                    arr[2] = parseFloat(arr[2]) || 0
                }
                arr.unshift('ms')
                var type = arr[1]
                if (directives[type]) {

                    var binding = {
                        type: type,
                        param: arr[2],
                        name: arr.join('-'),
                        expr: value,
                        priority: directives[type].priority || type.charCodeAt(0) * 100
                    }
                    if (type === 'on') {
                        binding.priority += arr[3]
                    }
                    if (!uniq[binding.name]) {
                        uniq[binding.name] = value
                        bindings.push(binding)
                        if (type === 'for') {
                            binding.begin = tuple[3]
                            bindings = [binding]
                            break
                        }
                    }

                }

            }

            bindings.forEach(function (binding) {
                this.parse(node, binding, scope)
            }, this)
        }
    }

    cp.parse = function (node, binding, scope) {
        var dir = avalon.directives[binding.type]
        if (dir) {
            if (dir.parse) {
                dir.parse(binding)
            }
            this.directives.push(new DirectiveWatcher(node, binding, scope))
        }
    }

    cp.parseText = function (node, dir, scope) {
        var rlineSp = /\n\r?/g
        var text = dir.nodeValue.trim().replace(rlineSp, '')
        var pieces = text.split(/\{\{(.+?)\}\}/g)
        var tokens = []
        pieces.forEach(function (piece) {
            var segment = '{{' + piece + '}}'
            if (text.indexOf(segment) > -1) {
                tokens.push('(' + piece + ')')
                text = text.replace(segment, '')
            } else if (piece) {
                tokens.push(avalon.quote(piece))
                text = text.replace(piece, '')
            }
        })
        var binding = {
            expr: tokens.join('+'),
            name: 'nodeValue',
            type: 'nodeValue'
        }

        this.directives.push(new DirectiveWatcher(node, binding, scope))
    }

    cp.destroy = function () {
        this.directives.forEach(function (directive) {
            directive.destroy()
        })
        for (var i in this) {
            delete this[i]
        }
    }

    //========== Eval  ========
    var stringNum = 0
    var stringPool = {
        map: {}
    }
    var rfill = /\?\?\d+/g
    function dig(a) {
        var key = '??' + stringNum++
        stringPool.map[key] = a
        return key + ' '
    }
    function fill(a) {
        var val = stringPool.map[a]
        return val
    }
    function clearString(str) {
        var array = readString(str)
        for (var i = 0, n = array.length; i < n; i++) {
            str = str.replace(array[i], dig)
        }
        return str
    }

    function readString(str) {
        var end, s = 0
        var ret = []
        for (var i = 0, n = str.length; i < n; i++) {
            var c = str.charAt(i)
            if (!end) {
                if (c === "'") {
                    end = "'"
                    s = i
                } else if (c === '"') {
                    end = '"'
                    s = i
                }
            } else {
                if (c === '\\') {
                    i += 1
                    continue
                }
                if (c === end) {
                    ret.push(str.slice(s, i + 1))
                    end = false
                }
            }
        }
        return ret
    }
    var keyMap = avalon.oneObject("break,case,catch,continue,debugger,default,delete,do,else,false," +
            "finally,for,function,if,in,instanceof,new,null,return,switch,this," +
            "throw,true,try,typeof,var,void,while,with," + /* 关键字*/
            "abstract,boolean,byte,char,class,const,double,enum,export,extends," +
            "final,float,goto,implements,import,int,interface,long,native," +
            "package,private,protected,public,short,static,super,synchronized," +
            "throws,transient,volatile")
    var skipMap = avalon.mix({
        Math: 1,
        Date: 1,
        $event: 1,
        __vmodel__: 1
    }, keyMap)
    var rguide = /(^|[^\w\u00c0-\uFFFF_])(@|##)(?=[$\w])/g
    var ruselessSp = /\s*(\.|\|)\s*/g
    var rlocal = /[$a-z_][$\.\w\_]*/gi
    var rregexp = /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/g
    function addScope(expr) {
        var body = expr.trim().replace(rregexp, dig)//移除所有正则
        body = clearString(body)      //移除所有字符串
        body = body.replace(ruselessSp, '$1').//移除.|两端空白
                replace(rguide, '$1__vmodel__.').//转换@与##
                replace(rlocal, function (a, b) {
                    var arr = a.split('.')
                    if (!skipMap[arr[0]]) {
                        return '__vmodel__.' + a
                    }
                    return a
                }).replace(rfill, fill).replace(rfill, fill)
        return body
    }
    function createGetter(expr) {
        var body = addScope(expr)
        try {
            return new Function('__vmodel__', 'return ' + body + ';')
        } catch (e) {
            avalon.log('parse getter: ', expr, body, ' error')
            return avalon.noop
        }
    }

    /**
     * 生成表达式设值函数
     * @param  {String}  expr
     */
    function createSetter(expr) {
        var body = addScope(expr)
        if (!startWith(body,'__vmodel__.')) {
            body = ' __vmodel__.' + body
        }
        body = 'try{ ' + body + ' = __value__}catch(e){}'
        try {
            return new Function('__vmodel__', '__value__', body + ';')
        } catch (e) {
            avalon.log('parse setter: ', expr, ' error')
            return avalon.noop
        }
    }
    //=================== 各种指令的实现  ==============
    /**
     * 一个watcher装饰器
     * @returns {watcher}
     */
    function DirectiveWatcher(node, binding, scope) {
        var type = binding.type
        var directive = avalon.directives[type]
        if (node.nodeType === 1) {
            node.removeAttribute('ms-' + type)
            node.removeAttribute(':' + type)
        }
        var callback = directive.update ? function (value) {
            directive.update.call(this, node, value)
        } : avalon.noop
        var watcher = new Watcher(scope, binding, callback)

        watcher.node = node
        watcher._destory = directive.destory
        if (directive.init)
            directive.init(watcher)
        delete watcher.value
        watcher.update()
        return watcher
    }

    avalon.directive('nodeValue', {
        update: function (node, value) {
            node.nodeValue = value
        }
    })
    avalon.directive('attr', {
        update: function (node, value) {
            for (var i in value) {
                node[i] = value[i]
            }
        }
    })
     avalon.directive('css', {
        update: function (node, value) {
             for (var i in value) {
                node.style.setPropertyValue(i, value[i])
            }
            
        }
    })
    avalon.directive('on', {
        init: function (watcher) {
            var node = watcher.node
            var body = addScope(watcher.expr)
            var rhandleName = /^__vmodel__\.[$\w\.]+$/i
            if (rhandleName.test(body)) {
                body = body + '($event)'
            }

            body = body.replace(/__vmodel__\.([^(]+)\(([^)]*)\)/, function (a, b, c) {
                return '__vmodel__.' + b + '.call(__vmodel__' + (/\S/.test(c) ? ',' + c : '') + ')'
            })
            var ret = [
                'try{',
                '\tvar __vmodel__ = this;',
                '\t' + body,
                '}catch(e){avalon.log(e)}']
            var fn = new Function('$event', ret.join('\n'))
            this.eventHandler = function (e) {
                return fn.call(watcher.vm, e)
            }
            node.addEventListener(watcher.param, this.eventHandler)
        },
        destory: function () {
            this.node.removeEventListener(this.param, this.eventHandler)
        }
    })
    avalon.directive('if', {
        delay: true,
        priority: 5,
        init: function (watcher) {
            var node = watcher.node
            node.removeAttribute('ms-if')
            node.removeAttribute(':if')
            watcher.node = node
            var parent = node.parentNode
            var c = watcher.placeholder = createAnchor('if')
            replaceNode(c, node)
            watcher.isShow = true
            var f = createFragment()
            f.appendChild(node)
            watcher.fragment = f.cloneNode(true)
            watcher.boss = avalon.scan(f, watcher.vm)
            if (!!watcher.value) {
                parent.replaceChild(f, c)
            }
        },
        update: function (node, value) {
            value = !!value
            if (this.isShow === value)
                return
            this.isShow = value
            if (value) {

                var c = this.placeholder
                var p = c.parentNode
                var node = this.fragment.cloneNode(true)
                this.boss = avalon.scan(node, this.vm)
                this.node = node.firstChild
                p.replaceChild(node, c)

            } else {

                var p = this.node.parentNode
                var c = this.placeholder
                p.replaceChild(c, this.node)
                this.boss.destroy()
            }


        }
    })
    avalon.directive('html', {
        update: function (node, value) {
            this.boss && this.boss.destroy()
            var div = document.createElement('div')
            div.innerHTML = value
            this.boss = avalon.scan(div, this.vm)
            emptyNode(node)
            node.appendChild(emptyNode(div))
        },
        delay: true
    })
    avalon.directive('duplex', {
        priority: 999999,
        init: function (watcher) {
            var node = watcher.node
            this.eventHandler = function () {
                watcher.setValue(node.value)
            }
            if (/password|text|hidden/i.test(node.type)) {
                node.addEventListener('input', this.eventHandler)
            }
        },
        update: function (node, value) {
            if (/password|text|hidden/i.test(node.type)) {
                node.value = value
            }
        },
        destory: function () {
            this.node.removeEventListener('input', this.eventHandler)
        }
    })
    avalon.directive('text', {
        delay: true,
        init: function (watcher) {
            var node = watcher.node
            emptyNode(node)
            var child = document.createTextNode(watcher.value)
            node.appendChild(child)
            watcher.node = child
            var type = 'nodeValue'
            watcher.type = watcher.name = type
            var directive = avalon.directives[type]
            watcher.callback = function (value) {
                directive.update.call(this, watcher.node, value)
            }
        }
    })
   
    

    var none = 'none'
    function getDisplay(el) {
        return window.getComputedStyle(el, null).display
    }
    function parseDisplay(elem, val) {
        //用于取得此类标签的默认display值
        var doc = elem.ownerDocument
        var nodeName = elem.nodeName
        var key = '_' + nodeName
        if (!parseDisplay[key]) {
            var temp = doc.body.appendChild(doc.createElement(nodeName))
            val = getDisplay(temp)
            doc.body.removeChild(temp)
            if (val === none) {
                val = 'block'
            }
            parseDisplay[key] = val
        }
        return parseDisplay[key]
    }
    avalon.directive('skip', {
        delay: true
    })
    avalon.directive('visible', {
        init: function (watcher) {
            watcher.isShow = true
        },
        update: function (node, value) {
            var isShow = !!value
            if (this.isShow === isShow)
                return
            this.isShow = isShow
            var display = node.style.display
            if (isShow) {
                if (display === none) {
                    value = this.displayValue
                    if (!value) {
                        node.style.display = ''
                        if (node.style.cssText === '') {
                            node.removeAttribute('style')
                        }
                    }
                }
                if (node.style.display === '' && getDisplay(node) === none &&
                        // fix firefox BUG,必须挂到页面上
                        node.ownerDocument.contains(node)) {

                    value = parseDisplay(node)
                }

            } else {
                if (display !== none) {
                    value = none
                    this.displayValue = display
                }
            }
            function cb() {
                if (value !== void 0) {
                    node.style.display = value
                }
            }
            cb()
        }
    })
    var rforAs = /\s+as\s+([$\w]+)/
    var rident = /^[$a-zA-Z_][$a-zA-Z0-9_]*$/
    var rinvalid = /^(null|undefined|NaN|window|this|\$index|\$id)$/
    var rargs = /[$\w_]+/g
    avalon.directive('for', {
        delay: true,
        priority: 3,
        parse: function (binding) {
            var str = binding.origExpr = binding.expr, asName
            str = str.replace(rforAs, function (a, b) {
                /* istanbul ignore if */
                if (!rident.test(b) || rinvalid.test(b)) {
                    avalon.error('alias ' + b + ' is invalid --- must be a valid JS identifier which is not a reserved name.')
                } else {
                    asName = b
                }
                return ''
            })

            var arr = str.split(' in ')
            var kv = arr[0].match(rargs)
            if (kv.length === 1) {//确保avalon._each的回调有三个参数
                kv.unshift('$key')
            }
            binding.expr = arr[1]
            binding.keyName = kv[0]
            binding.valName = kv[1]
            binding.signature = avalon.makeHashCode('for')
            if (asName) {
                binding.asName = asName
            }
        },
        init: function (watcher) {
            var node = watcher.node
            if (node.nodeType === 11) {
                watcher.fragment = node
                var begin = watcher.begin
                delete watcher.begin
            } else {
                begin = createAnchor('msfor:' + watcher.origExpr)
                var end = createAnchor('msfor-end:')
                var p = node.parentNode
                p.insertBefore(begin, node)
                p.replaceChild(end, node)
                var f = createFragment()
                f.appendChild(node)
                watcher.fragment = f
                var cb = node.getAttribute('data-for-rendered')
                if (cb) {
                    watcher.forCb = createGetter(cb)
                }
              
            }
            watcher.node = begin
            watcher.end = watcher.node.nextSibling


            watcher.fragment.appendChild(createAnchor(watcher.signature))
            watcher.cache = {}

            watcher.update = function () {
                var newVal = this.value = this.get()
                var traceIds = createFragments(this, newVal)
                var callback = this.callback
                if (this.oldTrackIds !== traceIds) {
                    this.oldTrackIds = traceIds
                    callback.call(this.context, newVal)
                }
            }

        },
        update: function (node, value) {
            if (!this.preFragments) {
                mountList(this)
            } else {
                diffList(this)
                updateList(this)
            }
            if (this.forCb) {
                this.forCb()
            }
        }
    })

    function getTraceKey(item) {
        var type = typeof item
        return item && type === 'object' ? item.$hashcode : type + ':' + item
    }

    //创建一组fragment的虚拟DOM
    function createFragments(watcher, obj) {
        if (isObject(obj)) {
            var array = isArray(obj)
            var ids = []
            var fragments = [], i = 0
            avalon.each(obj, function (key, value) {
                var k = array ? getTraceKey(value) : key
                fragments.push(new Fragment(k, value, i++))
                ids.push(k)
            })
            if (watcher.fragments) {
                watcher.preFragments = watcher.fragments
                watcher.fragments = fragments
            } else {
                watcher.fragments = fragments
            }
            return ids.join(';;')
        } else {
            return NaN
        }
    }


    function mountList(watcher) {
        var f = createFragment()
        watcher.fragments.forEach(function (fragment, index) {
            FragmentDecorator(fragment, watcher, index)
            saveInCache(watcher.cache, fragment)
            f.appendChild(fragment.dom)
        })
        watcher.end.parentNode.insertBefore(f, watcher.end)
    }

    function diffList(watcher) {
        var cache = watcher.cache
        var newCache = {}
        var fuzzy = []
        var list = watcher.preFragments
        list.forEach(function (el) {
            el._destory = true
        })
        watcher.fragments.forEach(function (c, index) {
            var fragment = isInCache(cache, c.key)
            //取出之前的文档碎片
            if (fragment) {
                delete fragment._destory
                fragment.oldIndex = fragment.index
                fragment.index = index // 相当于 c.index
                fragment.vm[watcher.keyName] = index
                saveInCache(newCache, fragment)
            } else {
                //如果找不到就进行模糊搜索
                fuzzy.push(c)
            }
        })

        fuzzy.forEach(function (c) {
            var fragment = fuzzyMatchCache(cache, c.key)
            if (fragment) {//重复利用
                fragment.oldIndex = fragment.index
                fragment.key = c.key
                var val = fragment.val = c.val
                var index = fragment.index = c.index
                fragment.vm[watcher.valName] = val
                fragment.vm[watcher.keyName] = index
                delete fragment._destory
            } else {
                fragment = FragmentDecorator(c, watcher, c.index)
                list.push(fragment)
            }
            saveInCache(newCache, fragment)
        })

        watcher.fragments = list
        list.sort(function (a, b) {
            return a.index - b.index
        })
        watcher.cache = newCache
    }
    function updateList(watcher) {

        var before = watcher.node
        var parent = before.parentNode
        var list = watcher.fragments
        for (var i = 0, item; item = list[i]; i++) {
            if (item._destory) {
                list.splice(i, 1)
                i--
                item.destory()
                continue
            }
            if (item.ordexIndex !== item.index) {
                if (item.dom && !item.dom.childNodes.length) {
                    item.move()
                }
                parent.insertBefore(item.dom, before.nextSibling)
            }
            before = item.split
        }
    }

    function Fragment(key, val, index) {
        this.name = '#document-fragment'
        this.key = key
        this.val = val
        this.index = index
    }
    Fragment.prototype = {
        destory: function () {
            this.move()
            this.boss.destroy()
            for (var i in this) {
                this[i] = null
            }
        },
        move: function () {
            var pre = this.split
            var f = this.dom
            var list = [pre]
            var w = this.watcher
            var a = 99999
            do {
                pre = pre.previousSibling
                if (!pre || pre === w.node || pre.nodeValue === w.signature) {
                    break
                }
                list.unshift(pre)

            } while (--a)
            list.forEach(function (el) {
                f.appendChild(el)
            })
            return f
        }
    }
    /**
     * 
     * @param {type} fragment
     * @param {type} watcher
     * @param {type} index
     * @returns { key, val, index, oldIndex, watcher, dom, split, boss, vm}
     */
    function FragmentDecorator(fragment, watcher, index) {
        var dom = fragment.dom = watcher.fragment.cloneNode(true)
        fragment.split = dom.lastChild
        fragment.watcher = watcher
        fragment.vm = observeItemObject(watcher.vm, {
            data: new function () {
                var data = {}
                data[watcher.keyName] = fragment.index
                data[watcher.valName] = fragment.val
                if (watcher.asName) {
                    data[watcher.asName] = []
                }
                return data
            }
        })
        fragment.index = index
        fragment.boss = avalon.scan(dom, fragment.vm)
        return fragment
    }
    // 新位置: 旧位置
    function isInCache(cache, id) {
        var c = cache[id]
        if (c) {
            var arr = c.arr
            /* istanbul ignore if*/
            if (arr) {
                var r = arr.pop()
                if (!arr.length) {
                    c.arr = 0
                }
                return r
            }
            delete cache[id]
            return c
        }
    }
    //[1,1,1] number1 number1_ number1__
    function saveInCache(cache, component) {
        var trackId = component.key
        if (!cache[trackId]) {
            cache[trackId] = component
        } else {
            var c = cache[trackId]
            var arr = c.arr || (c.arr = [])
            arr.push(component)
        }
    }

    var rfuzzy = /^(string|number|boolean)/
    var rkfuzzy = /^_*(string|number|boolean)/
    function fuzzyMatchCache(cache) {
        var key
        for (var id in cache) {
            var key = id
            break
        }
        if (key) {
            return isInCache(cache, key)
        }
    }
    avalon.directive('widget', {
        delay: true,
        priority: 4,
        init: function (watcher) {
            var node = watcher.node
            var is = node.getAttribute('is')
            var component = avalon.components[is]
            if (component) {
                var slots = {}, soleSlot
                if (component.soleSlot) {
                    soleSlot = avalon.scan(emptyNode(node), watcher.vm)
                } else {
                    avalon.each(node.childNodes, function (el) {
                        var name = el.getAttribute('slot')
                        if (name) {
                            slots[name] = avalon.scan(el, watcher.vm)
                        }
                    })
                }
                var opt = watcher.value
                if (isObject(watcher.value)) {
                    var def = avalon.mix({}, component.defaults)
                    for (var i in def) {
                        def[i] = opt[i]
                    }
                    if (opt.id) {
                        def.$id = opt.id
                    }
                    var vm = avalon.define(def)
                    var div = document.createElement('div')
                    div.innerHTML = component.template
                    var boss = avalon.scan(div, vm)
                    var com = div.children[0]
                    var els = com.querySelectorAll('slot')
                    var push = Array.prototype.push
                    if (soleSlot) {
                        push.apply(boss.directives, soleSlot.directives)
                        replaceNode(soleSlot.node, els[0])
                    } else {
                        avalon.each(function (el) {
                            var name = el.getAttribute('name')
                            replaceNode(slots[name].node, el)
                            push.apply(boss.directives, slots[name].directives)
                        })
                    }
                    replaceNode(com, node)
                    watcher.node = com
                    watcher.comBoss = boss
                }
            }
        },
        update: function (node, value) {
            
        },
        destory: function () {
            this.comBoss.destory()
        }
    })

    avalon.components = {}
    avalon.component = function (name, component) {
        /**
         * template: string
         * defaults: object
         * soleSlot: string
         */
        avalon.components[name] = component
    }
})()