import Vue from '../instance/index'
import VNode from './vnode'
import { callHook } from '../instance/lifecycle'
import { warn, isObject, hasOwn, hyphenate } from '../util/index'

const hooks = { init, prepatch, insert, destroy }
const hooksToMerge = Object.keys(hooks)

export function createComponent (Ctor, data, parent, children) {
  if (process.env.NODE_ENV !== 'production' &&
    children && typeof children !== 'function') {
    warn(
      'A component\'s children should be a function that returns the ' +
      'children array. This allows the component to track the children ' +
      'dependencies and optimizes re-rendering.'
    )
  }
  if (!Ctor) {
    return
  }
  if (isObject(Ctor)) {
    Ctor = Vue.extend(Ctor)
  }
  if (process.env.NODE_ENV !== 'production' && typeof Ctor !== 'function') {
    warn(`Invalid Component definition: ${Ctor}`, parent)
    return
  }

  // async component
  if (!Ctor.cid) {
    if (Ctor.resolved) {
      Ctor = Ctor.resolved
    } else {
      resolveAsyncComponent(Ctor, () => {
        // it's ok to queue this on every render because
        // $forceUpdate is buffered.
        parent.$forceUpdate()
      })
      return
    }
  }

  data = data || {}

  // merge component management hooks onto the placeholder node
  mergeHooks(data)

  // extract props
  const propsData = extractProps(data, Ctor)

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  if (listeners) {
    data.on = null
  }

  // return a placeholder vnode
  const id = `vue-component-${Ctor.cid}${
    Ctor.options.name ? `-${Ctor.options.name}` : ''
  }`
  const vnode = VNode(id, data)
  vnode.componentOptions = { Ctor, propsData, listeners, parent, children }
  return vnode
}

function init (vnode) {
  const { Ctor, propsData, listeners, parent, children } = vnode.componentOptions
  const child = new Ctor({
    parent,
    propsData,
    _parentVnode: vnode,
    _parentListeners: listeners,
    _renderChildren: children
  })
  // the child sets the parent vnode's elm when mounted
  // and when updated.
  child.$mount()
  vnode.child = child
}

function prepatch (oldVnode, vnode) {
  const oldCtor = oldVnode.componentOptions.Ctor
  const { Ctor, listeners, propsData, children } = vnode.componentOptions
  if (Ctor !== oldCtor) {
    // component changed, teardown and create new
    // TODO: keep-alive?
    oldVnode.child.$destroy()
    hooks.init(vnode)
  } else {
    vnode.child = oldVnode.child
    vnode.child._updateFromParent(
      propsData, // updated props
      listeners, // updated listeners
      vnode, // new parent vnode
      children // new children
    )
  }
}

function insert (vnode) {
  callHook(vnode.child, 'ready')
}

function destroy (vnode) {
  vnode.child.$destroy()
}

function resolveAsyncComponent (factory, cb) {
  if (factory.resolved) {
    // cached
    cb(factory.resolved)
  } else if (factory.requested) {
    // pool callbacks
    factory.pendingCallbacks.push(cb)
  } else {
    factory.requested = true
    const cbs = factory.pendingCallbacks = [cb]
    factory(function resolve (res) {
      if (isObject(res)) {
        res = Vue.extend(res)
      }
      // cache resolved
      factory.resolved = res
      // invoke callbacks
      for (var i = 0, l = cbs.length; i < l; i++) {
        cbs[i](res)
      }
    }, function reject (reason) {
      process.env.NODE_ENV !== 'production' && warn(
        `Failed to resolve async component: ${factory}` +
        (reason ? `\nReason: ${reason}` : '')
      )
    })
  }
}

function extractProps (data, Ctor) {
  // we are only extrating raw values here.
  // validation and default values are handled in the child
  // component itself.
  const propOptions = Ctor.options.props
  if (!propOptions) {
    return
  }
  const res = {}
  const attrs = data.attrs
  const props = data.props
  const staticAttrs = data.staticAttrs
  if (!attrs && !props) {
    return res
  }
  for (let key in propOptions) {
    let altKey = hyphenate(key)
    checkProp(res, attrs, key, altKey) ||
    checkProp(res, props, key, altKey) ||
    checkProp(res, staticAttrs, key, altKey)
  }
  return res
}

function checkProp (res, hash, key, altKey) {
  if (hash) {
    if (hasOwn(hash, key)) {
      res[key] = hash[key]
      delete hash[key]
      return true
    } else if (hasOwn(hash, altKey)) {
      res[key] = hash[altKey]
      delete hash[altKey]
      return true
    }
  }
}

function mergeHooks (data) {
  if (data.hook) {
    for (let i = 0; i < hooksToMerge.length; i++) {
      let key = hooksToMerge[i]
      let fromParent = data.hook[key]
      let ours = hooks[key]
      data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
    }
  } else {
    data.hook = hooks
  }
}

function mergeHook (a, b) {
  // since all hooks have at most two args, use fixed args
  // to avoid having to use fn.apply().
  return (_, __) => {
    a(_, __)
    b(_, __)
  }
}

@@ -1584,45 +1584,14 @@
 nst 
-id = %60vue-component-$%7BCtor.cid%7D$%7B%0A   
+name =
  Cto
@@ -1611,12 +1611,14 @@
 e ? 
-%60-$%7B
+'-' + 
 Ctor
@@ -1634,21 +1634,14 @@
 name
-%7D%60
  : ''%0A
-  %7D%60%0A
   co
@@ -1658,18 +1658,51 @@
 = VNode(
-id
+%60vue-component-$%7BCtor.cid%7D-$%7Bname%7D%60
 , data)%0A
@@ -2211,57 +2211,8 @@
 ) %7B%0A
-  const oldCtor = oldVnode.componentOptions.Ctor%0A
   co
@@ -2216,22 +2216,16 @@
  const %7B
- Ctor,
  listene
@@ -2279,174 +2279,8 @@
 ons%0A
-  if (Ctor !== oldCtor) %7B%0A    // component changed, teardown and create new%0A    // TODO: keep-alive?%0A    oldVnode.child.$destroy()%0A    hooks.init(vnode)%0A  %7D else %7B%0A  
   vn
@@ -2306,18 +2306,16 @@
 e.child%0A
-  
   vnode.
@@ -2343,18 +2343,16 @@
 nt(%0A    
-  
 propsDat
@@ -2371,18 +2371,16 @@
 d props%0A
-  
     list
@@ -2411,18 +2411,16 @@
 ers%0A    
-  
 vnode, /
@@ -2442,18 +2442,16 @@
 ode%0A    
- 
- 
 children
@@ -2469,23 +2469,17 @@
 ldren%0A  
-  )%0A  %7D
+)
 %0A%7D%0A%0Afunc

simplify component