text: var _ = require('./util')
  var config = require('./config')
  var Dep = require('./observer/dep')
  var expParser = require('./parsers/expression')
  var batcher = require('./batcher')
  var uid = 0
  
  /**
   * A watcher parses an expression, collects dependencies,
   * and fires callback when the expression value changes.
   * This is used for both the $watch() api and directives.
   *
   * @param {Vue} vm
   * @param {String} expression
   * @param {Function} cb
   * @param {Object} options
   *                 - {Array} filters
   *                 - {Boolean} twoWay
   *                 - {Boolean} deep
   *                 - {Boolean} user
   *                 - {Boolean} sync
   *                 - {Boolean} lazy
   *                 - {Function} [preProcess]
   * @constructor
   */
  
  function Watcher (vm, expOrFn, cb, options) {
    // mix in options
    if (options) {
      _.extend(this, options)
    }
    var isFn = typeof expOrFn === 'function'
    this.vm = vm
    vm._watchers.push(this)
    this.expression = isFn ? expOrFn.toString() : expOrFn
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = Object.create(null)
    this.newDeps = null
    this.prevError = null // for async error stacks
    // parse expression for getter/setter
    if (isFn) {
      this.getter = expOrFn
      this.setter = undefined
    } else {
      var res = expParser.parse(expOrFn, this.twoWay)
      this.getter = res.get
      this.setter = res.set
    }
    this.value = this.lazy
      ? undefined
      : this.get()
    // state for avoiding false triggers for deep and Array
    // watchers during vm._digest()
    this.queued = this.shallow = false
  }
  
  /**
   * Add a dependency to this directive.
   *
   * @param {Dep} dep
   */
  
  Watcher.prototype.addDep = function (dep) {
    var id = dep.id
    if (!this.newDeps[id]) {
      this.newDeps[id] = dep
      if (!this.deps[id]) {
        this.deps[id] = dep
        dep.addSub(this)
      }
    }
  }
  
  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  
  Watcher.prototype.get = function () {
    this.beforeGet()
    var vm = this.vm
    var value
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (
        process.env.NODE_ENV !== 'production' &&
        config.warnExpressionErrors
      ) {
        _.warn(
          'Error when evaluating expression "' +
          this.expression + '". ' +
          (config.debug
            ? ''
            : 'Turn on debug mode to see stack trace.'
          ), e
        )
      }
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    if (this.preProcess) {
      value = this.preProcess(value)
    }
    if (this.filters) {
      value = vm._applyFilters(value, null, this.filters, false)
    }
    this.afterGet()
    return value
  }
  
  /**
   * Set the corresponding value with the setter.
   *
   * @param {*} value
   */
  
  Watcher.prototype.set = function (value) {
    var vm = this.vm
    if (this.filters) {
      value = vm._applyFilters(
        value, this.value, this.filters, true)
    }
    try {
      this.setter.call(vm, vm, value)
    } catch (e) {
      if (
        process.env.NODE_ENV !== 'production' &&
        config.warnExpressionErrors
      ) {
        _.warn(
          'Error when evaluating setter "' +
          this.expression + '"', e
        )
      }
    }
  }
  
  /**
   * Prepare for dependency collection.
   */
  
  Watcher.prototype.beforeGet = function () {
    Dep.target = this
    this.newDeps = Object.create(null)
  }
  
  /**
   * Clean up for dependency collection.
   */
  
  Watcher.prototype.afterGet = function () {
    Dep.target = null
    var ids = Object.keys(this.deps)
    var i = ids.length
    while (i--) {
      var id = ids[i]
      if (!this.newDeps[id]) {
        this.deps[id].removeSub(this)
      }
    }
    this.deps = this.newDeps
  }
  
  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   *
   * @param {Boolean} shallow
   */
  
  Watcher.prototype.update = function (shallow) {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync || !config.async) {
      this.run()
    } else {
      // if queued, only overwrite shallow with non-shallow,
      // but not the other way around.
      this.shallow = this.queued
        ? shallow
          ? this.shallow
          : false
        : !!shallow
      this.queued = true
      // record before-push error stack in debug mode
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.debug) {
        this.prevError = new Error('[vue] async stack trace')
      }
      batcher.push(this)
    }
  }
  
  /**
   * Batcher job interface.
   * Will be called by the batcher.
   */
  
  Watcher.prototype.run = function () {
    if (this.active) {
      var value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and Array watchers should fire even
        // when the value is the same, because the value may
        // have mutated; but only do so if this is a
        // non-shallow update (caused by a vm digest).
        ((_.isArray(value) || this.deep) && !this.shallow)
      ) {
        // set new value
        var oldValue = this.value
        this.value = value
        // in debug + async mode, when a watcher callbacks
        // throws, we also throw the saved before-push error
        // so the full cross-tick stack trace is available.
        var prevError = this.prevError
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' &&
            config.debug && prevError) {
          this.prevError = null
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            _.nextTick(function () {
              throw prevError
            }, 0)
            throw e
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
      this.queued = this.shallow = false
    }
  }
  
  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  
  Watcher.prototype.evaluate = function () {
    // avoid overwriting another watcher that is being
    // collected.
    var current = Dep.target
    this.value = this.get()
    this.dirty = false
    Dep.target = current
  }
  
  /**
   * Depend on all deps collected by this watcher.
   */
  
  Watcher.prototype.depend = function () {
    var depIds = Object.keys(this.deps)
    var i = depIds.length
    while (i--) {
      this.deps[depIds[i]].depend()
    }
  }
  
  /**
   * Remove self from all dependencies' subcriber list.
   */
  
  Watcher.prototype.teardown = function () {
    if (this.active) {
      // remove self from vm's watcher list
      // we can skip this if the vm if being destroyed
      // which can improve teardown performance.
      if (!this.vm._isBeingDestroyed) {
        this.vm._watchers.$remove(this)
      }
      var depIds = Object.keys(this.deps)
      var i = depIds.length
      while (i--) {
        this.deps[depIds[i]].removeSub(this)
      }
      this.active = false
      this.vm = this.cb = this.value = null
    }
  }
  
  /**
   * Recrusively traverse an object to evoke all converted
   * getters, so that every nested property inside the object
   * is collected as a "deep" dependency.
   *
   * @param {Object} obj
   */
  
  function traverse (obj) {
    var key, val, i
    for (key in obj) {
      val = obj[key]
      if (_.isArray(val)) {
        i = val.length
        while (i--) traverse(val[i])
      } else if (_.isObject(val)) {
        traverse(val)
      }
    }
  }
  
  module.exports = Watcher
  