'use strict'

var Transform = require('readable-stream').Transform
var inherits = require('inherits')
var bl = require('bl')

function Base (opts) {
  opts = opts || {}

  opts.objectMode = true
  opts.highWaterMark = 16

  Transform.call(this, opts)

  this._msgpack = opts.msgpack
}

inherits(Base, Transform)

function Encoder (opts) {
  if (!(this instanceof Encoder)) {
    opts = opts || {}
    opts.msgpack = this
    return new Encoder(opts)
  }

  Base.call(this, opts)
  this._wrap = ('wrap' in opts) && opts.wrap
}

inherits(Encoder, Base)

Encoder.prototype._transform = function (obj, enc, done) {
  var buf = null

  try {
    buf = this._msgpack.encode(this._wrap ? obj.value : obj).slice(0)
  } catch (err) {
    this.emit('error', err)
    return done()
  }

  this.push(buf)
  done()
}

function Decoder (opts) {
  if (!(this instanceof Decoder)) {
    opts = opts || {}
    opts.msgpack = this
    return new Decoder(opts)
  }

  Base.call(this, opts)

  this._chunks = bl()
  this._wrap = ('wrap' in opts) && opts.wrap
}

inherits(Decoder, Base)

Decoder.prototype._transform = function (buf, enc, done) {
  if (buf) {
    this._chunks.append(buf)
  }

  try {
    var result = this._msgpack.decode(this._chunks)
    if (this._wrap) {
      result = {value: result}
    }
    this.push(result)
  } catch (err) {
    if (err instanceof this._msgpack.IncompleteBufferError) {
      done()
    } else {
      this.emit('error', err)
    }
    return
  }

  if (this._chunks.length > 0) {
    this._transform(null, enc, done)
  } else {
    done()
  }
}

module.exports.decoder = Decoder
module.exports.encoder = Encoder
