text: /* Copyright (c) 2012-2016 LevelUP contributors
   * See list at <https://github.com/level/levelup#contributing>
   * MIT License
   * <https://github.com/level/levelup/blob/master/LICENSE.md>
   */
  
  var EventEmitter        = require('events').EventEmitter
    , inherits            = require('util').inherits
    , deprecate           = require('util').deprecate
    , extend              = require('xtend')
    , prr                 = require('prr')
    , DeferredLevelDOWN   = require('deferred-leveldown')
    , IteratorStream      = require('level-iterator-stream')
  
    , errors              = require('level-errors')
    , WriteError          = errors.WriteError
    , ReadError           = errors.ReadError
    , NotFoundError       = errors.NotFoundError
    , OpenError           = errors.OpenError
    , EncodingError       = errors.EncodingError
    , InitializationError = errors.InitializationError
    , LevelUPError        = errors.LevelUPError
  
    , util                = require('./util')
    , Batch               = require('./batch')
    , Codec               = require('level-codec')
  
    , getOptions          = util.getOptions
    , defaultOptions      = util.defaultOptions
    , getLevelDOWN        = require('./leveldown')
    , dispatchError       = util.dispatchError
    , isDefined           = util.isDefined
  
  function getCallback (options, callback) {
    return typeof options == 'function' ? options : callback
  }
  
  // Possible LevelUP#_status values:
  //  - 'new'     - newly created, not opened or closed
  //  - 'opening' - waiting for the database to be opened, post open()
  //  - 'open'    - successfully opened the database, available for use
  //  - 'closing' - waiting for the database to be closed, post close()
  //  - 'closed'  - database has been successfully closed, should not be
  //                 used except for another open() operation
  
  function LevelUP (location, options, callback) {
    if (!(this instanceof LevelUP))
      return new LevelUP(location, options, callback)
  
    var error
  
    EventEmitter.call(this)
    this.setMaxListeners(Infinity)
  
    if (typeof location == 'function') {
      options = typeof options == 'object' ? options : {}
      options.db = location
      location = null
    } else if (typeof location == 'object' && typeof location.db == 'function') {
      options = location
      location = null
    }
  
  
    if (typeof options == 'function') {
      callback = options
      options  = {}
    }
  
    if ((!options || typeof options.db != 'function') && typeof location != 'string') {
      error = new InitializationError(
          'Must provide a location for the database')
      if (callback) {
        return process.nextTick(function () {
          callback(error)
        })
      }
      throw error
    }
  
    options      = getOptions(options)
    this.options = extend(defaultOptions, options)
    this._codec = new Codec(this.options)
    this._status = 'new'
    // set this.location as enumerable but not configurable or writable
    prr(this, 'location', location, 'e')
  
    this.open(callback)
  }
  
  inherits(LevelUP, EventEmitter)
  
  LevelUP.prototype.open = function (callback) {
    var self = this
      , dbFactory
      , db
  
    if (this.isOpen()) {
      if (callback)
        process.nextTick(function () { callback(null, self) })
      return this
    }
  
    if (this._isOpening()) {
      return callback && this.once(
          'open'
        , function () { callback(null, self) }
      )
    }
  
    this.emit('opening')
    this._status = 'opening'
    this.db      = new DeferredLevelDOWN(this.location)
  
    if (typeof this.options.db !== 'function' &&
        typeof getLevelDOWN !== 'function') {
      throw new LevelUPError('missing db factory, you need to set options.db')
    }
  
    dbFactory = this.options.db || getLevelDOWN()
    db = dbFactory(this.location)
  
    db.open(this.options, function (err) {
      if (err) {
        return dispatchError(self, new OpenError(err), callback)
      } else {
        self.db.setDb(db)
        self.db = db
        self._status = 'open'
        if (callback)
          callback(null, self)
        self.emit('open')
        self.emit('ready')
      }
    })
  }
  
  LevelUP.prototype.close = function (callback) {
    var self = this
  
    if (this.isOpen()) {
      this._status = 'closing'
      this.db.close(function () {
        self._status = 'closed'
        self.emit('closed')
        if (callback)
          callback.apply(null, arguments)
      })
      this.emit('closing')
      this.db = new DeferredLevelDOWN(this.location)
    } else if (this._status == 'closed' && callback) {
      return process.nextTick(callback)
    } else if (this._status == 'closing' && callback) {
      this.once('closed', callback)
    } else if (this._isOpening()) {
      this.once('open', function () {
        self.close(callback)
      })
    }
  }
  
  LevelUP.prototype.isOpen = function () {
    return this._status == 'open'
  }
  
  LevelUP.prototype._isOpening = function () {
    return this._status == 'opening'
  }
  
  LevelUP.prototype.isClosed = function () {
    return (/^clos/).test(this._status)
  }
  
  function maybeError(db, options, callback) {
    if (!db._isOpening() && !db.isOpen()) {
      dispatchError(
          db
        , new ReadError('Database is not open')
        , callback
      )
      return true
    }
  }
  
  function writeError (db, message, callback) {
    dispatchError(
        db
       , new WriteError(message)
       , callback
    )
  }
  
  function readError (db, message, callback) {
    dispatchError(
        db
       , new ReadError(message)
       , callback
    )
  }
  
  
  LevelUP.prototype.get = function (key_, options, callback) {
    var self = this
      , key
  
    callback = getCallback(options, callback)
  
    if (maybeError(this, options, callback))
      return
  
    if (key_ === null || key_ === undefined || 'function' !== typeof callback)
      return readError(this
        , 'get() requires key and callback arguments', callback)
  
    options = util.getOptions(options)
    key = this._codec.encodeKey(key_, options)
  
    options.asBuffer = this._codec.valueAsBuffer(options)
  
    this.db.get(key, options, function (err, value) {
      if (err) {
        if ((/notfound/i).test(err) || err.notFound) {
          err = new NotFoundError(
              'Key not found in database [' + key_ + ']', err)
        } else {
          err = new ReadError(err)
        }
        return dispatchError(self, err, callback)
      }
      if (callback) {
        try {
          value = self._codec.decodeValue(value, options)
        } catch (e) {
          return callback(new EncodingError(e))
        }
        callback(null, value)
      }
    })
  }
  
  LevelUP.prototype.put = function (key_, value_, options, callback) {
    var self = this
      , key
      , value
  
    callback = getCallback(options, callback)
  
    if (key_ === null || key_ === undefined)
      return writeError(this, 'put() requires a key argument', callback)
  
    if (maybeError(this, options, callback))
      return
  
    options = getOptions(options)
    key     = this._codec.encodeKey(key_, options)
    value   = this._codec.encodeValue(value_, options)
  
    this.db.put(key, value, options, function (err) {
      if (err) {
        return dispatchError(self, new WriteError(err), callback)
      } else {
        self.emit('put', key_, value_)
        if (callback)
          callback()
      }
    })
  }
  
  LevelUP.prototype.del = function (key_, options, callback) {
    var self = this
      , key
  
    callback = getCallback(options, callback)
  
    if (key_ === null || key_ === undefined)
      return writeError(this, 'del() requires a key argument', callback)
  
    if (maybeError(this, options, callback))
      return
  
    options = getOptions(options)
    key     = this._codec.encodeKey(key_, options)
  
    this.db.del(key, options, function (err) {
      if (err) {
        return dispatchError(self, new WriteError(err), callback)
      } else {
        self.emit('del', key_)
        if (callback)
          callback()
      }
    })
  }
  
  LevelUP.prototype.batch = function (arr_, options, callback) {
    var self = this
      , keyEnc
      , valueEnc
      , arr
  
    if (!arguments.length)
      return new Batch(this, this._codec)
  
    callback = getCallback(options, callback)
  
    if (!Array.isArray(arr_))
      return writeError(this, 'batch() requires an array argument', callback)
  
    if (maybeError(this, options, callback))
      return
  
    options  = getOptions(options)
    arr      = self._codec.encodeBatch(arr_, options)
    arr      = arr.map(function (op) {
      if (!op.type && op.key !== undefined && op.value !== undefined)
        op.type = 'put'
      return op
    })
  
    this.db.batch(arr, options, function (err) {
      if (err) {
        return dispatchError(self, new WriteError(err), callback)
      } else {
        self.emit('batch', arr_)
        if (callback)
          callback()
      }
    })
  }
  
  LevelUP.prototype.approximateSize = deprecate(function (start_, end_, options, callback) {   
    var self = this    
      , start    
      , end    
     
    callback = getCallback(options, callback)    
     
    options = getOptions(options)    
     
    if (start_ === null || start_ === undefined    
          || end_ === null || end_ === undefined || 'function' !== typeof callback)    
      return readError(this, 'approximateSize() requires start, end and callback arguments', callback)   
     
    start = this._codec.encodeKey(start_, options)   
    end   = this._codec.encodeKey(end_, options)   
     
    this.db.approximateSize(start, end, function (err, size) {   
      if (err) {   
        return dispatchError(self, new OpenError(err), callback)   
      } else if (callback) {   
        callback(null, size)   
      }    
    })   
  }, 'db.approximateSize() is deprecated. Use db.db.approximateSize() instead')
  
  LevelUP.prototype.readStream =
  LevelUP.prototype.createReadStream = function (options) {
    options = extend( {keys: true, values: true}, this.options, options)
  
    options.keyEncoding   = options.keyEncoding
    options.valueEncoding = options.valueEncoding
  
    options = this._codec.encodeLtgt(options);
    options.keyAsBuffer   = this._codec.keyAsBuffer(options)
    options.valueAsBuffer = this._codec.valueAsBuffer(options)
  
    if ('number' !== typeof options.limit)
      options.limit = -1
  
    return new IteratorStream(this.db.iterator(options), extend(options, {
      decoder: this._codec.createStreamDecoder(options)
    }))
  }
  
  LevelUP.prototype.keyStream =
  LevelUP.prototype.createKeyStream = function (options) {
    return this.createReadStream(extend(options, { keys: true, values: false }))
  }
  
  LevelUP.prototype.valueStream =
  LevelUP.prototype.createValueStream = function (options) {
    return this.createReadStream(extend(options, { keys: false, values: true }))
  }
  
  LevelUP.prototype.toString = function () {
    return 'LevelUP'
  }
  
  function utilStatic (name) {
    return function (location, callback) {
      getLevelDOWN()[name](location, callback || function () {})
    }
  }
  
  module.exports         = LevelUP
  module.exports.errors  = require('level-errors')
  module.exports.destroy = deprecate(
      utilStatic('destroy')
    , 'levelup.destroy() is deprecated. Use leveldown.destroy() instead'
  )
  module.exports.repair  = deprecate(
      utilStatic('repair')
    , 'levelup.repair() is deprecated. Use leveldown.repair() instead'
  )
  
  