var EventEmitter = require('events').EventEmitter
var addpre = require('./range').addPrefix

var errors = require('levelup/lib/errors')

function isFunction (f) {
  return 'function' === typeof f
}

function isString (s) {
  return 'string' === typeof s
}

function isObject (o) {
  return o && 'object' === typeof o
}

var version = require('./package.json').version

var sublevel = module.exports = function (nut, prefix, createStream, options) {
  var emitter = new EventEmitter()
  emitter.sublevels = {}
  emitter.options = options

  emitter.version = version

  emitter.methods = {}
  prefix = prefix || []

  function errback (err) { if (err) emitter.emit('error', err) }

  createStream = createStream || function (e) { return e }

  function mergeOpts(opts) {
    var o = {}
    if(options)
      for(var k in options)
        if(options[k] != undefined)o[k] = options[k]
    if(opts)
      for(var k in opts)
        if(opts[k] != undefined) o[k] = opts[k]
    return o
  }

  emitter.put = function (key, value, opts, cb) {
    if('function' === typeof opts) cb = opts, opts = {}
    if(!cb) cb = errback

    nut.apply([{
      key: key, value: value,
      prefix: prefix.slice(), type: 'put'
    }], mergeOpts(opts), function (err) {
      if(!err) { emitter.emit('put', key, value); cb(null) }
      if(err) return cb(err)
    })
  }

  emitter.prefix = function () {
    return prefix.slice()
  }

  emitter.del = function (key, opts, cb) {
    if('function' === typeof opts) cb = opts, opts = {}
    if(!cb) cb = errback

    nut.apply([{
      key: key,
      prefix: prefix.slice(), type: 'del'
    }], mergeOpts(opts), function (err) {
      if(!err) { emitter.emit('del', key); cb(null) }
      if(err) return cb(err)
    })
  }

  emitter.batch = function (ops, opts, cb) {
    if('function' === typeof opts)
      cb = opts, opts = {}
    if(!cb) cb = errback

    ops = ops.map(function (op) {
      return {
        key:           op.key,
        value:         op.value,
        prefix:        op.prefix || prefix,
        keyEncoding:   op.keyEncoding,    // *
        valueEncoding: op.valueEncoding,  // * (TODO: encodings on sublevel)
        type:          op.type
      }
    })

    nut.apply(ops, mergeOpts(opts), function (err) {
      if(!err) { emitter.emit('batch', ops); cb(null) }
      if(err) return cb(err)
    })
  }

  emitter.get = function (key, opts, cb) {
    if('function' === typeof opts)
      cb = opts, opts = {}
    nut.get(key, prefix, mergeOpts(opts), function (err, value) {
      if(err) cb(new errors.NotFoundError('Key not found in database', err))
      else cb(null, value)
    })
  }

  emitter.clone = function(opts) {
    return sublevel(nut, prefix, createStream, mergeOpts(opts))
  }

  emitter.sublevel = function (name, opts) {
    return emitter.sublevels[name] =
      emitter.sublevels[name] || sublevel(nut, prefix.concat(name), createStream, mergeOpts(opts))
  }

  emitter.pre = function (key, hook) {
    if(isFunction(key)) return nut.pre([prefix], key)
    if(isString(key)) return nut.pre([prefix, key], hook)
    if(isObject(key)) return nut.pre(addpre(prefix, key), hook)

    throw new Error('not implemented yet')
  }

  emitter.post = function (key, hook) {
    if(isFunction(key)) return nut.post([prefix], key)
    if(isString(key))   return nut.post([prefix, key], hook)
    if(isObject(key))   return nut.post(addpre(prefix, key), hook)

    //TODO: handle ranges, needed for level-live-stream, etc.
    throw new Error('not implemented yet')
  }

  emitter.readStream =
  emitter.createReadStream = function (opts) {
    opts = mergeOpts(opts)
    opts.prefix = prefix
    var stream
    var it = nut.iterator(opts, function (err, it) {
      stream.setIterator(it)
    })

    stream = createStream(opts, nut.createDecoder(opts))
    if(it) stream.setIterator(it)

    return stream
  }

  emitter.valueStream =
  emitter.createValueStream = function (opts) {
    opts = opts || {}
    opts.values = true
    opts.keys = false
    return emitter.createReadStream(opts)
  }

  emitter.keyStream =
  emitter.createKeyStream = function (opts) {
    opts = opts || {}
    opts.values = false
    opts.keys = true
    return emitter.createReadStream(opts)
  }

  emitter.close = function (cb) {
    //TODO: deregister all hooks
    cb = cb || function () {}
    if (!prefix.length) nut.close(cb)
    else process.nextTick(cb)
  }

  emitter.isOpen = nut.isOpen
  emitter.isClosed = nut.isClosed

  return emitter
}
